Error codes
Every failure the platform classifies carries a stable string code from the
B2B_* family — never a number. This page is the complete catalog. For the
shapes an error body can arrive in (there is more than one), see
handling errors.
{
"code": "B2B_INSUFFICIENT_BALANCE",
"category": "PERMANENT",
"message": "El balance disponible es insuficiente para completar la operación.",
"correlationId": "b6e1b6f0-0000-4000-8000-000000000000"
}
| Field | Type | Description |
|---|---|---|
code | string | A stable, machine-readable identifier (B2B_*). Safe to branch on. |
category | string | PERMANENT, TRANSIENT, or UNKNOWN — decides whether a retry can ever help. |
message | string | Human-readable explanation, in Spanish, whatever locale you read these docs in. For logs and operators — never branch on it. |
correlationId | string | Present when a correlation id was available. Include it when you contact support. |
details | string[] | Only on B2B_PROVIDER_REJECTED. The payment network's own verdict, one entry per rejected field, verbatim and in English. It tells you what to fix; it is not stable, so never branch on it. See what details tells you. |
Complete catalog​
| Code | HTTP | Category | What it means | Retry? |
|---|---|---|---|---|
B2B_UNAUTHORIZED | 401 | PERMANENT | Credentials, token, or request signature rejected. | No |
B2B_COMPANY_NOT_FOUND | 404 | PERMANENT | No company is associated with the credentials used. | No — contact support |
B2B_QUOTATION_NOT_FOUND | 404 | PERMANENT | The quotation does not exist, or does not belong to you. | No |
B2B_QUOTATION_EXPIRED | 422 | PERMANENT | The quotation expired before the operation was processed. | No — request a new quotation |
B2B_QUOTATION_MISMATCH | 422 | PERMANENT | The operation's amount, currency, corridor, or payout method does not match the quotation it references — e.g. bank_account on a P2P_PHONE_TRANSFER quotation. Compare payment_instructions against the quotation's destination.payout_method before sending. | No — fix the instructions, or request a new quotation with the right values |
B2B_QUOTATION_ALREADY_USED | 409 | PERMANENT | The quotation already backs another operation. A quotation is single-use, forever. | No — read the note below before retrying anything |
B2B_INSUFFICIENT_BALANCE | 422 | PERMANENT | Available wallet balance is lower than the operation requires. | No — top up first |
B2B_BENEFICIARY_INVALID | 422 | PERMANENT | Beneficiary data is invalid or incomplete for the selected payout method. | No — fix the payment instructions |
B2B_RATE_NOT_FOUND | 422 | PERMANENT | No active rate for the requested corridor (source/destination currency, country, payout method). | No — check GET /me/routes |
B2B_ORDER_NOT_FOUND | 404 | PERMANENT | The order does not exist, or does not belong to you. | No |
B2B_IDEMPOTENCY_CONFLICT | 409 | PERMANENT | The same X-Idempotency-Key was already used with a different body. | No — use a new key per distinct order |
B2B_WALLET_NOT_CONFIGURED | 409 | PERMANENT | Your company has no wallet provisioned yet. | No — contact support |
B2B_WALLET_NOT_FOUND | 404 | PERMANENT | The configured wallet does not exist at the provider. | No — contact support |
B2B_INVALID_PAGINATION_CURSOR | 400 | PERMANENT | The pagination cursor could not be decoded or was not issued by this listing. | No — use the next_cursor from the previous page, or drop the cursor to start over |
B2B_RATE_LIMITED | 429 | TRANSIENT | You exceeded the requests allowed in this interval. | Yes — exponential backoff |
B2B_PROVIDER_REJECTED | 422 | PERMANENT | The payment network rejected the operation with a reason we do not catalog yet. Carries details with the network's own message. | No — read details, fix the field it names |
B2B_PROVIDER_UNAVAILABLE | 503 | TRANSIENT | The payment network is unreachable right now. | Yes — exponential backoff |
B2B_UNKNOWN | 500 | UNKNOWN | An unexpected failure we could not classify. | Once, then escalate with the correlationId |
B2B_QUOTATION_ALREADY_USED is a 409, not a 422, and that is deliberateEvery other quotation error is a 422. This one is a 409 because an
operation already exists on the other side. A client that treats "422 +
quotation error" as "re-quote and resend" would issue the payout twice.
Reconcile against the existing operation first — look it up by the
X-Idempotency-Key you used — and only then decide whether a new order is
warranted. See idempotency.
Not on this page: why a payout failed​
Everything above is an API call that was rejected — it comes back as the HTTP
response to your request. A payout that was accepted and then failed downstream is
a different thing entirely: the call succeeded, and the order later reports its
own failure_code (the RET-* family) plus failure_reason.
Those live in failure details. Do not look for B2B_*
codes there, and do not look for RET-* codes here.
Retry policy by category​
category is the whole answer, and it is the field to build your retry logic on
— not the code, and never the message.
| Category | Policy |
|---|---|
PERMANENT | Do not retry. The identical request gets the identical answer; fix it first. |
TRANSIENT | Retry with exponential backoff. Today the transient set is exactly B2B_RATE_LIMITED and B2B_PROVIDER_UNAVAILABLE. |
UNKNOWN | Retry at most once. If it persists, contact support with the correlationId. |
A retry of POST /orders is only safe with the same X-Idempotency-Key as
the original attempt. Anything else risks a duplicate payment.
How payment-network failures land in this list​
Quotations and orders execute on an upstream payment network that reports its own error codes. Those are translated into the catalog above:
- A known upstream code — or a known secondary
reasonwhen the primary code is generic — maps to its specificB2B_*code. - An upstream code we do not catalog yet falls back by HTTP status:
429→B2B_RATE_LIMITED, any other4xx→B2B_PROVIDER_REJECTED, any5xx→B2B_PROVIDER_UNAVAILABLE. - A failure with no usable status at all →
B2B_UNKNOWN.
The practical consequence: an uncatalogued upstream failure still arrives with a
category you can act on. That is why branching on category never leaves you
without a decision, and why step 2 exists at all.
What details tells you​
Step 2 has a cost: a generic code cannot tell you which field the network
refused. That is what details is for. When the fallback lands on
B2B_PROVIDER_REJECTED, the response carries the network's own field-level
message, verbatim:
{
"code": "B2B_PROVIDER_REJECTED",
"category": "PERMANENT",
"message": "El proveedor rechazó la operación con un error no catalogado. Verifique los datos enviados; la operación no se reintentará automáticamente.",
"details": ["members.1.receiver.beneficiary documentType must be one of V, E, J, G"]
}
Read it the way you would read a validation error: it names the field and the
rule. It is written in English, by the network, and its wording is not part of
our contract — surface it in your logs and to your operators, do not parse it.
details is absent on every other code, and it is never present on a 5xx:
a network outage carries nothing you could fix.
The example above is the one we see most. A Venezuelan beneficiary's
document.type must be V, E, J, or G — the national identity
categories — not CI, DNI, or PASSPORT. The corridor accepts nothing else.
The second most common is the bank account for a BANK_TRANSFER payout:
"details": ["members.1.receiver.paymentInstructions.bankAccountNumber must be 20 digits"]
"details": ["members.1.receiver.paymentInstructions.bankAccountNumber must start with 0134 for BANCO_BANESCO_VE"]
A Venezuelan account number is exactly 20 digits, and its first four digits
are the bank code, so the network checks that the prefix matches the bank_name
you sent. The prefix rule is enforced for the main banks
(BANCO_BANESCO_VE → 0134, BANCO_VENEZUELA_VE → 0102, BANCO_BICENTENARIO_VE,
BANCAMIGA_VE, 100_BANCO_VE and others); the rest only get the 20-digit rule for
now. Both were accepted silently before 2026-09-10 — a wrong account then failed
downstream, as a payout failure, instead of at the API call.
Some requests never reach the network. A POST /orders with empty
payment_instructions, or with both bank_account and bank_phone_account
set, is rejected locally as 422 B2B_BENEFICIARY_INVALID. A body missing a
required object (sender, destination, source, quote…), or a quotation
with quote.amount of zero, is rejected even earlier as a 400
validation error —
it never gets a code at all.
Legacy numeric codes​
If your integration still reads the numeric codes of our previous B2B interface, these are the only five equivalences the current platform backs. There is no complete 1-to-1 mapping of the old table, and the codes below are no longer emitted — they are here to migrate off, not to build on.
| Legacy code | Current code |
|---|---|
3000 | B2B_UNAUTHORIZED |
3003 | B2B_QUOTATION_NOT_FOUND |
3004 | B2B_QUOTATION_EXPIRED |
3007 | B2B_INSUFFICIENT_BALANCE |
3020 | B2B_BENEFICIARY_INVALID |
This catalog grows. Branch on code for the cases you handle specifically, fall
back to category for everything else, and never assume the table above is
final.