Skip to Content
View as Markdown

Errores

Cada respuesta 4xx/5xx lleva el mismo envelope JSON:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" } ] }
  • code: el status HTTP numérico (400, 401, 404, …). Útil para manejo genérico de la capa HTTP, pero para la lógica de ramificación ramifica sobre message en su lugar: code no desambigua entre, digamos, invalid_input y payment_method_not_supported (ambos 400).
  • message: el nombre centinela en lower-snake-case (p. ej. INVALID_INPUT pasa a "invalid_input"). Estable a través de releases: ramifica sobre esto.
  • details: se rellena en errores de validación. Array de objetos { field, message } para que el cliente pueda fijar errores a inputs. Se omite en otro caso.

Los cuerpos de error no incluyen un trace ID ni un timestamp. Para reportar un problema, envía a soporte la cabecera de respuesta Date, las cabeceras X-RateLimit-*, tu merchant ID, el endpoint y la hora aproximada de la petición.

Los fallos de autenticación usan una forma diferente. El envelope de arriba es lo que devuelve la API en la mayoría de los errores. Las peticiones rechazadas antes de procesarse (una X-Signature faltante o inválida, un X-Client-ID desconocido, o un X-Timestamp obsoleto en una llamada /b2b/v1/*) regresan como { "error": "...", "message": "..." }, donde error es un slug grueso (unauthorized / bad_request / service_unavailable) y message lleva los detalles. No hay code numérico ni details. Ramifica primero por status HTTP, luego lee message. Ejemplo (401):

{ "error": "unauthorized", "message": "invalid signature" }

Status HTTP → códigos típicos

HTTPValores típicos de codeQué significa
400INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUMPetición incorrecta — mira details
401INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); SESSION_EXPIRED (solo dashboard)Falló la auth: clave incorrecta, timestamp expirado, firma equivocada. Los códigos de inicio de sesión del dashboard no son relevantes para las integraciones B2B.
402INSUFFICIENT_CREDITEl saldo prepagado del comerciante se agotó: recarga antes de reintentar
403FORBIDDEN, IP_BLOCKEDLa clave es válida pero le falta el scope/permiso de IP para esta llamada
404NOT_FOUND, RECORD_NOT_FOUNDEl recurso no existe (o no existe para este comerciante)
409ALREADY_EXISTSReplay de idempotencia con un body diferente, o la máquina de estados rechazó la transición
429TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTSLímite de tasa alcanzado; retrocede y reintenta
500INTERNAL_SERVER_ERROR, EXTERNAL_SERVICE_ERROREs culpa nuestra; es seguro reintentar con backoff.
503SERVICE_UNAVAILABLEUna dependencia aguas abajo está caída. Reintenta con backoff

Referencia completa de códigos

Los valores de code que puedes ver:

Autenticación (401)

  • INVALID_CREDENTIALS: combinación de clave API rechazada
  • INVALID_TOKEN: token no parseable o manipulado
  • TOKEN_EXPIRED: token expirado
  • SESSION_EXPIRED: sesión del dashboard expirada
  • INVALID_SIGNATURE: discrepancia de firma HMAC en llamadas B2B / webhook

Autorización (403)

  • FORBIDDEN: autenticado, pero no tienes permiso para realizar esta acción
  • IP_BLOCKED: las peticiones desde esta dirección IP están bloqueadas

No encontrado (404)

  • NOT_FOUND: genérico
  • RECORD_NOT_FOUND: fila ausente para el ID dado

Conflicto (409)

  • ALREADY_EXISTS: genérico

Validación (400)

  • INVALID_INPUT: genérico; revisa details
  • MISSING_REQUIRED: un campo obligatorio estaba ausente
  • INVALID_FORMAT: el valor no coincidió con el formato esperado (p. ej. UUID, URL, email)
  • INVALID_LENGTH: el valor es demasiado corto o demasiado largo
  • INVALID_VALUE: valor fuera del enum/rango permitido
  • PAYMENT_METHOD_NOT_SUPPORTED: la combinación proveedor/activo no está habilitada para el comerciante
  • AMOUNT_BELOW_MINIMUM: importe de la orden por debajo del piso por red o por entorno. El details.floor_usd de la respuesta lleva el piso configurado (USD) para que puedas mostrarlo directamente; el texto message también lo indica.
  • INSUFFICIENT_BALANCE: la wallet del comprador no tiene suficiente del activo de pago para cubrir la transferencia.
  • INSUFFICIENT_GAS: a la wallet del comprador le falta token nativo para pagar la comisión de red (gas) de la transferencia.

Pago / facturación (402)

  • INSUFFICIENT_CREDIT: el saldo prepagado del comerciante no puede cubrir la comisión de red (gas) y de la plataforma. Recarga vía dashboard, luego reintenta

Límite de tasa (429)

  • TOO_MANY_REQUESTS: límite de tasa por IP excedido
  • TOO_MANY_ATTEMPTS: intentos fallidos repetidos sobre el mismo recurso (p. ej. OTP) activaron un throttle

Errores del servidor (500)

  • EXTERNAL_SERVICE_ERROR: falló un proveedor de terceros
  • INTERNAL_SERVER_ERROR: error inesperado; contacta con soporte con la cabecera de respuesta Date y las cabeceras X-RateLimit-*

Disponibilidad (503)

  • SERVICE_UNAVAILABLE: el servicio no está disponible temporalmente; reintenta con backoff

Errores de validación (400)

Cuando el problema es un request body malformado, details es un array para que puedas mapear errores de vuelta a los campos:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" }, { "field": "success_url", "message": "must be a valid https URL" } ] }

Errores de auth (401)

INVALID_SIGNATURE tiene tres causas comunes:

  • Clave secreta incorrecta (revisa de nuevo la variable de entorno)
  • Desviación de timestamp > 5 min (sincroniza NTP)
  • Cadena canónica incorrecta (lo más frecuente: olvidaste los separadores \n, o firmaste un body parseado-y-luego-reserializado que difiere de lo que enviaste)

Consulta Autenticación para el algoritmo de firma exacto.

Límites de tasa (429)

SuperficieLímite
Todas las rutas de la API (incl. /b2b/v1/*)Por dirección IP: 500 req/min, bucket compartido. No por comerciante.
Checkout público (/checkout/*)Sub-bucket más estricto: 20 req/min por IP

X-RateLimit-Limit y X-RateLimit-Remaining se incluyen en cada respuesta con límite de tasa, no solo en las exitosas. Trátalas como el presupuesto en vivo para tu IP.

Las respuestas con límite de tasa incluyen una cabecera Retry-After (segundos hasta que se reinicia la ventana): respétala. Como fallback, retrocede con jitter: 1s base + exponencial hasta 30s.

Los límites de tasa pueden cambiar. Si los alcanzas con tráfico legítimo (p. ej., reconciliando un rango histórico grande), contacta con soporte.

Crédito insuficiente (402)

INSUFFICIENT_CREDIT (HTTP 402 Payment Required) significa que tu saldo prepagado no puede cubrir la comisión de red (gas) y de la plataforma para la operación que intentaste realizar: típicamente liquidar un pago cripto o una acción on-chain con comisión de red patrocinada. Recarga desde el dashboard del comerciante (Billing → Add credit), luego reintenta la operación. Las operaciones ya en curso continúan automáticamente una vez que recargas el saldo.

Errores del servidor (5xx)

Un 500 significa que no pudimos procesar la petición. Reintenta con backoff: tu idempotency_key garantiza que no cargarás doble si la petición original tuvo éxito parcial.

Si los reintentos no se recuperan en un minuto, muestra al comprador un genérico “pago temporalmente no disponible” y contacta con soporte con el endpoint que falla, tu merchant ID, los valores de la cabecera de respuesta Date y X-RateLimit-*, y la hora aproximada de la petición: eso es suficiente para que encontremos la petición.

Errores de entrega de webhook

Las entregas de webhook son un canal de fallo separado: no aparecen como errores de API porque tu servidor no es el que llama. Cuando una entrega devuelve un no-2xx (o agota el tiempo), se reintenta con backoff exponencial a 0s, 1min, 5min, 15min, 1h, 6h (seis intentos en total, el mismo calendario que Webhooks → Resumen). El historial completo por entrega aparece bajo Developers → Webhooks → [endpoint] → Delivery log en el dashboard. Tras el sexto intento la entrega se marca como “Failed” en el dashboard y puedes reenviarla manualmente una vez que tu servidor esté saludable.