Payaider Docs Non-custodial crypto payments API 2026-08-27

Errors

Every error Payaider can return — 215 codes, what each one means, and how to fix it.

The error envelope

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "amount_too_small",
    "message": "amount must be greater than zero. Amounts are integer minor units: send 10000 for $100.00.",
    "param": "amount",
    "doc_url": "https://docs.payaider.com/errors/amount_too_small",
    "request_log_url": "https://dashboard.payaider.com/logs/req_9Kq2Bn7Ls4Xd"
  }
}
  • type is what you branch on. It is one of four values and it never changes meaning.
  • code is the stable machine-readable identifier. New codes are added over time — parse tolerantly and fall back on type.
  • param names the offending field when there is exactly one.
  • doc_url is the page for that code on this site.
  • request_log_url deep-links this exact request in your dashboard: headers, body, response, timing. Quote it to support.

The four types

TypeMeaningRetry?
invalid_request_errorThe request was wrong.No — fix it first.
payment_errorThe request was fine; the payment could not proceed (underpaid, unsupported network, unverified wallet, compliance hold).Only after changing something.
idempotency_errorAn Idempotency-Key was reused with a different body.No — use a new key.
api_errorOur fault.Yes — with the same Idempotency-Key.

Status codes at a glance

StatusCodesExample
40099e.g. passkey_malformed
40119e.g. authentication_required
40212e.g. currency_not_quotable
40317e.g. insufficient_scope
40412e.g. unknown_passkey
4051e.g. method_not_allowed
40929e.g. passkey_already_registered
4131e.g. request_too_large
4151e.g. unsupported_media_type
4291e.g. rate_limit
50013e.g. admin_totp_secret_invalid
5011e.g. passkeys_not_configured
5023e.g. provider_response_invalid
5035e.g. passkey_challenges_full
5041e.g. screening_timeout

Every code

invalid_request_error · 179 codes

