Skip to main content

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"
}
FieldTypeDescription
codestringA stable, machine-readable identifier (B2B_*). Safe to branch on.
categorystringPERMANENT, TRANSIENT, or UNKNOWN — decides whether a retry can ever help.
messagestringHuman-readable explanation, in Spanish, whatever locale you read these docs in. For logs and operators — never branch on it.
correlationIdstringPresent when a correlation id was available. Include it when you contact support.
detailsstring[]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​

CodeHTTPCategoryWhat it meansRetry?
B2B_UNAUTHORIZED401PERMANENTCredentials, token, or request signature rejected.No
B2B_COMPANY_NOT_FOUND404PERMANENTNo company is associated with the credentials used.No — contact support
B2B_QUOTATION_NOT_FOUND404PERMANENTThe quotation does not exist, or does not belong to you.No
B2B_QUOTATION_EXPIRED422PERMANENTThe quotation expired before the operation was processed.No — request a new quotation
B2B_QUOTATION_MISMATCH422PERMANENTThe 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_USED409PERMANENTThe quotation already backs another operation. A quotation is single-use, forever.No — read the note below before retrying anything
B2B_INSUFFICIENT_BALANCE422PERMANENTAvailable wallet balance is lower than the operation requires.No — top up first
B2B_BENEFICIARY_INVALID422PERMANENTBeneficiary data is invalid or incomplete for the selected payout method.No — fix the payment instructions
B2B_RATE_NOT_FOUND422PERMANENTNo active rate for the requested corridor (source/destination currency, country, payout method).No — check GET /me/routes
B2B_ORDER_NOT_FOUND404PERMANENTThe order does not exist, or does not belong to you.No
B2B_IDEMPOTENCY_CONFLICT409PERMANENTThe same X-Idempotency-Key was already used with a different body.No — use a new key per distinct order
B2B_WALLET_NOT_CONFIGURED409PERMANENTYour company has no wallet provisioned yet.No — contact support
B2B_WALLET_NOT_FOUND404PERMANENTThe configured wallet does not exist at the provider.No — contact support
B2B_INVALID_PAGINATION_CURSOR400PERMANENTThe 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_LIMITED429TRANSIENTYou exceeded the requests allowed in this interval.Yes — exponential backoff
B2B_PROVIDER_REJECTED422PERMANENTThe 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_UNAVAILABLE503TRANSIENTThe payment network is unreachable right now.Yes — exponential backoff
B2B_UNKNOWN500UNKNOWNAn 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 deliberate

Every 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.

CategoryPolicy
PERMANENTDo not retry. The identical request gets the identical answer; fix it first.
TRANSIENTRetry with exponential backoff. Today the transient set is exactly B2B_RATE_LIMITED and B2B_PROVIDER_UNAVAILABLE.
UNKNOWNRetry 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:

  1. A known upstream code — or a known secondary reason when the primary code is generic — maps to its specific B2B_* code.
  2. An upstream code we do not catalog yet falls back by HTTP status: 429 → B2B_RATE_LIMITED, any other 4xx → B2B_PROVIDER_REJECTED, any 5xx → B2B_PROVIDER_UNAVAILABLE.
  3. 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 codeCurrent code
3000B2B_UNAUTHORIZED
3003B2B_QUOTATION_NOT_FOUND
3004B2B_QUOTATION_EXPIRED
3007B2B_INSUFFICIENT_BALANCE
3020B2B_BENEFICIARY_INVALID
note

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.