Create Payout Multiple
Creates up to 50 payouts in one request.
Send an array of payout objects with the same fields as Create Payout Single. Items are processed one by one, each in its own transaction: a refused item is reported on its own index and the rest of the batch still goes out. The HTTP status is 200 when every item was created, 207 when some were created and some refused, and 400 when every item was refused; in all three cases the body carries summary and one entry per item in results, in the order sent. If every item fails validation, the answer is instead a top-level invalid_parameters with no results. More than 50 items returns 400 max_payouts_exceeded and nothing is processed.
https://bridge.bifrostcrypto.com/v1/payoutpayout:write(a new key with no list is read-only)"In Heimdall's forge, multiple payments are woven together like the threads of fate. Each transaction carries the combined strength of Mjolnir and flows as a unified river through the realms."Brokkr & Sindri, Divine Forgers
Headers
| Key | Value | Description |
|---|---|---|
X-Bifrost-Invoke | {{api_key}} | Private API key |
Content-Type | application/json | - |
Idempotency-Key | {{uuid}} | Optional, up to 255 characters (a longer key is ignored and the batch runs without idempotency). Scoped to your account and kept for 24 hours: resending the same key returns the original status and body verbatim instead of reprocessing, even if the body differs, so use a new key for a new batch. While the first request is still running (up to 15 minutes) a resend gets 409 idempotency_key_in_progress. Not stored when the request fails before any payout is attempted (top-level 4xx) or is interrupted (500), so a retry with the same key is processed again; reference_id uniqueness still prevents a second payout for items already created. |
Request Body
[
{
"wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"crypto_currency": "USDT",
"fiat_currency": "USD",
"crypto_amount": "100.50",
"process_by": "crypto_amount",
"reference_id": "order_12345",
"network": "Ethereum",
"feetakenfromamount": 0,
"webhook_url": "https://your-site.com/webhook",
"customer_info": "customer123"
},
{
"wallet": "0x8Ba1f109551bD432803012645Ac136ddd64DBA72",
"crypto_currency": "BNB",
"crypto_amount": "0.5",
"process_by": "crypto_amount",
"reference_id": "order_12346",
"network": "BinanceSmartChain",
"feetakenfromamount": 1,
"webhook_url": "https://your-site.com/webhook"
}
]cURL Example
curl -X POST "https://bridge.bifrostcrypto.com/v1/payout" \
-H "X-Bifrost-Invoke: YOUR_API_KEY" \
-H "Idempotency-Key: 3f1c8b2a-9d54-4f6e-8c31-7b0a2e5d9411" \
-H "Content-Type: application/json" \
-d '[
{
"wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"crypto_currency": "USDT",
"fiat_currency": "USD",
"crypto_amount": "100.50",
"process_by": "crypto_amount",
"reference_id": "order_12345",
"network": "Ethereum",
"feetakenfromamount": 0,
"webhook_url": "https://your-site.com/webhook",
"customer_info": "customer123"
},
{
"wallet": "0x8Ba1f109551bD432803012645Ac136ddd64DBA72",
"crypto_currency": "BNB",
"crypto_amount": "0.5",
"process_by": "crypto_amount",
"reference_id": "order_12346",
"network": "BinanceSmartChain",
"feetakenfromamount": 1,
"webhook_url": "https://your-site.com/webhook"
}
]'Response Example
{
"message": "payment_processing_completed",
"summary": {
"total_requested": 2,
"successful": 2,
"failed": 0
},
"results": [
{
"index": 0,
"reference_id": "order_12345",
"internal_reference": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"crypto_amount": "100.5",
"fiat_amount": "100.50000000",
"crypto_currency": "USDT",
"fiat_currency": "USD",
"network_fee": "0.00100000",
"wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"memo": null,
"network": "Ethereum",
"process_by": "crypto_amount",
"status": "created"
},
{
"index": 1,
"reference_id": "order_12346",
"internal_reference": "9b2e4c1a-7d3f-4e8b-a1c6-5f0d2e9b7a43",
"crypto_amount": "0.4924",
"fiat_amount": "300.00000000",
"crypto_currency": "BNB",
"fiat_currency": "USD",
"network_fee": "0.00010000",
"wallet": "0x8Ba1f109551bD432803012645Ac136ddd64DBA72",
"memo": null,
"network": "BinanceSmartChain",
"process_by": "crypto_amount",
"status": "created"
}
]
}Network aliases
On create, network also accepts these aliases. Case, spaces, hyphens and underscores are ignored (ERC-20, erc_20 and erc20 are the same). Responses, webhooks and listings always return the canonical name. bnb means BinanceSmartChain (for opBNB send OpBnb) and arb means Arbitrum (for ArbitrumOne send ArbitrumOne).
| Network | Aliases |
|---|---|
Ethereum | eth, eth-mainnet, erc20 |
Tron | trx, trc20 |
BinanceSmartChain | bnb, bsc, bep20, bnbchain, bnb-smart-chain |
Arbitrum | arb, arbitrum-evm |
Polygon | matic, pol, polygon-pos |
Optimism | op, op-mainnet |
Avalanche | avax, avaxc, avalanche-c |
Solana | sol |
Bitcoin | btc |
Litecoin | ltc |
Monero | xmr |
Xlayer | okb |
Item refusal codes
Codes an entry can carry in results[].error.code. The rest of the batch continues and nothing is debited for the refused item. Codes marked No in reference_id come back without it: match those items by index.
| Code | When | reference_id | What to do |
|---|---|---|---|
invalid_parameters | A field failed validation; details.fields names it. | Yes | Fix the field and resend the item. |
duplicate_payment_reference | reference_id was already used by an earlier payout. | Yes | Use a new reference_id, or check the earlier payout with GET /v1/list-payout. |
duplicate_reference_id_in_request | Two items of the same request share a reference_id. | No | Give each item its own reference_id. |
invalid_crypto_amount · invalid_fiat_amount · fiat_currency_required | The amount or currency required by process_by is missing or invalid. | No | Fix the item and resend it. |
invalid_network_or_withdrawals_disabled · unsupported_token_or_withdrawals_disabled · token_network_mismatch | The token or network does not accept withdrawals for your account. | No | Choose another token or network (GET /v1/summary/tokens). |
token_not_active | The token is not active. | Yes | Choose another token. |
memo_required | The token needs a memo. | No | Send memo. |
invalid_format | The address does not match the network. | Yes | Check the address and network. |
limit_exceeded | Below the token minimum after fees, or above the maximum. | Yes | Adjust the amount. |
amount_does_not_cover_fees | With feetakenfromamount = 1, fees consume the whole amount. | Yes | Increase the amount. |
insufficient_balance | The balance does not cover the item and its fees. | Yes | Top up the balance. |
daily_payout_limit_exceeded | The key's daily payout limit (USD, UTC day) would be exceeded. | Yes | Wait for the next UTC day or ask for a higher limit. |
price_unavailable · daily_payout_limit_unavailable | A quote needed for the item was not available. | Yes | Retry the item later. |
error_fetching_active_tokens | The token catalogue could not be loaded. | No | Retry the item later. |
database_error · internal_error | Failure on our side. | Yes | Retry the item later. |
record_not_found · user_not_found | The account setup is incomplete. | Yes | Contact support. |
Errors
Every error has the shape error.code, error.message and, when useful, error.details. Authentication, scope and rate-limit errors (401, 403, 429) apply to every endpoint and are described under Authentication.
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | json_parse_exception · json_parse_failed | The body is missing or is not valid JSON. | Fix the request before sending it again. |
| 400 | invalid_payment_list | The body is neither a payout object nor a non-empty list. | Fix the request before sending it again. |
| 400 | max_payouts_exceeded | More than 50 payouts in one request. | Split into requests of up to 50. |
| 400 | invalid_parameters | Every item failed validation; details.fields is keyed by index.field. | Fix the request before sending it again. |
| 400 | insufficient_total_balance | The items of one currency add up to more than the available balance. Nothing is debited. | Top up the balance or send fewer payouts. |
| 200 · 207 · 400 | results[].error.code | An item was refused after validation (see the Item refusal codes table). | Fix and resend only the refused items, with a new Idempotency-Key. |
| 404 | user_not_found · record_not_found | The account setup is incomplete. | Contact support. |
| 409 | idempotency_key_in_progress | A request with this Idempotency-Key is still running. | Wait and retry with the same key. |
| 500 | error_fetching_active_currencies | The token catalogue could not be loaded. Nothing was processed. | Nothing was changed. Retry later. |
| 500 | internal_error | Unexpected failure; summary and results show what was created. | Resend only the items counted in summary.not_processed. |
Other responses
Responses that are not the happy path and still need handling in your integration.
Partial success
Returned when at least one item was created and at least one was refused; when every item is refused the same body comes with 400. Every entry in results carries its index, so you can match a failure back to the object you sent; a refused entry has error with code and message, and an item that failed validation lists the reason per field in error.details.fields. A reference_id already used by an earlier payout comes back with duplicate: true, the original payout's internal_reference, its current status (pending, completed, failed or refunded) and its creation time, and no second transfer is made. The same reference_id twice in one request is refused on the later index with duplicate_reference_id_in_request. Created entries carry the full fields shown in the 200 example (shortened here).
{
"message": "payment_processing_completed",
"summary": {
"total_requested": 5,
"successful": 2,
"failed": 3
},
"results": [
{
"index": 0,
"reference_id": "order_12345",
"internal_reference": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "created"
},
{
"index": 1,
"reference_id": "order_12346",
"error": {
"code": "invalid_parameters",
"message": "Invalid parameters provided",
"details": {
"fields": {
"crypto_amount": "Crypto amount must be a positive decimal number"
}
}
}
},
{
"index": 2,
"reference_id": "order_12347",
"internal_reference": "3c9d1e7f-2a4b-4f6c-8e0d-b5a7c3f1e902",
"status": "created"
},
{
"index": 3,
"reference_id": "order_11999",
"internal_reference": "6e1b8f2d-4c7a-4d9e-b3f0-a2c5e8d1f764",
"duplicate": true,
"original_status": "pending",
"original_created_at": "2026-01-16 10:00:00",
"error": {
"code": "duplicate_payment_reference",
"message": "A payout with this reference_id already exists"
}
},
{
"index": 4,
"error": {
"code": "duplicate_reference_id_in_request",
"message": "This reference_id appears more than once in the request"
}
}
]
}Every item invalid
When every item fails validation nothing is processed and there are no results: the reasons come at the top level, in details.fields keyed by index.field. An entry that is not a JSON object is reported as index.item.
{
"error": {
"code": "invalid_parameters",
"message": "Invalid parameters provided",
"details": {
"fields": {
"0.webhook_url": "Webhook URL is required",
"1.crypto_currency": "Invalid crypto currency"
}
}
}
}Request-level errors
Nothing is processed. The codes are listed in the Errors table.
{
"error": {
"code": "max_payouts_exceeded",
"message": "Too many payouts in a single request"
}
}Idempotency-Key still running
The first request with this key has not finished yet. Wait and retry with the same key; do not send the batch again without it. After 15 minutes without an answer the key is freed and the next request with it is processed.
{
"error": {
"code": "idempotency_key_in_progress",
"message": "A request with this Idempotency-Key is still being processed"
}
}Not enough balance for the batch
Checked before anything is debited. Valid items of the same currency are summed using the amount selected by process_by (fees not included), and compared with the available balance after holds. Nothing is created. Fees are checked per item afterwards, so an item can still be refused with insufficient_balance.
{
"error": {
"code": "insufficient_total_balance",
"message": "Insufficient balance to cover all payouts",
"details": {
"crypto_currency": "USDT",
"available": "150.00000000",
"required": "420.500000000000000000"
}
}
}Item refusal codes
Returned on the refused entry; the rest of the batch continues and nothing is debited for it. Every code, and whether it carries reference_id, is in the Item refusal codes table.
{
"index": 2,
"reference_id": "order_12347",
"error": {
"code": "limit_exceeded",
"message": "Amount is outside the allowed limits"
}
}Daily payout limit reached
Only when the key has a daily payout limit configured (in USD). Each payout counts its total debit converted to USD, and the limit covers the UTC calendar day (00:00 to 23:59 UTC), unlike the other dates of the API, which are in UTC−03:00. The item is refused on its index; the rest of the batch continues.
{
"index": 3,
"reference_id": "order_12348",
"error": {
"code": "daily_payout_limit_exceeded",
"message": "Daily payout limit for this API key exceeded"
}
}Amount does not cover fees
Only with feetakenfromamount = 1: fees consume the whole amount, so the recipient would receive nothing. The item is refused on its index; the rest of the batch continues.
{
"index": 1,
"reference_id": "order_12346",
"error": {
"code": "amount_does_not_cover_fees",
"message": "Amount does not cover the fees"
}
}Daily limit cannot be accounted
Only when the key has a daily limit configured and the spend record could not be written. The item is refused on its index; the rest of the batch continues. Safe to resend later with the same reference_id.
{
"index": 3,
"reference_id": "order_12348",
"error": {
"code": "daily_payout_limit_unavailable",
"message": "Daily payout limit could not be checked right now"
}
}Price unavailable
Returned on the item when a conversion price is missing (fiat_amount conversion, or the network fee converted into the token), and also when the key has a daily limit but the token has no USD quote to count the payout. The rest of the batch continues.
{
"index": 2,
"reference_id": "order_12347",
"error": {
"code": "price_unavailable",
"message": "Price information temporarily unavailable"
}
}Interrupted mid-batch
An unexpected failure stops the loop, but the items already created are returned: summary and results stay at the top level next to error. not_processed tells you how many never got that far, so you can resend only those, and the uniqueness of reference_id still protects you if you resend more than you should. The Idempotency-Key is not stored for this response.
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred during payment processing"
},
"summary": {
"total_requested": 50,
"successful": 31,
"failed": 1,
"not_processed": 18
},
"results": [
"..."
]
}