CodeStatusWhat to do
authentication_required401Send your secret key as Authorization: Bearer sk_test_…. Keys are shown once at creation; if you lost yours, roll a new one in the dashboard — the old key stays valid for the 7-day rotation window.
invalid_authorization_header401Use the exact form Authorization: Bearer <key> — one space, the literal word Bearer, no quotes and no trailing newline. Basic auth and query-string keys are not accepted.
invalid_api_key401Check that you copied the whole key and that you are calling the right environment: a sk_test_… key is invalid against live data and vice versa (§7.1 — test and live are parallel universes).
api_key_revoked401Issue a new key in the dashboard and redeploy. Revocation is immediate and permanent — a revoked key never comes back, by design.
api_key_expired401The 7-day dual-validity grace window on the rotated key has closed. Deploy the replacement key that was issued when you started the rotation.
invalid_credentials401Dashboard sign-in only; API traffic never uses a password. Reset the password from the sign-in page after three failed attempts rather than retrying.
invalid_password401Re-enter the password, or reset it from the sign-in page.
account_disabled401An administrator disabled the account. Ask an owner on your team to re-enable it; API keys belonging to a disabled account stop authenticating at the same moment.
admin_session_required401Sign in to the admin console. Admin endpoints are never reachable with a merchant API key, in either mode.
unknown_session401Sign in again to obtain a fresh session.
session_expired401Sign in again. Sessions have both an idle timeout and an absolute lifetime; the absolute one cannot be extended by activity.
session_revoked401Somebody signed this device out — often you, from the device-management screen, or an administrator responding to a compromise. Sign in again and review active devices.
passkeys_not_configured501A passkey is bound to the exact origin the console is served from, and that origin is configuration rather than something a request may state — a relying party taken from a request header is one an attacker can point at their own site. Set the deployment's passkey relying party (and, on more than one instance, shared passkey and challenge stores), or sign in with a password and an authenticator code.
passkey_malformed400The browser sent something that is not a WebAuthn credential. Start the registration again from the console; if it keeps happening, the browser or its extensions may be altering the response.
passkey_rejected400The detail.reason field says which check failed. user_not_verified means the device signed without confirming who you are — a passkey must be unlocked with a biometric or a PIN, so enable that on the device. unsupported_algorithm means the authenticator uses a key type this build cannot verify; use another device. The rest indicate a challenge that expired or a response that did not match this origin: start again.
passkey_already_registered409Each authenticator holds one credential per account, so this device is already enrolled here — sign in with it rather than registering it again. The answer is the same whether the credential belongs to you or to another account, on purpose.
too_many_passkeys400Remove a device you no longer use and register again. The cap exists so a compromised session cannot quietly accumulate ways back in.
unknown_passkey404It has already been removed, or it belongs to somebody else — the two are deliberately the same answer, so the console cannot be used to discover another account's credentials. List your own passkeys to see what is registered.
session_ip_mismatch401Sign in again from the current network. IP pinning is opt-in per merchant; disable it in security settings if your users roam between networks mid-session.
too_many_sessions401Sign out an existing device from the device-management screen, then sign in again.
mfa_required401Complete the TOTP challenge and retry. §12 gates wallet add/verify, route changes, API-key creation and webhook-endpoint changes behind MFA because those are exactly the actions that redirect future payments.
mfa_reverification_required401Re-enter a fresh TOTP code. A proof from earlier in the session is not accepted for high-risk actions — that is what stops a hijacked session from re-routing payouts.
mfa_proof_invalid401Check that your authenticator app clock is in sync and enter the current 6-digit code. Codes are single-use: a replayed code is rejected even inside its time window.
mfa_proof_stale401Generate a new code and retry within the proof lifetime.
mfa_method_unsupported401Use a TOTP authenticator app; it is the only method accepted at MVP.
insufficient_scope403Mint a key with the missing scope (write implies read, so payment:create also reads payments). Wallet-address and webhook-URL changes need their own high-privilege scope — they are how a leaked key steals future payments (§7.3).
permission_denied403Ask a team member with the required role to perform it, or have an owner change your role. Roles are enforced server-side; the dashboard hiding a button is not the control.
publishable_scope_forbidden403Publishable pk_… keys are read-only, checkout-facing credentials (§7.1). Move the call to your server and use a secret key: a browser can read an invoice, it can never mint one, re-route one or cancel one.
publishable_key_not_permitted403Send a secret (sk_…) or restricted (rk_…) key from your server instead. No endpoint currently accepts a pk_…: the scope a browser would need to fetch one payment intent is the same one that lists every intent you have, so per-object retrieval waits on a per-object capability (§7.1). To show payment state in a browser, render it from your own server or link the customer to the hosted checkout page.
payments_paused403An admin kill-switch or a billing suspension is active (§9, §12). In-flight payments always complete — Payaider never strands a customer’s money over our receivable — but no new intent can be created until the switch is cleared.
forbidden_actor403Every edge in the §3.2 table names its actor. expired → paid_late belongs to the matcher, not to the API; accept-as-paid belongs to the merchant. Use the endpoint that matches the actor you are.
invalid_actor400Use one of the §3.2 actors: api, checkout, matcher, watcher, sweeper, merchant, admin, compliance.
actor_missing403Every audited action records who performed it (§12). Attach the acting user or admin to the request context before calling.
missing_principal403The request reached an authorization check without having passed authentication. Route it through the authenticated edge; do not call internal handlers directly.
wallet_not_owned403Use a wallet id from GET /v1/wallets with this key. Cross-merchant ids are never usable, in either mode.
role_may_propose_only403High-risk admin actions are four-eyes: propose, then have a second authorized admin approve. Approval by the proposer is refused.
self_approval_forbidden403Ask a different authorized administrator to approve it.
second_approval_not_required409Execute it directly; only high-risk actions carry the four-eyes gate.
executor_not_party403Execute as the admin who proposed it, or as one who approved it. A third party cannot ride an approval that was not granted to them.
action_not_approved403Obtain the required second approval, then execute.
second_approval_required403Propose it at POST /admin/approvals, have a DIFFERENT admin approve it, then resend this request with approval_id. Irreversible actions — suspending a merchant, approving KYB, releasing a hold, resolving a payment, adjusting fees, changing admin access — are never reachable from one session (§12).
unknown_approval403Check the approval_id you sent against GET /admin/approvals. An approval is scoped to one permission, one target and one exact body, so an id from a different proposal will not authorize this request.
action_in_flight403Wait for the first request to finish rather than replaying it — the consent authorizes exactly one execution. If that request fails, the approval is handed back unspent and can be used again.
action_not_pending409It was already executed, denied or expired. Read the action to see its outcome and propose a fresh one if you still need the change.
action_expired409Propose the action again. Approvals are short-lived on purpose: a stale approval is an approval for a situation that no longer exists.
resource_missing404Check the id, and check the mode: a test key cannot see live objects and never will be told they exist (§7.1). A 404 here means "not visible to this key", which covers both "never existed" and "belongs to somebody else".
payment_not_found404Verify the pi_… id and that the key is for the same merchant and mode. Ids from the sandbox never resolve against live data.
wallet_not_found404List wallets with GET /v1/wallets and use an id from the response.
merchant_not_found404Admin surface only. Check the mch_… id in the admin console.
unknown_endpoint404List endpoints and use a we_… id belonging to this merchant and mode.
unknown_route404Compare the request line against the API reference. The most common cause is a POST to a path that only accepts GET, or a missing /v1 prefix.
unknown_token400Use one of the approved (asset, network) token ids — usdt-tron, usdt-bnb, usdc-base. Matching is on the approved contract address, never on a symbol (§5.3).
reservation_not_found404Reservations are released by the sweeper once the quote TTL plus the 24h late-payment grace window has passed (§5.2). Re-quote the payment to obtain a fresh one.
unmatched_transfer_not_found404Read the unmatched-transfers queue and use an id from it.
hold_not_found404Read the holds queue in the admin console and use an id from it.
kyb_case_not_found404Read the KYB queue in the admin console and use an id from it.
unknown_action404Read the approvals queue; the action may already have been executed or denied.
idempotency_key_in_use409Retryable. Wait briefly and send the same request again: once the first one stores its response, the retry replays it byte-for-byte, including the status code.
invalid_idempotency_key400Send a non-empty, opaque, client-generated string — a UUID v4 per logical operation is the standard choice. Never derive it from a timestamp alone.
idempotency_key_too_long400Use at most 255 characters; a UUID is 36.
invalid_json400Send a JSON object with Content-Type: application/json. Trailing commas, single quotes and unquoted keys are not JSON.
invalid_body400Top-level arrays, strings and numbers are rejected. Wrap the fields in an object, even when there is only one.
unsupported_media_type415Set Content-Type: application/json. Form encoding is not accepted; it cannot represent the nested metadata object without ambiguity.
method_not_allowed405Read the Allow response header for the methods this path accepts. Actions such as cancel are POSTs on a sub-path, never a DELETE on the object.
request_too_large413Trim the payload. metadata is for a handful of short keys — store large documents in your own system and reference them by id.
request_aborted400Usually a client timeout that is shorter than your own upload. Nothing was persisted; retry with the same Idempotency-Key.
invalid_path400URL-encode path segments; a raw space or control character is rejected.
missing_path_parameter400Fill the {id} placeholder. A literal undefined or empty segment in the URL is the usual cause — check the variable you interpolated.
parameter_missing400Read error.param for the field name and include it.
parameter_invalid400Read error.param for the field name and the message for the expected shape.
parameter_invalid_integer400Send digits only — no decimal point, no exponent, no thousands separators — and stay inside the documented bounds.
invalid_request400A generic validation failure. error.message and error.param name the field; the API reference gives its accepted values.
invalid_limit400Use an integer between 1 and 100. Page through results with starting_after rather than asking for one enormous page.
invalid_cursor400Pass the id of the LAST object in the previous page as starting_after. A cursor from another list, another mode or another merchant is rejected.
cursor_rewind400Internal to the chain watchers: block cursors are monotonic so a restart cannot re-credit already-processed transfers. Reset the cursor deliberately via the runbook.
invalid_filter400Use only the filters listed for this endpoint; unknown filters are refused, never ignored.
invalid_range400Ensure the start of the range is not after its end.
invalid_timestamp400Send ISO-8601 UTC, e.g. 2026-08-27T12:00:00.000Z (§7.3).
invalid_now400Pass a real date. Sandbox time-travel helpers accept ISO-8601 UTC only.
invalid_advance400Sandbox only. Time moves forward; to rewind, re-seed the sandbox.
invalid_duration400Send whole milliseconds. Durations are never fractional and never negative.
invalid_ttl400Send whole milliseconds greater than zero.
amount_too_small400Send amount in INTEGER MINOR UNITS — 10000 is $100.00, not $10000. An amount that quotes to less than the network fee a customer must pay to send it cannot be paid.
amount_too_large400Split the order, or ask support to raise the per-payment ceiling for your account. Large amounts also sit on a stricter finality tier before they confirm (§11).
invalid_amount400Amounts are integer minor units as a JSON integer or a decimal string — never a float. 19.99 is a bug; send 1999 with currency: "USD".
invalid_amount_range400On open-amount payment links, ensure min_amount is not above max_amount.
excess_precision400Decimals are per (token, chain), never per asset: USDT is 6 on TRON and 18 on BNB (§11). Round to the asset’s precision before sending; Payaider refuses to truncate money silently.
invalid_decimals400Use an integer between 0 and 36. Read the exponent from the token configuration.
invalid_currency400Send three uppercase letters, e.g. USD. Lowercase usd is rejected, not normalized.
currency_not_supported400Use a currency from GET /v1/exchange_rates. Adding a currency is a rate-sourcing decision (§4.2), not a config toggle.
invalid_currency_decimals400Do not send your own exponent: Payaider derives it from ISO-4217 (JPY is 0, USD is 2, KWD is 3). A wrong exponent is a 100x money error, silently.
invalid_rate400Rates are decimal STRINGS of token units per one MAJOR fiat unit, e.g. "1.000431". A float loses precision before it reaches us.
invalid_bps400100 bps is 1%. Fractional basis points are not accepted.
invalid_step400Use the token’s uniquifier_step; it is a per-(token, chain) configuration value.
invalid_tolerance400Payment tolerance is 0 by default (exact-or-better) and configurable up to 1% (§5.5). Do not confuse it with attribution tolerance, which is system-defined and not settable.
invalid_spacing400Concurrent reservations on one (wallet, token) must sit at least 2x the attribution tolerance apart (§5.2), or tolerant matching becomes ambiguous.
invalid_grace_window400The default is 24 hours after quote expiry — the post-expiry monitoring window that makes late-payment detection real (§5.4). Shortening it to zero loses late payments.
fee_already_accrued409The fee ledger holds one accrual per payment (§9). Correct a wrong accrual with an adjustment entry; ledger rows are append-only and never edited.
invalid_memo400Say why the adjustment exists. It is permanent audit evidence.
nothing_outstanding409Read the fee ledger balance before generating a fee invoice.
invalid_metadata_key400Use short ASCII keys, at most 40 characters, e.g. order_id.
invalid_metadata_value400Stringify numbers, booleans and nested objects yourself, so what comes back on the webhook is byte-identical to what you sent.
metadata_too_large400Keep metadata to a handful of short keys and store the rest in your own database, keyed by order_id. Metadata rides on every webhook delivery.
invalid_merchant400Internal callers must scope every operation to a merchant; the API derives it from the key.
missing_merchant400Supply merchantId. API keys carry it implicitly; internal calls must pass it.
merchant_closed403A closed account can still read historical data but can create nothing new. Contact support if the closure was not intentional.
kyb_case_already_decided409Open a new case rather than overwriting a decision; KYB outcomes are audit evidence.
asset_token_mismatch400Use the token id for the (asset, network) pair you mean; do not send an asset symbol.
token_not_enabled400Choose an enabled token from the payment method options returned by checkout.
invalid_token_contract400Payaider matches on the ONE approved contract per (asset, network) and never on a symbol — that is the fake-token guard (§5.3 step 1). On Base, bridged USDbC is a different asset from native USDC and is refused deliberately.
missing_chain_id400EIP-681 payment URIs carry the chain id (BNB Chain 56, Base 8453). TRON has no chain id and uses its own URI scheme (§6.3).
invalid_address400TRON addresses are 34-character base58check starting with T (the 41… hex form is also accepted); EVM addresses are 0x plus 40 hex characters. The checksum is verified — a typo is rejected here rather than losing a payment forever.
invalid_tx_hash400TRON transaction ids are 64 hex characters with no prefix; EVM hashes are 0x plus 64 hex characters. An exchange’s internal transfer id is not an on-chain hash — it goes to manual support review (§6.2, D10).
invalid_log_index400One transaction can carry several Transfer events; the log index is part of a transfer’s identity and cannot be omitted (§5.3 step 3).
duplicate_transaction409Expected and harmless: watchers poll overlapping ranges and providers replay pushes, so the matcher answers a repeat with an idempotent skip. Never re-credit it (§5.3 step 3).
wallet_exchange_hosted400Use a self-custodial wallet. A customer paying from the same exchange settles as an off-chain internal transfer with no on-chain transaction — invisible to every watcher — so the merchant is credited while the invoice expires unpaid (decision D10, §6.2).
exchange_hosted_wallet400Screening or your own onboarding answer identified it as custodial. Add a wallet whose private key you control; you must be able to sign for it (D10).
wallet_merchant_mismatch400Use a wallet id from this merchant’s own wallet list.
duplicate_wallet409Reuse the existing wallet, or relabel it. One address per (merchant, network).
invalid_challenge_field400Return the challenge exactly as issued, with the signature. Editing the message body invalidates the proof — that is the point of it.
route_conflict409A merchant has at most one active route per (asset, network) — enforced by a database constraint. Update the existing route instead of adding a second.
route_cooling_down409Wait out the cool-down. It exists so that an account takeover cannot redirect every future payment in one silent step (§6.1); the change already sent you an email.
route_wallet_missing400Create and verify the wallet first, then point the route at it.
invalid_state_transition409Read the payment’s current status and consult the §3.2 transition table. The commonest case is cancelling a payment a customer has already paid: there is no edge from detected onwards to canceled, so money on its way cannot be cancelled out from under the customer.
payment_not_quoted409Call select_method first: choosing asset and wallet is what locks the 15-minute quote and reserves the unique amount (§3.2, §5.2).
payment_not_underpaid409Accept-as-paid applies only to underpaid. Read the current status first; the shortfall may already have been covered by a remainder transfer.
payment_not_resolvable409Only underpaid and paid_late payments sit in a review queue awaiting a decision.
payment_already_paid409Both confirmed and overpaid count as paid for fulfilment. Dedupe on the webhook event.id; delivery is at-least-once and unordered by design (§8).
hold_already_resolved409Read the hold to see its resolution. Resolutions are permanent audit evidence.
invalid_hold_reason400Record why the hold was placed or lifted; every screening decision is logged (§12).
unmatched_transfer_already_resolved409Entries leave the review queue exactly once and are never deleted — a gateway’s reputation dies the first time it loses track of a real payment (§3.2 rule 4).
invalid_resolution400Say what was decided and why. The note is the permanent record of the decision.
unmatched_transfer_not_resolved409Re-opening exists for one case: a row whose claim landed and whose CREDIT then failed, so that it can be pointed at a different payment. An entry still in the review queue is simply resolved — and a row that WAS credited is never re-opened, because that money moved (§3.2 rule 4).
invalid_reason400Re-opening a resolved review entry replaces the claim the row was carrying, so the reason is what the audit log records in its place — and the audit log is the record nobody may rewrite (§12).
attribution_already_exists409One on-chain transfer is credited at most once, whatever the queue row says — the credit itself is the guard, so a retry of a failed attribution can never become a second credit. Read the payment to see where the money landed.
invalid_webhook_url400Use a public https URL with no embedded credentials. Private IP ranges, loopback and reserved names are blocked at registration and re-checked by DNS on every attempt — that is the SSRF guard (§8), and it is what stops a webhook URL becoming a probe of our internal network.
insecure_webhook_url400Point live endpoints at https. Test mode allows http so a local tunnel can reach your laptop; a signed event carries payment state and must not cross the internet in clear.
no_signing_secret409Every endpoint needs at least one active whsec_… secret. Roll a new one; the 24h dual-secret overlap means rolling is a config change, not an outage (§8).
invalid_signing_secret400Use the whsec_… value exactly as issued; it is shown once, at creation.
invalid_attempt400Attempts are 1-based and capped at 8 — the first try plus the seven §8 retries.
attempt_exhausted409The ~3-day schedule (1m, 5m, 30m, 2h, 6h, 12h, 24h) is spent. Redeliver manually from the dashboard or the API, and backfill anything you missed with GET /v1/webhook_events?delivered=false.
egress_resolver_required400Deployment-side, never returned to a merchant: pass systemDnsResolver (or a stub in tests) as egressResolver. Without a resolution no address is ever cleared, so every transport refuses to send for want of a pinned address (§8) — a total delivery outage that would otherwise look like a merchant endpoint being down.
duplicate_job409Worker-fleet internal. Job ids are unique so a restart cannot double-schedule work.
invalid_job400Worker-fleet internal. Check the job name and its handler registration.
invalid_job_interval400Worker-fleet internal. Intervals are whole milliseconds greater than zero.
rate_limit429Retryable. Honour the Retry-After and RateLimit-* response headers and back off exponentially with jitter. Poll payment status on webhooks plus a slow reconcile loop, never in a tight poll.
rate_limit_subject_required400Internal. Every limited call is keyed by API key or client IP; neither was available.
invalid_rate_limit400Limits and windows are positive integers. Admin configuration only.
client_address_unavailable400IP is required for geoblocking and per-IP limits (§12). Ensure the proxy chain sets a trusted forwarded-for header.
invalid_geo_config400Admin configuration only. Country codes are ISO-3166 alpha-2 uppercase; the prohibited-jurisdictions list is published in the ToS (§12).
invalid_screening_timeout400Admin configuration only.
invalid_velocity_limits400Admin configuration only. Counts and amounts are non-negative integers.
invalid_velocity_subject400Admin configuration only. Velocity rules are keyed by merchant, wallet or payer address.
invalid_velocity_window400Admin configuration only.
invalid_sanctions_address400Admin configuration only. Addresses are normalized per network before being stored.
invalid_sanctions_entry400Admin configuration only. Every entry carries a list source and a reference.
duplicate_sanctions_entry409Admin configuration only. Update the existing entry instead of adding a duplicate.
invalid_mode400Test and live are parallel universes with separate data (§7.1); there is no third mode.
invalid_key_kind400Use sk (secret), pk (publishable) or rk (restricted).
invalid_scope400Grant at least one scope from the documented vocabulary. Write implies read, so payment:create need not be paired with payment:read.
duplicate_api_key409Generate a fresh key. Keys are stored hashed and never collide in practice.
invalid_role400Use a role from the admin role vocabulary.
invalid_session_policy400Idle and absolute lifetimes are positive integers, and idle may not exceed absolute.
invalid_killswitch_subject400Name the merchant, network or asset the switch applies to.
invalid_killswitch_scope400Scopes are per merchant, per network and per asset (§12).
invalid_killswitch_reason400Say why payments were paused. The reason is shown to the on-call engineer who finds it.
invalid_totp_secret400Enrol again from the dashboard rather than hand-editing the seed.
invalid_totp_digits400Use 6 — every mainstream authenticator app assumes it.
invalid_totp_time_step400Use the 30-second standard step.
invalid_totp_time400Pass a non-negative unix timestamp in seconds.
invalid_totp_window400One step either side is the usual choice; a wide window weakens the factor.
invalid_otpauth_label400Labels may not contain a colon; the issuer and account are separate fields.
audit_field_missing400Every audit row names actor, action and target (§12). An audit write that cannot be attributed is refused rather than written anonymously.
audit_field_invalid400Before/after snapshots are objects or null; never a scalar.
audit_field_too_long400Truncate before writing; audit rows are permanent and are never edited afterwards.
audit_action_invalid400Use an action from the audit vocabulary so the trail stays queryable.
audit_actor_type_invalid400Actors are user, admin, api_key or system.
audit_ip_invalid400Record a valid IPv4 or IPv6 address. OFAC fined BitPay in part for holding IP data it did not use (§12) — so the field is validated, not decorative.
duplicate_id409Ids are generated, not chosen. Do not reuse one across objects.

