Error Codes
Every error code the API returns, grouped by kind, with what to do about each one.
ᚱ
"When the forces of chaos interfere, the gods send clear messages to mortals. Learn the sacred codes that reveal the nature of each obstacle along the path."The Guardians of Bifrost
Error format
Every /v1 error has the same shape, with the HTTP status of the table below. Compare error.code, which is stable snake_case. error.message is free English text for your logs. error.details appears only when there is useful data, such as invalid fields, limits or scopes.
json
{
"error": {
"code": "insufficient_total_balance",
"message": "Insufficient balance to cover all payouts",
"details": {
"crypto_currency": "USDT",
"available": "150.00000000",
"required": "420.50000000"
}
}
}In a POST /v1/payout batch, each refused item carries the same object in results[].error, next to its index and reference_id. The response is 200 when every item succeeded, 207 when some succeeded and some failed, and 400 when none succeeded:
json
{
"message": "payment_processing_completed",
"summary": {
"total_requested": 3,
"successful": 1,
"failed": 2
},
"results": [
{
"index": 0,
"reference_id": "order_1001",
"internal_reference": "BIFROST_REF_123456",
"status": "created"
},
{
"index": 1,
"reference_id": "order_1002",
"error": {
"code": "invalid_parameters",
"message": "Invalid parameters provided",
"details": {
"fields": {
"wallet": "Invalid wallet format"
}
}
}
},
{
"index": 2,
"reference_id": "order_1003",
"error": {
"code": "daily_payout_limit_exceeded",
"message": "Daily payout limit for this API key exceeded"
}
}
]
}- Validation messages always live in error.details.fields, keyed by field name, both at the top level and inside a batch item.
- A duplicate payout item counts as a failure: it carries error.code duplicate_payment_reference plus duplicate, original_status and original_created_at at item level. A batch made only of duplicates answers 400.
- Branch on error.code, never on error.message: messages can be reworded, codes do not change.
- 4xx means the request must change before it is sent again. 5xx means the failure was on our side and the same request may be retried; on POST /v1/payout, the reference_id still protects you from a double payout.
- An unknown /v1 path returns 404 route_not_found as JSON. A client that asks for text/html in Accept receives an HTML page instead.
Authentication and Access Errors
| HTTP Code | Error | Description |
|---|---|---|
| 401 | api_key_missing | The X-Bifrost-Invoke header was not sent. |
| 401 | api_key_invalid | Unknown, revoked or expired API Key. Also returned while the account is suspended. |
| 403 | api_access_denied | API access is disabled for this account. Checked on every request. |
| 403 | email_not_verified | The account e-mail address has not been verified yet. |
| 403 | ip_not_authorized | The source IP is not in the key's whitelist. |
| 400 | invalid_ip | The client IP address could not be determined. |
| 401 | signature_headers_missing | The key requires a signature and X-Bifrost-Signature, X-Bifrost-Timestamp or X-Bifrost-Nonce is missing. |
| 401 | signature_timestamp_invalid | X-Bifrost-Timestamp is not made of digits only (Unix seconds). |
| 401 | signature_timestamp_out_of_range | The timestamp differs from server time by more than 300 seconds. |
| 401 | signature_nonce_length_invalid | The nonce is shorter than 16 or longer than 80 characters. |
| 401 | signature_nonce_charset_invalid | The nonce has characters outside A-Z, a-z, 0-9, underscore and hyphen. |
| 401 | signature_invalid | The signature does not match the canonical string. |
| 401 | signature_replayed | This nonce was already used by this key. |
| 401 | signature_secret_unavailable | The key requires a signature but has no usable secret. Contact support. |
| 403 | scope_denied | The key lacks the scope this endpoint requires. details.required_scopes lists what works and details.granted_scopes what the key has. |
| 429 | rate_limit_exceeded | The key's quota for the current window is used up. Retry-After and details.retry_after_seconds say when to retry. |
| 429 | too_many_failed_authentications | This IP is temporarily blocked after repeated failed authentications. Sent with Retry-After. |
| 401 | user_not_authenticated | The request could not be tied to your API key. Retry it; if it happens again, contact support. |
Request Errors
| HTTP Code | Error | Description |
|---|---|---|
| 404 | route_not_found | No /v1 route matches the path and method. Answered before authentication. |
| 400 | json_parse_exception | The request body is not valid JSON. Any /v1 route. |
| 400 | json_parse_failed | POST /v1/payout: the body decoded to null or false. |
| 400 | invalid_parameters | Invalid or missing parameters. details.fields maps each field to its message (POST /v1/payout with every item invalid, POST /v1/payin, fixed wallets, statements filters). |
| 400 | invalid_payment_list | POST /v1/payout: the body is empty or is neither a payout object nor a list of payouts. |
| 400 | max_payouts_exceeded | POST /v1/payout: more than 50 payouts in one request. |
| 409 | idempotency_key_in_progress | A request with this Idempotency-Key is still running. Wait and retry with the same key. |
| 400 | crypto_amount_required | POST /v1/payin: process_by is crypto_amount and crypto_amount is missing. |
| 400 | fiat_amount_required | POST /v1/payin: process_by is fiat_amount and fiat_amount is missing. |
| 400 | fiat_currency_required | POST /v1/payin: process_by is fiat_amount and fiat_currency is missing. |
| 400 | invalid_page_number | list-payout / list-payin: page below 1. |
| 400 | invalid_per_page_limit | list-payout / list-payin: per_page outside 1 to 100. |
| 400 | invalid_status_filter | list-payout / list-payin: unknown status value. |
| 400 | invalid_network_filter | list-payout / list-payin: unknown network. |
| 400 | invalid_date_from | list-payout / list-payin: date_from is not a date. |
| 400 | invalid_date_to | list-payout / list-payin: date_to is not a date. |
| 400 | invalid_reference_id | check-payout / check-payin: reference shorter than 10 or longer than 756 characters. Statements: reference_id longer than 255. |
| 400 | invalid_pagination | Statements: page or per_page out of range. |
| 400 | invalid_format | Statements: date_from or date_to is not a date (details.field). |
| 400 | invalid_date_range | Statements: date_from is after date_to. |
| 400 | date_range_too_wide | Statements: the range is wider than allowed. details.max_days states the ceiling. |
| 400 | invalid_amount | Statements: amount_from or amount_to is invalid, or amount_from is above amount_to. |
| 400 | missing_parameters | get-price: currency1 or currency2 is missing. |
| 400 | invalid_currency | get-price: currency1 or currency2 is not a valid currency code. |
| 400 | invalid_webhook_url | PUT /v1/fixed-wallets/config: webhook_url points to a disallowed destination. |
Business Errors
| HTTP Code | Error | Description |
|---|---|---|
| 400 | insufficient_total_balance | POST /v1/payout: the items of one currency add up to more than the available balance. Nothing is debited. details carries crypto_currency, available and required. |
| 404 | user_not_found | POST /v1/payout: the account setup is incomplete. Contact support. |
| 404 | record_not_found | The payout or payin looked up by check-payout / check-payin does not exist on this account. Also returned when account data the request needs is missing. |
| 400 | duplicate_reference_id | POST /v1/payin: a payin with this reference_id already exists on the account. |
| 400 | invalid_network_or_deposits_disabled | POST /v1/payin: unknown network, or deposits are disabled on it. |
| 400 | unsupported_token_or_deposits_disabled | POST /v1/payin: unsupported token, or deposits are disabled for it. |
| 400 | token_network_mismatch | POST /v1/payin: the token is not available on this network. |
| 400 | memo_required | POST /v1/payin: this token requires a memo. |
| 400 | amount_out_of_limits | POST /v1/payin: the crypto amount is outside the token's limits. details carries min_allowed and max_allowed. |
| 400 | value_below_minimum | POST /v1/payin with fiat_amount: the converted amount is below the minimum. |
| 400 | value_above_maximum | POST /v1/payin with fiat_amount: the converted amount is above the maximum. |
| 400 | token_not_supported_or_inactive | POST /v1/payin with fiat_amount: the token is not supported or is inactive for conversion. |
| 400 | crypto_amount_does_not_correspond_to_fiat_amount_provided | POST /v1/payin: crypto_amount and fiat_amount were both sent and do not match the current rate. |
| 400 | invalid_parameters_for_process_by | POST /v1/payin: the amounts sent do not fit the chosen process_by. |
| 404 | price_not_found | get-price: there is no price for this currency pair. |
| 403 | feature_disabled | Fixed wallets are not enabled for the account (config, config update and create). |
| 422 | type_not_supported | POST /v1/fixed-wallets: this type is not available for fixed addresses. |
| 403 | limit_not_allowed | POST /v1/fixed-wallets: the account's limit for this type is zero. |
| 409 | limit_reached | POST /v1/fixed-wallets: the account already holds the maximum number of fixed addresses. |
| 409 | no_wallet_available | POST /v1/fixed-wallets: no address of that type is available right now. Retry later. |
Payout Batch Item Errors (results[].error)
| HTTP Code | Error | Description |
|---|---|---|
| 207/400 | invalid_parameters | The item failed validation. error.details.fields maps each field to its message. |
| 207/400 | missing_required_fields | A required field of the item is missing. |
| 207/400 | duplicate_reference_id_in_request | The same reference_id appears earlier in the same batch. |
| 207/400 | duplicate_payment_reference | The reference_id was already used on this account. The item carries duplicate, original_status, original_created_at and the original internal_reference; no second transfer is made. |
| 207/400 | invalid_crypto_amount | process_by is crypto_amount and crypto_amount is missing or invalid. |
| 207/400 | invalid_fiat_amount | process_by is fiat_amount and fiat_amount is missing or invalid. |
| 207/400 | fiat_currency_required | process_by is fiat_amount and fiat_currency is missing. |
| 207/400 | fiat_currency_not_accepted | The fiat_currency is not accepted. |
| 207/400 | memo_required | This token requires a memo. |
| 207/400 | invalid_network_or_withdrawals_disabled | Unknown network, or withdrawals are disabled on it. |
| 207/400 | unsupported_token_or_withdrawals_disabled | Unsupported token, or withdrawals are disabled for it. |
| 207/400 | token_network_mismatch | The token is not available on this network. |
| 207/400 | token_not_active | The token is not active for payouts or for conversion. |
| 207/400 | invalid_format | The wallet address is not valid for the network. |
| 207/400 | limit_exceeded | The amount is outside the token's minimum and maximum (also when the fiat amount converts outside them). |
| 207/400 | amount_does_not_cover_fees | Fees consume the whole amount, so the recipient would receive nothing. |
| 207/400 | crypto_amount_does_not_correspond_to_fiat_amount_provided | crypto_amount and fiat_amount do not match the current rate. |
| 207/400 | price_unavailable | No price to convert the amount, or the key has a daily limit and the token has no USD quote to count the payout. |
| 207/400 | insufficient_balance | The available balance does not cover this item. |
| 207/400 | daily_payout_limit_exceeded | The item would exceed the daily limit configured for the key. |
| 207/400 | daily_payout_limit_unavailable | The key has a daily limit but the spend could not be recorded. Retry the item. |
| 207/400 | error_fetching_active_tokens · error_fetching_active_currencies | The token or currency catalog could not be loaded. Retry the item. |
| 207/400 | user_not_found · record_not_found | Account data the payout needs is missing. |
| 207/400 | internal_error · database_error · transaction_failed · network_unavailable · duplicate_record | Recording the payout failed on our side and was rolled back. Nothing was debited for this item; check it with check-payout or list-payout before resending. |
System Errors
| HTTP Code | Error | Description |
|---|---|---|
| 500 | internal_error | Unexpected failure. On POST /v1/payout, a failure mid-batch also returns summary (with not_processed) and results at the top level, so you can resend only what is missing. |
| 500 | database_error | Statements: the query failed. |
| 500 | error_fetching_active_currencies | POST /v1/payout and POST /v1/payin: the token or fiat catalog could not be loaded. Nothing was processed. |
| 500 | token_type_not_found · receipt_creation_failed · database_transaction_failed | POST /v1/payin: the payin could not be created on our side. |
| 502 | external_processor_error | POST /v1/payin and POST /v1/fixed-wallets: the deposit address could not be created right now. Nothing was created; retry later. |
| 503 | no_processor_configured · no_wallet_available | POST /v1/payin: no processor or no deposit address is available for this token and network right now. Retry later. |
| 503 | price_not_available_for_conversion · price_not_available_for_validation | POST /v1/payin: no price is available to convert or check the amount. Retry later. |
| 503 | service_unavailable | POST /v1/payin and summary endpoints: a dependency is temporarily unavailable. |
| 500 | config_update_failed | PUT /v1/fixed-wallets/config: the configuration could not be saved. |
| 500 | assign_failed | POST /v1/fixed-wallets: the address could not be assigned. |
Best Practices
ᚱ
"The wisdom of the elders teaches that following the right paths ensures a safe and prosperous journey through the nine digital realms."Mímir, Keeper of Ancient Wisdom
Security
Do
- •Store your API Key in environment variables
- •Configure IP Whitelist
- •Verify the X-Bifrost-Signature of every webhook
- •Use HTTPS in all requests
- •Implement request timeouts
Don't
- •Share API Key publicly
- •Commit API Keys to Git
- •Use the same API Key in multiple environments
Performance
- •Statements and summary data can be up to 5 minutes old; avoid polling them more often than that
- •Implement proper pagination
- •Avoid excessive requests (respect rate limit)
- •Process webhooks asynchronously
Idempotency
- •Use a unique, meaningful reference_id: it is the lock against a double payout, and presenting it again returns the original payment instead of creating another
- •Send an Idempotency-Key on batches: if the connection drops, retrying with the same key returns the original response instead of reprocessing
- •POST /v1/payin has no Idempotency-Key: after a timeout resend with the same reference_id, and on duplicate_reference_id fetch the payin with GET /v1/list-payin?reference_id=…
- •Handle 207 and 500 by reading results: both carry what was already created, so you resend only what is missing
- •Handle webhook retries by deduplicating on data.internal_reference together with event, so the same notice is not processed twice