authentication_required | 401 | Send 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_header | 401 | Use 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_key | 401 | Check 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_revoked | 401 | Issue a new key in the dashboard and redeploy. Revocation is immediate and permanent — a revoked key never comes back, by design. |
api_key_expired | 401 | The 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_credentials | 401 | Dashboard 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_password | 401 | Re-enter the password, or reset it from the sign-in page. |
account_disabled | 401 | An 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_required | 401 | Sign in to the admin console. Admin endpoints are never reachable with a merchant API key, in either mode. |
unknown_session | 401 | Sign in again to obtain a fresh session. |
session_expired | 401 | Sign in again. Sessions have both an idle timeout and an absolute lifetime; the absolute one cannot be extended by activity. |
session_revoked | 401 | Somebody 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_configured | 501 | A 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_malformed | 400 | The 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_rejected | 400 | The 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_registered | 409 | Each 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_passkeys | 400 | Remove a device you no longer use and register again. The cap exists so a compromised session cannot quietly accumulate ways back in. |
unknown_passkey | 404 | It 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_mismatch | 401 | Sign 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_sessions | 401 | Sign out an existing device from the device-management screen, then sign in again. |
mfa_required | 401 | Complete 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_required | 401 | Re-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_invalid | 401 | Check 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_stale | 401 | Generate a new code and retry within the proof lifetime. |
mfa_method_unsupported | 401 | Use a TOTP authenticator app; it is the only method accepted at MVP. |
insufficient_scope | 403 | Mint 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_denied | 403 | Ask 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_forbidden | 403 | Publishable 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_permitted | 403 | Send 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_paused | 403 | An 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_actor | 403 | Every 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_actor | 400 | Use one of the §3.2 actors: api, checkout, matcher, watcher, sweeper, merchant, admin, compliance. |
actor_missing | 403 | Every audited action records who performed it (§12). Attach the acting user or admin to the request context before calling. |
missing_principal | 403 | The request reached an authorization check without having passed authentication. Route it through the authenticated edge; do not call internal handlers directly. |
wallet_not_owned | 403 | Use a wallet id from GET /v1/wallets with this key. Cross-merchant ids are never usable, in either mode. |
role_may_propose_only | 403 | High-risk admin actions are four-eyes: propose, then have a second authorized admin approve. Approval by the proposer is refused. |
self_approval_forbidden | 403 | Ask a different authorized administrator to approve it. |
second_approval_not_required | 409 | Execute it directly; only high-risk actions carry the four-eyes gate. |
executor_not_party | 403 | Execute 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_approved | 403 | Obtain the required second approval, then execute. |
second_approval_required | 403 | Propose 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_approval | 403 | Check 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_flight | 403 | Wait 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_pending | 409 | It 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_expired | 409 | Propose the action again. Approvals are short-lived on purpose: a stale approval is an approval for a situation that no longer exists. |
resource_missing | 404 | Check 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_found | 404 | Verify 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_found | 404 | List wallets with GET /v1/wallets and use an id from the response. |
merchant_not_found | 404 | Admin surface only. Check the mch_… id in the admin console. |
unknown_endpoint | 404 | List endpoints and use a we_… id belonging to this merchant and mode. |
unknown_route | 404 | Compare 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_token | 400 | Use 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_found | 404 | Reservations 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_found | 404 | Read the unmatched-transfers queue and use an id from it. |
hold_not_found | 404 | Read the holds queue in the admin console and use an id from it. |
kyb_case_not_found | 404 | Read the KYB queue in the admin console and use an id from it. |
unknown_action | 404 | Read the approvals queue; the action may already have been executed or denied. |
idempotency_key_in_use | 409 | Retryable. 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_key | 400 | Send 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_long | 400 | Use at most 255 characters; a UUID is 36. |
invalid_json | 400 | Send a JSON object with Content-Type: application/json. Trailing commas, single quotes and unquoted keys are not JSON. |
invalid_body | 400 | Top-level arrays, strings and numbers are rejected. Wrap the fields in an object, even when there is only one. |
unsupported_media_type | 415 | Set Content-Type: application/json. Form encoding is not accepted; it cannot represent the nested metadata object without ambiguity. |
method_not_allowed | 405 | Read 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_large | 413 | Trim the payload. metadata is for a handful of short keys — store large documents in your own system and reference them by id. |
request_aborted | 400 | Usually a client timeout that is shorter than your own upload. Nothing was persisted; retry with the same Idempotency-Key. |
invalid_path | 400 | URL-encode path segments; a raw space or control character is rejected. |
missing_path_parameter | 400 | Fill the {id} placeholder. A literal undefined or empty segment in the URL is the usual cause — check the variable you interpolated. |
parameter_missing | 400 | Read error.param for the field name and include it. |
parameter_invalid | 400 | Read error.param for the field name and the message for the expected shape. |
parameter_invalid_integer | 400 | Send digits only — no decimal point, no exponent, no thousands separators — and stay inside the documented bounds. |
invalid_request | 400 | A generic validation failure. error.message and error.param name the field; the API reference gives its accepted values. |
invalid_limit | 400 | Use an integer between 1 and 100. Page through results with starting_after rather than asking for one enormous page. |
invalid_cursor | 400 | Pass 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_rewind | 400 | Internal 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_filter | 400 | Use only the filters listed for this endpoint; unknown filters are refused, never ignored. |
invalid_range | 400 | Ensure the start of the range is not after its end. |
invalid_timestamp | 400 | Send ISO-8601 UTC, e.g. 2026-08-27T12:00:00.000Z (§7.3). |
invalid_now | 400 | Pass a real date. Sandbox time-travel helpers accept ISO-8601 UTC only. |
invalid_advance | 400 | Sandbox only. Time moves forward; to rewind, re-seed the sandbox. |
invalid_duration | 400 | Send whole milliseconds. Durations are never fractional and never negative. |
invalid_ttl | 400 | Send whole milliseconds greater than zero. |
amount_too_small | 400 | Send 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_large | 400 | Split 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_amount | 400 | Amounts 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_range | 400 | On open-amount payment links, ensure min_amount is not above max_amount. |
excess_precision | 400 | Decimals 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_decimals | 400 | Use an integer between 0 and 36. Read the exponent from the token configuration. |
invalid_currency | 400 | Send three uppercase letters, e.g. USD. Lowercase usd is rejected, not normalized. |
currency_not_supported | 400 | Use a currency from GET /v1/exchange_rates. Adding a currency is a rate-sourcing decision (§4.2), not a config toggle. |
invalid_currency_decimals | 400 | Do 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_rate | 400 | Rates are decimal STRINGS of token units per one MAJOR fiat unit, e.g. "1.000431". A float loses precision before it reaches us. |
invalid_bps | 400 | 100 bps is 1%. Fractional basis points are not accepted. |
invalid_step | 400 | Use the token’s uniquifier_step; it is a per-(token, chain) configuration value. |
invalid_tolerance | 400 | Payment 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_spacing | 400 | Concurrent reservations on one (wallet, token) must sit at least 2x the attribution tolerance apart (§5.2), or tolerant matching becomes ambiguous. |
invalid_grace_window | 400 | The 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_accrued | 409 | The 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_memo | 400 | Say why the adjustment exists. It is permanent audit evidence. |
nothing_outstanding | 409 | Read the fee ledger balance before generating a fee invoice. |
invalid_metadata_key | 400 | Use short ASCII keys, at most 40 characters, e.g. order_id. |
invalid_metadata_value | 400 | Stringify numbers, booleans and nested objects yourself, so what comes back on the webhook is byte-identical to what you sent. |
metadata_too_large | 400 | Keep 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_merchant | 400 | Internal callers must scope every operation to a merchant; the API derives it from the key. |
missing_merchant | 400 | Supply merchantId. API keys carry it implicitly; internal calls must pass it. |
merchant_closed | 403 | A closed account can still read historical data but can create nothing new. Contact support if the closure was not intentional. |
kyb_case_already_decided | 409 | Open a new case rather than overwriting a decision; KYB outcomes are audit evidence. |
asset_token_mismatch | 400 | Use the token id for the (asset, network) pair you mean; do not send an asset symbol. |
token_not_enabled | 400 | Choose an enabled token from the payment method options returned by checkout. |
invalid_token_contract | 400 | Payaider 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_id | 400 | EIP-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_address | 400 | TRON 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_hash | 400 | TRON 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_index | 400 | One 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_transaction | 409 | Expected 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_hosted | 400 | Use 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_wallet | 400 | Screening 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_mismatch | 400 | Use a wallet id from this merchant’s own wallet list. |
duplicate_wallet | 409 | Reuse the existing wallet, or relabel it. One address per (merchant, network). |
invalid_challenge_field | 400 | Return the challenge exactly as issued, with the signature. Editing the message body invalidates the proof — that is the point of it. |
route_conflict | 409 | A 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_down | 409 | Wait 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_missing | 400 | Create and verify the wallet first, then point the route at it. |
invalid_state_transition | 409 | Read 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_quoted | 409 | Call 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_underpaid | 409 | Accept-as-paid applies only to underpaid. Read the current status first; the shortfall may already have been covered by a remainder transfer. |
payment_not_resolvable | 409 | Only underpaid and paid_late payments sit in a review queue awaiting a decision. |
payment_already_paid | 409 | Both 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_resolved | 409 | Read the hold to see its resolution. Resolutions are permanent audit evidence. |
invalid_hold_reason | 400 | Record why the hold was placed or lifted; every screening decision is logged (§12). |
unmatched_transfer_already_resolved | 409 | Entries 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_resolution | 400 | Say what was decided and why. The note is the permanent record of the decision. |
unmatched_transfer_not_resolved | 409 | Re-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_reason | 400 | Re-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_exists | 409 | One 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_url | 400 | Use 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_url | 400 | Point 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_secret | 409 | Every 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_secret | 400 | Use the whsec_… value exactly as issued; it is shown once, at creation. |
invalid_attempt | 400 | Attempts are 1-based and capped at 8 — the first try plus the seven §8 retries. |
attempt_exhausted | 409 | The ~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_required | 400 | Deployment-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_job | 409 | Worker-fleet internal. Job ids are unique so a restart cannot double-schedule work. |
invalid_job | 400 | Worker-fleet internal. Check the job name and its handler registration. |
invalid_job_interval | 400 | Worker-fleet internal. Intervals are whole milliseconds greater than zero. |
rate_limit | 429 | Retryable. 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_required | 400 | Internal. Every limited call is keyed by API key or client IP; neither was available. |
invalid_rate_limit | 400 | Limits and windows are positive integers. Admin configuration only. |
client_address_unavailable | 400 | IP is required for geoblocking and per-IP limits (§12). Ensure the proxy chain sets a trusted forwarded-for header. |
invalid_geo_config | 400 | Admin configuration only. Country codes are ISO-3166 alpha-2 uppercase; the prohibited-jurisdictions list is published in the ToS (§12). |
invalid_screening_timeout | 400 | Admin configuration only. |
invalid_velocity_limits | 400 | Admin configuration only. Counts and amounts are non-negative integers. |
invalid_velocity_subject | 400 | Admin configuration only. Velocity rules are keyed by merchant, wallet or payer address. |
invalid_velocity_window | 400 | Admin configuration only. |
invalid_sanctions_address | 400 | Admin configuration only. Addresses are normalized per network before being stored. |
invalid_sanctions_entry | 400 | Admin configuration only. Every entry carries a list source and a reference. |
duplicate_sanctions_entry | 409 | Admin configuration only. Update the existing entry instead of adding a duplicate. |
invalid_mode | 400 | Test and live are parallel universes with separate data (§7.1); there is no third mode. |
invalid_key_kind | 400 | Use sk (secret), pk (publishable) or rk (restricted). |
invalid_scope | 400 | Grant at least one scope from the documented vocabulary. Write implies read, so payment:create need not be paired with payment:read. |
duplicate_api_key | 409 | Generate a fresh key. Keys are stored hashed and never collide in practice. |
invalid_role | 400 | Use a role from the admin role vocabulary. |
invalid_session_policy | 400 | Idle and absolute lifetimes are positive integers, and idle may not exceed absolute. |
invalid_killswitch_subject | 400 | Name the merchant, network or asset the switch applies to. |
invalid_killswitch_scope | 400 | Scopes are per merchant, per network and per asset (§12). |
invalid_killswitch_reason | 400 | Say why payments were paused. The reason is shown to the on-call engineer who finds it. |
invalid_totp_secret | 400 | Enrol again from the dashboard rather than hand-editing the seed. |
invalid_totp_digits | 400 | Use 6 — every mainstream authenticator app assumes it. |
invalid_totp_time_step | 400 | Use the 30-second standard step. |
invalid_totp_time | 400 | Pass a non-negative unix timestamp in seconds. |
invalid_totp_window | 400 | One step either side is the usual choice; a wide window weakens the factor. |
invalid_otpauth_label | 400 | Labels may not contain a colon; the issuer and account are separate fields. |
audit_field_missing | 400 | Every audit row names actor, action and target (§12). An audit write that cannot be attributed is refused rather than written anonymously. |
audit_field_invalid | 400 | Before/after snapshots are objects or null; never a scalar. |
audit_field_too_long | 400 | Truncate before writing; audit rows are permanent and are never edited afterwards. |
audit_action_invalid | 400 | Use an action from the audit vocabulary so the trail stays queryable. |
audit_actor_type_invalid | 400 | Actors are user, admin, api_key or system. |
audit_ip_invalid | 400 | Record 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_id | 409 | Ids are generated, not chosen. Do not reuse one across objects. |