payment_error · 13 codes

CodeStatusWhat to do
currency_not_quotable402Its rate sources are unavailable or diverging right now. Retry shortly, or price the order in a major currency. §4.2: a currency is quotable only while its sourcing is vetted and its two legs agree.
unsupported_network402Use tron, bnb or base. If a customer already sent funds on another network, the money is in the merchant’s address on THAT chain and is recoverable by the merchant — see the wrong-network runbook (§5.4).
network_mismatch402Pick a wallet whose network matches the token: a TRON wallet cannot receive USDC on Base. Routes are keyed by (asset, network) for exactly this reason (§6.1).
token_disabled402An admin kill-switch is active on the asset or network (§12). Offer the customer another asset; in-flight payments on the disabled token still complete.
invalid_wallet_address402Re-check the address on the route for this (asset, network). Payaider holds no key for any merchant address and cannot move funds out of a wrong one — verification exists because there is no recovery (§6.2).
wallet_not_verified402Verify ownership first: sign the challenge message with the address, or complete the micro-deposit round-trip (§6.2). An unverified wallet never receives quotes — an address nobody proved they control is an address nobody can be paid at.
no_route_configured402Add a route mapping (asset, network) to a verified wallet with PUT /v1/routes before quoting that pair (§6.1). Without a route there is no address to pay.
payment_underpaid402The payment is in underpaid, not failed, and the money is safe in the merchant’s wallet. Show the customer the remaining amount — cumulative matching completes it — or accept it as paid (an audited merchant action, §5.4).
payment_overpaid402overpaid is a TERMINAL SUCCESS state: fulfil the order. The surplus is recorded on the payment and surfaced to the merchant; refund tooling ships in Phase 2 (§5.4).
payment_expired402Quotes lock for 15 minutes (§4.3). Create a new payment intent, or re-run select_method for a fresh quote and a new unique amount. Keep watching: a payment that arrives inside the 24h post-expiry window becomes paid_late, not lost money.
payment_canceled402Canceled is terminal. Create a new payment intent for a new attempt.
payment_failed402The transfer reverted, was reorged out and not re-included, or failed verification (§3.2). No funds moved. Ask the customer to retry from a new intent.
compliance_hold409The payer address screened as high-risk, so the transition to confirmed is blocked until a human reviews it (§5.3 step 6). Do not fulfil. The hold resolves to confirmed or to a documented rejection, and either way a webhook follows.

