Errores
Comprende el envoltorio de error estándar de Tokeflow, los tipos de error, los códigos de estado HTTP y cómo gestionar fallos de forma fiable en producción.
Tokeflow devuelve los errores con una forma única y predecible en cada endpoint. Construye tu gestión de errores una sola vez y funcionará en todas partes.
Cada solicitud fallida devuelve:
- Un código de estado HTTP significativo (la señal a nivel de transporte).
- Un envoltorio de error estructurado con un
typey uncodelegibles por máquina, unmessagelegible por humanos y unrequest_idpara soporte.
Guíate por el type y el code en tus rutas de código, muestra el message a las personas y registra siempre el request_id.
Una respuesta exitosa se envuelve en el envoltorio de éxito (success, data, meta). Una respuesta fallida no lo está: tiene un único objeto error de nivel superior y ningún campo success. Ramifica primero según el código de estado HTTP.
El envoltorio de error
{
"error": {
"type": "validation_error",
"code": "INVALID_FIELD",
"message": "Validation failed",
"details": {
"amount": ["amount must be a positive integer"]
},
"request_id": "req_8f3c2a1b9d7e4f60",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}| Field | Type | Siempre presente | Descripción |
|---|---|---|---|
type | string | Sí | Categoría de error de alto nivel. Uno de los tipos de error que se muestran a continuación. Úsalo para ramificar tu gestión. |
code | string | Sí | Código estable y legible por máquina (p. ej. INVALID_FIELD, RESOURCE_NOT_FOUND). Más específico que type. Seguro para usar en un switch. |
message | string | Sí | Explicación legible por humanos. Segura para mostrar, pero su redacción puede cambiar con el tiempo: nunca la analices. |
details | object | No | Contexto opcional. Para errores de validación, un mapa de nombre de campo a un arreglo de mensajes. Puede omitirse por completo. |
decline_reason | string | No | Presente solo en los errores de pago que tienen uno: el nombre que Tokeflow da al motivo por el que falló el pago, el mismo valor sea cual sea el proveedor que respondió (p. ej. invalid_cvv, insufficient_funds). Consulta Motivos de rechazo. |
request_id | string | Sí | Identificador único de esta solicitud (req_…). Regístralo e inclúyelo en cualquier solicitud de soporte. |
timestamp | string | Sí | Hora ISO 8601 UTC en la que se generó el error. |
Usa un switch sobre type para una gestión amplia (reintentar frente a corregir la solicitud) y sobre code cuando necesites reaccionar a un fallo específico (por ejemplo, IDEMPOTENCY_KEY_CONFLICT frente a RESOURCE_ALREADY_EXISTS, ambos bajo conflict_error). Trata code como el contrato estable; trata message como solo de visualización.
Cómo fluyen los errores
Tipos de error
Cada error lleva un type que se corresponde uno a uno con un código de estado HTTP. Usa el código de estado para decidir tu estrategia; usa type y code para decidir tu comportamiento.
| Type | HTTP | Significado | Cómo gestionarlo |
|---|---|---|---|
validation_error | 400 | El cuerpo de la solicitud o los parámetros de consulta no superaron la validación. | Corrige la solicitud. Inspecciona details en busca de mensajes a nivel de campo. No reintentes sin cambios. |
bad_request_error | 400 | La solicitud estaba malformada o era semánticamente inválida de una forma que no es validación de campos. | Corrige la solicitud. No reintentes sin cambios. |
authentication_error | 401 | La clave de API falta, está malformada o es inválida. | Revisa el encabezado Authorization y la clave. No reintentes sin cambios. |
authorization_error | 403 | La clave es válida pero carece del alcance requerido, o el recurso objetivo está fuera del alcance de la clave. | Usa una clave con el alcance correcto. No reintentes sin cambios. |
not_found_error | 404 | El recurso o el endpoint no existe (o está fuera de tu alcance). | Revisa el ID y la ruta. No reintentes sin cambios. |
conflict_error | 409 | La solicitud entra en conflicto con el estado actual (p. ej. duplicado, reutilización de clave de idempotencia, recurso en uso). | Resuelve el conflicto y luego reintenta. Consulta code para más detalles. |
business_rule_error | 422 | La solicitud estaba bien formada pero viola una regla de negocio (p. ej. una transición de estado inválida). | Corrige la operación para satisfacer la regla. No reintentes sin cambios. |
rate_limit_error | 429 | Superaste el límite de frecuencia para esta clave de API. | Espera y reintenta con backoff exponencial. |
external_service_error | 502 | Un proveedor downstream en la ruta de orquestación devolvió un error o era inaccesible. | Reintenta con backoff. Si persiste, inspecciona el estado del recurso. |
internal_server_error | 500 | Ocurrió un error inesperado del lado de Tokeflow. | Reintenta con backoff. Si persiste, contacta a soporte con el request_id. |
El campo code es más granular que type. Por ejemplo, un conflict_error puede llevar RESOURCE_ALREADY_EXISTS, DUPLICATE_ENTRY, RESOURCE_IN_USE o IDEMPOTENCY_KEY_CONFLICT. Ramifica según type para la estrategia y según code para los detalles.
Estrategia de reintentos de un vistazo
- 4xx (excepto 429): corrige la solicitud. El problema está en la entrada: un campo incorrecto, un alcance faltante, un ID desconocido o una violación de regla. Reintentar la misma solicitud fallará de la misma manera.
- 429: espera. Estás siendo limitado por frecuencia. Reintenta con backoff exponencial y jitter.
- 5xx (
500,502): reintentable. Son transitorios. Reintenta de forma idempotente con backoff exponencial. Envía siempre unaIdempotency-Keyen las mutaciones de creación/cobro para que los reintentos nunca dupliquen el trabajo.
Nunca reintentes un 4xx que no sea 429 sin cambios: fallará de forma idéntica y desperdiciará tu presupuesto de límite de frecuencia. Lee type, code y details, corrige la solicitud y luego vuelve a intentarlo.
Ejemplos de respuestas de error
400 — Error de validación (validation_error)
Los fallos a nivel de campo aparecen bajo details, indexados por nombre de campo. Cada valor es un arreglo de mensajes.
{
"error": {
"type": "validation_error",
"code": "INVALID_FIELD",
"message": "Validation failed",
"details": {
"amount": ["amount must be a positive integer"],
"currency": ["currency must be a valid ISO 4217 code"]
},
"request_id": "req_8f3c2a1b9d7e4f60",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Los importes son unidades menores enteras (p. ej. 15000 para R$150,00) y currency es ISO 4217. Consulta Convenciones para conocer las reglas de dinero y formato.
401 — Error de autenticación (authentication_error)
La clave de API falta, está malformada o es inválida.
{
"error": {
"type": "authentication_error",
"code": "INVALID_CREDENTIALS",
"message": "Authentication failed",
"request_id": "req_3a91c0ed7b224f18",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Las claves secretas (sk_…) son exclusivamente del lado del servidor y nunca deben aparecer en código de navegador. Envíalas en el encabezado Authorization: Bearer <secret_key> desde tu backend. Consulta Autenticación.
403 — Error de autorización (authorization_error)
La clave es válida pero carece del alcance requerido, o el recurso objetivo está fuera del alcance de la clave.
{
"error": {
"type": "authorization_error",
"code": "INSUFFICIENT_PERMISSIONS",
"message": "This API key does not have the required scope: transactions:write",
"request_id": "req_77ba2c41e9f04d3a",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Los alcances siguen el patrón resource:action (p. ej. transactions:read, products:write). Al usar una clave de Organization contra recursos de alcance de merchant, también debes identificar al Merchant objetivo mediante el parámetro de consulta o el campo del cuerpo merchant_id. Consulta Autenticación.
404 — Error de no encontrado (not_found_error)
El recurso no existe, o está fuera del alcance de tu clave.
{
"error": {
"type": "not_found_error",
"code": "RESOURCE_NOT_FOUND",
"message": "Transaction not found",
"request_id": "req_5e0d8f3a6c124b97",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}409 — Error de conflicto (conflict_error)
La solicitud entra en conflicto con el estado actual. Una causa común es reproducir una Idempotency-Key cuyo cuerpo difiere de la solicitud original.
{
"error": {
"type": "conflict_error",
"code": "IDEMPOTENCY_KEY_CONFLICT",
"message": "This idempotency key was already used with a different request body.",
"request_id": "req_1c47a98f2b6e40d5",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Reproducir la misma Idempotency-Key con el mismo cuerpo devuelve el resultado original (HTTP 200 en la reproducción frente a 201 en la primera creación), no un conflicto. Un conflicto solo ocurre cuando la clave se reutiliza con una carga útil diferente. Consulta Idempotencia.
Otros códigos de conflicto incluyen RESOURCE_ALREADY_EXISTS (ya existe un registro con estos valores), DUPLICATE_ENTRY y RESOURCE_IN_USE (el recurso está referenciado por otros registros y no puede eliminarse ni modificarse).
422 — Error de regla de negocio (business_rule_error)
La solicitud estaba bien formada pero viola una regla de negocio, por ejemplo, al intentar una transición de estado inválida.
{
"error": {
"type": "business_rule_error",
"code": "INVALID_STATE_TRANSITION",
"message": "Cannot capture a transaction that has already been voided.",
"details": {
"current_status": "voided",
"requested_action": "capture"
},
"request_id": "req_9b2f6d1a3e8c47f0",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}422 significa "Entendí la solicitud, pero no puede aplicarse en el estado actual". Inspecciona el estado actual del recurso antes de reintentar. Los códigos relacionados incluyen TRANSACTION_ALREADY_VOIDED, TRANSACTION_ALREADY_CAPTURED y TRANSACTION_ALREADY_REFUNDED.
429 — Error de límite de frecuencia (rate_limit_error)
Superaste un límite de solicitudes para esta clave de API en este endpoint. Los límites se cuentan por clave y por ruta en ventanas de 60 segundos, y dependen de la clase de ruta — consulta Límites de tasa. La respuesta incluye Retry-After y, en details, retry_after_seconds y limit.
{
"error": {
"type": "rate_limit_error",
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after a short delay.",
"request_id": "req_4d8e1a07f5c9426b",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}En un 429, reintenta con backoff exponencial y jitter en lugar de un retardo fijo. Distribuir los reintentos evita una estampida cuando muchos clientes esperan al mismo tiempo.
5xx — Errores de servidor y upstream (internal_server_error, external_service_error)
Un 500 indica un error inesperado del lado de Tokeflow. Un 502 indica que un proveedor downstream en la ruta de orquestación falló o era inaccesible. Ambos son transitorios: reintenta con backoff.
{
"error": {
"type": "internal_server_error",
"code": "INTERNAL_ERROR",
"message": "Internal server error",
"request_id": "req_a1b2c3d4e5f60718",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Si un 5xx persiste tras varios reintentos con backoff, contacta a soporte e incluye el request_id. Nos permite rastrear la solicitud exacta de extremo a extremo.
Gestión de errores en el código
Lee el estado HTTP para decidir si reintentar, y el envoltorio para decidir cómo reaccionar. El ejemplo siguiente reintenta 429 y 5xx con backoff exponencial y expone todo lo demás para que quien realiza la llamada lo corrija.
async function tokeflowRequest(url, options, attempt = 0) {
const MAX_RETRIES = 4;
const res = await fetch(url, options);
if (res.ok) {
return res.json();
}
const body = await res.json();
const { type, code, message, request_id } = body.error;
// Always log the request_id — it is your support handle.
console.error(`Tokeflow error ${res.status} ${code}: ${message} (request_id=${request_id})`);
const isRetryable = res.status === 429 || res.status >= 500;
if (isRetryable && attempt < MAX_RETRIES) {
// Exponential backoff with jitter: 0.5s, 1s, 2s, 4s (+/- jitter).
const delay = Math.pow(2, attempt) * 500 + Math.random() * 250;
await new Promise((r) => setTimeout(r, delay));
return tokeflowRequest(url, options, attempt + 1);
}
// 4xx (except 429): the request must change. Surface it to the caller.
const error = new Error(message);
Object.assign(error, { type, code, request_id, status: res.status });
throw error;
}# Inspect the status code and envelope on a failed request.
curl -i https://api.tokeflow.com/api/v1/transactions/tx_does_not_exist \
-H "Authorization: Bearer sk_live_mer_xxxxxxxxxxxxxxxx"
# HTTP/1.1 404 Not Found
# {
# "error": {
# "type": "not_found_error",
# "code": "RESOURCE_NOT_FOUND",
# "message": "Transaction not found",
# "request_id": "req_5e0d8f3a6c124b97",
# "timestamp": "2026-01-15T12:30:00.000Z"
# }
# }Buenas prácticas
- Registra el
request_iden cada fallo. Es la forma más rápida para que soporte rastree una solicitud. Inclúyelo en los tickets y en los registros internos. - Ramifica según
typeycode, nunca segúnmessage. Los mensajes son para humanos y pueden cambiar;typeycodeson el contrato estable. - Trata
5xxy429como reintentables con backoff exponencial y jitter; trata los demás4xxcomo "corrige la solicitud". - Envía siempre una
Idempotency-Keyen las mutaciones de creación/cobro para que los reintentos seguros nunca dupliquen el trabajo. - Lee
detailsen un400para señalar el campo exacto que falló la validación.
Relacionado
- Convenciones — envoltorios, paginación, idempotencia, dinero y límites de frecuencia.
- Autenticación — claves, alcances y encabezados.
- Webhooks — verificación de firma y entrega de eventos.