Error Handling
All API error responses follow a standard JSON format to facilitate programmatic handling.
Error Format
{
"errorCode": "missing_tenant",
"message": "X-Tenant-Id header is required"
}
The stable contract is errorCode — branch on it. message is presentation text (in Brazilian Portuguese) and may be rewritten without notice.
When the error originates at the banking provider, the response also carries a partner block with its raw code and message, so you can diagnose without opening a ticket:
{
"errorCode": "limit_exceeded_daily",
"message": "O valor excede o limite diurno desta conta. Limite restante: R$ 0,00.",
"partner": {
"code": "request.query.daily_limit_exceeded",
"message": "O valor R$ 1.000,00 excede seu limite diurno de pagamentos por Pix. Limite restante: R$ 0,00"
}
}
The partner block is informational: use it in logs and support tickets, never as a decision key in your code. It may carry code, message and field (the field the provider pointed at), and is omitted when the provider gives no detail.
General Error Catalog
| Code | HTTP | Description |
|---|---|---|
missing_tenant | 400 | The X-Tenant-Id header is required and was not provided. |
tenant_mismatch | 403 | X-Tenant-Id does not match the tenant of the account in the path (or the body's tenantId). |
tenant_forbidden | 403 | The token is not authorized for the tenant in X-Tenant-Id. Applies to every /v1/ route: credential and header must point to the same tenant. |
tenant_suspended | 403 | There is an open pendency and write operations are suspended. Reads keep working. Contact CorpX support to clear it. |
tenant_disabled | 403 | The tenant is disabled: no API route responds. Contact CorpX support to restore access. |
account_not_found | 404 | The accountId in the path does not exist. |
not_found | 404 | The requested resource does not exist. |
invalid_payload | 400 | The request body (JSON) is invalid or contains validation errors. |
invalid_body | 400 | The body is not valid JSON. |
missing_fields / missing_field | 400 | Required fields are missing. The message names them. |
invalid_field | 400 | A field value is outside the accepted format or limit. The message names the field and the limit. |
idempotency_conflict | 409 | On POST /v1/accreditations/biometry-evidence, the evidenceId derived from the Idempotency-Key already belongs to another tenant. Reusing an Idempotency-Key with a different body does not return a conflict on the other endpoints: you get the result of the original operation. See Idempotency. |
invalid_state | 409 | The operation does not apply to the resource's current state. |
forbidden | 403 | You do not have permission to perform this action on the requested resource. |
unauthorized | 401 | Missing Authorization header. An invalid or expired token returns 403 (obtain a new token via client_credentials). |
insufficient_scope | 403 | The credential lacks the scope required by the route (e.g. qrcode.manage, pix_out.create), or is read-only attempting a write. The message names the missing scope. See the Authentication Guide. |
user_token_forbidden_on_financial_op | 403 | User tokens cannot move money: financial operations require an application credential (client_credentials). |
db_unavailable / banking_unavailable / temporal_unavailable | 503 | An internal dependency is temporarily unavailable. Safe to retry. |
db_error | 500 | Internal persistence error. |
internal_error | 500 | An unexpected error occurred on our servers. |
workflow_start_failed / workflow_signal_failed | 502 | Failed to start or signal the internal workflow; retry with the same Idempotency-Key. |
Banking Provider Errors
This is the complete list of errorCode values that represent a refusal or failure at the banking provider, as opposed to CorpX validation. It is generated from the catalog in our own code, so it will not drift from what the API actually emits.
Two things worth knowing:
- The HTTP status is ours, not the provider's. The provider answers
403for an exceeded limit, which many clients read as an authentication failure; we answer422. Likewise,410 Gonefor a removed QR Code becomes409, and any provider5xxbecomes502. - Retryable below means repeating the same request (with the same
Idempotency-Key) may yield a different outcome. Where it says "no", fix the data or the condition before resending.
The same codes appear in failure webhooks under data.errorCode, with the matching data.partner block.
| Code | HTTP | What happened and what to do | Retryable |
|---|---|---|---|
account_not_entitled | 409 | The account is fully accredited, but the banking provider has not enabled this service for it — other operations on the same account keep working. This is neither a transient failure nor a problem with your request: retrying will not help until the provider enables it. Contact support with the accountId. | no |
account_not_ready | 409 | The account has no counterpart at the banking provider yet — accreditation is in progress, was refused, or never completed. This is not an API outage: no banking operation works on this account until the accreditation reaches ACTIVE. | no |
already_at_partner | 409 | The document is already accredited at the banking provider by another participant. Requires a control transfer through support. | no |
bacen_rejected_content | 400 | Text content (description/message) contains a character the Brazilian Central Bank does not accept. | no |
balance_reservation_failed | 422 | Balance reservation failed (funds locked, settling, or racing with another operation). | no |
boleto_already_settled | 409 | The bank slip is already settled. Check the original payment before resending. | no |
boleto_amount_mismatch | 422 | Amount differs from the registered bank slip amount (consider interest, fine and discount). | no |
boleto_scheduled | 409 | There is an active schedule for the same barcode. | no |
challenge_failure | 422 | PIN/2FA validation failed at the banking provider. | no |
conflict | 409 | State conflict at the banking provider (e.g. resource already processed). | no |
identifier_conflict | 409 | The Identifier was already used at the banking provider. Resending the same value does not create a new operation. | no |
insufficient_funds | 422 | Insufficient balance as computed by the banking provider at settlement time. | no |
invalid_field | 400 | Field-level validation at the banking provider. partner.field names the field and the message carries the limit when available. | no |
invalid_payload | 400 | The banking provider rejected the request body. The partner block carries the original validation message. | no |
invalid_pix_key | 400 | Key format does not match the declared type (CPF, CNPJ, EMAIL, PHONE, EVP). | no |
key_inactive | 422 | The key exists but is inactive or under portability. Use another key or bank details. | no |
key_not_found | 404 | Key not found in DICT. Check for typos or ask the recipient for another key. | no |
key_ownership_mismatch | 422 | Attempt to register or use a key whose document differs from the account holder's. | no |
limit_exceeded | 422 | A limit was exceeded but the provider did not say which window. | no |
limit_exceeded_daily | 422 | Daytime window limit (06:00–20:00). The message carries the remaining limit when the provider reports it. | no |
limit_exceeded_monthly | 422 | Accumulated monthly limit. Only clears at month rollover or with a limit increase. | no |
limit_exceeded_nightly | 422 | Nighttime window limit (20:00–06:00), usually lower than the daytime one. | no |
limit_exceeded_transaction | 422 | Per-transaction limit. Split the operation or request a limit increase. | no |
not_found | 404 | Resource does not exist at the banking provider (id, key or transaction). | no |
partial_refund_not_supported | 400 | Legacy — no longer returned: partial refunds are now accepted. An amount above the original returns refund_amount_exceeded. | no |
partner_account_disabled | 422 | Source account is disabled at the banking provider (reactivation required). | no |
partner_error | 502 | Unclassified failure while talking to the banking provider. Safe to retry. | yes |
partner_forbidden | 502 | Operation blocked by the banking provider for this account (product or permission not enabled). | no |
partner_internal | 502 | Internal (5xx) error at the banking provider. Safe to retry. | yes |
partner_rate_limited | 429 | The banking provider throttled CorpX's traffic. Nothing is wrong with your request — slow down and retry with backoff. | yes |
partner_rejected | 422 | Refused by the banking provider on internal policy, risk or fraud grounds. The partner block carries the stated reason. | no |
partner_unauthorized | 502 | CorpX's own credential at the banking provider was rejected. Not related to your credential; wait a few minutes before resending. | no |
partner_unavailable | 502 | Provider outage or gateway failure. Safe to retry. | yes |
pix_key_limit_exceeded | 422 | Maximum number of keys per account reached (5 for individuals, 20 for companies). | no |
qr_amount_mismatch | 422 | A fixed-amount QR Code does not accept a different amount. | no |
qr_gone | 409 | QR Code removed or expired at the banking provider. | no |
recipient_account_not_found | 422 | Destination bank/branch/account does not exist. Applies to TED and internal transfers. | no |
refund_amount_exceeded | 400 | Refund amount is larger than the refundable balance of the original PIX. | no |
timeout | 504 | Timeout calling the banking provider. The operation may exist — query it before resending. | yes |
Business-Specific Errors
PIX Out (Transfers)
| Code | HTTP | Description |
|---|---|---|
policy_denied | 422 | A policy rule configured for the account or tenant refused the operation — transfer, QR code creation, PIX key creation, or refund. The body carries violations with the rule and message of each violated rule. See Policies and Rules. |
invalid_identifier | 400 | identifier exceeds the accepted length or uses disallowed characters. |
invalid_bank_code | 400 | On PIX by bank-account details, bankCode accepts a 3-digit Compe code or an 8-digit ISPB. TED accepts only the Compe code — see TED. |
invalid_field | 400 | A known provider limit caught before sending — description over 140 characters, or branch/accountBranch over 4 digits. |
dict_lookup_limit_exceeded | 429 | DICT lookup limits were exceeded: either an absolute count (maxLookupsPerDay, maxLookupsPerMinute, maxNotFoundPer5min) or a rate over a time window (lookups per transfer, or the fraction of lookups resolving no key, from 5 min to 30 days). Both the tenant/account policy and, for fields left blank, the global default apply — always counted per account, never summed across the tenant. The message indicates which counter or window triggered. Wait for the window to reset (00:00 BRT for per-day; sliding for the rates) or adjust the limits in the Backoffice. The ratio reason (maxLookupRatio) was discontinued in v2.53.0 and no longer appears. |
beneficiary_not_found | 404 | The given beneficiary does not exist in the account's address book. |
beneficiary_incomplete | 422 | The stored beneficiary lacks data required by the chosen operation. |
beneficiary_not_active_at_partner | 422 | The beneficiary exists locally but is not active at the provider yet. |
batch_too_large | 400 | BigPix: the requested amount would produce more than 99 chunks. |
batch_not_found | 404 | BigPix: the requested batchId does not exist. |
Balance, limits, PIX key and provider refusals use the codes in Banking Provider Errors.
QR Code
| Code | HTTP | Description |
|---|---|---|
missing_field | 400 | pixKey missing — and, on dynamic QR, value missing or not positive. |
invalid_field | 400 | message over 140 characters, a provider limit. |
invalid_identifier | 400 | identifier exceeds the accepted length or uses disallowed characters. |
invalid_payload | 400 | expirationDate is not RFC 3339 or is in the past. |
missing_param | 400 | Lookup and cancellation require the identifier query param (alias txid). |
policy_denied | 422 | A policy rule refused the creation (qrCode.canCreateStatic, qrCode.canCreateDynamic, qrCode.minAmount, qrCode.maxAmount). |
PIX Keys
| Code | HTTP | Description |
|---|---|---|
invalid_body | 400 | The body is not valid JSON. |
missing_field | 400 | keyType missing, or pixKey missing for any type other than random. |
invalid_pix_key | 400 | The key in the path could not be decoded (remember to URL-encode email and phone keys). |
policy_denied | 422 | A policy rule refused the creation (keys.canCreate, keys.maxKeys). |
The key format itself is validated by the provider and comes back as invalid_pix_key; their per-account key limit comes back as pix_key_limit_exceeded.
Refunds (PIX Refund)
| Code | HTTP | Description |
|---|---|---|
invalid_payload | 400 | reason missing or outside the official refund reason list. |
original_transaction_not_found | 404 | The original transaction (originalEndToEnd) was not found. |
refund_amount_exceeded (amount above the original, or above the balance still refundable) comes from the provider — see the previous section. partial_refund_not_supported is no longer returned: partial refunds are now accepted.
API-initiated refunds also go through the policy: policy_denied (422) when the Refund section is switched off (refund.disabled) or the amount exceeds refund.maxAmount.
TED
| Code | HTTP | Description |
|---|---|---|
invalid_bank_code | 400 | bankCode must be the 3-digit Compe code. TED does not accept ISPB — the provider contract has no such field, and sending it would leave the TED hanging. |
invalid_account_type | 400 | accountType must be CHECKING, SAVINGS, PAYMENT or SALARY. Omitted, it defaults to CHECKING. |
invalid_tax_number | 400 | taxNumber must have 11 (CPF) or 14 (CNPJ) digits. |
invalid_identifier | 400 | identifier exceeds the accepted length or uses disallowed characters. |
invalid_field | 400 | description over 140 characters, or branch over 4 digits (send the branch without the check digit). |
missing_fields | 400 | Missing value, holderName, branch, or account. |
TED deduplicates by identifier and never re-executes — resending an identifier already used returns 200 with the current state, including a failed one. See Idempotency.
Internal Transfers
| Code | HTTP | Description |
|---|---|---|
multiple_destination_accounts | 409 | On /transfers/internal/by-document, the document has more than one active account. Address it by account instead: /transfers/internal/by-bank-account (branch + number) or /transfers/internal (destinationAccountId). |
A provider refusal comes back as 422 with status: "FAILED" and a code from Banking Provider Errors — usually insufficient_funds or partner_rejected.
Statement, Entries, and Exports
| Code | HTTP | Description |
|---|---|---|
invalid_date_range | 400 | startDate / endDate outside the YYYY-MM-DD format. |
date_range_too_wide | 400 | The statement window exceeds 31 days. Split it into smaller periods. |
invalid_operation | 400 | The entry detail requires the operation query param: PIX, TED, BOLETO, INTERNAL, or FEE. |
invalid_query | 400 | The per-account timeline requires exactly one of endToEndId and identifier — never both, never neither. |
invalid_format | 400 | Export format must be csv or pdf. |
not_ready | 409 | Download requested before the export finished. Poll the status until completed. |
presign_failed | 500 | Failed to generate the download link for an already finished file. Safe to retry. |
Webhooks (subscriptions)
| Code | HTTP | Description |
|---|---|---|
missing_field | 400 | url missing when creating the subscription. |
invalid_url | 400 | url must use https://. |
hookdeck_sync_failed | 502 | Failed to register the destination at our webhook delivery service. Safe to retry. |
Accreditation / Account Opening
Errors from POST /v1/accreditations/pf|pj, POST /v1/accreditations/biometry-evidence, POST /v1/accreditations/{id}/persons/{cpf}/retry and queries.
| Code | HTTP | Description |
|---|---|---|
feature_disabled | 403 | The accreditation (BaaS) feature is not enabled for your tenant. Contact CorpX to enable it. (Reads remain available.) |
missing_fields | 400 | Required fields missing (e.g. documentType, document, legalName; PF: person.cpf; PJ: company.cnpj and at least one partner). |
invalid_document_type | 400 | Invalid documentType — use CPF or CNPJ. |
invalid_document | 400 | Document has an unexpected digit count (CPF=11, CNPJ=14). |
invalid_field | 400 | A provider limit caught at POST time: address.number over 10 characters (move the rest to address.complement) or address.cityIbgeCode without 7 digits / inconsistent with address.state. An address.complement shorter than 3 characters is not an error: the field is optional at the provider and is omitted from the submission. |
mixed_biometry_mode | 422 | PJ: all partners must use the same biometry mode (all unico or all BYO), no mixing. |
unsupported_provider | 422 | Unrecognized BYO biometry provider. Accepted: unico, serpro, idwall, sumsub, clearsale, caf, valid. |
provider_not_enabled | 422 | The given BYO provider is not enabled for your tenant. |
evidence_not_found | 422 | The biometry.evidenceId does not exist (or does not belong to your tenant). Upload it via POST /v1/accreditations/biometry-evidence first. |
not_pj | 422 | Company document upload is only accepted on PJ accreditations (POST .../documents). |
invalid_kind | 422 | Invalid company document kind. Accepted: company_articles, company_proof_of_address, cnpj_card, financial_statements, business_license, regulatory_license, other. |
invalid_content_type | 422 | contentType must be application/pdf for company document uploads. |
invalid_state | 409 | Operation not applicable to the current state (e.g. retry of an already-approved biometry, or accreditation in a terminal state). |
invalid_status | 409 | Company document upload blocked for the current status (only PENDING_BIOMETRY / BIOMETRY_APPROVED / PENDING_REVIEW). |
not_found | 404 | Accreditation (or a person within it) not found. |
rate_limited | 429 | Too many authorization requests for CPFs that already have an account in the last hour. Retry later; sustained bursts are reviewed by CorpX. |
presign_error | 500 | Failed to generate the upload link for the evidence or corporate document. Safe to retry. |
storage_unavailable | 503 | KYC archive (evidence bucket) temporarily unavailable. |
workflow_signal_failed | 502 | Failed to signal the accreditation workflow; retry. |
CorpX validation errors on the same endpoints, with text generated from the code's own catalog:
| Code | HTTP | What happened and what to do | Retryable |
|---|---|---|---|
invalid_callback_uri | 400 | The callbackUri you sent is not registered for your tenant, or is not accepted (only https:// and an application scheme; no credentials in the URI, no #, no localhost or internal network address). The URI must be registered by CorpX support before its first use, and the comparison is exact. See Returning to your app. | no |
A provider refusal of the accreditation arrives asynchronously, in the accreditation.failed webhook and in GET /v1/accreditations/{id}, under reason:
| Reason | What happened |
|---|---|
partner_validation_failed | The provider rejected a registration field. The message names the field and the partner block carries the original validation. |
existing_partner_account | The document already has an account at the provider — a control transfer through support is required. |
partner_unavailable | Provider outage. Transient: resend the accreditation. |
partner_rejected | Refusal on provider policy/risk grounds, with no specific field. |
Common Error Examples
Insufficient Balance
{
"errorCode": "insufficient_funds",
"message": "Saldo insuficiente na conta do liquidante para concluir a operação.",
"partner": {
"code": "request.client.insufficient_balance",
"message": "Saldo insuficiente para realizar a operação"
}
}
Field Over the Provider's Limit
{
"errorCode": "invalid_field",
"message": "O campo \"Identifier\" não é aceito pelo liquidante com o valor enviado. O valor deve ter no máximo 50 caracteres.",
"partner": {
"code": "request.value.above_maximum",
"field": "Identifier",
"message": "O valor 'Identifier' deve ter no máximo 50 caracteres."
}
}
Idempotency Conflict
{
"errorCode": "idempotency_conflict",
"message": "Request with same Idempotency-Key but different payload"
}
Insufficient Scope
{
"errorCode": "insufficient_scope",
"message": "esta credencial não tem escopo para esta operação; é necessário o escopo qrcode.manage"
}
Create a credential with the required scope in the backoffice panel (API Credentials). Money-moving scopes are issued by CorpX.
Monitoring and Support
If you receive frequent 5xx errors (internal_error, partner_error, partner_unavailable, partner_internal), we recommend:
- Implement exponential retry with the same idempotency key (maximum 3 attempts), only for codes marked as retryable.
- Check the status of our platform in case of prolonged unavailability.
- Contact support providing:
- The received
errorCode, plus thepartnerblock when present - The sent payload (without sensitive data)
- The
X-Request-Idfrom the response (if available) - Approximate timestamp of the error
- The received
Support Channels
- Slack: Private channel per client — request access from the CorpX team during onboarding.
- Email:
api@corpx.com