> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bachs.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Money is always a decimal string at the currency's precision (for example "29.00"), paired with an ISO 4217 currency field. Never use minor units.
> Build against the sandbox first: base URL https://sandbox-api.bachs.io with sk_sandbox_ keys. Production is https://api.bachs.io with sk_live_ keys; going live is a key swap.
> Treat webhooks (for example collection.succeeded) as the source of truth for fulfilment, never client-side events or redirects.
> Subscriptions are created by completing a checkout for a recurring product. There is no direct create-subscription endpoint.
> IDs carry resource prefixes (cust_, prod_, sub_, chk_, inv_, ref_) and timestamps are ISO 8601 UTC.

# Payout using API

> Pay someone with a bank code and an account number, out of your own balance. Or give each payee a recipient account with its own balance and destinations.

Paying someone out is a bank code, an account number, and an amount.

```bash theme={"dark"}
# 1. Say where the money goes. Resolves and approves in this one call.
curl -X POST https://sandbox-api.bachs.io/v1/payouts/destinations \
  -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
  -H "Content-Type: application/json" \
  -d '{ "currency": "NGN", "account_number": "0123456789", "bank_code": "058" }'

# 2. Send the money.
curl -X POST https://sandbox-api.bachs.io/v1/payouts \
  -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-001" \
  -d '{ "destination": "pd_7Kq2mNv4XbR9dLc0", "amount": "9000.00" }'
```

The first call looks the account up at the bank. An account that resolves comes back `approved` and is payable straight away. Store the `pd_...` against your payee and reuse it.

