Errores
Esta página se abre cuando algo ya falló. Las dieciséis operaciones de la API comparten una forma de rechazar: un código estable, un texto que no lo es, y dos envolturas distintas que el código de estado no distingue. Lo que sigue es cómo leer cualquier fallo de esta API y qué hacer con él; los fallos de la credencial en concreto —qué significa cada 401, qué abre cada Permiso— están en Autenticación y Claves API.
Hay dos envolturas y el estado no dice cuál llegó
Esta API construye sus respuestas de error en dos sitios, y el reparto es por capa, no por código de estado.
Todo lo que responde un controlador —y todo lo que pasa por el manejador de excepciones— llega con success, code y message, más un errors o un meta cuando hay algo que poner en ellos:
{ "success": false, "code": "PAYMENT_LINK_NOT_FOUND", "message": "Payment link not found" }Las capas que corren antes de los controladores —autenticación, origen y el límite por clave— responden con error y code. Ni success ni message:
{ "error": "API key required", "code": "missing_api_key" }Un cliente que lea message a ciegas recibe undefined en cualquiera de estas últimas. Y no sirve mirar el estado para saber cuál llegó: el 401, el 403 y el 429 llegan en las dos envolturas, según qué capa haya rechazado la petición.
La comprobación fiable es qué clave trae el cuerpo: la envoltura ordinaria trae message, la de capa trae error. Mirar success no basta, porque hay un rechazo que trae success: false y pone el texto bajo error de todos modos —el de origen bloqueado, ORIGIN_NOT_ALLOWED—.
Ocho rechazos vienen de esas capas: los cinco 401 de autenticación, insufficient_scope, ORIGIN_NOT_ALLOWED —el que además trae success— y el 429 del límite de la clave. Todo lo demás de esta página usa la envoltura ordinaria.
Lo que no cubre ninguna de las dos es una respuesta que no llegó a construirse como respuesta de la API: un proxy, un balanceador o un tiempo de espera agotado devuelven lo que devuelvan, y puede no ser JSON. Por eso un cliente necesita un camino para un cuerpo que no se puede interpretar o que no trae code: ahí lo único que queda es el estado.
El code es lo que se compara
code es lo que se comparaSobre el code, y no sobre el message. Los mensajes son prosa: se reescriben, se afinan y en algunos casos interpolan valores de la petición. Un cliente que compare textos se rompe el día que alguien mejora una frase, y se rompe en silencio, porque la comparación simplemente deja de coincidir.
Tampoco sobre el estado a secas. El mismo estado lleva formas distintas: el 401 tiene dos envolturas, el 403 viene de tres sitios distintos, el 429 tiene dos cuerpos y el 500 tiene tres códigos de contrato, más uno que no lo es.
Y la comparación distingue mayúsculas. Hay dos pares de códigos que se diferencian solo en eso, y cada par son dos cosas distintas:
invalid_api_keyes la clave que no verificó, en la envoltura de capa.INVALID_API_KEYes un registro de clave incompleto, en la ordinaria, y solo en seis de las dieciséis operaciones. Los dos son401.rate_limit_exceededes el límite de la clave.RATE_LIMIT_EXCEEDEDes el límite por IP. Los dos son429, y las mayúsculas son la manera más rápida de saber cuál de los dos límites se agotó: los cuerpos también son distintos, y solo uno de los dos trae la cabeceraRetry-After.
Un switch que normalice a minúsculas colapsa los dos pares y confunde dos fallos que piden cosas distintas.
Si llega un code que el cliente no conoce, la regla es no adivinar: se trata por estado y se registra el cuerpo entero.
Los códigos y qué hacer con cada uno
Qué hacer es una de tres cosas. Corregir: la petición está mal y hay que cambiarla antes de repetirla. Reintentar: el fallo puede ser temporal; se reintenta una vez, con espera, y si vuelve se escala. Parar: nada de lo que se cambie en la petición lo arregla —o el recurso ya está en su estado final, o hace falta una persona—.
La tabla reúne los códigos que declaran las dieciséis operaciones. Cada operación documenta cuáles de estos puede devolver de verdad, y ninguna los devuelve todos.
code | Estado | Envoltura | Qué pasó | Qué hacer |
|---|---|---|---|---|
KYC_VERIFICATION_REQUIRED | 400 | ordinaria, con meta | La cuenta no tiene completo el paso de Verificar Identidad | Parar: se completa en el panel |
KYB_VERIFICATION_REQUIRED | 400 | ordinaria, con meta | La cuenta no tiene completa la Verificación Comercial | Parar: se completa en el panel |
BUSINESS_PAYMENT_LINK_MINIMUM_AMOUNT | 400 | ordinaria, con meta | El Monto no llega al mínimo de 2,00 USD equivalentes. Esta comprobación corre antes de la validación del cuerpo | Corregir: subir el Monto. Si el Monto ya está muy por encima del mínimo, revisar que el cuerpo lleve currency |
missing_api_key | 401 | de capa | No llegó credencial en ninguna de las cabeceras aceptadas | Corregir |
invalid_key_format | 401 | de capa | El prefijo de la clave no se pudo interpretar | Corregir |
invalid_tenant | 401 | de capa | El prefijo se interpreta y no corresponde a ninguna cuenta | Corregir |
tenant_init_failed | 401 | de capa | La cuenta se encontró y no se pudo cargar | Reintentar |
invalid_api_key | 401 | de capa | La clave no verificó: mal copiada, desactivada o caducada | Parar: revisar la clave en el panel |
INVALID_API_KEY | 401 | ordinaria | La clave verificó y su registro está incompleto. Solo en las seis operaciones de Solicitud de Pago y de cobro con tarjeta | Parar: sustituir la clave |
insufficient_scope | 403 | de capa | La clave no lleva el Permiso que la operación exige. Solo en las doce operaciones que exigen uno | Parar: ajustar los Permisos en el panel |
ORIGIN_NOT_ALLOWED | 403 | success y error | El Origin de la petición no está en los Orígenes Permitidos de la clave, o la petición no llevó esa cabecera | Parar: corregir la lista en el panel |
BUSINESS_TENANT_PAUSED | 403 | ordinaria | La cuenta está en pausa. Leer y cancelar siguen funcionando; crear no | Parar: hablar con soporte |
CARD_PAYMENTS_NOT_ENABLED | 403 | ordinaria | La cuenta no tiene habilitado el cobro con tarjeta | Parar: hablar con soporte |
USER_NOT_FOUND | 404 | ordinaria | La clave verificó y el usuario que la creó ya no existe. Nada de la petición lo provoca. Solo en las mismas seis operaciones que devuelven INVALID_API_KEY | Parar: sustituir la clave |
PAYMENT_LINK_NOT_FOUND | 404 | ordinaria | No hay Solicitud de Pago con ese id al alcance de la clave | Corregir el id |
CARD_PAYMENT_LINK_NOT_FOUND | 404 | ordinaria | Lo mismo, en el carril de tarjeta | Corregir el id |
WEBHOOK_NOT_FOUND | 404 | ordinaria | No hay endpoint de webhook con ese id en la cuenta | Corregir el id |
RESOURCE_NOT_FOUND | 404 | ordinaria | La petición no corresponde a ninguna ruta. En la práctica, un {id} que no es un UUID bien formado. Llega antes de la autenticación, así que una petición sin clave y con un id mal formado vuelve como 404 y no como 401 | Corregir el id |
VALIDATION_FAILED | 422 | ordinaria, errors opcional | El cuerpo se entendió y se rechazó. Dos cuerpos distintos comparten este código y solo uno trae errors | Corregir |
USD_ACCOUNT_NOT_ENABLED | 422 | ordinaria, sin errors | La cuenta no tiene habilitado el cobro en USD | Parar: hablar con soporte |
WALLET_NOT_FOUND | 422 | ordinaria, sin errors | La cuenta tiene Billeteras, pero ninguna en el carril local | Parar: hablar con soporte |
PAYMENT_LINK_NOT_ACTIVE | 422 | ordinaria, errors.status es un texto | La Solicitud de Pago ya no está activa | Parar: el Estado actual viene en errors.status |
PAYMENT_LINK_CANNOT_CANCEL | 422 | ordinaria, errors.current_uses es un entero | La Solicitud de Pago ya se usó | Parar: no se puede cancelar |
CARD_PAYMENT_LINK_NOT_ACTIVE | 422 | ordinaria, errors.status es un texto | El cobro con tarjeta ya no está activo | Parar: el Estado actual viene en errors.status |
EXCHANGE_RATE_UNAVAILABLE | 422 | ordinaria, sin errors | No hay Tasa de Cambio utilizable para ese par | Depende de la operación: ver bajo la tabla |
WEBHOOK_ALREADY_EXISTS | 422 | ordinaria, sin errors | Esa URL ya está registrada en la cuenta, por cualquier Clave API de la cuenta y no solo por la que llama | Parar |
INVALID_CURRENCY | 422 | ordinaria, errors.error es un texto | La Moneda de origen pasó la validación y aun así no se puede cotizar | Corregir la Moneda de origen |
RATE_LIMIT_EXCEEDED | 429 | ordinaria, con retry_after | Se agotó el límite por IP | Reintentar tras retry_after |
rate_limit_exceeded | 429 | de capa, con retry_after | Se agotó el límite de la clave | Reintentar tras retry_after |
SYSTEM_DATABASE_ERROR | 500 | ordinaria | Fallo interno. Lo pueden devolver catorce de las dieciséis operaciones; las dos de conversión, no. Una cuenta sin ninguna Billetera aprovisionada lo recibe además en cada intento de crear una Solicitud de Pago | Reintentar. Si llega en todos los intentos de crear, es aprovisionamiento: parar y hablar con soporte |
CARD_PAYMENT_ERROR | 500 | ordinaria | Fallo al hablar con la plataforma de tarjetas, al crear o al cancelar un cobro con tarjeta | Reintentar |
CONVERSION_QUOTE_ERROR | 500 | ordinaria | La cotización no se pudo calcular. Un destination que la operación no puede cotizar llega también con este código | Revisar primero destination; si es el correcto, reintentar una vez. Si el problema es el destination, reintentar no lo arregla nunca |
EXCHANGE_RATE_UNAVAILABLE no significa lo mismo en las dos operaciones que lo devuelven. En POST /v1/conversions/rate puede ser temporal: esa operación no declara ningún 500 propio y convierte a propósito los fallos que ocurren dentro de ella en este mismo 422, así que conviene reintentar una vez antes de concluir que el par no se puede convertir. Al crear un cobro con tarjeta significa otra cosa —hay una Tasa de Cambio guardada y no es utilizable—, y ahí no hay reintento que valga.
Un VALIDATION_FAILED no siempre nombra el campo
VALIDATION_FAILED no siempre nombra el campoEste es el rechazo que más clientes rompe, porque parece el más predecible.
El que produce un validador trae errors, un mapa de nombre de campo a lista de textos:
{
"success": false,
"code": "VALIDATION_FAILED",
"message": "Validation failed",
"errors": { "amount": ["The amount field is required."] }
}Hay otro rechazo con el mismo code y el mismo estado que trae un message en prosa y ningún errors. Un cliente que ramifique sobre el código y acto seguido entre en errors se rompe con ese, no con este. Al crear una Solicitud de Pago, una currency fuera de las admitidas puede llegar en esa segunda forma, y es justo el error más fácil de cometer. Y si ese mensaje enumera monedas, esa lista no es el contrato: la que manda es la que declara cada operación en su referencia.
La regla es leer message siempre y tratar errors como detalle adicional cuando esté.
Cuando errors está, sus valores no son siempre listas de textos. Esta API pone tres tipos distintos ahí dentro:
- Una lista de textos por campo, que es lo que produce un validador.
- Un texto suelto:
PAYMENT_LINK_NOT_ACTIVEyCARD_PAYMENT_LINK_NOT_ACTIVEponen el Estado actual bajoerrors.status, eINVALID_CURRENCYrepite su propio mensaje bajoerrors.error. - Un entero:
{
"success": false,
"code": "PAYMENT_LINK_CANNOT_CANCEL",
"message": "Payment link cannot be cancelled because it has already been used",
"errors": { "current_uses": 1 }
}Y varios 422 no traen errors en absoluto —WALLET_NOT_FOUND, USD_ACCOUNT_NOT_ENABLED, EXCHANGE_RATE_UNAVAILABLE y WEBHOOK_ALREADY_EXISTS entre ellos—:
{ "success": false, "code": "WEBHOOK_ALREADY_EXISTS", "message": "A webhook with this URL already exists" }En un lenguaje con tipos, errors se declara como un objeto de valores desconocidos y se comprueba antes de usarlo. En cualquier otro, se comprueba igual.
Los dos 429
429Hay dos límites y corren los dos en las dieciséis operaciones: uno por IP y otro por Clave API. Se agotan por separado y contestan distinto.
{ "success": false, "message": "Too many requests. Please try again later.", "code": "RATE_LIMIT_EXCEEDED", "retry_after": 37 }{ "error": "Too many requests", "code": "rate_limit_exceeded", "retry_after": 37 }El primero es el de IP y el segundo el de la clave. Aparte de las mayúsculas del code, la diferencia que importa para reintentar es esta: solo el límite por IP pone la cabecera Retry-After. Un 429 del límite de la clave no la lleva, así que un cliente que espere leyendo cabeceras se queda sin saber cuánto esperar.
retry_after está en los dos cuerpos, en segundos enteros. Es el valor sobre el que se espera.
Las dos ventanas son fijas, no deslizantes: se abren con la primera petición y el contador se reinicia entero al expirar. Eso significa que esperar retry_after es suficiente, y que repartir las peticiones no adelanta la reapertura.
Una respuesta correcta trae cuatro contadores —X-RateLimit-Limit-IP y X-RateLimit-Remaining-IP para el límite por IP, X-RateLimit-Limit y X-RateLimit-Remaining para el de la clave— y ninguna cabecera de reinicio. El presupuesto de cada uno y por qué el de IP se comparte entre cuentas están en Autenticación y Claves API.
Un 500 dice qué falló, no por qué
500 dice qué falló, no por quéEn un 500, solo el code es contractual. El message no lo es. El texto de esa respuesta describe un fallo interno, no sale de ningún conjunto de cadenas documentado y puede cambiar sin aviso.
Un 500 puede llegar además con un code que la tabla de arriba no recoge —hoy, SYSTEM_UNEXPECTED_ERROR—. No es contrato, no se documenta su causa y se trata como cualquier otro 500.
De ahí tres cosas: no se interpreta, no se compara, y no se le enseña al pagador. Guardarlo entero en los registros propios sí ayuda, y es lo que conviene pegar en un ticket de soporte.
Un 500 tampoco suele ser una señal para cambiar la petición. Se reintenta una vez y, si vuelve, se escala: un 500 que se repite casi siempre es nuestro y no de quien integra, y un cliente que reintente en bucle solo añade carga a algo que ya está roto.
Ese «casi» es deliberado: hay entradas conocidas que sí provocan un 500 y que sí se arreglan cambiando la petición. Están en la sección siguiente, y son pocas y concretas.
Entradas que la validación acepta y la operación no puede servir
Hay entradas que pasan la validación y aun así no se pueden atender. Evitarlas es más barato que interpretar lo que devuelven.
- Un campo de moneda que no sea un texto. Algunos clientes serializan un campo de formulario repetido o vacío como una lista JSON. Al crear un cobro con tarjeta y en las dos operaciones de conversión, eso no vuelve como un
422que nombre el campo. Se manda siempre un texto de tres letras. amount_typevacío o nulo enPOST /v1/conversions/quote. Para tomar el valor por defecto se omite el campo; mandarlo como""o comonullno es lo mismo que omitirlo. Es la forma que toma un desplegable sin seleccionar o un campo opcional que el cliente rellena siempre.
Ninguna de esas dos respuestas está documentada y no se promete ningún estado para ellas. No conviene construir sobre lo que se observe hoy.
Dos más, estas sí con respuesta conocida:
- Una Moneda de origen fuera de las cinco verificadas en
POST /v1/conversions/quote—ARS,BRL,CLP,COPyMXN—.PENes el caso conocido: pasa la validación y vuelve como422 INVALID_CURRENCYnombrando la Moneda. Para convertir aPENo desdePENsin cotizar una liquidación sirvePOST /v1/conversions/rate. - Un
destinationque la operación no pueda cotizar. La validación de ese campo solo comprueba que sean tres caracteres, sin lista de Monedas admitidas, así que un código desconocido no se rechaza ahí: llega hasta el cálculo y vuelve como un500 CONVERSION_QUOTE_ERROR, no como un422. Reintentarlo no lo arregla nunca; hay que cambiar eldestination. Conviene mandar la Moneda de la Billetera en la que se quiere cobrar.
Seguir por aquí
- Autenticación y Claves API — qué significa cada
401, qué abre cada Permiso y qué presupuesto tiene cada límite. - Inicio rápido — las cuatro llamadas que van de la clave al cobro, con las respuestas que llegan de verdad.
- Ambientes y Sandbox — qué se puede reproducir en Sandbox y qué no.
- Glosario — qué significa cada término que aparece en esta página.
Updated 26 days ago
