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 type y un code legibles por máquina, un message legible por humanos y un request_id para 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"
  }
}
FieldTypeSiempre presenteDescripción
typestringSí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.
codestringSí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.
messagestringSíExplicación legible por humanos. Segura para mostrar, pero su redacción puede cambiar con el tiempo: nunca la analices.
detailsobjectNoContexto opcional. Para errores de validación, un mapa de nombre de campo a un arreglo de mensajes. Puede omitirse por completo.
decline_reasonstringNoPresente 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_idstringSíIdentificador único de esta solicitud (req_…). Regístralo e inclúyelo en cualquier solicitud de soporte.
timestampstringSí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.

TypeHTTPSignificadoCómo gestionarlo
validation_error400El 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_error400La 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_error401La clave de API falta, está malformada o es inválida.Revisa el encabezado Authorization y la clave. No reintentes sin cambios.
authorization_error403La 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_error404El recurso o el endpoint no existe (o está fuera de tu alcance).Revisa el ID y la ruta. No reintentes sin cambios.
conflict_error409La 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_error422La 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_error429Superaste el límite de frecuencia para esta clave de API.Espera y reintenta con backoff exponencial.
external_service_error502Un 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_error500Ocurrió 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 una Idempotency-Key en 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_id en 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 type y code, nunca según message. Los mensajes son para humanos y pueden cambiar; type y code son el contrato estable.
  • Trata 5xx y 429 como reintentables con backoff exponencial y jitter; trata los demás 4xx como "corrige la solicitud".
  • Envía siempre una Idempotency-Key en las mutaciones de creación/cobro para que los reintentos seguros nunca dupliquen el trabajo.
  • Lee details en un 400 para 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.

En esta página