# Payouts — Swaps API

Pay an invoice — fiat payouts funded with crypto.

**Jump to an operation:**

- [GET /payouts](#list-payouts-available)
- [POST /payouts](#create-a-draft-payout-available)
- [GET /payouts/eligibility](#read-the-holders-payout-eligibility-available)
- [GET /payouts/{id}](#get-a-payout-available)
- [POST /payouts/{id}/beneficiary](#add-the-beneficiary-to-a-draft-payout-available)
- [GET /payouts/{id}/funding_instructions](#read-the-current-funding-instructions-available)
- [POST /payouts/{id}/funding_instructions](#create-or-refresh-the-funding-instructions-available)
- [GET /payouts/{id}/wallet_funding_quote](#quote-funding-this-payout-from-the-swaps-wallet-available)
- [GET /payouts/{id}/receipt](#get-the-settlement-receipt-available)
- [GET /payouts/{id}/attempts](#list-a-payouts-funding-attempts-available)
- [GET /payouts/{id}/events](#list-a-payouts-events-available)
- [POST /payouts/{id}/cancel](#cancel-a-payout-available)
- [POST /payouts/{id}/mark_sent](#record-the-payers-self-reported-ive-sent-it-available)
- [POST /payouts/{id}/replace](#create-a-replacement-payout-available)

> Generated from the bundled OpenAPI contract. The HTML page and this Markdown twin share the same processed operation descriptions and status annotations.

<a id="list-payouts-available"></a>
## `GET /payouts` — List payouts — available

**Operation ID:** `payouts.list`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable

List the caller's payouts, newest first. `status_group` mirrors the dashboard's own bucketing (`needs_you` = `draft` ∪ `awaiting_funds` ∪ `paid_with_shortfall` ∪ `needs_attention:true`; `in_progress` = `funds_received` ∪ `processing` ∪ `paid`; `done` = `settled` ∪ `failed` ∪ `returned` ∪ `expired` ∪ `cancelled`; RESOURCE-MODEL §2.2 v2 amendments). Do not promise a query by date, status or recipient beyond this grouping.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |
| `status_group` | query | no | `needs_you` \| `in_progress` \| `done` | This resource's own bucket set (`needs_you`, `in_progress`, `done`), shared with payroll runs and orders only. |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of payouts. | `application/json` [PayoutList](/reference/~schemas.md#payoutlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="create-a-draft-payout-available"></a>
## `POST /payouts` — Create a draft payout — available

**Operation ID:** `payouts.create`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.write` · **Test mode:** unavailable

Creates a `draft` payout for an external invoice. Nothing is charged and no money moves — that happens later, at `POST /v1/payouts/{id}/funding_instructions`. Two different things are stamped onto the payout at this moment as `capability_snapshot` (RESOURCE-MODEL §2.2 v2 amendments, K4c): the capability-contract `version`, and the corridor's sender identity (`sender_display`/`legal_entity_name`) — neither is re-resolved later even if the corridor's terms or the customer's account naming change (see `PayoutCapabilitySnapshot`'s own description for why). `eta_seconds`/`minimum` are the OPPOSITE: presentation-only facts resolved fresh from the LIVE catalog on every read, never frozen. Refused with `409 capability_unavailable` when `corridor_id` is not currently executable — do not call this before `GET /v1/capabilities?product=payouts` (or `list_capabilities`) reports the corridor available, and do not retry on that error. `payer_type` is optional: Swaps derives it (`business` only when the account and the payer's Bridge customer are both business) and refuses a different value with `422 payer_type_mismatch`. `source_chain` is optional too: a draft may be created before the payer picks a chain (`source_chain_chosen: false`); the chain is then sent when funding.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Request body

Required: **yes**

| Media type | Schema |
| --- | --- |
| `application/json` | [PayoutCreateRequest](/reference/~schemas.md#payoutcreaterequest) |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `201` | The new draft payout. | `application/json` [Payout](/reference/~schemas.md#payout) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | Idempotency-Key reused with a different body (`idempotency_error`), or the corridor cannot serve this request right now (`capability_unavailable` — do not retry). | `application/json` [Error](/reference/~schemas.md#error) |
| `413` | — | `PayloadTooLarge` |
| `422` | The request is well-formed but cannot be processed. `payer_type_mismatch`: the `payer_type` sent differs from the payer type Swaps derives for the account the request acts for; `details.expected` is that type, `details.received` the value sent. Nothing is created: resend without `payer_type`. | `application/json` [Error](/reference/~schemas.md#error) |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | A kill switch is thrown or a dependency is out (`temporarily_unavailable`), or the payer type could not be verified (`payer_type_unverifiable`); nothing was created. Retry after `Retry-After`, with the same `Idempotency-Key`. | `application/json` [Error](/reference/~schemas.md#error) |

---

<a id="read-the-holders-payout-eligibility-available"></a>
## `GET /payouts/eligibility` — Read the holder's payout eligibility — available

**Operation ID:** `payouts.eligibility.get`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable

The holder's KYC status and, per corridor, whether a payout can be created and funded right now, with the blocker named when it cannot. This is availability, not authorization: the server re-checks at create and again at the funding money boundary (RESOURCE-MODEL §0.10). Treat `in_review` and `gathering_no_path` as different answers — never tell someone to submit a form for the second.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The holder's payout eligibility. | `application/json` [PayoutEligibility](/reference/~schemas.md#payouteligibility) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="get-a-payout-available"></a>
## `GET /payouts/{id}` — Get a payout — available

**Operation ID:** `payouts.get`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable

Return one payout the caller owns. A payout paid with a shortfall is terminal, has no receipt by design, and never becomes `settled` on its own — read `provider_paid_amount`/`invoice_shortfall_amount` rather than waiting for a receipt. Cross-account ids answer `404 not_found`, never `403`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The payout. | `application/json` [Payout](/reference/~schemas.md#payout) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="add-the-beneficiary-to-a-draft-payout-available"></a>
## `POST /payouts/{id}/beneficiary` — Add the beneficiary to a draft payout — available

**Operation ID:** `payouts.beneficiary.set`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.write` · **Test mode:** unavailable · **Money boundary:** per-transaction human confirmation required

Adds the destination bank account to a `draft` payout — once, ever; there is no update and no second call (RESOURCE-MODEL §2.2). Raw bank details cross this boundary once and are then held only by the provider, so this operation is **excluded from MCP entirely** — no generated tool can take an IBAN or account number as an argument, because most MCP clients log tool arguments verbatim (D-6). Structurally REST-only: business key or agent over HTTPS, never a tool call.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Request body

Required: **yes**

| Media type | Schema |
| --- | --- |
| `application/json` | [PayoutBeneficiaryRequest](/reference/~schemas.md#payoutbeneficiaryrequest) |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `201` | The payout, now carrying its masked beneficiary. | `application/json` [Payout](/reference/~schemas.md#payout) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | Idempotency-Key reused with a different body, or the payout is not `draft` (a beneficiary already exists, or the payout has moved past the point one can be added). | `application/json` [Error](/reference/~schemas.md#error) |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="read-the-current-funding-instructions-available"></a>
## `GET /payouts/{id}/funding_instructions` — Read the current funding instructions — available

**Operation ID:** `payouts.get_funding_instructions`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable · **Money boundary:** per-transaction human confirmation required

Read the funding instructions already created for this payout, without side effects. `404 not_found` is reserved for a payout that does not exist or is not yours — identical body either way, no oracle. A payout that exists and is yours but was never funded (`draft`, or any other status `POST` was never called from) answers `409 payout_funding_instructions_not_ready` instead, naming the status. To create the provider transfer, or to refresh the estimate, call `POST` on this same path.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The current funding instructions. | `application/json` [PayoutFundingInstructions](/reference/~schemas.md#payoutfundinginstructions) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | `payout_funding_instructions_not_ready` — this payout exists and is yours, but has never been funded (no provider transfer exists yet for its current status), so there is nothing to read. | `application/json` [Error](/reference/~schemas.md#error) |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="create-or-refresh-the-funding-instructions-available"></a>
## `POST /payouts/{id}/funding_instructions` — Create or refresh the funding instructions — available

**Operation ID:** `payouts.fund`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.write` · **Test mode:** unavailable · **Money boundary:** per-transaction human confirmation required

The money boundary. On the first call, resolves a deposit address and creates the provider transfer; every later call on the same payout is a safe, idempotent refresh that returns the same address with a re-priced estimate — never a new address. Show this to a human and get per-transaction confirmation before anything is sent; never chain it from a quote or call it unattended.

**Fund ordering** (RESOURCE-MODEL §2.2 invariants — fixed, never reordered): recover an already-succeeded provider operation, if one exists, before doing anything else → re- resolve the corridor from scratch (a capability read taken at create time never authorizes a transfer by itself) → verify the payer against a **fresh** provider customer read → price the transfer in USD, failing **closed** if pricing is unavailable → check the per-payer cap → check the Travel-Rule ceiling. Refused with `409 capability_unavailable` if the corridor is blocked at any of these checks — do not retry. The cap reads the payer type live (the account and the fresh provider customer), never the stored `payer_type`; a refresh of a payout stored `business` re-checks it and answers `403` above it.

**One open transfer per deposit address (2026-10-28).** The funding transfer is a flexible USDC transfer of the payer's own Bridge customer on the source chain, at the deposit address that customer's sells of USDC on that chain use too, and a sell takes the first deposit that arrives there, whatever its amount. So once the source chain is known (the caller's, or the server's for `funding_source: swaps_wallet`) and the corridor is re-resolved, the call refuses before any Bridge call while a sell of the payer's USDC on that chain holds the address: `409 deposit_in_use` (`sideEffectFree: true`) — `error.details.order_id` names an open sell order (send to it, or cancel it with `POST /v1/orders/{id}/cancel`, first); a sell still being created, or not yet confirmed by Bridge, is named by nothing (try again later). Another payout of the payer that is still open on that chain (waiting for its deposit, or its transfer still being created) refuses it the same way, named by `error.details.payout_id`: fund or cancel that payout first. This payout itself never does, so a retry of this call is not refused by its own unfinished transfer. It is not asked when the payout's transfer already exists at Bridge (an earlier attempt's, or the refresh of an `awaiting_funds` payout). When the check cannot be read the answer is `503 temporarily_unavailable` with `Retry-After`, side-effect free: nothing was created, retry with the same `Idempotency-Key`. The other way round, while a payout's funding waits for its deposit, a sell of USDC on that chain is refused (`orders.create`, `409 deposit_in_use`).

**The 1% fee law**: the Swaps fee is exactly 1% of the **gross source amount**, computed by dividing — `gross = invoice_amount × 10000 / (10000 − fee_bps)` — never `invoice_amount × 1.01`. A stored `fee_bps` that disagrees with the live fee contract is refused here, at fund, even if it was accepted at create. `deposit_address` is returned case-preserved, verbatim; `amount` is always an **estimate** of the source amount, never an exact total — do not represent it as final. It is the provider quote rounded **up** to the cent plus a buffer of at most 0.05 % (`buffer`), so the recipient receives the full invoice; a receipt above the invoice by at most 0.05 % of it plus 10 minor units (25 for MXN) settles (`settled_amount` = what was paid). `paid_with_shortfall` is a terminal payout status: it produces no receipt and no success notification, and never becomes `settled`.

Returns `503 temporarily_unavailable` (with `Retry-After`), not `provider_error`, when the source-amount quote or the deposit address cannot be resolved right now — this is a transient kill-switch condition, not a permanent block on the corridor.

**Optional body** (additive; an absent body is the implicit fund above, unchanged). `source_chain` picks the USDC chain the payer funds from — accepted only while no provider transfer exists yet and only among the corridor's executable source chains; it is persisted in the same transaction as the transfer claim, and any later different value is `409 payout_source_chain_locked`. A payout created without `source_chain` needs it here (or `funding_source: swaps_wallet`): without one the call is `400 payout_source_chain_missing` (`param: source_chain`) and nothing is changed; Swaps never picks a chain for another wallet. `funding_source: swaps_wallet` («Wallet balance», behind the `payouts_wallet_funding` kill switch) runs the same fund ordering, then prepares ONE unsigned, non-custodial Tempo→Relay send from the payer's own Swaps wallet to exactly this deposit address on the chain the server picks (the first of base, arbitrum, ethereum that is both executable for the corridor and open for the wallet): Relay `EXACT_OUTPUT` for exactly `amount` (the same quote rounded up to the cent plus the buffer), no Swaps fee on the leg. The response then carries `funding_source: swaps_wallet` and `wallet_send_intent`, whose `send_instructions.steps[]` the holder signs with the wallet passkey and records through `POST /v1/wallet/send_intents/{id}/source_tx`. Swaps never signs. Preparing the unsigned intent is not evidence that funds were sent or that the payout settled; the holder must sign and the provider must confirm the outcome. The send is handed to ONE session: the call claims it for the calling dashboard session (the verified sign-in's session; its status becomes `awaiting_signature`), so one signature is one transfer. A repeat call from the same session while that send is unsigned and unexpired returns it; another session gets `409 wallet_funding_claimed` and no send, with `details.retry_after` — when a fresh send can be prepared (30 minutes after it expires, if it is never broadcast). A claim is never handed to another session, also after it expires; sign only a send this call handed to your session. Use a fresh random `Idempotency-Key` per session: a replay (same key and body) returns the stored answer, send included, to any session of the account. Once its source transaction is recorded, or Relay has seen it, the attempt answers `409 wallet_funding_in_flight` forever — as it does for 30 minutes after an unsigned send expires (it may still be broadcast) and when the payout is already marked sent with no recorded wallet send. A send Relay refunded to the wallet is superseded by the next call. Nothing is prepared when the wallet balance does not cover the send (`409 wallet_balance_short`, `details.short_by`). `swaps_wallet` is dashboard-only: the wallet's passkey holder signs in a dashboard session, so a business key or an agent is refused with `409 wallet_funding_unavailable` (`wallet_not_provisioned`), and so is a sign-in that carries no session to hand the send to (`session_unbound`); `PAYOUTS_ENABLED` off refuses it too (`flag_off`), because a wallet send is new money. Whatever the body, an attempt funded from the wallet answers with `funding_source: swaps_wallet` (the unsigned `wallet_send_intent` only to the dashboard session), also for 30 minutes after its send expires. Without a body or with `funding_source: external_wallet`, a linked wallet send that was signed or seen by Relay (and not refunded) is `409 wallet_funding_in_flight`, and an unsigned one that can still be signed (until 30 minutes after it expires) is `409 wallet_funding_pending` with `details.retry_after` — never a second funding path while the first can still move. While a cancel of the payout is in progress, `funding_source: swaps_wallet` is `409 payout_not_fundable` and no send is handed out. The payout's settlement is still decided only by what the provider pays out.

**Refund address** («Another wallet» only; PI-REFUND-ADDR-1-T). `refund_address` is where Bridge returns the USDC if this transfer can't be completed — an address on the funding chain that the payer controls. Swaps vets it before any provider call: EVM only and never the zero address, a mixed-case address must carry a valid EIP-55 checksum (`422 payout_refund_address_invalid`); not the Swaps fee wallet, nor a deposit address Swaps issued for payouts, payment links, wallet funding, Relay deposits, wallet off-ramps or Bridge transfers (`422 payout_refund_address_not_allowed`); a Swaps Wallet address only as every inbound door admits one — on Tempo, with a key Swaps can confirm (`422 destination_rail_keyless` on another chain; while no funding chain is chosen, `422 destination_wallet_disabled`, `422 destination_wallet_key_reported_lost`, `422 destination_wallet_signer_at_risk` or `422 destination_wallet_signer_unconfirmed`); evidence that cannot be read refuses (`503 destination_risk_unverifiable`, nothing changed). Those wallet codes go only to the account owner's own wallet: someone else's Swaps Wallet answers `422 destination_not_accepted` for any of them, or `503 destination_check_unavailable` when the wallet or key-mark lookup itself failed — never the reason. With `funding_source: swaps_wallet` it is `400 payout_refund_address_unsupported`. On the first fund it is sent with the transfer as Bridge's `return_instructions.address`; a retry before the transfer exists must send the same `refund_address` (or none, if the first call sent none: `409 payout_refund_address_locked` otherwise), and if Bridge refuses the address there the transfer is not created and the payout can no longer be funded (`422 payout_refund_address_invalid`; create a new payout). Once the transfer exists, a new address is sent to Bridge under one claimed provider operation after a fresh read of the transfer, and only while Bridge reports it `awaiting_funds` (`409 payout_refund_address_locked` otherwise, nothing stored). The same address again changes nothing. It is recorded, and returned as `refund_address`, only once Bridge's own response carries it. Without one, Swaps has no refund address on file: a deposit Bridge cannot deliver may then wait in `missing_return_policy`, where the payout reads `processing` with `needs_attention` and `failure_code: missing_return_policy`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Request body

Required: **no**

| Media type | Schema |
| --- | --- |
| `application/json` | [PayoutFundRequest](/reference/~schemas.md#payoutfundrequest) |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The funding instructions — created on the first call, refreshed on every later one. | `application/json` [PayoutFundingInstructions](/reference/~schemas.md#payoutfundinginstructions) |
| `400` | The request is malformed or fails validation — fix the argument and retry. Also `payout_source_chain_missing` (`param: source_chain`) and `payout_refund_address_unsupported` (`param: refund_address`: a refund address with `funding_source: swaps_wallet`); nothing was changed. | `application/json` [Error](/reference/~schemas.md#error) |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | Idempotency-Key reused with a different body; the corridor cannot serve this payout any longer (`capability_unavailable`, resolved fresh at this call — do not retry); the payout's own status genuinely never funds (`payout_not_fundable`, e.g. `settled`/`cancelled`/`failed`); or it is `awaiting_funds` with no provider transfer on record (`payout_transfer_missing` — a data anomaly, not a policy refusal; contact support rather than retrying). With a body: `payout_source_chain_locked` (a different `source_chain` after funding started); `wallet_funding_unavailable` (`details.reason`: `flag_off` — `payouts_wallet_funding` or `PAYOUTS_ENABLED` off, `wallet_not_provisioned` — also every non-dashboard caller, `session_unbound` — the sign-in carries no session, `wallet_paused`, `route_unavailable` — «Another wallet» still works); `wallet_funding_claimed` (the payout's wallet send was handed to another session to sign — no send in the answer; `details.retry_after`, RFC 3339, is when a fresh send can be prepared if it is never broadcast; retry then under a new `Idempotency-Key`); `wallet_balance_short` (`details.short_by`, `wallet_send_estimate`, `wallet_balance`, `required_delivery` — no send prepared); `wallet_funding_in_flight` (a wallet send for this attempt was already signed or seen by Relay — never a second one; the previous unsigned send is still inside its 30-minute broadcast grace; or the payout is already marked sent from another wallet; without a body or with `funding_source: external_wallet`: a wallet send for this payout was signed or seen by Relay and not refunded); `wallet_funding_pending` (no body or `funding_source: external_wallet` while an unsigned wallet send for this payout can still be signed — `details.retry_after`, RFC 3339, is when it no longer can; retry then under a new `Idempotency-Key`, nothing was changed); `payout_not_fundable` with `funding_source: swaps_wallet` also while a cancel of the payout is in progress (no send is handed out); `payout_refund_address_locked` (the refund address cannot change now: Bridge no longer reports the transfer `awaiting_funds`, could not confirm its state, did not accept or confirm the change, answered it with other transfer terms, or an earlier change of this payout is unresolved — nothing was stored; or the transfer is still being created with the `refund_address` the first call sent, or none — retry with that); `deposit_in_use` (2026-10-28, `sideEffectFree: true`: a sell of the payer's USDC on the source chain holds the deposit address the funding transfer would use — `error.details.order_id` names an open sell order: send to it or cancel it first; with no order named, a sell is still being created or confirmed by Bridge: try again later; or another payout of the payer still open on that chain holds it — `error.details.payout_id` names it: fund or cancel it first; nothing was created). | `application/json` [Error](/reference/~schemas.md#error) |
| `413` | — | `PayloadTooLarge` |
| `422` | The request is well-formed but cannot be carried out (`invalid_request`). `payout_refund_address_invalid`: not an EVM address, the zero address, or a mixed-case address with a bad EIP-55 checksum — nothing was created; or Bridge refused it while creating the transfer — no transfer was created and the payout can no longer be funded, create a new payout. `payout_refund_address_not_allowed`: the Swaps fee wallet or a deposit address Swaps issued for payouts, payment links, wallet funding, Relay deposits, wallet off-ramps or Bridge transfers. `destination_rail_keyless`: a Swaps Wallet address, which nothing can sign for off Tempo. `destination_wallet_disabled`, `destination_wallet_key_reported_lost`, `destination_wallet_signer_at_risk`, `destination_wallet_signer_unconfirmed`: a Swaps Wallet that is disabled, whose key was reported lost, whose signer is at risk or whose key cannot be confirmed, while no funding chain is chosen — to the owner of that wallet only. `destination_not_accepted`: any of them for a Swaps Wallet that is not the account owner's own; it names no reason. `param: refund_address` on each. | `application/json` [Error](/reference/~schemas.md#error) |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | The source-amount quote or the deposit address could not be resolved right now (`temporarily_unavailable`), or the payer type could not be verified for the cap (`payer_type_unverifiable`) — retry after `Retry-After`. Do not send funds while this is the answer. For `funding_source: swaps_wallet` also: `relay_quote_failed` (Relay cannot guarantee the delivery), `wallet_state_unverifiable` (the wallet balance, signer or send state could not be read — without a body or with `funding_source: external_wallet` too, then nothing was changed) and `test_mode_unsupported` (Relay has no sandbox). With `refund_address`: `destination_risk_unverifiable` (the address could not be vetted; nothing was changed), `destination_check_unavailable` (its wallet or key-mark lookup failed; nothing was changed; `Retry-After: 5`) and `temporarily_unavailable` when Bridge did not confirm a refund address change in time (nothing stored; retry with the same address under a new `Idempotency-Key`). Before the first transfer exists, also `temporarily_unavailable` when the check for an open sell on the source chain could not be read (2026-10-28, side-effect free: nothing was created; retry after `Retry-After` with the same `Idempotency-Key`). | `application/json` [Error](/reference/~schemas.md#error) |

---

<a id="quote-funding-this-payout-from-the-swaps-wallet-available"></a>
## `GET /payouts/{id}/wallet_funding_quote` — Quote funding this payout from the Swaps wallet — available

**Operation ID:** `payouts.wallet_funding_quote.get`

> **Status:** Available · **Callers:** dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable

Side-effect-free read behind the «Pay with» step: whether «Wallet balance» can fund this payout right now, on which chain, what must arrive at the deposit address (the funding estimate rounded up to the cent plus at most 5 bps), what leaves the wallet including the Relay leg's own cost, the wallet's spendable USDC.e and whether it covers the send. No transfer is created and nothing is written; before the payout is funded the figures are an estimate that `payouts.fund` re-quotes. `available: false` carries a `reason` (`flag_off` — also while `PAYOUTS_ENABLED` is off, `wallet_not_provisioned` — also every non-dashboard caller, `wallet_paused`, `route_unavailable`, `test_mode`, `marked_sent` — the payout is already marked sent with no recorded wallet send) and null figures. Dashboard-only: the wallet balance is never shown to a business key. Show «Another wallet» whatever this answers.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The wallet-funding quote, or why Wallet balance is unavailable. | `application/json` [PayoutWalletFundingQuote](/reference/~schemas.md#payoutwalletfundingquote) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | `payout_not_fundable` (the payout's status never funds, or a cancel of it is in progress) or `wallet_funding_in_flight` (a wallet send for this payout was already signed). | `application/json` [Error](/reference/~schemas.md#error) |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | `payout_source_quote_unavailable`, `relay_quote_failed` or `wallet_state_unverifiable` — retry after `Retry-After`. | `application/json` [Error](/reference/~schemas.md#error) |

---

<a id="get-the-settlement-receipt-available"></a>
## `GET /payouts/{id}/receipt` — Get the settlement receipt — available

**Operation ID:** `payouts.receipt.get`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable

Exists only once a payout has reached `settled`. A payout `paid_with_shortfall` is terminal and by design never produces one — `404 not_found` is the correct, expected answer there, not an error to retry past.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The settlement receipt. | `application/json` [PayoutReceipt](/reference/~schemas.md#payoutreceipt) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="list-a-payouts-funding-attempts-available"></a>
## `GET /payouts/{id}/attempts` — List a payout's funding attempts — available

**Operation ID:** `payouts.attempts.list`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** unavailable

List the source-chain funding attempts made against this payout, newest first.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of funding attempts. | `application/json` [PayoutAttemptList](/reference/~schemas.md#payoutattemptlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="list-a-payouts-events-available"></a>
## `GET /payouts/{id}/events` — List a payout's events — available

**Operation ID:** `payouts.events.list`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.read` · **Test mode:** sandbox

List this payout's immutable event timeline, newest first. This is the only timestamp source for the dashboard's 4-step tracker ladder — there are no separate ladder-step columns anywhere else in the contract (RESOURCE-MODEL §2.2 v2 amendments, "required"). Test mode: sandbox (K11-6, §4.2 drift, was declared `full`) — a test payout's events are written by DEV handlers into DEV's own `api_events` outbox; a prod-local read would return an empty page forever.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of payout events. | `application/json` [PayoutEventList](/reference/~schemas.md#payouteventlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="cancel-a-payout-available"></a>
## `POST /payouts/{id}/cancel` — Cancel a payout — available

**Operation ID:** `payouts.cancel`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.write` · **Test mode:** unavailable · **Money boundary:** per-transaction human confirmation required

Cancel a payout that has not yet received funds. Re-reads the live provider transfer first and refuses if money may already be moving — never describe this as a way to claw back a payment in flight; there is no tool for that, only support. Calling it twice is safe.

A wallet send prepared for this payout («Wallet balance», `payouts.fund` with `funding_source: swaps_wallet`) is signed by the holder outside Swaps, so while it can still be signed — until it expires, and for 30 minutes after that — cancel is refused with `409 wallet_funding_pending` and `details.retry_after`: wait until then, there is no earlier way. Once nothing can be signed, the payout's unsigned wallet sends are closed first, and only then is the provider transfer cancelled; if they cannot be closed nothing else is touched (`503 wallet_funding_cancel_failed`).

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The cancelled payout. | `application/json` [Payout](/reference/~schemas.md#payout) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | Idempotency-Key reused with a different body, or money may already be moving on this payout — re-read it before deciding anything. `wallet_funding_in_flight`: a wallet send toward this payout's deposit address was signed or seen by Relay, so its transfer is kept. `wallet_funding_pending`: an unsigned wallet send prepared for this payout can still be signed; `details.retry_after` (RFC 3339) is when that window ends and cancel can be retried under a new `Idempotency-Key` — nothing was changed. | `application/json` [Error](/reference/~schemas.md#error) |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | A kill switch is thrown or a dependency is out (`temporarily_unavailable`, `payout_cancel_retryable`, `payout_cancel_commit_pending`) — retry after `Retry-After`. `wallet_funding_cancel_failed`: the unsigned wallet send prepared for this payout could not be closed, so the provider transfer was not touched and the payout is still active (retry after `Retry-After`); without `Retry-After`, an earlier attempt had already cancelled the provider transfer and the payout needs attention — contact support, do not retry. | `application/json` [Error](/reference/~schemas.md#error) |

---

<a id="record-the-payers-self-reported-ive-sent-it-available"></a>
## `POST /payouts/{id}/mark_sent` — Record the payer's self-reported "I've sent it" — available

**Operation ID:** `payouts.mark_sent`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.write` · **Test mode:** unavailable

Records the payer's own claim that the funding transfer was sent. This is a self-report, `authoritative: false` (RESOURCE-MODEL §3) — it never advances `status` on its own and is never a substitute for the funds-received signal the provider webhook produces. REST-only; not curated as an MCP tool, since an agent should never manufacture this claim on a person's behalf.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The payout, with the self-report recorded. | `application/json` [Payout](/reference/~schemas.md#payout) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | — | `Conflict` |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="create-a-replacement-payout-available"></a>
## `POST /payouts/{id}/replace` — Create a replacement payout — available

**Operation ID:** `payouts.replace`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payouts.write` · **Test mode:** unavailable

Creates a new `draft` payout carrying this one's corridor and invoice details forward, for a payout that failed, expired or was cancelled before funding — there is **no update** on `payouts`; the flow is always cancel (or a terminal failure) plus a fresh create (RESOURCE-MODEL §2.2). Emits `payout.replaced` on the old payout and `payout.created_as_replacement` on the new one, linking both ids. REST-only, no MCP tool: creating a replacement is a deliberate, reviewed action, never one an agent should chain automatically off a failure.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `201` | The new replacement payout, in `draft`. | `application/json` [Payout](/reference/~schemas.md#payout) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | Idempotency-Key reused with a different body, or this payout is not in a replaceable state (only pre-funding `failed`, `expired` and `cancelled` payouts may be replaced). | `application/json` [Error](/reference/~schemas.md#error) |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |
