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 CodeErrorDescription
401api_key_missingThe X-Bifrost-Invoke header was not sent.
401api_key_invalidUnknown, revoked or expired API Key. Also returned while the account is suspended.
403api_access_deniedAPI access is disabled for this account. Checked on every request.
403email_not_verifiedThe account e-mail address has not been verified yet.
403ip_not_authorizedThe source IP is not in the key's whitelist.
400invalid_ipThe client IP address could not be determined.
401signature_headers_missingThe key requires a signature and X-Bifrost-Signature, X-Bifrost-Timestamp or X-Bifrost-Nonce is missing.
401signature_timestamp_invalidX-Bifrost-Timestamp is not made of digits only (Unix seconds).
401signature_timestamp_out_of_rangeThe timestamp differs from server time by more than 300 seconds.
401signature_nonce_length_invalidThe nonce is shorter than 16 or longer than 80 characters.
401signature_nonce_charset_invalidThe nonce has characters outside A-Z, a-z, 0-9, underscore and hyphen.
401signature_invalidThe signature does not match the canonical string.
401signature_replayedThis nonce was already used by this key.
401signature_secret_unavailableThe key requires a signature but has no usable secret. Contact support.
403scope_deniedThe key lacks the scope this endpoint requires. details.required_scopes lists what works and details.granted_scopes what the key has.
429rate_limit_exceededThe key's quota for the current window is used up. Retry-After and details.retry_after_seconds say when to retry.
429too_many_failed_authenticationsThis IP is temporarily blocked after repeated failed authentications. Sent with Retry-After.
401user_not_authenticatedThe request could not be tied to your API key. Retry it; if it happens again, contact support.

Request Errors

HTTP CodeErrorDescription
404route_not_foundNo /v1 route matches the path and method. Answered before authentication.
400json_parse_exceptionThe request body is not valid JSON. Any /v1 route.
400json_parse_failedPOST /v1/payout: the body decoded to null or false.
400invalid_parametersInvalid 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).
400invalid_payment_listPOST /v1/payout: the body is empty or is neither a payout object nor a list of payouts.
400max_payouts_exceededPOST /v1/payout: more than 50 payouts in one request.
409idempotency_key_in_progressA request with this Idempotency-Key is still running. Wait and retry with the same key.
400crypto_amount_requiredPOST /v1/payin: process_by is crypto_amount and crypto_amount is missing.
400fiat_amount_requiredPOST /v1/payin: process_by is fiat_amount and fiat_amount is missing.
400fiat_currency_requiredPOST /v1/payin: process_by is fiat_amount and fiat_currency is missing.
400invalid_page_numberlist-payout / list-payin: page below 1.
400invalid_per_page_limitlist-payout / list-payin: per_page outside 1 to 100.
400invalid_status_filterlist-payout / list-payin: unknown status value.
400invalid_network_filterlist-payout / list-payin: unknown network.
400invalid_date_fromlist-payout / list-payin: date_from is not a date.
400invalid_date_tolist-payout / list-payin: date_to is not a date.
400invalid_reference_idcheck-payout / check-payin: reference shorter than 10 or longer than 756 characters. Statements: reference_id longer than 255.
400invalid_paginationStatements: page or per_page out of range.
400invalid_formatStatements: date_from or date_to is not a date (details.field).
400invalid_date_rangeStatements: date_from is after date_to.
400date_range_too_wideStatements: the range is wider than allowed. details.max_days states the ceiling.
400invalid_amountStatements: amount_from or amount_to is invalid, or amount_from is above amount_to.
400missing_parametersget-price: currency1 or currency2 is missing.
400invalid_currencyget-price: currency1 or currency2 is not a valid currency code.
400invalid_webhook_urlPUT /v1/fixed-wallets/config: webhook_url points to a disallowed destination.

Business Errors

