Códigos de error
Toda falla que la plataforma clasifica viene con un código string estable de
la familia B2B_* — nunca un número. Esta página es el catálogo completo. Para
las formas en que puede llegar un cuerpo de error (hay más de una), ver
manejo de errores.
{
"code": "B2B_INSUFFICIENT_BALANCE",
"category": "PERMANENT",
"message": "El balance disponible es insuficiente para completar la operación.",
"correlationId": "b6e1b6f0-0000-4000-8000-000000000000"
}
| Campo | Tipo | Descripción |
|---|---|---|
code | string | Identificador estable y legible por máquina (B2B_*). Es seguro ramificar sobre él. |
category | string | PERMANENT, TRANSIENT o UNKNOWN — define si un reintento puede llegar a servir. |
message | string | Explicación legible por humanos, en español, sea cual sea el idioma en que leas esta doc. Para logs y operadores — nunca ramifiques sobre él. |
correlationId | string | Presente cuando había un ID de correlación disponible. Inclúyelo cuando contactes a soporte. |
details | string[] | Solo en B2B_PROVIDER_REJECTED. El veredicto de la red de pagos, una entrada por campo rechazado, textual y en inglés. Te dice qué corregir; no es estable, así que nunca ramifiques sobre él. Ver qué te dice details. |
Catálogo completo
| Código | HTTP | Categoría | Qué significa | ¿Reintentar? |
|---|---|---|---|---|
B2B_UNAUTHORIZED | 401 | PERMANENT | Credenciales, token o firma de la request rechazados. | No |
B2B_COMPANY_NOT_FOUND | 404 | PERMANENT | No hay una compañía asociada a las credenciales usadas. | No — contacta a soporte |
B2B_QUOTATION_NOT_FOUND | 404 | PERMANENT | La cotización no existe, o no te pertenece. | No |
B2B_QUOTATION_EXPIRED | 422 | PERMANENT | La cotización expiró antes de que se procesara la operación. | No — pide una cotización nueva |
B2B_QUOTATION_MISMATCH | 422 | PERMANENT | El monto, la moneda, el corredor o el método de pago de la operación no coinciden con la cotización que referencia — p. ej. bank_account sobre una cotización P2P_PHONE_TRANSFER. Compara payment_instructions con el destination.payout_method de la cotización antes de enviar. | No — corrige las instrucciones, o pide una cotización nueva con los valores correctos |
B2B_QUOTATION_ALREADY_USED | 409 | PERMANENT | La cotización ya respalda otra operación. Una cotización es de uso único, para siempre. | No — lee la nota de abajo antes de reintentar nada |
B2B_INSUFFICIENT_BALANCE | 422 | PERMANENT | El balance disponible de la wallet es menor al que requiere la operación. | No — carga saldo primero |
B2B_BENEFICIARY_INVALID | 422 | PERMANENT | Los datos del beneficiario son inválidos o incompletos para el payout method elegido. | No — corrige las payment instructions |
B2B_RATE_NOT_FOUND | 422 | PERMANENT | No hay tarifa activa para el corredor pedido (moneda origen/destino, país, payout method). | No — revisa GET /me/routes |
B2B_ORDER_NOT_FOUND | 404 | PERMANENT | La orden no existe, o no te pertenece. | No |
B2B_IDEMPOTENCY_CONFLICT | 409 | PERMANENT | El mismo X-Idempotency-Key ya se usó con un body distinto. | No — usa una clave nueva por cada orden distinta |
B2B_WALLET_NOT_CONFIGURED | 409 | PERMANENT | Tu compañía todavía no tiene wallet aprovisionada. | No — contacta a soporte |
B2B_WALLET_NOT_FOUND | 404 | PERMANENT | La wallet configurada no existe en el proveedor. | No — contacta a soporte |
B2B_INVALID_PAGINATION_CURSOR | 400 | PERMANENT | El cursor de paginación no se pudo decodificar o no fue emitido por este listado. | No — usa el next_cursor de la página anterior, o descarta el cursor para volver a empezar |
B2B_RATE_LIMITED | 429 | TRANSIENT | Superaste las requests permitidas en este intervalo. | Sí — con backoff exponencial |
B2B_PROVIDER_REJECTED | 422 | PERMANENT | La red de pagos rechazó la operación con un motivo que todavía no catalogamos. Trae details con el mensaje de la propia red. | No — lee details y corrige el campo que nombra |
B2B_PROVIDER_UNAVAILABLE | 503 | TRANSIENT | La red de pagos no está disponible en este momento. | Sí — con backoff exponencial |
B2B_UNKNOWN | 500 | UNKNOWN | Una falla inesperada que no pudimos clasificar. | Una vez, después escala con el correlationId |
B2B_QUOTATION_ALREADY_USED es un 409, no un 422, y es a propósitoTodos los demás errores de cotización son 422. Este es 409 porque ya existe
una operación del otro lado. Un cliente que trate "422 + error de cotización"
como "re-cotizo y reenvío" emitiría el payout dos veces.
Reconcilia primero contra la operación existente — búscala por el
X-Idempotency-Key que usaste — y solo después decide si corresponde una orden
nueva. Ver idempotencia.
Lo que NO está en esta página: por qué falló un pago
Todo lo de arriba es una llamada a la API que fue rechazada — vuelve como la
respuesta HTTP de tu request. Un pago que se aceptó y después falló aguas abajo es
otra cosa completamente distinta: la llamada salió bien, y la orden reporta más
tarde su propio failure_code (la familia RET-*) más failure_reason.
Eso vive en detalle de falla. No busques códigos B2B_*
ahí, ni códigos RET-* aquí.
Política de reintento por categoría
category es la respuesta completa, y es el campo sobre el que tienes que construir
tu lógica de reintentos — no el código, y nunca el mensaje.
| Categoría | Política |
|---|---|
PERMANENT | No reintentar. La request idéntica devuelve la respuesta idéntica; corrígela primero. |
TRANSIENT | Reintentar con backoff exponencial. Hoy el conjunto transitorio es exactamente B2B_RATE_LIMITED y B2B_PROVIDER_UNAVAILABLE. |
UNKNOWN | Reintentar como máximo una vez. Si persiste, contacta a soporte con el correlationId. |
Un reintento de POST /orders solo es seguro con el mismo
X-Idempotency-Key del intento original. Cualquier otra cosa arriesga un pago
duplicado.
Cómo caen aquí las fallas de la red de pagos
Las cotizaciones y las órdenes se ejecutan sobre una red de pagos que reporta sus propios códigos de error. Esos se traducen al catálogo de arriba:
- Un código conocido — o un
reasonsecundario conocido cuando el código principal es genérico — mapea a su códigoB2B_*específico. - Un código que todavía no catalogamos cae por HTTP status:
429→B2B_RATE_LIMITED, cualquier otro4xx→B2B_PROVIDER_REJECTED, cualquier5xx→B2B_PROVIDER_UNAVAILABLE. - Una falla sin ningún status usable →
B2B_UNKNOWN.
La consecuencia práctica: una falla no catalogada igual llega con una category
sobre la que puedes actuar. Por eso ramificar por category nunca te deja sin
decisión, y por eso existe el paso 2.
Qué te dice details
El paso 2 tiene un costo: un código genérico no puede decirte qué campo
rechazó la red. Para eso existe details. Cuando el fallback cae en
B2B_PROVIDER_REJECTED, la respuesta trae el mensaje de la propia red, campo por
campo y textual:
{
"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"]
}
Léelo como leerías un error de validación: nombra el campo y la regla. Está
escrito en inglés, por la red, y su redacción no es parte de nuestro contrato —
muéstralo en tus logs y a tus operadores, no lo parsees. details está ausente
en todos los demás códigos, y nunca aparece en un 5xx: una caída de la red no
trae nada que puedas corregir.
El ejemplo de arriba es el que más vemos. El document.type de un
beneficiario venezolano tiene que ser V, E, J o G — las categorías de
la cédula — no CI, DNI ni PASSPORT. El corredor no acepta otra cosa.
El segundo más común es la cuenta bancaria de un payout BANK_TRANSFER:
"details": ["members.1.receiver.paymentInstructions.bankAccountNumber must be 20 digits"]
"details": ["members.1.receiver.paymentInstructions.bankAccountNumber must start with 0134 for BANCO_BANESCO_VE"]
Un número de cuenta venezolano tiene exactamente 20 dígitos, y los primeros
cuatro son el código del banco, así que la red verifica que el prefijo coincida
con el bank_name que enviaste. La regla del prefijo aplica a los bancos
principales (BANCO_BANESCO_VE → 0134, BANCO_VENEZUELA_VE → 0102,
BANCO_BICENTENARIO_VE, BANCAMIGA_VE, 100_BANCO_VE y otros); el resto solo
recibe la regla de 20 dígitos por ahora. Antes del 2026-09-10 ambas cosas se
aceptaban en silencio — una cuenta mal escrita fallaba después, como falla de
pago, en lugar de en la llamada a la API.
Algunas requests nunca llegan a la red. Un POST /orders con
payment_instructions vacío, o con bank_account y bank_phone_account a la
vez, se rechaza localmente como 422 B2B_BENEFICIARY_INVALID. Un body al que le
falta un objeto obligatorio (sender, destination, source, quote…), o una
cotización con quote.amount en cero, se rechaza aún antes como 400 de
validación —
nunca llega a tener un code.
Códigos numéricos heredados
Si tu integración todavía lee los códigos numéricos de nuestra interfaz B2B anterior, estas son las únicas cinco equivalencias que respalda la plataforma actual. No existe un mapeo 1 a 1 completo de la tabla vieja, y los códigos de abajo ya no se emiten — están aquí para migrar, no para construir sobre ellos.
| Código heredado | Código actual |
|---|---|
3000 | B2B_UNAUTHORIZED |
3003 | B2B_QUOTATION_NOT_FOUND |
3004 | B2B_QUOTATION_EXPIRED |
3007 | B2B_INSUFFICIENT_BALANCE |
3020 | B2B_BENEFICIARY_INVALID |
Este catálogo crece. Ramifica sobre code para los casos que manejas
puntualmente, usa category como respaldo para el resto, y nunca asumas que la
tabla de arriba es definitiva.