idempotency_error · 1 codes

CodeStatusWhat to do
idempotency_key_reuse409Generate a new key for a new request, and reuse a key only to retry the *identical* request. This is permanent: retrying will never succeed. For a crypto gateway the alternative is worse than an error — a double-created payment means two quoted amounts for one order, and a customer who pays both cannot be reversed (§7.3).

api_error · 22 codes

CodeStatusWhat to do
passkey_challenges_full503Back-pressure, not a fault: the challenge store is bounded so that unauthenticated requests cannot grow it without limit. Retry in a few seconds. If it persists, something is minting challenges it never spends.
admin_totp_secret_invalid500The encrypted seed is unreadable — a KMS or migration fault, never a client mistake. Escalate to platform on-call; the administrator must re-enrol MFA.
idempotency_record_corrupt500A storage fault on our side, not a client mistake. Retry with a NEW key, then check whether the original operation took effect before assuming it did not.
idempotency_record_orphaned500Retry the identical request. The stale lock is reclaimed automatically; nothing was persisted by the crashed attempt (§7.3: nothing is persisted if a request fails).
idempotency_response_unavailable500Only a REPLAY returns this — the first call succeeded and was answered in full. Do not retry the operation: fetch the object (or list it by your own reference) to pick up what was created. It happens when a response carries a value that must not sit in the 24h replay buffer, such as a one-time signing secret; if you lost that first response, roll the secret rather than creating a second object.
idempotency_response_unreadable500A deployment fault, never a client mistake: the stored response is sealed with a KMS key this instance was not given (§12 keeps response bodies encrypted at rest). It is reported rather than answered with “no stored response”, because the original request DID succeed and treating it as absent would re-run it. Do not retry — fetch the object to see what was created, and page whoever owns the key configuration.
insufficient_rate_sources503§4.2 requires at least two independent observations per quote and will not lock one on a single source. Retry shortly; this clears on its own when a source recovers.
rate_divergence503Quoting is halted on purpose while the two legs disagree (§4.2) — a bad rate in a non-custodial flow lands in the merchant’s wallet and cannot be corrected after the fact. Retry shortly; the alarm is already paging someone.
invalid_divisor500An internal invariant failure. Report the request id; no money math is ever done on floats.
reservation_capacity_exhausted503Too many concurrent invoices sit near one price point on one (wallet, token) — the §5.2 capacity limit. Retry in a moment, or route this asset to a second verified wallet to double the slot space.
invalid_token_config500A deploy-blocking config bug (a non-positive uniquifier step, a malformed contract). Escalate to platform on-call; do not retry.
provider_response_invalid502Transient. The watcher fails over to the backup provider and re-reads the range; no transfer is lost because the cursor only advances on a parsed response.
provider_unavailable503Retry with backoff. Failover is automatic; the RPC-failover runbook covers a sustained outage.
provider_network_mismatch502A misconfigured endpoint URL. The response is discarded rather than trusted — crediting a payment from the wrong chain’s data is unrecoverable. Escalate to platform on-call.
verification_invariant500A verified wallet was found without its evidence, or evidence without a wallet. Escalate; the wallet stays ineligible for quotes until it is resolved.
screening_timeout504Screening fails CLOSED: no payment reaches confirmed unscreened, because sanctions liability is strict and architecture is no defence (§12, the BitPay precedent). Retry; the payment waits, it is never silently passed.
attribution_failed502The transfer is NOT credited and the queue row is resolved — the claim happens first, on purpose, because it is the only thing stopping one transfer from being credited to two invoices. Search the audit log for UNMATCHED_ATTRIBUTION_FAILED with the request id in the message, fix what the engine refused, then re-open the row and resolve it again.
append_only_violation500payment_events, fee_ledger and audit_logs are append-only, enforced by a Postgres REVOKE UPDATE, DELETE and not by convention (§10). Correct a mistake with a new row.
invalid_config500A deploy-time misconfiguration. Escalate to platform on-call; do not retry.
bootstrap_admin_credentials_required500Supply ADMIN_BOOTSTRAP_PASSWORD and ADMIN_BOOTSTRAP_TOTP_SECRET from a secret store. Outside development the process will not generate them, because a generated credential has to be printed to be usable and both factors of the account that can revoke live API keys would then sit in one log line (§12). Set NODE_ENV=development only for a throwaway local console.
rng_failure500The process refuses to mint keys, secrets or ids from a degraded entropy source. Escalate immediately; do not restart into the same host without investigating.
internal_error500Our bug, not yours. Quote the request id from request_log_url to support. Retry with the SAME Idempotency-Key: nothing is persisted when a request fails validation, and a replay cannot double-create a payment.