> ## 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.

# Payouts

> Move an account's balance out to a bank account, mobile money wallet, or crypto wallet.

A **payout** moves funds from an account's `available_balance` to an external destination. It works the same from your platform balance and from an account's.

Paying out from an account requires `payouts` to be `active` on that account. See [Capabilities](/connect/capabilities).

<Warning>
  Payouts move money. Creating one debits the balance immediately. There is no cancellation endpoint. Verify the destination and amount before you send the request.
</Warning>

***

## Pay out as an account

Send the account id in `X-Account-Id`. The payout is created against that account's balance and its destinations.

```mermaid theme={"dark"}
flowchart LR
    B["Account<br/>available balance"] -->|payout| D["Bank account,<br/>mobile money,<br/>or crypto wallet"]
```

<Steps>
  <Step title="Register a destination">
    `POST /v1/payouts/destinations` · scope `payouts:write` · [full field reference →](/api-reference/payouts/create-payout-destination)

    `type` is `bank_account`, `mobile_money`, or `crypto_wallet`, and the fields you send depend on which; omit it and it is inferred from the currency. Bank codes come from [List banks](/api-reference/reference/list-reference-banks). The account is looked up at the bank as part of this call, so you do not need to resolve it first.

    The first destination an account registers for a currency becomes that currency's **default** once it is usable, the destination [scheduled and instant payouts](/connect/balances) draw on. Registering a second destination does not change the default; set it explicitly if you want to switch.
  </Step>

  <Step title="Wait for it to become usable">
    A registered destination is not yet a usable one: it goes through review, and only `is_usable: true` can receive money. A bank account whose number **resolves at the bank** clears automatically on registration; anything that cannot be resolved, such as an unresolvable number or a mobile-money or crypto destination with no automated check, waits for a human. Poll [Get Destination](/api-reference/payouts/get-payout-destination) or read `is_usable` from the registration response.
  </Step>

  <Step title="Quote the payout">
    `POST /v1/payouts/quotes` · scope `payouts:write` · [full field reference →](/api-reference/payouts/create-payout-quote)

    Required when `from_currency` and `to_currency` differ. The quote fixes the rate and expires, so create it immediately before the payout.
  </Step>

  <Step title="Create the payout">
    `POST /v1/payouts` · scope `payouts:write` · [full field reference →](/api-reference/payouts/create-payout)
  </Step>

  <Step title="Track it to a terminal status">
    Payouts are asynchronous. Subscribe to `payout.paid` and `payout.failed`, or poll [Get Payout](/api-reference/payouts/get-payout).
  </Step>
</Steps>

***

## Create a payout

```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: WD-20260807-0007" \
  -d '{
    "destination": "pd_7Kq2mNv4XbR9dLc0",
    "amount": "7000.00",
    "reference": "WD-20260807-0007"
  }'
```

```json theme={"dark"}
{
  "id": "pay_4Xr9dLc0mNv7Kq2B",
  "status": "pending",
  "amount": "7000.00",
  "currency": "NGN",
  "source_currency": "NGN",
  "fee": "100.00",
  "total_debited": "7100.00",
  "destination": "pd_7Kq2mNv4XbR9dLc0",
  "reference": "WD-20260807-0007",
  "failure_reason": null,
  "created_at": "2026-08-07T14:30:00.000Z"
}
```

`amount` is a decimal string, in the destination's currency, and is what the destination receives. The fee is charged on top, so the balance has to cover `total_debited`, not `amount`. `reference` is your own identifier and must be unique for the account. `destination` names a registered [payout destination](/api-reference/payouts/create-payout-destination), and the currency, rail and payment method all follow from it; a cross-currency payout passes `quote_id` instead of `amount`.

`status` starts at `pending`, the balance debit already applied, and moves to `processing` once the destination has been submitted. It ends at a terminal `completed` or `failed`. Each value is documented on the [Get Payout](/api-reference/payouts/get-payout) response.

<Info>
  **An account's first payout takes longer.** The first payout an account ever makes is held for a one-time review before it reaches a rail, so it can sit at `pending` for a few minutes longer than later ones. This is per account, not per destination: it is triggered by the account having no completed payout yet, so once one payout reaches `completed`, every later payout, including to a brand-new destination, dispatches without the extra wait. It applies to the accounts you create, including from the dashboard; a recipient-only account you register purely as a payee is not held. Track state from `payout.paid` and `payout.failed` rather than assuming a fixed delay.
</Info>

<Info>
  A payout that fails at the destination is not always moved straight to `failed`. It can be held at `pending` for review instead, with the balance still debited, so `pending` does not always mean newly created. Track state from `payout.paid` and `payout.failed` rather than assuming a fixed delay.
