> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cbpayapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Error format and complete code catalog

All errors share the same shape:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "error": "insufficient_funds",
  "message": "account balance is not enough for this operation"
}
```

* `error`: stable `snake_case` code — use it in your logic.
* `message`: human-readable explanation — may change, don't parse it.

<Note>
  **Sanitized error messages.** An error `message` will never expose provider names, infrastructure details, URLs, raw upstream bodies (JSON/HTML), or internal configuration — not in API responses, webhooks, or stored status fields. Business rejections from the payment processor keep their actionable reason (for example, why a document or account was rejected); infrastructure failures are replaced by the fixed generic message `"the payment provider could not process the request"` — retry those operations with the same `idempotency_key`.
</Note>

\| `payin_corridor_unsupported` | The requested country/currency/method has no active payin provider — re-read `GET /v1/payins/methods` |
\| 502 | `deposit_account_failed` | The dedicated receiving account could not be provisioned; read the account list before retrying |

## Codes by category

### Authentication and permissions

| HTTP | `error` | Meaning |
| - | - | - |
| 401 | `unauthorized` | Missing or invalid credential |
| 401 | `invalid_credentials` | Wrong email or password (login) |
| 401 | `invalid_refresh_token` | Refresh token invalid, expired, already used or revoked — send the user back to login |
| 403 | `account_required` | Endpoint requires an account credential |
| 403 | `org_admin_required` | Endpoint requires an admin credential |
| 403 | `forbidden` | Credential level not allowed |
| 403 | `account_blocked` | The account is not active; this also blocks `POST /v1/me/verification/link` — ask an administrator to restore it |
| 403 | `verification_rejected_requires_admin` | Compliance rejected the account's KYC/KYB; self-service onboarding is disabled — contact your administrator to reopen verification |
| 403 | `service_disabled` | The service is not enabled for your account (check `GET /v1/services`) |
| 403 | `org_suspended` | Service suspended; contact the CBPay team |
| 403 | `company_only` | Feature only for company accounts |
| 403 | `company_required` | Feature only for company accounts (e.g. [third-party banking](/en/guides/banking)) |
| 403 | `human_session_required` | The operation handles a private key (segregated wallet import/export) and requires a signed-in user session with 2FA — API keys are not allowed |
| 403 | `member_disabled` | The mirror user is disabled in the test environment; contact support |
| 403 | `passkey_rejected` | The passkey could not be verified; retry or sign in with another method |
| 403 | `owner_required` | Only the account owner can change the 2FA preferences |

### OTP / 2FA

Full flow and details in [security and 2FA](/en/security-2fa).

| HTTP | `error` | Meaning |
| - | - | - |
| 403 | `otp_required` | The action requires OTP: verify a challenge and retry with `X-OTP-Token` |
| 403 | `otp_invalid` | OTP token invalid, expired or already used |
| 403 | `session_required` | OTP challenges require a user session, not an API key |
| 403 | `phone_binding_cooldown` | Phone linked less than 24 h ago without verification and no alternative factor (authenticator app or verified email) |
| 409 | `phone_verification_required` | Verify your phone (OTP challenge) before enabling login 2FA over SMS/WhatsApp |
| 401 | `invalid_code` | The code does not match |
| 401 | `invalid_pending_token` | The intermediate login token expired; log in again |
| 400 | `invalid_action` / `invalid_channel` | Action or channel outside the catalog (challenge `channel` must be `sms`, `whatsapp`, `email` or `totp`) |
| 409 | `phone_required` | The account has no phone (`PATCH /v1/me`) |
| 409 | `otp_phone_missing` | Login requires OTP and the account has no phone; contact your operator |
| 409 | `challenge_not_pending` | The challenge expired or was already used; create a new one |
| 429 | `too_many_attempts` | Send/verification limits reached; wait a few minutes |
| 409 | `otp_disabled_for_org` | Two-factor authentication is not enabled for your organization; contact support |
| 409 | `totp_not_started` | Confirm requires starting enrollment first (`POST /v1/me/totp/enroll`) |
| 503 | `otp_unavailable` | Verification service unavailable (the action stays blocked; OTP is never skipped) |

### Social login (OAuth)

Full flow and details in [social login](/en/guides/social-login).

| HTTP | `error` | Meaning |
| - | - | - |
| 400 | `invalid_provider` | Provider outside `google/apple/microsoft/facebook` |
| 400 | `provider_not_configured` | Your organization has not enabled that provider |
| 401 | `invalid_credential` | The provider credential is invalid, expired or from another app |
| 409 | `email_conflict` | An account with that email already exists; sign in and link the provider |
| 409 | `identity_taken` | That provider is already linked to another account |
| 409 | `last_login_method` | You cannot unlink your only sign-in method |

### Organization admin panel

These codes come from **organization administration surfaces** (the [CBPay Admin](https://cbpayapp.com) panel), not from the account-level API documented above — they never appear on `/v1/*` account endpoints.

| HTTP | `error` | Meaning |
| - | - | - |
| 403 | `global_treasury_access_disabled` | The organization has not enabled global treasury visibility for its admin panel; ask a platform administrator to enable `global_treasury_read` in the organization settings |
| 503 | `core_admin_credential_missing` | A platform administrator catalog requires a configured core credential; contact the platform administrator |
| 400 | `invalid_value` | An organization setting was sent with the wrong type (e.g. `global_treasury_read` must be a boolean, not a string) |
| 409 | `approval_in_progress` | Another admin is currently applying this adjustment; wait for that action to finish before trying again |

### Validation (400)

| `error` | Meaning |
| - | - |
| `invalid_json` | Body is not valid JSON or has unknown fields |
| `invalid_lang` | `lang` must be `es`, `en` or `zh` |
| `invalid_settings` | Organization settings are not a valid JSON object or could not be normalized; correct the settings payload and retry |
| `invalid_type` | `type` must be `person` or `company` |
| `invalid_email` / `invalid_display_name` | Invalid required field |
| `weak_password` | Password shorter than 8 characters |
| `invalid_role` | Invalid member role |
| `unknown_org` | Wrong organization slug (use `cbpay`) |
| `invalid_request` | Missing `country`/`currency` |
| `invalid_purpose` | `purpose` must be `fondeo` or `banking` when creating a deposit destination; use a supported Banking corridor |
| `idempotency_key_required` | Missing idempotency key |
| `reserved_idempotency_key` | The key uses a system-reserved prefix (`payin-convert:` or `checkout-swap:`, owned by auto-conversions) — pick another key |
| `beneficiary_required` | Missing payout beneficiary |
| `invalid_cuit` | Argentina (`AR`) payouts require `beneficiary.tax_id` to be an 11-digit CUIT or CUIL — send the beneficiary's 11-digit identifier; see [payouts](/en/guides/payouts) |
| `invalid_cbu` | Argentina (`AR`) payouts require `beneficiary.account_number` to be a 22-digit CBU or CVU; USD payouts accept only CBU — send a valid 22-digit CBU/CVU (CBU for USD); see [payouts](/en/guides/payouts) |
| `invalid_amount` | Amount is not a valid positive decimal |
| `recipient_required` / `self_transfer` | Invalid transfer destination |
| `invalid_chain` / `invalid_asset` | Unsupported network or asset |
| `to_address_required` | Missing withdrawal destination address |
| `invalid_payload` | Missing a required field (e.g. `enabled` on AML monitoring, `external_customer_id` on verifications) |
| `invalid_iban` | The SEPA payout IBAN is invalid, unsupported for the `EU` route, or belongs to GB in SEPA Instant V1; correct it and retry with a new payout idempotency key |
| `invalid_qr_payload` | The payout QR is unreadable or unsupported (corrupt BR Code, bad checksum, or a dynamic PIX QR); the `message` explains the exact reason |
| `liveness_already_completed` | That verification's liveness check already passed |
| `invalid_event_type` / `weak_secret` / `invalid_callback_url` | Invalid webhook subscription |
| `invalid_phone` | Phone not normalizable to E.164 (contacts and `to_phone`) |
| `invalid_language` | The PDF report `lang` is not `en`, `es` or `zh` (AML report and verification report) |
| `invalid_locale` | `PATCH /v1/me` (or register) sent a non-empty locale outside `en` / `es` / `zh`. Empty string stores English. Distinct from `invalid_language` (PDF report `lang`) and from org-admin `invalid_value` on `default_locale` |
| `invalid_format` | The verification report `format` is not `pdf` or `json` |
| `batch_too_large` | Contact import with more than 1,000 entries (paginate the upload) |
| `invalid_body` | The request body could not be read (binary uploads, webhooks) |
| `invalid_image` | The avatar body is empty or not a supported image (PNG/JPEG/WebP, max 512 KB) |
| `invalid_interval` | Subscription `interval` must be `daily`, `weekly`, `monthly` or `yearly` |
| `query_required` | `GET /v1/resolve` needs `alias=` or `qr=` |
| `same_email` | The new login email is the current one; use `POST /v1/me/email/verify` to verify it instead |
| `invalid_status` / `invalid_kyc_status` / `invalid_direction` / `reason_required` / `account_id_required` / `invalid_service` / `invalid_fee` | Administration validations |
| `invalid_settlement_hours` | `settlement_hours` is only accepted on the `payin_card` fee service and must be a non-negative integer (`0` = immediate credit) — see [fees](/en/concepts/fees) |
| `invalid_country` | Malformed or missing country code — ISO 3166-1 alpha-2 (e.g. `GET /v1/aml/catalogs/cities?country=`); also a valid but unsupported country filter (e.g. a non-`US` `country` on the bank directory lookup) |

### QR payin idempotency

For `POST /v1/payins` with `method: "qr"`, `idempotency_key` is optional. You may
send it in the JSON body or as `Idempotency-Key`; when both are present, the
body value wins. A key can be up to 256 characters and cannot contain CR/LF.
The platform scopes the key to the account server-side; the integrator sends
the key unchanged.

* `400 invalid_idempotency_key`: the key is longer than 256 characters or
  contains CR/LF. Shorten it or remove those characters, then retry with the
  same corrected request.
* `409 idempotency_conflict`: the same key was used with a different country,
  currency, method or numeric amount. Keep the original payload for a replay,
  or choose a new key for a new charge.

With the same key and payload, the API returns the original charge with
`idempotency_hit: true` and does not create another charge. Numeric amounts are
compared by value (`100` equals `100.00`); `glosa` is display-only and is not
part of the replay identity. Read the live status with
`GET /v1/payins/{payinID}`.

### Money and state (402 / 404 / 409 / 422 / 429)

| HTTP | `error` | Meaning |
| - | - | - |
| 402 | `insufficient_funds` | Not enough available balance — on outbound banking transfers the check includes the rail fee (`balance >= amount + fee`); the operation is not created when it does not hold |
| 404 | `not_found` | Resource does not exist (or belongs to another account) |
| 404 | `not_available` | The document does not exist in the requested language |
| 404 | `country_not_found` | Unknown ISO 3166-1 alpha-2 country code (cities catalog) |
| 404 | `bank_not_found` | The routing number or SWIFT/BIC is not in the embedded bank directory — keep the payout/counterparty form manual |
| 404 | `postal_code_not_found` | Unknown postal code, or a country without a dataset (postal code lookup) — keep the address fields manual |
| 404 | `recipient_not_found` | Transfer destination does not exist |
| 404 | `verification_not_found` | The account has no submitted verification yet (`GET /v1/me/verification/report` before onboarding) |
| 409 | `duplicate` | The resource already exists |
| 409 | `receipt_not_available` | A payout receipt is not available while technical screening is pending or after a pre-debit screening failure; wait for a final financial state |
| 403 | `verification_requires_2fa` | A new self-service KYC/KYB link requires a confirmed TOTP or a registered passkey; an existing open link is re-served before this gate. **Solution:** enroll one factor and retry `POST /v1/me/verification/link` |
| 403 | `verification_required` | Your account has not approved its identity verification yet (person=KYC, company=KYB); creating authenticated payins, collect operations, dedicated deposit accounts, stored-card charges and subscriptions is blocked until approval — request your link at `POST /v1/me/verification/link`; passive deposits and reads remain available |
| 422 | `verification_required` | The operation requires the `verification_id` of an approved third-party verification (third-party banking registration, designated card) |
| 422 | `verification_not_approved` | The referenced verification is not approved yet |
| 422 | `verification_kind_mismatch` | The verification kind does not match the product (KYC ⇒ person/INDIVIDUAL, KYB ⇒ company/COMPANY) |
| 422 | `verification_invalid` | You referenced an own-onboarding verification where a third-party one is required |
| 403 | `company_account_required` | Third-party verification (KYC/KYB links/submissions) is for company accounts only |
| 409 | `already_verified` | Onboarding link requested with an already-verified account |
| 409 | `identity_locked` | With the verification approved, `display_name`, `tax_id` and `country` come from the verified identity and cannot be changed via `PATCH /v1/me`; contact support |
| 409 | `no_screening` | AML rescreen/monitoring without a prior screening |
| 409 | `no_banking_customer` | Classic Banking operation without a generic banking profile (`POST /v1/banking/customer` first); EUR Banking uses the direct virtual-IBAN flow |
| 400 | `banking_eur_requires_viban` | EUR Banking accounts are opened as virtual IBANs. Request `purpose: "banking_eur"` from Operar EUR instead of creating a generic banking customer |
| 409 | `banking_customer_exists` | The account already has a banking profile (one per account) |
| 409 | `idempotency_conflict` | The same banking profile claim is still pending or the payload changed — keep the same request data and wait for reconciliation |
| 503 | `banking_recovery_pending` | The customer creation outcome is ambiguous — operations must reconcile the durable claim before retrying with a new key |
| 409 | `bank_ref_conflict` | The bank reference is already claimed by another operation (the response names it with `claimed_by`/`claimed_kind` when it belongs to your tenant) — fix the evidence instead of retrying with a different reference |
| 503 | `bank_ref_recovery_pending` | A previous confirmation left the bank reference in an ambiguous state and the platform is reconciling the durable claim — retry the same confirmation (same reference and idempotency key); the retry resumes instead of duplicating |
| 400 | `unsupported_document_type` | The banking document type is not supported. Use one of the document types in the banking document allowlist. |
| 400 | `invalid_document_file` | The banking document file is empty, unreadable, corrupt, or not a PDF, JPEG, or PNG. Upload a valid file. |
| 422 | `currency_not_supported` | No FX rate for that currency |
| 422 | `core_rejected` | The processor rejected the operation — when the message reports an **incomplete billing address** (or a missing state/region), the stored card has no usable billing address on file: have the payer save it again with `save_card: true` ; the processor can also reject an Argentina alias payout before debit when the alias cannot be resolved or a USD alias resolves to a CVU; correct the beneficiary and retry with a new idempotency key. |
| 400 | `invalid_payload` | For `method: "card"`, `expires_at` must be RFC3339, at least 5 minutes ahead and no more than 48 hours ahead |
| 422 | `checkout_expiring_soon` | The checkout link expires in less than 5 minutes. Ask the merchant for a new link before paying by card |
| 429 | `too_many_open_card_sessions` | The account has 50 open card sessions and no unused, zero-attempt pending session can be evicted — complete or wait for an existing session to expire before creating another |
| 503 | `card_recovery_pending` | The platform is reconciling an ambiguous card request. Retry with the **same** idempotency key; do not create a new key or a second charge |
| 409 | `core_charge_terminal` | The underlying card charge already has a final status, so the platform will not credit it again — read the current payin/charge state and do not create a second charge |
| 503 | `card_reuse_unavailable` | The platform could not verify an existing open card payment. Retry the original request with the **same** idempotency key; do not create a second charge |
| 503 | `core_reuse_unavailable` | The core did not return a complete recoverable card session. Retry the original request with the **same** idempotency key |
| 422 | `recipient_unavailable` | The destination account cannot receive |
| 422 | `recipient_ambiguous` | More than one account shares the `to_phone` number (use `to_account_id` or `to_email`) |
| 422 | `contact_not_linked` | The contact has no linked CBPay account to transfer to |
| 422 | `no_saved_destination` | The contact has no saved destination for that corridor/chain |
| 422 | `wallet_limit_reached` | The account already holds its wallet for that network+asset pair (deposit: every account; [segregated](/en/guides/segregated-wallets): persons) |
| 422 | `insufficient_gas` | The [segregated wallet](/en/guides/segregated-wallets) has no native gas (TRX/ETH) for the network fee; fund the address and retry |
| 409 | `idempotency_conflict` | Another wallet creation/send with the same key is still in flight; retry with the same key |
| 400 | `physical_temporarily_unavailable` | New physical card issuance is temporarily paused; request a digital card. Existing physical cards continue operating. |
| 409 | `card_limit_reached` | A person account already has one live card or one open card application; resolve the existing application before requesting another |
| 409 | `card_cancelled` | The card is already cancelled and cannot be updated |
| 409 | `card_not_pending` | Only cards in `pending_activation` can be activated |
| 409 | `cardholder_kyc_pending` | The designated cardholder requires identity documents |
| 400 | `invalid_occupation` | `occupation` is not a catalog code (`GET /v1/cards/catalog/occupations`) |
| 400 | `invalid_kind_of_business` | `kind_of_business` is not a catalog code (`GET /v1/cards/catalog/business-activities`) |
| 422 | `issuer_data_incomplete` | Cardholder issuer fields are missing or invalid after request data, verification autofill and KYB-derived values are assembled; fix the keys listed in `message` and retry |
| 422 | `screening_required` | The account has no AML verification screening from which to derive the account holder's PEP value; complete screening and retry the same card request |
| 422 | `cardholder_name_too_long` | The person's `first_name` and `last_name` exceed the issuer's 22-character combined card-printing limit; shorten the printed name and retry |
| 400 | `invalid_settlement_asset` | `settlement_asset` is not USDT, USDC, BTC or GOLD |
| 400 | `settlement_asset_disabled` | Your organization disabled that asset as settlement source |
| 422 | `settlement_limit_exceeded` | The operation exceeds the per-operation limit for volatile assets (BTC/GOLD/SILVER/PLATINUM); use USDT/USDC or split the operation |
| 422 | `settlement_daily_limit_exceeded` | The account exceeded its 24h volume for volatile assets (BTC/GOLD/SILVER/PLATINUM); use USDT/USDC or retry later |
| 400 | `invalid_pair` | Swap with the same source and destination currency |
| 400 | `amount_too_small` | The swap amount does not reach the destination currency's minimum unit |
| 400 | `swap_asset_disabled` | One of the swap currencies is disabled for your organization |
| 409 | `already_paid` | The [universal checkout link](/en/guides/checkout) was already paid through another method |
| 410 | `checkout_expired` | The universal checkout link expired unpaid |
| 422 | `method_unavailable` | The method chosen on the checkout link is not available for that link or country |
| 400 | `country_required` | Fiat materialization on the checkout link is missing `?country=XX` |
| 400 | `currency_required` | The country offers the checkout link method in several currencies; `?currency=YYY` is missing |
| 422 | `country_unavailable` | That country has no payment methods available on the checkout link |
| 422 | `collect_otp_failed` | The rail rejected the OTP delivery for the link's pull collection |
| 422 | `collect_rejected` | The rail rejected the link's pull charge (invalid OTP or wrong data); the link stays pending |
| 422 | `provider_rejected` | For `VE/VES/c2p`, do not call `/collect/otp`; the payer supplies the bank-generated `claveDinamica` in the collect request's `otp` field. The internal adapter signal is `collect_otp_unsupported`. |
| 422 | `settlement_asset_disabled` | The checkout link's `settlement_asset` is disabled for your organization |
| 422 | `checkout_amount_mismatch` | The CBPay transfer does not cover the checkout link's current due; the message carries the updated amount |
| 503 | `checkout_recovery_pending` | The selected checkout payment option is being reconciled. Retry the same materialization request; do not create a second payment option |
| 422 | `stored_card_revoked` | The [saved card](/en/guides/stored-cards-subscriptions) is revoked; it no longer accepts charges |
| 422 | `verification_required` | [QR Crypto POS](/en/guides/qr-pos): register the merchant with the `verification_id` of their approved third-party KYC/KYB |
| 422 | `merchant_disabled` | The [QR Crypto POS](/en/guides/qr-pos) merchant is disabled; re-enable it before charging |
| 422 | `nothing_received` | The [QR Crypto POS](/en/guides/qr-pos) charge has not received any on-chain payment: there is nothing to refund |
| 422 | `refund_exceeds_received` | The refund exceeds what the [QR Crypto POS](/en/guides/qr-pos) charge received minus prior refunds |
| 503 | `refund_recovery_pending` | The refund linkage is temporarily unavailable; retry with the same idempotency key and do not originate a second withdrawal |
| 400 | `to_address_required` | The [QR Crypto POS](/en/guides/qr-pos) refund (and crypto withdrawals) require an explicit destination address |
| 422 | `deposit_account_limit_reached` | Person accounts and unsupported or existing corridors keep one deposit account per corridor (created automatically); it cannot be changed or deleted |
| 409 | `deposit_account_conflict` | The core returned a deposit destination already assigned to another organization — reconcile before retrying |

\| 422 | `deposit_account_not_recoverable` | The pending destination is not an MX/MXN/bank\_transfer instrument that can be recovered |
\| 422 | `export_rejected` | The processor rejected the segregated wallet key export |
\| 422 | `stored_card_corridor_mismatch` | The [saved card](/en/guides/stored-cards-subscriptions) belongs to a different country/currency corridor than the charge |
\| 409 | `subscription_state` | The [subscription](/en/guides/stored-cards-subscriptions) is not in a state that allows that action (e.g. pausing a canceled plan) |
\| 409 | `email_required` | The login has no real email address; set one with `POST /v1/me/email/change` |
\| 422 | `payin_not_refundable` | The payin is not credited or has no processor credential; it cannot be [refunded](/en/guides/refunds) |
\| 422 | `refund_not_supported` | That rail does not support [refunds](/en/guides/refunds) (QR, announced transfer, dedicated account, collect). POS charges are refunded through the crypto rail |
\| 422 | `refund_exceeds_payin` | The [refund](/en/guides/refunds) exceeds what is left to refund on the payin; your balance was not touched |
\| 422 | `settlement_pending` | The payin's balance is still scheduled for [settlement](/en/concepts/fees#card-payin-settlement-delay) and cannot be [refunded](/en/guides/refunds) until it is released (at `settle_at` or by an org-admin release) |
\| 400 | `invalid_amount` | The [refund](/en/guides/refunds) `amount` must be a positive decimal in the payin currency |
\| 503 | `refund_event_pending` | The refund result is stored, but its final event is still being persisted. Retry with the **same** idempotency key; do not send a new refund |
\| 503 | `payin_event_pending` | The payin is credited, but its final event is still being reconciled. Retry with the **same** idempotency key; do not create a new payin |

### Compliance (403 / 503)

| HTTP | `error` | Meaning |
| - | - | - |
| 403 | `compliance_hold` | The operation was held by the platform's compliance controls. It is not a request error: contact support with the timestamp — by policy the exact reason is not disclosed |
| 403 | `geo_restricted` | The service or the operation is not available for the origin or counterparty jurisdiction |
| 503 | `compliance_check_unavailable` | The compliance check could not be evaluated and the payout could not be placed in the technical screening queue; retry with the **same** idempotency key. When that queue is available, `POST /v1/payouts` returns `202` with `status: pending` instead. Other validation and security errors keep their own codes |
| 422 | `travel_rule_required` | On-chain withdrawal above the Travel Rule threshold without beneficiary data — add `travel_address` or `wallet_type: "self_hosted"` + `beneficiary_name` ([crypto guide](/en/guides/crypto)) |
| 422 | `travel_rule_beneficiary_required` | `beneficiary_name` is missing on a withdrawal subject to the Travel Rule |
| 422 | `travel_rule_address_mismatch` | Your `to_address` does not match the payment address approved by the receiving institution — omit it or use the one from the exchange |
| 422 | `travel_rule_rejected` | The receiving institution rejected the transfer; verify the beneficiary data |
| 422 | `travel_rule_pending` | The receiving institution has not resolved the exchange yet; retry with the **same** idempotency key |
| 422 | `travel_rule_incomplete_approval` | The receiving institution approved without providing a payment address; contact support |
| 503 | `travel_rule_unavailable` | Travel Rule exchange temporarily unavailable; retry with the **same** idempotency key |

### Qscore

Errors from the credit bureau endpoints ([guide](/en/guides/qscore)).

| HTTP | `error` | Meaning |
| - | - | - |
| 400 | `purpose_required` | Missing `purpose` when creating a report — data protection law requires declaring it (`credit_evaluation`, `tenant_screening`, `hiring`, `supplier_onboarding` or `other`) |
| 400 | `invalid_purpose` | The `purpose` is not one of the allowed values (`credit_evaluation`, `tenant_screening`, `hiring`, `supplier_onboarding`, `other`) — fix the value. `self_access` is rejected here: your own report only goes through `POST /v1/qscore/my-report` |
| 400 | `invalid_doc_id` | The `doc_id` is not valid for the given `country` (e.g. bad RUT check digit in Chile) — fix the document format |
| 400 | `invalid_subject_type` | `subject_type` is not `person`/`company` and could not be inferred from the document — send it explicitly |
| 400 | `invalid_tax_id` | The verified `tax_id` of the account is not valid for its country (self report) — contact support to fix your verified data |
| 403 | `kyc_required` | Pulling your own report requires an approved KYC/KYB — complete the identity verification first |
| 403 | `report_required` | Enabling monitoring requires a purchased report for that subject — buy one first ([guide](/en/guides/qscore)); for privacy, a subject that does not exist gets this same response |
| 404 | `no_score` | The subject has no computed score yet — buy a report first |
| 404 | `pdf_not_ready` | The report PDF is not available yet — poll the detail until `status=ready` (the `risk_report_ready` webhook tells you) |
| 409 | `no_tax_id` | The verified account has no `tax_id` on file, so the self report cannot resolve its subject — complete your verified data first |
| 409 | `identity_mismatch` | The account `tax_id` does not match the verified identity document (self report) — contact support; your verified data must be consistent |
| 422 | `unsupported_country` | This Qscore fraud/identity flow is not available for the selected country — use a supported country or contact support |
| 503 | `recovery_pending` | The fraud report is still being finalized — poll the report with the same credentials and retry the PDF after it reaches `ready` |
| 400 | `no_valid_items` | Every row of the batch was rejected (`invalid_doc_id` / `duplicate_in_batch`) and no batch was created — validate the file locally (each `doc_id` must pass the country check digit and be unique) and resubmit with a **new** idempotency key |
| 400 | `too_many_items` | A batch accepts at most 5,000 subjects — split the portfolio into multiple batches, each with its own idempotency key |
| 409 | `already_decided` | The consent link was already decided (`granted`, `revoked` or `expired`) and cannot transition again — create a new link if you need a fresh authorization |
| 409 | `link_inactive` | The banking connection behind the consent is not `active` (the widget session expired or the link was revoked) — the holder must reconnect from the same consent link |
| 409 | `holder_mismatch` | The document verified by the bank does not match the subject `doc_id` — an account owned by a different document can never grant the consent; check that you created the link for the right document |
| 409 | `seal_companies_only` | The public Qscore seal is only available for **company** accounts — person accounts have no seal ([guide](/en/guides/qscore-seal)) |
| 409 | `seal_not_eligible` | The account's Qscore does not currently qualify for a public seal (band A or B with an evaluation no older than 90 days) — buy a fresh report ([guide](/en/guides/qscore)) and, if the band stays below B, the seal stays unavailable until the score improves |
| 404 | `no_active_seal` | The account has no active seal to revoke — activate one first with `POST /v1/qscore/my-seal`; a revoked seal stays revoked permanently (create a new one instead) |

### Transactional firewall

Errors from the review of operations held by the transactional firewall ([guide](/en/guides/transaction-reviews)). The hold itself is **not an error**: the create call answers `202 Accepted` with `status: in_review` and a `review_id`.

| HTTP | `error` | Meaning |
| - | - | - |
| 400 | `invalid_status` | Invalid `status` filter — use `in_review`, `info_requested`, `released`, `rejected` or `all` |
| 400 | `invalid_range` | Invalid `from`/`to` dates — `YYYY-MM-DD` format (organization timezone) |
| 400 | `invalid_name` | Missing filename in the `name` query param (max 200 chars, no path separators) |
| 400 | `empty_file` | The file body arrived empty |
| 404 | `not_found` | The review or file does not belong to your account — by design the API never answers 403 |
| 409 | `not_awaiting_info` | The review is not waiting for information (it already moved back to `in_review` or has a final decision) — check its current state |
| 413 | `file_too_large` | The file exceeds the 50 MB limit — compress it or split it |
| 415 | `unsupported_file_type` | Unsupported type — use PDF, PNG, JPEG, WEBP, TXT, CSV, DOC(X) or XLS(X) with the correct `Content-Type` header |
| 422 | `file_limit_reached` | The review already has 20 files |
| 503 | `firewall_unavailable` | The policy could not be evaluated or the review could not be persisted — retry with the **same** idempotency key |
| 503 | `storage_unavailable` | File storage is unavailable — retry in a few seconds |

### Real-time events (SSE)

Codes from [`GET /v1/events`](/en/realtime-events) and its history.

| HTTP | `error` | Meaning |
| - | - | - |
| 400 | `invalid_event_type` | A value in `?types=` is not in the catalog — `message` lists the valid ones |
| 429 | `too_many_streams` | Concurrent stream limit reached (per account or per organization) — close one before opening another |
| 429 | `rate_limited` | Too many stream openings from this IP (600 per hour) — the quota counts *attempts*, not live connections: stop the reconnect loop and back off |
| 500 | `streaming_unsupported` | The connection does not support streaming (an intermediate proxy is buffering) — remove the buffering or fall back to webhooks |
| 503 | `stream_unavailable` | The stream could not be opened; retry with backoff and keep your `Last-Event-ID` |

### Message signing & wallet linking

Codes from message signing with wallets (EIP-191 on EVM, TIP-191 on TRON): server-side signing with segregated wallets (`POST /v1/segregated-wallets/{id}/signatures`, which issues a publicly verifiable signature proof) and external wallet linking via challenge (`POST /v1/wallet-links/challenges` → sign the challenge with your wallet → `POST /v1/wallet-links/verify`).

| HTTP | `error` | Meaning |
| - | - | - |
| 400 | `unsupported_chain` | The `chain` must be `tron` or `eth` |
| 400 | `invalid_statement` | The `statement` exceeds 280 characters — shorten it (note: the signed envelope itself accepts at most 140) |
| 409 | `proof_not_signable` | The proof is no longer in a signable state (already signed, expired or canceled) — request a new one |
| 409 | `custody_transferred` | The wallet's custody was transferred to the client, so server-side signing is no longer possible — sign with your own key |
| 409 | `challenge_consumed` | This challenge was already used or is no longer valid — request a new one |
| 410 | `challenge_expired` | This challenge expired — request a new one and sign it within its validity window |
| 422 | `signature_mismatch` | The signature does not match the challenge address — sign the exact challenge message with the wallet you are linking |
| 422 | `sign_rejected` | The signer rejected the request — the `message` carries the scrubbed reason. A `statement` of 141–280 characters also lands here (the envelope accepts at most 140) |

### Service (5xx)

| HTTP | `error` | Meaning |
| - | - | - |
| 500 | `internal_error` | Unexpected error; retry with the same idempotency key |
| 500 | `fee_config_invalid` | The account's fee configuration is invalid; contact support (the operation did not run) |
| 502 | `rates_unavailable` | FX rates temporarily unavailable |
| 502 | `core_unavailable` | Processor temporarily unavailable |
| 502 | `core_invalid_response` | The processor returned an unexpected response; retry with the **same** idempotency key |
| 502 | `compliance_unavailable` | AML screening temporarily unavailable |
| 503 | `verifications_unavailable` | Identity verification temporarily unavailable |
| 503 | `org_credential_missing` | Service being configured; contact CBPay support |
| 503 | `withdrawals_unavailable` | On-chain withdrawals not enabled for the corridor |
| 503 | `pricing_unavailable` | BTC/GOLD/SILVER/PLATINUM execution price unavailable or stale; retry later or settle in USDT/USDC |
| 503 | `channel_unavailable` | The payout channel is temporarily unavailable; retry later with the **same** idempotency key |
| 503 | `webhook_processing_pending` | An inbound webhook event is already being processed; retry the same delivery after a short backoff and do not create a new operation |
| 503 | `export_unavailable` | Segregated wallet private key export is not enabled on this environment |

## How to handle them

* **Validation 4xx**: fix the request. Don't retry as-is.
* **402**: fund the account and retry (new idempotency key only if the
  operation was never created).
* **5xx / timeouts**: retry with **the same** idempotency key; the operation
  will never duplicate.

## Qscore fraud and Index

| HTTP | `code` | Meaning and solution |
| -: | - | - |
| 400 | `invalid_period` | The Index period is not `YYYY-Q1` through `YYYY-Q4` — use a closed-quarter value |
| 429 | `too_many_attempts` | Public fraud verification is rate-limited — retry after a short delay |
| 502 | `generation_failed` | Fraud report generation failed after the charge path — the fee is refunded; retry with a new idempotency key |

## Corridor lifecycle

| HTTP | Code | Meaning and fix |
| - | - | - |
| 409 | `corridor_disabled` | The selected corridor is administratively disabled. Use another enabled method or contact the organization operator. |

## Banking creation recovery errors

| Code | HTTP | Meaning and action |
| - | -: | - |
| `idempotency_key_mismatch` | 400 | Header/body keys differ or the same key is reused with a different request hash. Correct the request or use a new key. |
| `idempotency_conflict` | 409 | The same key is already claimed with a conflicting payload. Do not resend with a new payload. |
| `idempotency_failed` | 409 | The claim is terminally failed. Start a new operation with a new key after reviewing the failure. |
| `claim_not_pending` | 409 | The requested human recovery is no longer pending. Read the claim and use the terminal result. |
| `claim_customer_owned_elsewhere` | 409 | The provider customer is already linked to another account. Stop and investigate ownership. |
| `banking_customer_creation_in_progress` | 409 | A lock is held by another banking operation. Retry the read/recovery flow later. |
| `banking_recovery_pending` | 503 | The result or refund remains ambiguous. Do not originate a second provider request; use the recovery endpoint. |
| `banking_invalid_response` | 422 | Provider account ownership did not match the claimed customer's `ProviderID`. |

## Banking creation recovery errors

| Code | HTTP | Meaning and action |
| - | -: | - |
| `claim_identity_mismatch` | 422 | The provider customer identity does not match the original banking request. Stop recovery and review the claim. |
| `claim_not_pending` | 409 | The claim is no longer pending. Read the terminal result instead of retrying recovery. |
| `claim_customer_owned_elsewhere` | 409 | The provider customer is already owned by another account. Stop and investigate ownership. |
| `banking_recovery_pending` | 503 | The provider result remains ambiguous. Use the admin recovery route; do not originate a second create request. |

## EUR banking and virtual IBAN errors

| Code | Meaning and action | | |
| - | - | - | - |
| `banking_customer_incomplete` | The banking customer lacks the provider identity required for the operation. Complete the customer/KYC/KYB data, then retry with the same idempotency key. | | |
| `registrant_incomplete` | 422 | Virtual IBAN request / approval | The verified KYC/KYB profile is missing required registrant fields; provide the missing document or incorporation data named in the message and retry the same request. |
| `provider_rejected` | The upstream banking operation was rejected. Do not create a new money operation automatically; inspect the resource and follow its retry/reconciliation state. | | |
| `customer_id_required` | A banking operation requires an explicit customer identifier. Provide the customer ID from the account-owned resource. | | |
| `iban_required` | The operation requires the IBAN associated with the owned virtual account. | | |
| `operation_in_progress` | The same banking operation is still being reconciled. Poll the resource and retry with the original idempotency key. | | |
| `ownership_required` | The resource belongs to another organization or account. Use only an owned resource; never guess or substitute an ID. | | |

## Account broadcast error codes

These codes are returned by the organization administration broadcast
endpoints. They are listed here so shared SDKs can keep one error catalog.

| HTTP | `error` | Cause | Solution |
| -: | - | - | - |
| 400 | `invalid_channels` | No supported delivery channel was selected | Send `email`, `notification`, or both |
| 400 | `invalid_content` | Subject/body is empty or too long | Use subject 1–160 runes and body 1–4000 runes |
| 400 | `invalid_audience` | Audience mode, filters or IDs are invalid | Use `filters` or 1–5000 organization-owned account IDs |
| 400 | `invalid_reason` | Rejection reason is missing or too long | Send a 1–500 rune audited reason |
| 403 | `named_admin_required` | The actor is not a named administrator | Use a named `god` or `super_admin` administrator |
| 403 | `partner_forbidden` | A bank partner tried to use an organization broadcast | Use an allowed organization administrator |
| 409 | `empty_audience` | No account matched at submit/approval time | Correct the audience and resubmit |
| 409 | `audience_too_large` | The filters match more than 20000 accounts | Narrow the filters and resubmit |
| 409 | `second_approver_required` | The creator tried to approve their own campaign | Have a different named God approve it |

## Banking EUR and company-wallet errors

| HTTP | Code | What to do |
| -: | - | - |
| 409 | `virtual_iban_limit_reached` | A person already has a virtual IBAN for this purpose. Reuse it; companies are not subject to this per-purpose cap. |
| 409 | `no_fee_charged` | No fee was charged for this virtual IBAN (the first funding IBAN is free). Nothing to refund. |
| 409 | `wallet_not_ready` | The company wallet is still pending/provisioning. Poll the detail and wait for `active`. |
| 422 | `virtual_iban_limit_reached` | The person-per-purpose limit was reached while reserving the request. Do not create a second key. |
| 422 | `registrant_incomplete` | Complete the missing verified KYC/KYB or incorporation fields named in `message`, then retry the same durable request. |
| 502 | `wallet_reservation_failed` | The reservation response had no usable order reference. Reconcile before any retry. |
| 502 | `wallet_reservation_status_failed` | The status read failed. Keep the same `order_reference` or `client_order`. |
| 502 | `wallet_read_failed` | The live wallet response or EUR amount was unreadable. Retry the read after checking the wallet state. |
| 502 | `wallet_statement_failed` | The statement response was unreadable. Use a valid completed-day window and retry the read. |

## Account integration policy

| HTTP | `error` | Meaning and solution |
| -: | - | - |
| 400 | `email_domain_blocked` | The email domain is blocked for registration or email change; use a permitted domain. |
| 400 | `invalid_secret` | The webhook secret must contain 16 to 256 characters. |
| 403 | `session_required` | Account API-key and webhook creation requires a human JWT session, not an API key. |
| 403 | `integration_company_only` | A person needs an effective administrator grant; a company needs approved KYB. |
| 403 | `integration_kyb_required` | Approve the account's KYC/KYB before creating or reactivating the integration. |
| 403 | `account_blocked` | The account is not active; ask the administrator to restore it. |
| 403 | `otp_required` | Complete the `api_key_create` or `webhook_manage` OTP challenge and retry. |
| 403 | `integration_forbidden` | The account cannot use integrations; read `GET /v1/integration-status` and resolve the returned eligibility reason. |

## Disputes and fraud

| HTTP | Code | Resolution |
| -: | - | - |
| 400 | `idempotency_key_required` | Send the key in the JSON body or `Idempotency-Key` header for a money-changing dispute action. |
| 400 | `invalid_payload` | Correct the amount, date, UUID, multipart file or required field. |
| 404 | `not_found` | The case, import, row, evidence or account is outside the caller scope. |
| 409 | `case_already_open` | Review the existing open case for the payin; do not create a new key. |
| 409 | `payin_not_credited` | The payin is not `credited`, so there is nothing to hold; wait for credit before opening a case or binding an import row. |
| 409 | `second_approver_required` | A different named admin must close the case. |
| 409 | `invalid_state` | The requested note, submit, evidence or close transition is not valid now. |
| 409 | `bind_failed` | The import row is no longer unmatched or the selected payin cannot be bound. |
| 409 | `import_in_progress` | An import with this key is still processing; wait and replay with the same key. |
| 503 | `pack_unavailable` | Retry after private storage or PDF rendering recovers. |
| 503 | `storage_unavailable` | Restore private storage and retry the evidence operation. |

## SEPA funding-source errors

These errors are returned before a customer payout or EUR Banking operation
is debited or dispatched:

| HTTP | `error` | Meaning and solution |
| -: | - | - |
| 400 | `invalid_request` | `source_virtual_iban_id` must be a valid UUID. |
| 404 | `not_found` | The explicit virtual IBAN does not belong to the account. |
| 422 | `funding_account_required` | No allocated, active virtual IBAN exists for the required product purpose. Use an active source for this flow. |
| 422 | `ambiguous_source_viban` | More than one active EUR funding account exists. Send the selected `source_virtual_iban_id`. |
| 503 | `funding_account_unavailable` | The source-account check is temporarily unavailable. Retry with the same idempotency key; no debit or dispatch was performed. |

### Money-out limit errors

| HTTP | `error` | Meaning |
| - | - | - |
| 422 | `amount_below_minimum` | The operation amount is below the effective minimum; read `GET /v1/org/txn-firewall/limits/effective`. |
| 422 | `amount_above_maximum` | The operation amount is above the effective maximum; read the effective policy before retrying. |
| 422 | `velocity_limit_exceeded` | An internal transfer exceeded a rolling amount or operation-count cap. Payouts and crypto withdrawals enter review with `reason=velocity_limit` instead. |
| 503 | `limits_unavailable` | The limits could not be evaluated before debit; retry with the same idempotency key after storage recovers. |

## `dispute_fiat_unsupported`

A dispute cannot be opened for a payin credited in locally held BOB, MXN or ARS. The funds remain in the local balance; use a supported USD or USDT credit or wait for the product to support local-fiat disputes.

## Argentine bank aliases and profile aliases

| HTTP | `error` | Meaning / action |
| - | - | - |
| 400 | `missing_alias` | An AR/ARS platform deposit-account creation must include the caller-chosen `bank_alias` |
| 400 | `invalid_alias` | `bank_alias` must be 6–20 ASCII characters from `[A-Za-z0-9.-]` and contain at least one letter |
| 400 | `invalid_account` | The core could not resolve the supplied deposit-account number |
| 400 | `invalid_request` | A profile alias body value did not match a candidate derived from the verified name, or `bank_alias` was sent to an unsupported corridor |
| 409 | `alias_already_set` | The profile alias is permanent and has already been set |
| 409 | `alias_taken` | **Profile alias:** every candidate derived from the verified name is occupied; contact support. This is not actionable by inventing a value |
| 422 | `alias_taken` | **Bank alias:** the Argentine banking system already uses this value; choose another |
| 422 | `alias_unsupported` | The selected deposit-account rail does not support bank aliases |

## Grupo CB contract ceremony

These errors apply to `POST /v1/me/contracts/envelopes/{envelopeID}/sign` and
the account contract resources. Messages may be generalized for privacy; the
code is the stable integration contract.

| HTTP | Code | Cause and solution |
| -: | - | - |
| 400 | `idempotency_key_required` | Organization issuance requires a body key or `Idempotency-Key`; retry with a stable key. |
| 400 | `invalid_idempotency_key` | The key is invalid or too long; use a shorter stable key. |
| 400 | `invalid_reason` | Void requires a non-empty reason; send the reason in the void body. |
| 400 | `invalid_content_type` | Signature-library upload must use `image/png`, `image/jpeg` or `image/jpg`. |
| 400 | `empty_file` | The signature upload has no bytes; send the original image bytes. |
| 400 | `invalid_image` | The file is not a valid PNG/JPG image; export it again in a supported format. |
| 403 | `contract_counsel_required` | English issuance requires counsel approval; approve it or issue Spanish. |
| 403 | `human_session_required` | Signing requires a human member session; API keys cannot sign. |
| 403 | `account_blocked` | The account is not active; ask an administrator to restore it. |
| 401 | `invalid_otp` | The one-time OTP is invalid, expired or already used; send a fresh token in `X-OTP-Token`. |
| 409 | `contract_invalid_state` | The envelope is already completed or voided; do not repeat the operation. |
| 422 | `contract_template_superseded` | The envelope was issued on an older template version; ask for reissue before signing. |
| 413 | `file_too_large` | The raw upload is over 5 MiB, or the normalized signature image is over 8 MiB. |
| 422 | `invalid_lang` | Use only `es` or `en`; invalid values are rejected. |
| 422 | `contract_account_ineligible` | The target must be an active company account with approved KYB. |
| 422 | `contract_unfillable` | Inspect the response `missing` array: it may list identity data, counsel approval, or `pricing:<flag>` when an enabled service has no effective price row. |
| 422 | `invalid_consent` | The signing body must contain the JSON boolean `"consent": true`. |
| 422 | `invalid_signer` | Signer name/title are empty or exceed the permitted length; use a human owner/operator. |
| 422 | `invalid_signature_image` | `signature_png` is not a valid, non-blank PNG, exceeds the encoded or decoded size limit, or is below the minimum dimensions; send a valid signature image and retry with a fresh OTP. |
| 502 | `storage_failed` | Private storage failed while creating the final artifact; reconcile the envelope before retrying. |
| 502 | `email_failed` | Completion email delivery failed after signing; the completed envelope remains authoritative. |
| 503 | `storage_unavailable` | Private storage is unavailable; retry after the service recovers. |

## Environment handoff

| HTTP | `error` | Meaning and fix |
| - | - | - |
| 401 | `invalid_handoff` | The handoff token is missing, expired, already consumed, or was issued for the other environment. Mint a fresh handoff and exchange it in the same environment. |
| 401 | `handoff_replayed` | This handoff token was already consumed (single use). Mint a fresh handoff. |

## 2FA and passkeys

| HTTP | `error` | Meaning and fix |
| - | - | - |
| 403 | `channel_locked_by_org` | The organization policy enables TOTP only and blocks passkey registration for this account; it also rejects challenges whose explicit `channel` is weaker than the org floor. Contact an org admin to change the policy. |
| 409 | `passkey_exists` | The credential ID is already registered on this account. Sign in with the existing passkey. |
| 409 | `totp_already_enrolled` | TOTP is already active on this account. Authenticate with the existing secret. |

## Checkout and payout documents

| HTTP | `error` | Meaning and fix |
| - | - | - |
| 409 | `materializing` | Another materialization for this checkout link is in flight. Retry after it finishes. |
| 400 | `invalid_file_name` | The file name is empty, too long, or contains path characters (`/`, `\`, `..`). Send a plain file name. |
| 415 | `unsupported_media_type` | The Content-Type is not an accepted document type. Send PDF, JPEG or PNG. |

## `destination_recently_rejected`

| HTTP | Code | Meaning | What to do |
| -: | - | - | - |
| 422 | `destination_recently_rejected` | The bank recently rejected the same normalized destination for this account. No payout is created, no balance is debited, and the idempotency key is not consumed. | Replay the original request with the same key first. If you intentionally retry, use a new idempotency key and set `options.destination_retry_ack` to the most recent rejected payout ID. |

The acknowledgement is valid only for the most recent rejection of that
destination. A retry with the same key but different `options` is an
`idempotency_conflict` (`409`). Other payout methods and destinations without
a recent rejection continue through their normal validation and dispatch path.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.