Payouts
Outbound transfers to external addresses: send funds to an external address; list payouts; what a payout would cost and what would arrive — without making it…
/v1/payoutsAuthorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Query Parameters
1 <= value <= 10025Opaque cursor from the previous page's next_cursor.
uuidValue in
- "ethereum"
- "bsc"
- "polygon"
- "arbitrum"
- "base"
- "avalanche"
- "tron"
- "solana"
- "ton"
- "bitcoin"
- "dogecoin"
- "optimism"
- "linea"
- "zksync"
- "scroll"
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/payouts"{ "object": "list", "has_more": true, "next_cursor": "string", "data": [ { "object": "payout", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ref": "MOLN-7F3A9C21B04E", "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e", "chain": "ethereum", "asset": "USDT", "asset_logo_url": "string", "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4", "amount": "1500000000000000000", "amount_decimal": "string", "fee_amount": "1500000000000000000", "to_address": "string", "memo": "string", "status": "queued", "reference": "string", "tx_hash": "string", "explorer_url": "string", "network_fee": "21000000000000", "network_fee_decimal": "string", "network_fee_asset": "string", "network_fee_usd": "string", "amount_usd": "string", "source": "auto", "from_wallet_id": "8b9c84c1-d66f-45a0-b01d-9f87581202d6", "release_at": "2019-08-24T14:15:22Z", "waiting_on": "string", "failure_code": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z" } ]}/v1/payoutsReturns 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.
Authorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Header Parameters
REQUIRED. A retry without one can send funds twice, so the request is rejected outright.
length <= 255Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/payouts" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "chain": "ethereum", "asset": "USDT", "decimals": 0, "amount": "string", "to_address": "string" }'{ "object": "payout", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ref": "MOLN-7F3A9C21B04E", "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e", "chain": "ethereum", "asset": "USDT", "asset_logo_url": "string", "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4", "amount": "1500000000000000000", "amount_decimal": "string", "fee_amount": "1500000000000000000", "to_address": "string", "memo": "string", "status": "queued", "reference": "string", "tx_hash": "string", "explorer_url": "string", "network_fee": "21000000000000", "network_fee_decimal": "string", "network_fee_asset": "string", "network_fee_usd": "string", "amount_usd": "string", "source": "auto", "from_wallet_id": "8b9c84c1-d66f-45a0-b01d-9f87581202d6", "release_at": "2019-08-24T14:15:22Z", "waiting_on": "string", "failure_code": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z"}/v1/payouts/quoteThe 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.
Authorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/payouts/quote" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e", "chain": "ethereum", "asset": "USDT", "decimals": 0, "amount": "string", "to_address": "string" }'{ "object": "payout_quote", "chain": "string", "asset": "string", "decimals": 0, "amount": "string", "amount_decimal": "string", "amount_usd_cents": "string", "network_fee": { "amount": "string", "amount_decimal": "string", "asset": "string", "decimals": 0, "usd_cents": "string", "estimated": true }, "network_fee_asset": "string", "network_fee_paid_by": "molnpay", "network_fee_held_cents": "string", "network_fee_markup_cents": "string", "network_fee_markup_bps": 0, "service_fee_cents": "string", "plan": "string", "total_debit_cents": "string", "receives": "string", "receives_decimal": "string", "balance_after_cents": "string", "balance_short": true, "requires_approval": true, "auto_approve_usd": "string", "daily_remaining_usd": "string", "exceeds_daily_cap": true, "available": "1500000000000000000", "available_decimal": "string", "funds_sufficient": true, "delay_seconds": 0, "to_address": "string", "memo": "string"}/v1/payouts/settingsAuthorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/payouts/settings"{ "object": "payout_settings", "delay_seconds": 0, "max_delay_seconds": 0}/v1/payouts/settingsA 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.
Authorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X PUT "https://example.com/v1/payouts/settings" \ -H "Content-Type: application/json" \ -d '{ "delay_seconds": 600 }'{ "object": "payout_settings", "delay_seconds": 0, "max_delay_seconds": 0}/v1/payouts/{id}Authorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Path Parameters
uuidResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/payouts/497f6eca-6276-4993-bfeb-53cbbbba6f08"{ "object": "payout", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ref": "MOLN-7F3A9C21B04E", "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e", "chain": "ethereum", "asset": "USDT", "asset_logo_url": "string", "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4", "amount": "1500000000000000000", "amount_decimal": "string", "fee_amount": "1500000000000000000", "to_address": "string", "memo": "string", "status": "queued", "reference": "string", "tx_hash": "string", "explorer_url": "string", "network_fee": "21000000000000", "network_fee_decimal": "string", "network_fee_asset": "string", "network_fee_usd": "string", "amount_usd": "string", "source": "auto", "from_wallet_id": "8b9c84c1-d66f-45a0-b01d-9f87581202d6", "release_at": "2019-08-24T14:15:22Z", "waiting_on": "string", "failure_code": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z"}/v1/payouts/{id}/cancelReleases 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.
Authorization
apiKey Authorization: Bearer mp<64 hex> for live, mptest<64 hex> for test.
The token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.
Send it in the Authorization header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and Referer headers, and a key that lands in any of those is a leaked key.
A key's KIND (checkout, payout or wallet) is a property of the key, not of the string: it is chosen at creation, returned by GET /v1/ping, and decides which routes the key may reach.
The full key is shown exactly once, at creation. No endpoint can return it afterwards. Moving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration.
In: header
Path Parameters
uuidResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/payouts/497f6eca-6276-4993-bfeb-53cbbbba6f08/cancel"{ "object": "payout", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "ref": "MOLN-7F3A9C21B04E", "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e", "chain": "ethereum", "asset": "USDT", "asset_logo_url": "string", "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4", "amount": "1500000000000000000", "amount_decimal": "string", "fee_amount": "1500000000000000000", "to_address": "string", "memo": "string", "status": "queued", "reference": "string", "tx_hash": "string", "explorer_url": "string", "network_fee": "21000000000000", "network_fee_decimal": "string", "network_fee_asset": "string", "network_fee_usd": "string", "amount_usd": "string", "source": "auto", "from_wallet_id": "8b9c84c1-d66f-45a0-b01d-9f87581202d6", "release_at": "2019-08-24T14:15:22Z", "waiting_on": "string", "failure_code": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z"}