/v1/* carries the same JSON error envelope describing what went wrong. Every error — whether it originates in a handler or in middleware (authentication, permissions, rate limiting, idempotency, request timeouts) — includes a human-readable message and a machine-readable code your client can branch on.
Standard error envelope
| Field | Type | Always present | Description |
|---|---|---|---|
error.message | string | Yes | Human-readable description. Safe to log; never contains PII. For 5xx errors, this is always the string internal server error — the detailed error is recorded server-side and can be correlated via X-Request-ID. |
error.code | string | Yes | Machine-readable snake_case identifier. Safe to branch on. Every merchant-facing error returned by the API carries a code; the codes listed in the catalog below are the complete public surface. |
error.details | array or object | No | Additional context. Present on validation errors (see below). May be omitted from other error types. |
Validation error envelope
Requests that fail field-level validation return422 Unprocessable Entity with a details array. Each entry identifies the offending field and what it needs.
field is dotted-path notation for nested objects (for example individual.address.country). Fix every entry in details and retry.
HTTP status reference
| Status | Meaning in the Anton API |
|---|---|
400 Bad Request | Request body could not be parsed, contained unknown fields, or a URL parameter/path was malformed. |
401 Unauthorized | Missing, malformed, expired, or revoked credential. Re-authenticate before retrying. |
403 Forbidden | Authenticated but not permitted. Caused by insufficient role permissions, suspended/terminated merchant status, test-key-in-production, MFA enforcement, or a velocity block. |
404 Not Found | Resource does not exist under the calling merchant’s scope. May also mean the resource exists but belongs to a different merchant — the API never distinguishes the two, to prevent tenant enumeration. |
409 Conflict | The resource’s current state disallows the requested transition (for example, cancelling a completed payout), or an idempotency key was replayed with a different payload. |
410 Gone | The endpoint has been deprecated and removed. Follow the migration note in the response message. |
413 Payload Too Large | Request body exceeds the endpoint’s limit (1 MB on most routes, 26 MB on document upload routes). |
422 Unprocessable Entity | Request was syntactically valid but semantically rejected — validation failure, insufficient balance, or a business-rule refusal. |
429 Too Many Requests | Per-merchant rate limit exceeded. Respect Retry-After. See Rate limits. |
500 Internal Server Error | An unexpected server error. Safe to retry with exponential backoff. The detailed error is never exposed to clients — correlate via X-Request-ID. |
503 Service Unavailable | A downstream dependency (rail provider, FX provider, Basis Theory, WorkOS) is degraded or circuit-broken. Retry with backoff. |
504 Gateway Timeout | The request exceeded the 25-second handler timeout. Retry; consider whether your payload can be split. |
The X-Request-ID header
Every response includes an X-Request-ID header. This ID is generated by the API when a request arrives (or echoed from the client when you set one on the request). Include this value when opening a support ticket — Anton retains the server-side log for every request keyed by this ID, and we can trace the failure without asking you to reproduce it.
Error code catalog
The following codes are returned via thecode field of the error envelope. Codes not listed here do not currently exist in the API — the merchant-facing surface has a small, deliberate set.
Authentication
| HTTP | code | Typical error.message | Cause | Remediation |
|---|---|---|---|---|
401 | unauthorized | missing Authorization header | No Authorization header sent. | Add Authorization: Bearer <token>. |
401 | unauthorized | invalid Authorization header format | Header is not Bearer <token>. | Use the Bearer prefix. |
401 | unauthorized | invalid or expired token | JWT signature, expiry, or issuer is invalid. | Have the portal session refresh. |
401 | unauthorized | API keys are not accepted on this endpoint | Used an API key on a JWT-only endpoint. | Use a WorkOS portal JWT. |
401 | invalid_api_key | invalid API key format | Token does not begin with ak_. | Use a valid ak_live_... or ak_test_... key. |
401 | invalid_api_key | invalid API key | Key was not found, is revoked, expired, or has been deleted. | Rotate via the dashboard. |
401 | not_authenticated | not authenticated | A handler ran without a resolved merchant or user context. Defense-in-depth check after the auth middleware. | Re-authenticate. The portal should redirect to the login flow. |
401 | merchant_context_required | merchant context required | Token validated, but Anton could not resolve a merchant from it. | Re-authenticate; confirm the merchant binding on your portal user. |
403 | key_environment_mismatch | test API keys are not allowed in production | An ak_test_... key was used against the production or staging environment. | Use a live key in production; keep test keys to sandbox. |
Authorization and access
| HTTP | code | error.message | Cause | Remediation |
|---|---|---|---|---|
403 | insufficient_permissions | insufficient permissions | The caller’s role does not grant the required permission on this endpoint. | Have a user with the right role perform the action, or adjust team role assignments. |
403 | role_forbidden | various role-specific phrases (e.g. only admin users can…, access denied) | A handler-level role gate refused the action — admin-only fields, owner-only resources, technical/admin-only branding writes, etc. Distinct from insufficient_permissions (which is the middleware-level RBAC gate). | Have a user with the required role take the action. The UI should hide the affected control. |
403 | self_modification_forbidden | cannot change your own role / cannot deactivate your own account | A user tried to modify their own role or active status via the team-management endpoints. | Have a different admin perform the change. |
403 | last_admin_protection | cannot change the last admin's role… / cannot deactivate the last admin… | The merchant must always have at least one active admin; the request would have left zero. | Promote another user to admin first, then retry. |
403 | current_session_forbidden | cannot revoke your current session — sign out instead | The user attempted to revoke the same session that authenticated the request. | Sign out via the normal flow so the cookie clears cleanly. |
403 | sandbox_only | sandbox … is not available in this environment | A /v1/merchant/sandbox/* endpoint was called against production. | Only call sandbox endpoints in the sandbox environment. |
403 | merchant_suspended | merchant account is suspended | Merchant account is in the suspended state. | Contact Anton support. |
403 | merchant_terminated | merchant account is terminated | Merchant account is in the terminated state. | Contact Anton support. |
403 | merchant_not_active | merchant account is not yet active | Onboarding/provisioning is still in progress. | Finish onboarding; only /v1/onboarding/*, /v1/documents/*, and /v1/rfis/* are callable during this stage. |
403 | merchant_not_found | merchant not found | The authenticated merchant ID could not be resolved. | Re-authenticate; confirm the merchant exists. |
403 | user_not_found | user not found or inactive | The JWT subject does not resolve to an active user for this merchant. | Re-authenticate; confirm the user is active. |
MFA enforcement
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
403 | mfa_enrollment_required | The merchant has enforced MFA, the grace period has expired, and the calling user has no active TOTP factor. | Have the user enroll a TOTP factor via /v1/users/me/mfa/enroll. The portal converts this code into a forced-enrollment flow. |
Validation
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
422 | validation_error | One or more fields failed validation. The details array carries per-field errors. Endpoints that previously returned individual 400s for each field (notably PATCH /v1/merchant/branding) now collect all errors into this single envelope so the merchant can fix the form in one round trip. | Inspect error.details[] and fix each entry. |
400 | bad_request | Returned by the idempotency middleware when the request body could not be read. | Retry the request; verify the body is well-formed JSON and under 1 MB. |
400 | invalid_request_body | The request body could not be JSON-decoded — malformed JSON, unknown fields (the decoder rejects them), or wrong content type. | Confirm the body matches the documented schema. |
400 | missing_required_field | An ad-hoc required field was missing — for example, currency, id, domain, or email. | Add the missing field. |
400 | invalid_email | Email field is not a valid RFC 5322 address. | Fix the email value. |
400 | invalid_role | Role value is not one of the supported merchant roles (admin, accounting, technical, operator, viewer). | Use a valid role slug. |
400 | invalid_country_code | Country value is not a 2-letter ISO 3166-1 alpha-2 code. | Use a valid country code (e.g. US, GB). |
400 | invalid_threshold | Numeric value (velocity threshold, FX amount, simulator amount) is missing, non-numeric, or not positive. | Send a valid positive decimal. |
400 | invalid_action | Velocity rule action is not one of block, review, alert. | Use a valid action. |
400 | invalid_file | Multipart upload was missing the file field. | Include a file under the file form field. |
400 | invalid_file_format | Batch upload file is not .csv or .xlsx. | Resave the file in a supported format. |
400 | invalid_file_type | Branding asset or avatar upload had an unsupported Content-Type. Branding assets accept PNG and SVG; avatars accept PNG, JPEG, and WebP. | Re-export the asset in a supported format. |
400 | invalid_multipart_form | The multipart form could not be parsed. | Confirm the request is a properly-encoded multipart/form-data body. |
400 | invalid_template_format | GET /v1/batches/template?format= was called with anything other than xlsx or csv. | Use one of the supported template formats. |
400 | invalid_asset_kind | Branding asset path parameter is not a recognized asset kind. | Use a documented asset kind. |
400 | invalid_svg_content | Uploaded SVG contains scripts or event handlers (XSS-prevention policy). | Strip scripts/event handlers from the SVG before uploading. |
400 | invalid_intent | Admin Portal link request used an unsupported intent. Allowed values: sso, dsync, domain_verification. | Use a valid intent. |
400 | invalid_hostname | custom_domain does not look like a hostname. | Send a valid hostname (e.g. pay.example.com). |
400 | invalid_color | primary_color or accent_color is not a valid hex color. | Send a #RRGGBB hex value. |
400 | invalid_url | support_url, terms_url, or privacy_url is not a valid HTTPS URL. | Use an HTTPS URL. |
400 | invalid_css | custom_css exceeds the 64 KB cap. | Trim the CSS to under 64 KB. |
400 | invalid_sender_name | email_sender_name exceeds 100 characters. | Shorten the sender name. |
400 | invalid_sender_email | email_sender_address is not a valid email. | Use a valid email. |
400 | invalid_display_name | display_name exceeds 100 characters, or beneficiary display_name is missing/invalid. | Use a shorter / valid display name. |
400 | invalid_support_email | support_email is not a valid email. | Use a valid email. |
400 | invalid_statement_descriptor | Statement descriptor failed validation (length / charset). | Fix per the statement descriptor rules. |
400 | invalid_scope | scope query param on sandbox reset was not one of the allowed values. | Use balances, all, delete-all, or full-reset. |
400 | weak_password | New password is shorter than 12 characters. | Use a password with at least 12 characters. |
400 | file_hash_mismatch | Client-supplied file_hash does not match the server-computed SHA-256. | Recompute the hash on the file as it is being sent and resubmit. |
Resource not found
404 Not Found responses always carry a code identifying the missing resource. Anton never distinguishes “does not exist” from “exists but belongs to another merchant” — both return the same *_not_found envelope to prevent tenant enumeration.
| HTTP | code | Returned by |
|---|---|---|
404 | payout_not_found | GET /v1/payouts/{id}, GET /v1/payouts/{id}/events, GET /v1/payouts/{id}/velocity-results, GET /v1/payouts/{id}/engine-verdict |
404 | beneficiary_not_found | All /v1/beneficiaries/{id}* reads/writes; instrument creation under a beneficiary |
404 | instrument_not_found | GET/PUT/DELETE /v1/instruments/{id} |
404 | batch_not_found | All /v1/batches/{id}* endpoints |
404 | template_not_found | GET /v1/batches/template when the template file is missing on the server |
404 | webhook_subscription_not_found | /v1/webhooks/{id}* reads, secret rotation, test, deactivate |
404 | webhook_event_not_found | GET /v1/webhooks/events/{id} |
404 | merchant_not_found | GET /v1/merchant, merchant security/users surfaces |
404 | user_not_found | /v1/users/me, /v1/users/{user_id}/* |
404 | session_not_found | /v1/users/me/sessions/{session_id}/revoke |
404 | domain_not_found | /v1/merchant/security/domains/{id}* |
404 | invitation_not_found | /v1/users/invitations/{id}* |
404 | channel_not_found | /v1/users/me/notifications/channels/{id}*, /v1/merchant/notifications/routes/{id}* |
404 | rule_not_found | /v1/velocity/rules/{id}* |
404 | rfi_not_found | /v1/rfis/{id}* |
404 | document_not_found | /v1/documents/{id}* |
404 | application_not_found | /v1/onboarding, /v1/onboarding/status/{token}, /v1/onboarding/submit |
404 | pricing_plan_not_found | POST /v1/pricing/quote when no plan applies |
404 | account_not_found | /v1/accounts/{currency}* |
404 | balance_not_found | GET /v1/balances/{currency} |
404 | country_not_supported | GET /v1/instruments/methods?country=<CC> for unsupported countries |
404 | quote_not_found | POST /v1/fx/exchange with a quote_id that does not exist or is not owned by the merchant |
Idempotency
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
400 | missing_idempotency_key | The endpoint requires an Idempotency-Key header and none was supplied. | Add a unique Idempotency-Key header. See Idempotency. |
409 | idempotency_conflict | An Idempotency-Key was reused with a different request payload. | Either retry the original payload with the same key, or use a new key for the new payload. Never mix. |
Rate limiting
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
429 | rate_limit_exceeded | Per-merchant rate limit has been exceeded (1,000 requests/minute on /v1/*; tighter limits apply to /v1/fx/quote, /v1/fx/exchange, and sensitive endpoints). | Honor the Retry-After header. Back off with jitter. See Rate limits. |
Merchant onboarding
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
409 | merchant_exists | POST /v1/register was called with an email that already has an account. | Direct the user to sign in instead. |
Payouts
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
422 | insufficient_balance | The source balance cannot cover the payout amount plus fees. (Also returned by POST /v1/fx/exchange when the sell-side balance is too low.) | Top up the relevant currency balance, or reduce the amount. |
403 | velocity_blocked | The payout was rejected by Anton’s real-time risk policy (velocity/thresholds/geo). | Inspect GET /v1/payouts/{id}/velocity-results for the triggered rules. A payout.velocity_blocked webhook is also dispatched with the reason. |
422 | payout_rejected | Catch-all for payout creation failures that are not validation, balance, or velocity related. | Inspect the message; correct the input or retry. |
422 | payout_not_cancellable | The payout’s current state does not allow cancellation (already submitted to a rail, completed, etc.). | Re-fetch the payout; cancellation is only valid before submission. |
Batches
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
409 | duplicate_file | A batch file with the same SHA-256 hash is already pending, validating, or processing for this merchant. | Wait for the pending batch to finish, or cancel it. Use the pending_batches array on a non-error upload response to correlate. |
409 | batch_not_validated | POST /v1/batches/{id}/confirm was called before the batch finished validating. | Poll for validated status before confirming. |
409 | batch_confirmation_expired | The batch validation result expired before confirm was called. | Re-upload the file and retry. |
409 | batch_not_cancellable | The batch is not in a cancellable state. | Re-fetch the batch; cancellation is only valid in early states. |
422 | batch_upload_failed | Generic upload failure not covered by duplicate_file or invalid_file_format. | Retry; if persistent, file a support ticket with the X-Request-ID. |
422 | batch_confirm_failed | Generic confirmation failure. | Inspect the message and retry. |
422 | batch_cancel_failed | Generic cancellation failure. | Inspect the message and retry. |
Beneficiaries
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
422 | duplicate_beneficiary | A beneficiary with the same fingerprint already exists for the merchant. | Reuse the existing beneficiary. |
422 | invalid_display_name | The supplied display name is missing or invalid. | Provide a valid display name. |
422 | missing_beneficiary_details | Neither individual nor business details were provided. | Include the appropriate detail block for the beneficiary type. |
422 | beneficiary_create_failed | Catch-all for creation failures (Basis Theory tokenization, database). The detailed cause is logged server-side. | Retry; share the X-Request-ID if persistent. |
422 | beneficiary_update_failed | Generic update failure. | Inspect the message; retry. |
422 | beneficiary_pii_update_failed | PII update via Basis Theory failed. | Retry; if persistent, share the X-Request-ID. |
422 | beneficiary_archive_failed | Generic archive failure. | Inspect the message; retry. |
422 | beneficiary_restore_failed | Generic restore failure. | Inspect the message; retry. |
422 | beneficiary_delete_failed | Generic delete failure. | Inspect the message; retry. |
Instruments
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
410 | deprecated_endpoint | The flat POST /v1/instruments route is deprecated. | Use POST /v1/beneficiaries/{id}/instruments. |
422 | duplicate_instrument | An instrument with the same fingerprint already exists for the beneficiary. | Reuse the existing instrument. |
422 | invalid_credentials | The instrument credentials (IBAN check digit, account number, routing number) failed validation. | Fix the input. |
422 | method_not_supported | The requested payment method is not supported for the country. | Use a method valid for the destination country. |
422 | instrument_create_failed | Catch-all creation failure (Basis Theory, database). | Retry; share the X-Request-ID if persistent. |
422 | instrument_update_failed | Generic update failure. | Inspect the message; retry. |
422 | instrument_delete_failed | Generic delete failure. | Inspect the message; retry. |
FX
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
400 | currency_not_supported | One or both currencies in the quote/exchange request are not supported. | Use a supported corridor. |
410 | quote_expired | The locked quote referenced by quote_id has expired. | Generate a new quote and retry. |
400 | quote_not_locked | POST /v1/fx/exchange was called with a quote_id that points to an indicative (non-lockable) quote. | Use a locked quote — call POST /v1/fx/quote first. |
422 | no_funding_account | The merchant has no funding account in the sell currency. | Fund the account first via /v1/accounts/.... |
502 | fx_rates_unavailable | The upstream FX rate provider is degraded or unavailable. | Retry with backoff. |
503 | locked_rate_unavailable | The provider returned an indicative rate when a locked rate was requested. | Retry shortly. |
500 | fx_execution_failed | Exchange execution failed in a way that does not map to insufficient balance or a quote issue. The message is sanitized; the real cause is logged with the X-Request-ID. | Retry with backoff; share the X-Request-ID if persistent. |
Webhook subscriptions
Structured codes returned byPOST /v1/webhooks:
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
400 | webhook_url_required | url field was missing or empty. | Send a non-empty url. |
400 | webhook_url_invalid | url field could not be parsed as a URL. | Send a syntactically valid URL. |
400 | webhook_url_not_https | The url field used a non-https:// scheme. HTTP is rejected in production, staging, and sandbox — it is only permitted against local development APIs. | Use https:// and a valid TLS certificate. |
400 | webhook_url_private_address | The url host is a loopback, RFC1918/ULA private address, link-local address, .local / .internal hostname, or a cloud metadata endpoint (for example 169.254.169.254). Anton refuses to register these targets (SSRF and credential-exfiltration defense). | Use a publicly reachable, internet-routable endpoint. |
Settings — branding, security, notifications, preferences, users
These codes are returned by the merchant portal settings endpoints (/v1/merchant/branding, /v1/merchant/security, /v1/merchant/notifications, /v1/merchant/preferences, /v1/users/*).
| HTTP | code | Returned by |
|---|---|---|
422 | branding_update_failed | PATCH /v1/merchant/branding |
422 | branding_asset_save_failed | POST /v1/merchant/branding/assets/{kind} |
422 | security_update_failed | PATCH /v1/merchant/security |
422 | domain_add_failed | POST /v1/merchant/security/domains (commonly: domain claimed by another organization) |
422 | domain_verify_failed | POST /v1/merchant/security/domains/{id}/verify (DNS TXT not yet detected) |
422 | domain_remove_failed | DELETE /v1/merchant/security/domains/{id} |
412 | merchant_workos_unlinked | Merchant is not linked to a WorkOS organization (precondition for SSO/dsync/portal-link routes). |
422 | workos_org_not_configured | Merchant org is not configured (e.g. user-invite when the merchant has no WorkOS org). |
503 | workos_integration_unavailable | WorkOS integration is not configured or unreachable. |
503 | storage_unavailable | Object storage is not configured (branding asset / avatar uploads). |
503 | mfa_service_unavailable | MFA service is not configured (/v1/users/me/mfa/*). |
503 | session_service_unavailable | Session service is not configured (/v1/users/me/sessions*). |
503 | password_service_unavailable | Password service is not configured (POST /v1/users/me/password). |
503 | portal_link_failed | POST /v1/merchant/security/portal-link could not generate an Admin Portal link. |
422 | notifications_update_failed | Generic notifications-settings update failure (/v1/merchant/notifications/global, /v1/merchant/notifications/beneficiary, /v1/users/me/notifications/preferences, etc.). |
422 | notifications_route_create_failed | POST /v1/merchant/notifications/routes |
422 | notifications_route_delete_failed | DELETE /v1/merchant/notifications/routes/{id} |
422 | preferences_update_failed | PATCH /v1/merchant/preferences |
422 | channel_create_failed | POST /v1/users/me/notifications/channels |
422 | channel_update_failed | PUT /v1/users/me/notifications/channels/{id} |
422 | channel_delete_failed | DELETE /v1/users/me/notifications/channels/{id} |
422 | channel_test_failed | POST /v1/users/me/notifications/channels/{id}/test |
422 | channel_verify_failed | Channel verification step inside the test endpoint. |
422 | profile_update_failed | PUT /v1/users/me, PATCH /v1/merchant/profile |
422 | profile_complete_failed | POST /v1/users/me/profile/complete (welcome flow). |
422 | password_change_failed | POST /v1/users/me/password |
422 | avatar_save_failed | POST /v1/users/me/avatar |
422 | mfa_enroll_failed | POST /v1/users/me/mfa/enroll, GET /v1/users/me/mfa |
422 | mfa_verify_failed | POST /v1/users/me/mfa/verify (challenge step) |
422 | mfa_invalid_code | POST /v1/users/me/mfa/verify (code mismatch) |
422 | session_list_failed | GET /v1/users/me/sessions |
422 | session_revoke_failed | POST /v1/users/me/sessions/{session_id}/revoke |
422 | session_ownership_failed | Internal scope check inside the session-revoke flow. |
422 | invitation_failed | POST /v1/users/invite |
422 | invitation_resend_failed | POST /v1/users/invitations/{id}/resend |
422 | invitation_revoke_failed | DELETE /v1/users/invitations/{id} |
422 | user_role_update_failed | PUT /v1/users/{user_id}/role |
422 | user_deactivate_failed | POST /v1/users/{user_id}/deactivate |
422 | user_activate_failed | POST /v1/users/{user_id}/activate |
422 | apikey_create_failed | POST /v1/api-keys |
422 | apikey_revoke_failed | POST /v1/api-keys/{id}/revoke |
Onboarding and compliance
| HTTP | code | Returned by |
|---|---|---|
409 | application_locked | Tried to edit an onboarding application that is no longer in draft or info_needed. |
422 | registration_failed | POST /v1/register failure not covered by merchant_exists. |
422 | application_update_failed | PUT /v1/onboarding |
422 | application_submit_failed | POST /v1/onboarding/submit |
422 | rfi_respond_failed | POST /v1/rfis/{id}/respond |
422 | document_upload_failed | Document upload (multipart or metadata-only) failure. |
422 | document_download_failed | GET /v1/documents/{id}/download could not generate a presigned URL. |
Velocity (merchant-scoped rules)
| HTTP | code | Returned by |
|---|---|---|
422 | rule_create_failed | POST /v1/velocity/rules |
422 | rule_update_failed | PATCH /v1/velocity/rules/{id} |
422 | rule_delete_failed | DELETE /v1/velocity/rules/{id} |
422 | simulation_failed | POST /v1/velocity/simulate |
Payload size
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
413 | file_too_large | Upload exceeds the per-endpoint cap (2 MB for branding assets, 5 MB for user avatars, 25 MB for documents, 32 MB for batches). | Reduce the file size. The message includes the applicable limit. |
Sandbox-only
These codes are returned only by the/v1/merchant/sandbox/* routes, which are registered only in sandbox-class environments. Production merchants will never see them.
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
422 | no_beneficiaries | POST /v1/merchant/sandbox/seed-payouts was called before any beneficiaries existed for the merchant. | Call /v1/merchant/sandbox/seed-beneficiaries first. |
429 | rate_limited_sandbox_reset | Sandbox reset is capped at 10 invocations per merchant per hour. | Wait for the counter to reset. |
429 | rate_limited_sandbox_seed | Sandbox seeding is capped at 10 invocations per merchant per hour. | Wait for the counter to reset. |
503 | sandbox_seeder_unavailable | Sandbox account seeder is not configured. | Surface a support ticket; this is an environment misconfiguration. |
503 | beneficiary_seeder_unavailable | Beneficiary seeder is not configured. | Same as above. |
503 | payout_seeder_unavailable | Payout seeder is not configured. | Same as above. |
500 | sandbox_reset_failed | Sandbox reset failed during wipe or reseed. | Retry; share the X-Request-ID if persistent. |
500 | seed_payouts_failed | Sample payout seeder failed. | Retry; share the X-Request-ID if persistent. |
Server and infrastructure
| HTTP | code | Meaning | Remediation |
|---|---|---|---|
500 | internal_error | Unexpected server error. message is always internal server error — the real cause is logged with the X-Request-ID. | Retry with exponential backoff. If the failure persists, share the X-Request-ID with support. |
504 | timeout | Handler exceeded the 25-second request timeout. | Retry. For large batches or reports, prefer asynchronous endpoints. |
Retry semantics
Not every error is safe to retry. The table below is a safe default — apply stricter rules where your integration’s correctness depends on it.| Category | Safe to retry? | Notes |
|---|---|---|
500, 502, 503, 504 | Yes | Use exponential backoff with jitter. Always include the same Idempotency-Key on mutating retries so a partially-processed request is not duplicated. |
429 rate_limit_exceeded | Yes | Wait at least the Retry-After value before the first retry. |
| Transient network errors (connection reset, DNS failure, TLS reset) | Yes | Treat the same as a 5xx. Retry with the same Idempotency-Key. |
400, 404, 410, 413, 422 validation_error | No | Fix the request. Retrying will fail identically. |
401 | No | Re-authenticate first. Do not loop on 401. |
403 insufficient_permissions, 403 mfa_enrollment_required, 403 velocity_blocked | No | Policy or permission issue. Resolve upstream before retrying. |
409 idempotency_conflict | No | You sent a new payload under a used key. Generate a new key. |
409 state conflicts (batch, payout, approval) | Conditional | Re-fetch the resource; retry only if the current state actually permits the action. |
422 insufficient_balance | No | Top up first. |
422 beneficiary/instrument service errors (duplicate_beneficiary, invalid_display_name, missing_beneficiary_details, duplicate_instrument, invalid_credentials, method_not_supported, etc.) | No | Fix input, then retry with a fresh idempotency key. |
422 quote_expired, 422 fx_rates_unavailable, 503 locked_rate_unavailable | Yes (re-quote) | Generate a fresh quote and retry the exchange. |
Handling errors in code
When contacting Anton support about a failed request, include the
X-Request-ID header value from the response. Anton retains a full server-side trace keyed by that ID and can diagnose without asking you to reproduce.