# Payouts (/reference/payouts)

## GET /v1/payouts

`GET /v1/payouts`

### Parameters

- `limit` (query, optional, integer)
- `cursor` (query, optional, string) — Opaque cursor from the previous page's `next_cursor`.
- `customer_id` (query, optional, string (uuid))
- `chain` (query, optional, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
- `status` (query, optional, string)
- `reference` (query, optional, string)

### Responses

- `200` — Payouts.
  - `object` (required, string)
  - `data` (required, array)
  - `has_more` (required, boolean)
  - `next_cursor` (optional, string | null) — Opaque. Pass as `cursor` for the next page. Do not construct or parse it.
  - `data` (optional, array)
    - `object` (required, string)
    - `id` (required, string (uuid))
    - `ref` (required, string) — The MolnPay ID of this payout — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
    - `customer_id` (optional, string | null (uuid))
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (optional, string)
    - `asset_logo_url` (optional, string | null) — Decoration; see `Asset.logo_url`.
    - `asset_id` (optional, string (uuid))
    - `amount` (required, string) — Amount to send, atomic units.
    - `amount_decimal` (optional, string | null) — `amount` in whole units. Present on `GET /v1/payouts` rows.
    - `fee_amount` (optional, string) — Fee charged to the customer, same asset.
    - `to_address` (required, string)
    - `memo` (optional, string | null)
    - `status` (required, string, one of: queued, pending_approval, approved, rejected, broadcast, confirmed, failed, canceled) — `pending_approval` means it exceeded your auto-approve threshold and awaits a human. That is a safety feature, not an error.
    - `reference` (optional, string | null)
    - `tx_hash` (optional, string | null) — Populated once broadcast.
    - `explorer_url` (optional, string | null) — Block-explorer link for `tx_hash`. Null before broadcast, and on a chain with no explorer we publish.
    - `network_fee` (optional, string | null) — What the transaction cost the network, in the chain's NATIVE coin, atomic units. Null until it confirms. Paid on chain by the operator and never deducted from `amount`; a live merchant account is charged it in USD from the prepaid balance at cost (`payout_network_fee`).
    - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
    - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
    - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
    - `amount_usd` (optional, string | null) — Report-only estimate.
    - `source` (optional, string, one of: auto, customer_account) — Where it may leave from, as accepted. `customer_account`: only the customer's own deposit address (topped up by a reverse sweep first when it held less).
    - `from_wallet_id` (optional, string | null (uuid)) — The one wallet it was told to leave from, when named.
    - `release_at` (optional, string | null (date-time)) — Not signed before this moment — the cancel window the payout was accepted with (your account's `delay_seconds`, or the request's own). Its funds are already reserved. Until then `POST /v1/payouts/{id}/cancel` succeeds. Null = sent as soon as it is accepted.
    - `waiting_on` (optional, string | null) — Why a queued payout has not gone yet: `delay` (its cancel window is open, see `release_at`), `account_topup` (the shortfall is being moved into the customer's address), `consolidation`. Null when it is not waiting.
    - `failure_code` (optional, string | null) — On `payout.canceled` when MolnPay canceled it at the end of its cancel window: `destination_revoked` (the address was taken off your allowlist meanwhile) or `api_key_revoked` (the key that made it was revoked). Reserve and fee are returned.
    - `created_at` (optional, string (date-time))
    - `updated_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Send funds to an external address

`POST /v1/payouts`

Returns **202** — accepted, not sent. The transaction hash arrives via the `payout.submitted` and `payout.confirmed` webhooks.

`Idempotency-Key` is REQUIRED: a retried POST without one can send funds twice.

`decimals` must match the asset's configured scale. This is checked because it is where the BSC trap bites — `"1000000"` is 1 USDT on Tron and 0.000000000001 USDT on BSC.

Above your auto-approve threshold the payout enters `pending_approval` and waits for a human. So does a payout that takes today's (UTC) total past your daily automatic line ($1,000 by default) — there is no daily cap, nothing is refused for the total, it is reviewed. That is a safety feature, not an error.

A destination on your allowlist is usable 24 hours after it was added (`active_at` on the destination); before then the payout is refused with `destination_not_allowlisted`, and the message says when it opens.

**Cancel window.** With a delay (your account's `delay_seconds`, see `PUT /v1/payouts/settings`, or this request's `delay_seconds`) the payout is accepted and its funds reserved at once, then held until `release_at`; until then `POST /v1/payouts/{id}/cancel` takes it back. A delay never defers a refusal: a customer whose balance cannot cover the payout gets `402 insufficient_funds` now, and nothing is created. When the window closes the payout is canceled instead of sent if its destination left your allowlist or its API key was revoked meanwhile (`payout.canceled` with `failure_code`).

**Console sessions** (not API keys) must confirm every withdrawal with a second factor: `passkey` (an assertion over a challenge from `POST /v1/merchant/passkeys/challenge`) or `totp_code`. A session with neither factor enrolled is refused (`403 second_factor_not_enrolled`). API keys ignore both fields.

### Parameters

- `Idempotency-Key` (header, required, string) — REQUIRED. A retry without one can send funds twice, so the request is rejected outright.

### Request body (required)

- `customer_id` (optional, string (uuid)) — Whose balance is debited.
- `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
- `asset` (required, string)
- `decimals` (required, integer) — Must equal the asset's configured decimals. See GET /v1/chains.
- `amount` (required, string) — Atomic units, as a string. No decimal point.
- `to_address` (required, string)
- `memo` (optional, string | null) — Required on TON.
- `reference` (optional, string) — Your own reference.
- `metadata` (optional, object)
- `source` (optional, string, one of: auto, customer_account) — Where the coins may leave from. `auto` (default unless your account's default says otherwise): any sender that covers it. `customer_account`: ONLY this customer's own deposit address on the chain — the brokerage / exchange shape. When that address holds less than the payout (a client who made money), MolnPay first moves the shortfall into it (a reverse sweep) and the payout waits (`waiting_on: account_topup`) until that lands, then leaves from the customer's address. The customer's BALANCE must still cover it. EVM chains and Tron only; elsewhere `422 strict_source_unsupported`.
- `from_wallet_id` (optional, string (uuid)) — One of THIS customer's wallets on the chain to send from. Implies `source: customer_account`.
- `delay_seconds` (optional, integer) — Hold it this many seconds before signing, cancelable meanwhile. Can lengthen your account's `delay_seconds`, never shorten it. Console withdrawals are instant unless they ask.
- `passkey` (optional, object) — Console sessions: the browser's WebAuthn `AuthenticationResponseJSON` over a challenge from `POST /v1/merchant/passkeys/challenge`. Single use.
- `totp_code` (optional, string) — Console sessions: a current authenticator code, when not using a passkey. Each code is spent once.

### Responses

- `202` — Accepted.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this payout — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, string)
  - `asset_logo_url` (optional, string | null) — Decoration; see `Asset.logo_url`.
  - `asset_id` (optional, string (uuid))
  - `amount` (required, string) — Amount to send, atomic units.
  - `amount_decimal` (optional, string | null) — `amount` in whole units. Present on `GET /v1/payouts` rows.
  - `fee_amount` (optional, string) — Fee charged to the customer, same asset.
  - `to_address` (required, string)
  - `memo` (optional, string | null)
  - `status` (required, string, one of: queued, pending_approval, approved, rejected, broadcast, confirmed, failed, canceled) — `pending_approval` means it exceeded your auto-approve threshold and awaits a human. That is a safety feature, not an error.
  - `reference` (optional, string | null)
  - `tx_hash` (optional, string | null) — Populated once broadcast.
  - `explorer_url` (optional, string | null) — Block-explorer link for `tx_hash`. Null before broadcast, and on a chain with no explorer we publish.
  - `network_fee` (optional, string | null) — What the transaction cost the network, in the chain's NATIVE coin, atomic units. Null until it confirms. Paid on chain by the operator and never deducted from `amount`; a live merchant account is charged it in USD from the prepaid balance at cost (`payout_network_fee`).
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `amount_usd` (optional, string | null) — Report-only estimate.
  - `source` (optional, string, one of: auto, customer_account) — Where it may leave from, as accepted. `customer_account`: only the customer's own deposit address (topped up by a reverse sweep first when it held less).
  - `from_wallet_id` (optional, string | null (uuid)) — The one wallet it was told to leave from, when named.
  - `release_at` (optional, string | null (date-time)) — Not signed before this moment — the cancel window the payout was accepted with (your account's `delay_seconds`, or the request's own). Its funds are already reserved. Until then `POST /v1/payouts/{id}/cancel` succeeds. Null = sent as soon as it is accepted.
  - `waiting_on` (optional, string | null) — Why a queued payout has not gone yet: `delay` (its cancel window is open, see `release_at`), `account_topup` (the shortfall is being moved into the customer's address), `consolidation`. Null when it is not waiting.
  - `failure_code` (optional, string | null) — On `payout.canceled` when MolnPay canceled it at the end of its cancel window: `destination_revoked` (the address was taken off your allowlist meanwhile) or `api_key_revoked` (the key that made it was revoked). Reserve and fee are returned.
  - `created_at` (optional, string (date-time))
  - `updated_at` (optional, string (date-time))
- `400` — `idempotency_key_required`, `decimals_mismatch`, or a malformed amount.
- `401` — Invalid credential; or, for console sessions, `second_factor_required`, `invalid_totp`, `code_already_used`, `passkey_invalid`, `challenge_expired`.
- `402` — `insufficient_funds` — the customer's balance cannot cover it; nothing was created, delay or not. Retryable after a deposit.
- `403` — The key lacks the scope or is the wrong kind, its IP allowlist rejected the source, or signing is disabled; or, for console sessions, `second_factor_not_enrolled`, `totp_not_enrolled`, `no_passkey`.
- `409` — `idempotency_key_reuse` or `idempotency_in_progress`.
- `422` — `invalid_address`, `self_payout_forbidden`, `destination_not_allowlisted`, `chain_not_enabled`, `no_master_wallet`, `custody_intake_pending` (the coins are on their way into custody — retry in about a minute); with `source: customer_account`: `strict_source_unsupported` (not on this chain), `customer_account_missing` (the customer has no deposit address on this chain), `from_wallet_invalid`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## What a payout would cost and what would arrive — without making it

`POST /v1/payouts/quote`

The same body as `POST /v1/payouts`, minus the idempotency key. Nothing is created, reserved or locked, and no key that can send money is needed — `payouts:read` is enough.

**The amount is not reduced by either fee.** The plan's payout fee is a USD debit against your prepaid operational balance, and the network fee is MolnPay's — a withdrawal leaves the customer's keyless account through `factory.execute()`, which the chain operator calls and pays for. So `receives` equals `amount`, and `total_debit_cents` is what comes off the balance.

Hard refusals throw the same errors the create would — including `no_master_wallet` and `custody_intake_pending` — so you find out about a bad address or a chain with no sender here rather than at submit. The soft gates — needing approval (including past the daily automatic line, `exceeds_daily_cap`, which means review and never a refusal), an exhausted balance — are reported as FIELDS, because they are figures a person should see before deciding.

`network_fee` is an estimate and `null` where we cannot honestly make one. It is never zero: gas is never free, and a zero on that line reads as if it were.

### Request body (required)

- `customer_id` (required, string (uuid))
- `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
- `asset` (required, string)
- `decimals` (required, integer) — Must equal the asset's configured decimals.
- `amount` (required, string) — Atomic units, as a string.
- `to_address` (required, string)
- `memo` (optional, string | null)
- `reference` (optional, string)
- `delay_seconds` (optional, integer)

### Responses

- `200` — The quote.
  - `object` (required, string)
  - `chain` (required, string)
  - `asset` (required, string)
  - `decimals` (required, integer)
  - `amount` (required, string) — Atomic, echoed back after validation.
  - `amount_decimal` (required, string)
  - `amount_usd_cents` (optional, string | null) — Report-only value at spot. `null` when the coin cannot be priced right now — which also means no fee is charged and the payout goes to review.
  - `network_fee` (optional, object | null) — An ESTIMATE of what the transaction will cost the network, in the chain's own coin, worst-cased the way the signer worst-cases. `null` where we cannot honestly estimate it (Solana and the UTXO chains price a send from the sender's own state, which a quote does not know). Never a zero standing in for null: gas is never free.
    - `amount` (required, string)
    - `amount_decimal` (required, string)
    - `asset` (required, string)
    - `decimals` (required, integer)
    - `usd_cents` (optional, string | null)
    - `estimated` (required, boolean)
  - `network_fee_asset` (optional, string | null) — The coin gas is paid in, even when the amount could not be estimated.
  - `network_fee_paid_by` (required, string, one of: molnpay, merchant) — Who the network fee falls on. `merchant` for a live merchant account: the gas is paid on chain by the operator and never deducted from the coin, and the merchant pays it in USD from the prepaid balance — `network_fee_held_cents` now, settled to the real gas at confirmation with the difference returned or charged. `molnpay` for a test project or a contract integrator.
  - `network_fee_held_cents` (optional, string | null) — What is held from the prepaid balance for the network fee at acceptance: the gas at cost plus our markup (`network_fee_markup_cents`). `null` for a test project or a contract integrator (`network_fee_paid_by` = `molnpay`), and when this chain cannot be estimated (the real figure is charged at confirmation).
  - `network_fee_markup_cents` (optional, string | null) — Of `network_fee_held_cents`, our markup — the card's 10% by default, `0` on a raw-cost account. Booked as its own `network_markup` line and settled on the real gas at confirmation.
  - `network_fee_markup_bps` (optional, integer) — The markup rate in basis points (1000 = 10%); 0 on a raw-cost account or when MolnPay pays the gas.
  - `service_fee_cents` (required, string) — The plan's payout fee, drawn from the prepaid operational balance in USD — not from the coin. The same for a withdrawal from your own wallet (the own-wallet customer) as for a payout to anyone else; `0` on an at-cost account.
  - `plan` (optional, string | null)
  - `total_debit_cents` (required, string) — Everything that comes off the prepaid balance for this payout: the service fee plus the held network fee.
  - `receives` (required, string) — Atomic amount that lands at the destination.
  - `receives_decimal` (required, string)
  - `balance_after_cents` (optional, string | null) — The prepaid balance once the fee is taken. `null` for a tenant that is not a merchant account.
  - `balance_short` (optional, boolean) — The fee is more than the balance left — the create refuses with `billing_balance_exhausted`.
  - `requires_approval` (required, boolean) — A person at MolnPay reviews it before it is sent: it is above the auto-approve ceiling, or it takes today's payouts past the daily automatic line (`exceeds_daily_cap`). Review is never a refusal — the payout is accepted and reserved, and goes out once approved.
  - `auto_approve_usd` (optional, string)
  - `daily_remaining_usd` (optional, string | null) — What is left today (UTC) before payouts stop being sent automatically and wait for review instead. There is no daily cap: past this line a payout is still accepted, it is only reviewed first. `null` where no line is set.
  - `exceeds_daily_cap` (optional, boolean) — This payout takes today's total past the daily automatic line, so it will be reviewed (`requires_approval` is true). The name is kept for compatibility; nothing is refused for it.
  - `available` (optional, string) — What the customer holds of this coin to pay from right now, atomic units. A reading: the create is what decides.
  - `available_decimal` (optional, string) — `available` in whole units.
  - `funds_sufficient` (optional, boolean) — `available` covers `amount`. When false, the create is refused with `402 insufficient_funds` straight away — a delay never defers that refusal.
  - `delay_seconds` (optional, integer) — How long the payout would be held, cancelable, before signing (0 = instant). See `PUT /v1/payouts/settings`.
  - `to_address` (required, string)
  - `memo` (optional, string | null) — The memo this payout may carry — the allow-listed one when the destination has one.
- `400` — `invalid_body`, `decimals_mismatch`, or a malformed amount.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `customer_not_found`.
- `422` — `invalid_address`, `self_payout_forbidden`, `destination_not_allowlisted`, `chain_not_enabled`, `no_master_wallet`, `custody_intake_pending` (the coins are on their way into custody — retry in about a minute).
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Your account's payout cancel window

`GET /v1/payouts/settings`

### Responses

- `200` — The settings.
  - `object` (required, string)
  - `delay_seconds` (required, integer) — Every payout made with an API key is held this long, cancelable, before it is signed. 0 = instant (the default).
  - `max_delay_seconds` (required, integer) — The longest allowed: 86400 (24 hours).
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Set how long every API-key payout is held before signing

`PUT /v1/payouts/settings`

A window in which your integration, or an owner or admin in the console, can cancel a payout that looks wrong (`POST /v1/payouts/{id}/cancel`). 0 = instant (the default); at most 86400 (24 hours).

The funds are reserved when the payout is accepted, so a held payout is always one that can be paid — an empty balance is refused with `402 insufficient_funds` at once. Applies to payouts made with an API key; a withdrawal a person confirms in the console stays instant unless it asks for a delay. A change applies to the next payout; one already accepted keeps its `release_at`.

Needs a payout key with `payouts:create`, or an owner or admin in the console. Every change is audited.

### Request body (required)

- `delay_seconds` (required, integer)

### Responses

- `200` — Updated.
  - `object` (required, string)
  - `delay_seconds` (required, integer) — Every payout made with an API key is held this long, cancelable, before it is signed. 0 = instant (the default).
  - `max_delay_seconds` (required, integer) — The longest allowed: 86400 (24 hours).
- `400` — `invalid_body` — not an integer from 0 to 86400.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## GET /v1/payouts/{id}

`GET /v1/payouts/{id}`

### Parameters

- `id` (path, required, string (uuid))

### Responses

- `200` — The payout.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this payout — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, string)
  - `asset_logo_url` (optional, string | null) — Decoration; see `Asset.logo_url`.
  - `asset_id` (optional, string (uuid))
  - `amount` (required, string) — Amount to send, atomic units.
  - `amount_decimal` (optional, string | null) — `amount` in whole units. Present on `GET /v1/payouts` rows.
  - `fee_amount` (optional, string) — Fee charged to the customer, same asset.
  - `to_address` (required, string)
  - `memo` (optional, string | null)
  - `status` (required, string, one of: queued, pending_approval, approved, rejected, broadcast, confirmed, failed, canceled) — `pending_approval` means it exceeded your auto-approve threshold and awaits a human. That is a safety feature, not an error.
  - `reference` (optional, string | null)
  - `tx_hash` (optional, string | null) — Populated once broadcast.
  - `explorer_url` (optional, string | null) — Block-explorer link for `tx_hash`. Null before broadcast, and on a chain with no explorer we publish.
  - `network_fee` (optional, string | null) — What the transaction cost the network, in the chain's NATIVE coin, atomic units. Null until it confirms. Paid on chain by the operator and never deducted from `amount`; a live merchant account is charged it in USD from the prepaid balance at cost (`payout_network_fee`).
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `amount_usd` (optional, string | null) — Report-only estimate.
  - `source` (optional, string, one of: auto, customer_account) — Where it may leave from, as accepted. `customer_account`: only the customer's own deposit address (topped up by a reverse sweep first when it held less).
  - `from_wallet_id` (optional, string | null (uuid)) — The one wallet it was told to leave from, when named.
  - `release_at` (optional, string | null (date-time)) — Not signed before this moment — the cancel window the payout was accepted with (your account's `delay_seconds`, or the request's own). Its funds are already reserved. Until then `POST /v1/payouts/{id}/cancel` succeeds. Null = sent as soon as it is accepted.
  - `waiting_on` (optional, string | null) — Why a queued payout has not gone yet: `delay` (its cancel window is open, see `release_at`), `account_topup` (the shortfall is being moved into the customer's address), `consolidation`. Null when it is not waiting.
  - `failure_code` (optional, string | null) — On `payout.canceled` when MolnPay canceled it at the end of its cancel window: `destination_revoked` (the address was taken off your allowlist meanwhile) or `api_key_revoked` (the key that made it was revoked). Reserve and fee are returned.
  - `created_at` (optional, string (date-time))
  - `updated_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — Not found.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Cancel a payout that has not been signed yet

`POST /v1/payouts/{id}/cancel`

Releases the reserved funds back to the customer's available balance and returns the payout fee. Always succeeds while the payout is inside its cancel window (`release_at` in the future). Once signing has begun the chain owns the transaction and cancellation is impossible.

### Parameters

- `id` (path, required, string (uuid))

### Responses

- `200` — Canceled.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `ref` (required, string) — The MolnPay ID of this payout — `MOLN-` and twelve hex characters, shown on the receipt in the console and quoted to support. Stable for the life of the record; unlike `id`, safe to put in front of a person.
  - `customer_id` (optional, string | null (uuid))
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (optional, string)
  - `asset_logo_url` (optional, string | null) — Decoration; see `Asset.logo_url`.
  - `asset_id` (optional, string (uuid))
  - `amount` (required, string) — Amount to send, atomic units.
  - `amount_decimal` (optional, string | null) — `amount` in whole units. Present on `GET /v1/payouts` rows.
  - `fee_amount` (optional, string) — Fee charged to the customer, same asset.
  - `to_address` (required, string)
  - `memo` (optional, string | null)
  - `status` (required, string, one of: queued, pending_approval, approved, rejected, broadcast, confirmed, failed, canceled) — `pending_approval` means it exceeded your auto-approve threshold and awaits a human. That is a safety feature, not an error.
  - `reference` (optional, string | null)
  - `tx_hash` (optional, string | null) — Populated once broadcast.
  - `explorer_url` (optional, string | null) — Block-explorer link for `tx_hash`. Null before broadcast, and on a chain with no explorer we publish.
  - `network_fee` (optional, string | null) — What the transaction cost the network, in the chain's NATIVE coin, atomic units. Null until it confirms. Paid on chain by the operator and never deducted from `amount`; a live merchant account is charged it in USD from the prepaid balance at cost (`payout_network_fee`).
  - `network_fee_decimal` (optional, string | null) — `network_fee` in whole units.
  - `network_fee_asset` (optional, string | null) — The native coin `network_fee` is denominated in — BNB, ETH, TRX.
  - `network_fee_usd` (optional, string | null) — `network_fee` in US dollars **at the price it held when the fee was paid**, not at spot — frozen when we first priced it, so an old receipt keeps saying what it cost that day. Null where we could not price it (a chain we have no quote for, a moment beyond the price history, a failed lookup) — never "it was free".
  - `amount_usd` (optional, string | null) — Report-only estimate.
  - `source` (optional, string, one of: auto, customer_account) — Where it may leave from, as accepted. `customer_account`: only the customer's own deposit address (topped up by a reverse sweep first when it held less).
  - `from_wallet_id` (optional, string | null (uuid)) — The one wallet it was told to leave from, when named.
  - `release_at` (optional, string | null (date-time)) — Not signed before this moment — the cancel window the payout was accepted with (your account's `delay_seconds`, or the request's own). Its funds are already reserved. Until then `POST /v1/payouts/{id}/cancel` succeeds. Null = sent as soon as it is accepted.
  - `waiting_on` (optional, string | null) — Why a queued payout has not gone yet: `delay` (its cancel window is open, see `release_at`), `account_topup` (the shortfall is being moved into the customer's address), `consolidation`. Null when it is not waiting.
  - `failure_code` (optional, string | null) — On `payout.canceled` when MolnPay canceled it at the end of its cancel window: `destination_revoked` (the address was taken off your allowlist meanwhile) or `api_key_revoked` (the key that made it was revoked). Reserve and fee are returned.
  - `created_at` (optional, string (date-time))
  - `updated_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — Not found.
- `409` — `payout_not_cancelable` — signing has already begun.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.