This pays out of your own balance. To give each payee a balance of their own, see [Giving each payee their own balance](#giving-each-payee-their-own-balance).

## Paying out of your own balance

### What the destination call gives you

The first call returns the destination with the bank's own answer filled in:

```json theme={"dark"}
{
  "id": "pd_7Kq2mNv4XbR9dLc0",
  "currency": "NGN",
  "status": "approved",
  "is_usable": true,
  "is_default": true,
  "account_number": "0123456789",
  "account_name": "ADA OKAFOR",
  "bank_name": "Guaranty Trust Bank"
}
```

`account_name` is the name the bank holds on that account, not a value you sent. Show it to the payee to confirm before paying.

Bank codes come from `GET /v1/reference/banks?country=NG`. `POST /v1/misc/bank-accounts/resolve` runs the same lookup on its own, if you want the account name in your form before submitting.

<Warning>
  `amount` is what the payee receives, and the fee is charged on top. A balance of `9000.00` cannot fund a `9000.00` payout. Reconcile against `total_debited`. Send an `Idempotency-Key` on every payout, because a retry without one pays twice, and a `5xx` is not proof the payout was not created.
</Warning>

Payouts are asynchronous. `pending` becomes `completed` or `failed`, and you learn which from the [`payout.paid`](/guides/webhooks/events/payout-paid) and [`payout.failed`](/guides/webhooks/events/payout-failed) webhooks. Do not tell anyone they have been paid on the `201`.

### What a destination is

A destination is where money leaves to. It belongs to a balance and holds one currency.

* The account is verified once, at creation. Payouts after that reference the `pd_...` id.
* A payee can have several. One is `is_default`; name any of them per payout.
* A schedule can pay to the default one, on a balance fed by your own collections. See [Payout schedules](/guides/payouts/payout-schedules).

### Pay the same person again

Reuse the `pd_...`:

```bash theme={"dark"}
curl -X POST https://sandbox-api.bachs.io/v1/payouts \
  -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-002" \
  -d '{ "destination": "pd_7Kq2mNv4XbR9dLc0", "amount": "4500.00", "reference": "invoice-8812" }'
```

`GET /v1/payouts/destinations` lists them with the full account number, bank name and holder name, so a settings screen can render from the API.

To change a bank account, create a new destination and use its id. `PATCH` restates a destination in full, so changing an account number sends it back for review. Only `name` and `is_default` are safe to patch.

***

## Giving each payee their own balance

A payee can have a **recipient account**: their own balance, their own destinations, and their own payout history. You fund it with a transfer, and pay out of it by acting as that account.

|                 | Your balance                        | A balance per payee                                        |
| --------------- | ----------------------------------- | ---------------------------------------------------------- |
| Whose money     | Yours until you send it             | The payee's once you transfer it                           |
| Setup per payee | A destination                       | An account, then a destination                             |
| Funding         | Your own collections                | A transfer from you                                        |
| Used for        | Suppliers, one-off payouts, payroll | Sellers, contractors and drivers you owe a running balance |

A recipient account only receives money. To let a payee collect payments directly, it needs the `merchant` configuration too, which is a longer onboarding. See [Choose your integration](/connect/choose-your-integration).

<Steps>
  <Step title="Create the recipient account">
    ```bash theme={"dark"}
    curl -X POST https://sandbox-api.bachs.io/v1/accounts \
      -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
      -H "Content-Type: application/json" \
      -d '{
        "contact_email": "ada@example.com",
        "display_name": "Ada Obi",
        "country": "NG",
        "entity_type": "individual",
        "configuration": {
          "recipient": {
            "capabilities": { "payouts": { "requested": true }, "transfers": { "requested": true } }
          }
        }
      }'
    ```

    Omit `configuration` and the account can never be paid out to. Store the `acct_...`.
  </Step>

  <Step title="Submit the holder and their destination">
    The name activates `transfers` and the destination activates `payouts`, so one call takes the account live.

    ```bash theme={"dark"}
    curl -X POST https://sandbox-api.bachs.io/v1/accounts/acct_3Wq8ZfT1yHnJ5sVe \
      -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
      -H "Content-Type: application/json" \
      -d '{
        "fields": {
          "persons": [
            { "first_name": "Ada", "last_name": "Obi", "relationship": { "representative": true } }
          ],
          "payout_destination": {
            "currency": "NGN",
            "type": "bank_account",
            "account_number": "0123456789",
            "account_name": "ADA OKAFOR",
            "bank_code": "058"
          }
        }
      }'
    ```

    <Note>
      `account_name` is required here, and ignored on `POST /v1/payouts/destinations` where the bank's answer always wins.
    </Note>
  </Step>

  <Step title="Fund their balance">
    A recipient account holds no payment-accepting capability, so it never collects money itself. You move money into it with a transfer. A transfer debits whoever is authenticated, so send no `X-Account-Id` to fund a payee from your own balance.

    ```bash theme={"dark"}
    curl -X POST https://sandbox-api.bachs.io/v1/transfers \
      -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: fund-ada-001" \
      -d '{ "destination": "acct_3Wq8ZfT1yHnJ5sVe", "amount": "10000.00", "currency": "NGN" }'
    ```

    Transfers move one currency and do not convert, so send the currency the payee's destination is registered in. The transfer posts against both balances at once.
  </Step>

  <Step title="Pay out of their balance">
    `X-Account-Id` makes any call above act as that payee:

    ```bash theme={"dark"}
    curl -X POST https://sandbox-api.bachs.io/v1/payouts \
      -H "Authorization: Bearer sk_sandbox_abc123xyz..." \
      -H "X-Account-Id: acct_3Wq8ZfT1yHnJ5sVe" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: payout-ada-001" \
      -d '{ "destination": "pd_7Kq2mNv4XbR9dLc0", "amount": "9000.00" }'
    ```

    <Warning>
      Payout schedules do not apply to a recipient account. A schedule moves settled customer collections, and a balance credited by transfer is not eligible, so setting one on a recipient pays nothing on any interval. Payouts here are always a call you make.

      Schedules are for accounts that collect their own payments, which needs the `merchant` configuration as well. See [Payout schedules](/guides/payouts/payout-schedules) and [Capabilities](/connect/capabilities).
    </Warning>
  </Step>
</Steps>

See [Payouts in Connect](/connect/payouts) for the full onboarding path, and [Capabilities](/connect/capabilities) for what a recipient can do.

***

## Hand this to your coding agent

Copy this into Claude Code, Cursor, or whatever you build with.

```markdown Agent prompt theme={"dark"}
Integrate payouts with the Bachs API.

Sandbox https://sandbox-api.bachs.io with an `sk_sandbox_` key; live
https://api.bachs.io with `sk_live_`. Going live is a key swap.
Header: `Authorization: Bearer <key>`.

## The short path
Paying someone is two calls.

1. POST /v1/payouts/destinations
   {"currency": "NGN", "account_number": "0123456789", "bank_code": "058"}
   -> {"id": "pd_...", "status": "approved", "is_usable": true,
       "account_name": "ADA OKAFOR", "bank_name": "..."}
   This call resolves the account at the bank itself. If it resolves, it is
   approved and payable immediately. Do NOT call a resolve endpoint first, and
   do NOT build a polling loop waiting for approval.
   `account_name` in the response is the BANK's answer, not your input. Sending
   `account_name` here is ignored. Show the returned one back to the user as
   confirmation.
   Bank codes: GET /v1/reference/banks?country=NG -> {country, banks:[{name, code}]}

2. POST /v1/payouts
   {"destination": "pd_...", "amount": "9000.00", "reference": "invoice-8812"}
   -> {"id": "pay_...", "status": "pending", "fee", "total_debited"}

Store the `pd_...` against your payee and reuse it. That is the whole
integration for a simple "pay this person" feature.

## The destination model
A destination is where money leaves to. It belongs to a balance and holds one
currency. Payouts reference the `pd_...` id; bank details are never sent on a
payout call.
- Verified once at creation. Do not re-verify before each payout.
- A payee can have several. One is `is_default`; name any of them per payout.
- A schedule can pay to the default one, on a balance fed by collections.
Store the `pd_...` against your payee record. Do not store the bank details and
re-send them, and do not create a destination per payout.

## Rules
1. IDEMPOTENCY. Unique `Idempotency-Key` header on every POST that moves money
   (/v1/payouts, /v1/transfers). Derive it from your own payout id so a retry
   reuses it. Without it a retry pays twice.
2. `amount` IS WHAT THE PAYEE RECEIVES; the fee is added on top. The balance
   must cover `total_debited`. Reconcile on `total_debited`, never `amount`.
3. `amount` MUST BE A JSON STRING. "9000.00" is accepted, 9000.00 is rejected
   with 422. Hold money in a decimal type; never a float.
4. ASYNCHRONOUS. pending -> processing -> completed | failed. Only the last two
   are terminal. Never tell a user they are paid on the 201. Subscribe to
   `payout.paid` and `payout.failed`, or poll GET /v1/payouts/{id}. A payout
   that fails downstream can sit at `pending`, so do not infer state from time.
5. A 5xx OR NETWORK ERROR IS NOT A FAILED PAYOUT. Verify with
   GET /v1/payouts/{id} before retrying, or retry with the same key.
6. NEVER EDIT A LIVE DESTINATION'S BANK DETAILS. PATCH restates it in full, and
   changing account_number/bank_code/wallet_address/network/phone_number drops
   its approval and clears `is_default`. Only `name` and `is_default` are safe.
   To change a bank account: create a NEW destination and use its id.
7. DELETE IS A SOFT DELETE and silently clears `is_default`, which stops that
   currency's automatic payouts. Promote the replacement first.

## The second model: a balance per payee
Everything above pays out of the API key's own balance. To give each payee their
own balance, own destinations and own payout history (contractors, sellers,
drivers you owe a running balance), create a recipient account per payee.

1. POST /v1/accounts
   {"contact_email": ..., "country": "NG", "entity_type": "individual",
    "configuration": {"recipient": {"capabilities": {
      "payouts": {"requested": true}, "transfers": {"requested": true}}}}}
   Omit `configuration` and the account can NEVER be paid out to.
2. POST /v1/accounts/{id} with `persons` and `payout_destination` together in
   ONE call to take it live. `account_name` IS required on this path, unlike
   POST /v1/payouts/destinations where it is ignored.
3. POST /v1/transfers {"destination": "acct_...", "amount", "currency"} to fund
   it. Send NO X-Account-Id: a transfer debits whoever is authenticated, so this
   moves money from your balance to theirs. One currency, no conversion.
4. POST /v1/payouts with `X-Account-Id: acct_...` to pay out of their balance.

DO NOT set a payout schedule on a recipient account. A recipient holds no
payment-accepting capability, so it never collects its own sales and its balance
is always transfer-funded. Schedules move SETTLED CUSTOMER COLLECTIONS only, and
transfer-credited balances are not eligible, so a schedule here pays nothing on
any interval and reports no error. Payouts for a recipient are always an explicit
POST /v1/payouts. Schedules require the `merchant` configuration, which is a
different and longer onboarding; do not add it unless the user asks for payees
who collect payments directly.

## Also available
- Cross-currency: POST /v1/payouts/quotes, then send `quote_id` instead of
  `amount`. Quotes expire, so quote immediately before paying.
- Funding a payee's balance: POST /v1/transfers
  {"destination": "acct_...", "amount", "currency"}. Debits whoever is
  authenticated, so send NO X-Account-Id to fund from your own balance.

## Errors
- DESTINATION_PENDING_REVIEW (400): could not be auto-verified; a human must clear it.
- DESTINATION_REJECTED (400): never becomes usable; create a new one.
- INSUFFICIENT_BALANCE (400): balance < amount + fee. Response states the shortfall.
- ORGANIZATION_IN_DEBT (400): a balance in SOME currency is negative, which blocks
  EVERY payout for the account. Surface it; do not retry in a loop.
- QUOTE_EXPIRED / QUOTE_REQUIRED (400): re-quote and retry immediately.
- IDEMPOTENCY_IN_PROGRESS (409): same key in flight; retry after a short delay.
- VALIDATION_ERROR (422): inspect `errors[]`.

## Build
1. `addPayee(bankCode, accountNumber, currency)` -> pd_id. Show the returned
   `account_name` back for confirmation before saving.
2. `payPayee(pdId, amount, yourPayoutId)` -> create with
   `Idempotency-Key = yourPayoutId`; persist `pay_...` and `total_debited`.
3. Webhook handler for `payout.paid` / `payout.failed` marking your record
   terminal. This is the only place a payout may be declared settled.
Log the `pay_...` id and your `reference` on every payout.
```

## Next steps

* [Payout schedules](/guides/payouts/payout-schedules): automatic payouts, minimums and anchor times.
* [Payouts in Connect](/connect/payouts): giving each payee their own balance.
* [Payouts](/connect/payouts): the full payout reference.
* [Payout networks](/connect/payout-networks): which rails and currencies pay out today.