</Info>

<Info>
  Do not treat a network or `5xx` error as proof the payout was not created. Verify with [Get Payout](/api-reference/payouts/get-payout) before retrying, or retry with the same `Idempotency-Key`.
</Info>

***

## Get an account able to pay out

An account needs `payouts` `active` before it can pay out, and a recipient-configuration account is the short path there: it only receives money, so it asks for a name and a destination and nothing else.

* Submit the account holder's **name** and the requirement blocking `transfers` clears, activating it.
* Submit a **payout destination whose account resolves at the bank** and it is approved by the system on the spot, clearing its requirement and activating `payouts`.

So an account with a real, resolvable bank account is live the moment you finish submitting, with no human in the loop. A destination that cannot be resolved waits for a reviewer instead: it sits `pending_review`, and `payouts` stays `restricted` until someone approves it.

<Warning>
  Gate on the capability being `active`, never on an empty requirements list. An account can have nothing left to provide and still be unable to pay out, because its destination has not cleared. See [Capabilities](/connect/capabilities).
</Warning>

Both requirements go in one call:

```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"
      }
    }
  }'
```

A rejected submission saves nothing, not even its valid fields, so fix the field the error names and send the whole object again. See [Requirements](/connect/requirements).

For the full onboarding path, see [Create an account](/connect/guides/create-an-account). For a task-shaped walkthrough of paying someone, see [Payout using API](/guides/payouts/payout-using-api).

***

## Payout destinations

A payout destination is where an account's money leaves to.

| Field    | Notes                                                                                                                                                                                                       |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`   | `bank_account`, `mobile_money`, or `crypto_wallet`. The fields you send depend on which, and it is inferred from the currency when omitted.                                                                 |
| Approval | A bank account whose number resolves is approved automatically on registration. Anything unresolvable waits for a human (`pending_review`), and the capability it gates stays `restricted` until it clears. |
| Default  | The first approved destination for a currency becomes that currency's default, the one scheduled payouts use. Set another as default explicitly to switch.                                                  |
| Currency | A destination holds one currency, and payouts route on it.                                                                                                                                                  |

An account can hold several destinations. Only one is the default per currency; the rest are addressable by id on a payout. You register them two ways, both ending at the same approved row: as a requirement field on the account, which is what activates `payouts` during onboarding, or directly with `POST /v1/payouts/destinations` once the account is live.

***

## Where the money comes from

A payout draws on `available_balance` on the account named in `X-Account-Id`, in the currency the destination is registered in, or, for a cross-currency payout, the source currency the quote names. In a [split payment](/connect/split-payments), that balance is the share you transferred, and it is available as soon as the transfer posts.

<Warning>
  Once an account pays out its balance, you cannot recover it with a transfer. A payout never takes a balance below zero on its own, but a lost dispute can, and while any currency is negative every payout for that account is blocked, not only in that currency. See [Disputes](/connect/disputes). If you may need to reverse a share, transfer it later rather than earlier. See [Split payments](/connect/split-payments).
</Warning>

***

## Errors

Payouts return the standard [error envelope](/errors). Common cases:

* `FORBIDDEN` (403), the account's `payouts` capability is not `active`, or the key lacks `payouts:write`. See [Capabilities](/connect/capabilities).
* `DESTINATION_NOT_FOUND` (404), no destination with that id belongs to this account.
* `DESTINATION_PENDING_REVIEW` (400), the destination has not cleared review yet. Wait for `is_usable`.
* `DESTINATION_REJECTED` (400), the destination was rejected in review and never becomes usable. Register a new one.
* `ORGANIZATION_IN_DEBT` (400), the account has a negative balance in some currency, even one other than `from_currency`. Every payout is blocked until that currency's balance clears. See [Disputes](/connect/disputes).
* `INSUFFICIENT_BALANCE` (400), `available_balance` will not cover `amount` plus the fee. The response states the shortfall. Check the balance and the settlement date. See [Balances](/connect/balances).
* `QUOTE_REQUIRED` (400), the destination's currency differs from the balance being debited, so a `quote_id` is required.
* `QUOTE_EXPIRED` (400), the quote has lapsed. Create a new one and retry immediately.
* `VALIDATION_ERROR` (422), a field failed validation. Inspect `errors[]`.
* `IDEMPOTENCY_IN_PROGRESS` (409), a request with the same `Idempotency-Key` is still in flight. Retry after a short delay; the winner's response is replayed once it lands.

***

## Related

* [Split payments](/connect/split-payments)
* [Balances](/connect/balances)
* [Payout using API](/guides/payouts/payout-using-api)
* [Payouts overview](/guides/payouts/overview)
* [Get Payout](/api-reference/payouts/get-payout)