HTTP CodeErrorDescription
400insufficient_total_balancePOST /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.
404user_not_foundPOST /v1/payout: the account setup is incomplete. Contact support.
404record_not_foundThe 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.
400duplicate_reference_idPOST /v1/payin: a payin with this reference_id already exists on the account.
400invalid_network_or_deposits_disabledPOST /v1/payin: unknown network, or deposits are disabled on it.
400unsupported_token_or_deposits_disabledPOST /v1/payin: unsupported token, or deposits are disabled for it.
400token_network_mismatchPOST /v1/payin: the token is not available on this network.
400memo_requiredPOST /v1/payin: this token requires a memo.
400amount_out_of_limitsPOST /v1/payin: the crypto amount is outside the token's limits. details carries min_allowed and max_allowed.
400value_below_minimumPOST /v1/payin with fiat_amount: the converted amount is below the minimum.
400value_above_maximumPOST /v1/payin with fiat_amount: the converted amount is above the maximum.
400token_not_supported_or_inactivePOST /v1/payin with fiat_amount: the token is not supported or is inactive for conversion.
400crypto_amount_does_not_correspond_to_fiat_amount_providedPOST /v1/payin: crypto_amount and fiat_amount were both sent and do not match the current rate.
400invalid_parameters_for_process_byPOST /v1/payin: the amounts sent do not fit the chosen process_by.
404price_not_foundget-price: there is no price for this currency pair.
403feature_disabledFixed wallets are not enabled for the account (config, config update and create).
422type_not_supportedPOST /v1/fixed-wallets: this type is not available for fixed addresses.
403limit_not_allowedPOST /v1/fixed-wallets: the account's limit for this type is zero.
409limit_reachedPOST /v1/fixed-wallets: the account already holds the maximum number of fixed addresses.
409no_wallet_availablePOST /v1/fixed-wallets: no address of that type is available right now. Retry later.

Payout Batch Item Errors (results[].error)

HTTP CodeErrorDescription
207/400invalid_parametersThe item failed validation. error.details.fields maps each field to its message.
207/400missing_required_fieldsA required field of the item is missing.
207/400duplicate_reference_id_in_requestThe same reference_id appears earlier in the same batch.
207/400duplicate_payment_referenceThe 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/400invalid_crypto_amountprocess_by is crypto_amount and crypto_amount is missing or invalid.
207/400invalid_fiat_amountprocess_by is fiat_amount and fiat_amount is missing or invalid.
207/400fiat_currency_requiredprocess_by is fiat_amount and fiat_currency is missing.
207/400fiat_currency_not_acceptedThe fiat_currency is not accepted.
207/400memo_requiredThis token requires a memo.
207/400invalid_network_or_withdrawals_disabledUnknown network, or withdrawals are disabled on it.
207/400unsupported_token_or_withdrawals_disabledUnsupported token, or withdrawals are disabled for it.
207/400token_network_mismatchThe token is not available on this network.
207/400token_not_activeThe token is not active for payouts or for conversion.
207/400invalid_formatThe wallet address is not valid for the network.
207/400limit_exceededThe amount is outside the token's minimum and maximum (also when the fiat amount converts outside them).
207/400amount_does_not_cover_feesFees consume the whole amount, so the recipient would receive nothing.
207/400crypto_amount_does_not_correspond_to_fiat_amount_providedcrypto_amount and fiat_amount do not match the current rate.
207/400price_unavailableNo price to convert the amount, or the key has a daily limit and the token has no USD quote to count the payout.
207/400insufficient_balanceThe available balance does not cover this item.
207/400daily_payout_limit_exceededThe item would exceed the daily limit configured for the key.
207/400daily_payout_limit_unavailableThe key has a daily limit but the spend could not be recorded. Retry the item.
207/400error_fetching_active_tokens · error_fetching_active_currenciesThe token or currency catalog could not be loaded. Retry the item.
207/400user_not_found · record_not_foundAccount data the payout needs is missing.
207/400internal_error · database_error · transaction_failed · network_unavailable · duplicate_recordRecording 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 CodeErrorDescription
500internal_errorUnexpected 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.
500database_errorStatements: the query failed.
500error_fetching_active_currenciesPOST /v1/payout and POST /v1/payin: the token or fiat catalog could not be loaded. Nothing was processed.
500token_type_not_found · receipt_creation_failed · database_transaction_failedPOST /v1/payin: the payin could not be created on our side.
502external_processor_errorPOST /v1/payin and POST /v1/fixed-wallets: the deposit address could not be created right now. Nothing was created; retry later.
503no_processor_configured · no_wallet_availablePOST /v1/payin: no processor or no deposit address is available for this token and network right now. Retry later.
503price_not_available_for_conversion · price_not_available_for_validationPOST /v1/payin: no price is available to convert or check the amount. Retry later.
503service_unavailablePOST /v1/payin and summary endpoints: a dependency is temporarily unavailable.
500config_update_failedPUT /v1/fixed-wallets/config: the configuration could not be saved.
500assign_failedPOST /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