Códigos de error


Cómo leer un error

Hay tres capas distintas y conviene no confundirlas:

Capa Ejemplo Qué significa
HTTP 422 Cómo respondió nuestra API
error.code payment_already_used Qué pasó, con una clave estable. Es lo que tu código debe leer
Validación errors.payment_card.cvv Tu petición no cumple las reglas. Se arregla en tu código
Código de red 51, M12, V68 La operación llegó a la red y la red decidió. No siempre se arregla reintentando

Un pago rechazado no es un error de tu integración: la petición fue correcta y el resultado fue «no». Devuelve 200 u 422 según el caso, y siempre trae el estado de la transacción para que sepas en qué quedó.

{warning} Nunca reintentes automáticamente un rechazo. Si la razón fue fondos insuficientes o una tarjeta bloqueada, reintentar da el mismo resultado y algunos emisores penalizan los reintentos.

El objeto error

Todo error de la API v1 (HTTP 4xx) trae un objeto error con la misma forma, además de los campos que ya traía (message, errors en un 422, changed/canceled/applied: false en las acciones de suscripción). Nada de lo anterior desaparece.

{
    "message": "Este cobro ya tiene una transacción y no permite reintentos",
    "error": {
        "type": "invalid_request_error",
        "code": "payment_already_used",
        "message": "Este cobro ya tiene una transacción y no permite reintentos",
        "param": "payment.id"
    }
}
Campo Para qué
error.type Familia del error. Ver la tabla de abajo
error.code Clave estable en inglés. Decide con esta, no con el texto de message ni solo con el HTTP
error.message Texto legible, para tu log. Puede cambiar de redacción
error.param El campo que causó el error, cuando aplica (payment.id, metadata, el primer campo inválido de un 422). Si no, null
error.type HTTP Cuándo
invalid_request_error 400, 404, 409, 422 La petición no se puede aplicar: regla de negocio, recurso inexistente, validación
authentication_error 401 Falta el token o no es válido
card_error 402 La tarjeta fue rechazada al cobrar
authorization_error 403 Token válido, pero sin permiso para esto
rate_limit_error 429 Ya hay una operación igual en curso
idempotency_error 409 Choque de Idempotency-Key

Si el error no tiene un código propio, error.code toma el de su HTTP:

HTTP error.code por defecto
400 bad_request
401 unauthenticated
402 card_declined
403 forbidden
404 not_found
405 method_not_allowed
409 conflict
422 validation_failed (con param = el primer campo inválido)
429 too_many_requests

{warning} Cambio de forma en el 429 de «otra transacción en proceso». Antes respondía {"error": "texto"}, con error como string. Ahora error es el objeto de siempre y el texto pasa a message:

{
    "message": "Otra transacción esta siendo procesada, por favor intenta de nuevo o mas tarde.",
    "error": {
        "type": "rate_limit_error",
        "code": "payment_in_progress",
        "message": "Otra transacción esta siendo procesada, por favor intenta de nuevo o mas tarde.",
        "param": null
    }
}

Si tu integración leía error como texto, cámbiala a leer message o error.code. Es el único caso en que un campo existente cambia de tipo.

Códigos de negocio

Cobros

Los devuelve el checkout por API cuando el par payment.id + payment.token no permite pagar.

error.code HTTP param Qué pasó Qué hacer
invalid_payment_credentials 403 payment.token El id no existe o el token no le corresponde. Es el mismo código en ambos casos, para no revelar qué ids existen Revisar el par que guardaste de generate-payment
payment_already_used 403 payment.id El cobro ya tiene una transacción (pagada o no) y no admite reintentos Generar un cobro nuevo
payment_already_paid 403 payment.id El cobro ya fue pagado No cobrar de nuevo
payment_in_progress 403 payment.id Tiene transacciones en progreso que agotan su límite Esperar el resultado de la que está en curso
payment_in_progress 429 — Otra transacción de este cobro se está procesando ahora mismo (tipo rate_limit_error) Esperar y consultar el estado
payment_expired 403 payment.id Pasó la fecha límite del cobro («Este cobro ya expiro») Generar un cobro nuevo
payment_inactive 403 payment.id El cobro fue desactivado Generar un cobro nuevo

{info} Antes, un cobro ya pagado respondía un 403 sin detalle («This action is unauthorized.»), indistinguible de un problema de permisos. Ahora cada caso tiene su error.code.

Idempotencia

error.code HTTP Qué pasó
idempotency_key_reused 409 La Idempotency-Key ya se usó con parámetros distintos
idempotency_key_in_progress 409 La petición original con esa clave todavía se está procesando

Suscripciones

error.code Qué pasó
subscription_already_canceled La suscripción ya estaba cancelada
subscription_canceled La operación no aplica a una suscripción cancelada (cambio de plan, cambio de tarjeta, mode: "direct")
subscription_inactive La suscripción está inactiva; usa la invitación o la renovación con pago
subscription_same_plan La suscripción ya está en ese plan
invitation_already_pending Ya hay una invitación de cambio de plan o de renovación pendiente
subscriber_without_card El suscriptor no tiene tarjeta guardada
subscriber_already_subscribed El suscriptor ya está suscrito a ese plan
subscriber_has_active_subscriptions No se puede eliminar: tiene suscripciones activas
subscriber_office_unresolved No se pudo determinar la sucursal del suscriptor
plan_inactive El plan está inactivo
plan_has_active_subscriptions No se puede eliminar: el plan tiene suscripciones activas
group_has_plans No se puede eliminar: el grupo tiene planes
coupon_invalid Cupón inválido o no disponible
card_declined 402 en un cambio de plan con always_invoice: el cobro inmediato del prorrateo fue rechazado y el plan no cambió. Trae además error.decline_code, el response_code de la transacción

Estas respuestas conservan los campos que ya traían (changed: false, canceled: false, applied: false), así que el código que los lee sigue funcionando.

Códigos HTTP

Código Cuándo Qué hacer
200 La operación se procesó. Revisa el estado del cuerpo: puede ser un rechazo Leer status / success
201 Se creó algo (una reserva autorizada, por ejemplo) Guardar el id
202 Estado indeterminado: no sabemos aún el resultado No reintentar. Esperar el webhook o consultar
400 Regla de negocio incumplida (por ejemplo, borrar un suscriptor con suscripción activa) Leer error.code
401 Falta el token o no es válido Ver Autenticación
402 Rechazo de tarjeta en un cobro inmediato de suscripción Leer error.decline_code
403 Token válido pero sin permiso, comercio deshabilitado, o cobro que ya no acepta pagos Leer error.code
404 El recurso no existe, o es de otro comercio, o del otro ambiente Revisar el id y el tipo de token
409 Conflicto: cobro ya aplicado, o Idempotency-Key reutilizada con otros parámetros Leer error.code
422 Validación, o la red rechazó Leer errors (o error.param) o el estado
429 Ya hay una operación igual en curso Esperar el resultado, no reintentar
5xx Error de nuestro lado o de la red Reintentable con espera

Errores de validación

Siempre tienen la misma forma: un message con el primer error, un objeto errors con todos, indexados por el nombre del campo, y el objeto error con code: "validation_failed" y param apuntando al primer campo inválido.

{danger} Validación code: 422

{
"message": "El campo payment_card.cvv es obligatorio.",
"errors": {
"payment_card.cvv": ["El campo payment_card.cvv es obligatorio."],
"customer_payer.email": ["El campo customer_payer.email debe ser un correo válido."]
},
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "El campo payment_card.cvv es obligatorio.",
"param": "payment_card.cvv"
}
}

Las claves de errors usan notación de punto para los campos anidados. Úsalas para marcar el campo exacto en tu formulario en lugar de mostrar un mensaje genérico.

{warning} Un caso especial: generar un pago devolvía solo el mapa de errores, con los campos en la raíz. Esas claves se conservan —si las lees así, sigues funcionando— y ahora se suman message, errors y error:

{
    "payment.amount": ["The payment.amount field is required."],
    "errors": {
        "payment.amount": ["The payment.amount field is required."]
    },
    "message": "The payment.amount field is required.",
    "error": {
        "type": "invalid_request_error",
        "code": "validation_failed",
        "message": "The payment.amount field is required.",
        "param": "payment.amount"
    }
}

Y si en esa misma llamada omites payment.currency_type, la validación se detiene ahí: recibirás ese único error y ninguno más, porque los límites de monto dependen de la moneda.

Conflictos de idempotencia

Las escrituras de suscripciones aceptan el header Idempotency-Key. Cuando esa clave choca, responde 409 con error.type: "idempotency_error" y un error.code propio.

{danger} Misma clave, parámetros distintos code: 409

{
"message": "La Idempotency-Key ya fue usada con parámetros distintos.",
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "La Idempotency-Key ya fue usada con parámetros distintos.",
"param": null
}
}

{danger} Misma clave, misma petición todavía en curso code: 409

{
"message": "Una solicitud con esta Idempotency-Key aún está en proceso.",
"error": {
"type": "idempotency_error",
"code": "idempotency_key_in_progress",
"message": "Una solicitud con esta Idempotency-Key aún está en proceso.",
"param": null
}
}

Cuando en cambio repites exactamente la misma llamada con la misma clave, te devolvemos la respuesta original con el header Idempotency-Replayed: true y sin volver a ejecutar el cobro.

Detalle Comportamiento
Vigencia de la clave 24 horas
Métodos afectados POST, PUT, PATCH, DELETE
Rutas Las escrituras de /api/v1/subscriptions/*, /api/v1/payment/* y /api/v1/mit/pre-authorizations/*. Ver el detalle en Convenciones
Alcance de la clave Por comercio y usuario del token
Respuestas 5xx No se memorizan, para que puedas reintentar un fallo transitorio

{success} Úsala en todo cobro. El caso que evita: cobras, tu proceso se cae antes de guardar la respuesta, y al reintentar no sabes si el primer intento llegó. Con la misma Idempotency-Key el segundo intento te devuelve la respuesta original en vez de cobrar dos veces.

{info} La clave la eliges tú: una distinta por operación, no por reintento. Un UUID generado al empezar el cobro y reutilizado en todos los reintentos de esa misma operación es lo habitual.

Rechazos de la red

Los más frecuentes. El código llega en response_code de la transacción y, cuando no fue aprobada, también en error.code con su message, su retryable y su action. Este error de la transacción es distinto del objeto error de la API: viene dentro de una respuesta 200 (o de un webhook), no en un 4xx. Llega igual en la respuesta del checkout, en los webhooks de transacción y en el webhook retry de suscripción (transaction.error).

{
    "status": "Rechazada",
    "status_key": "rejected",
    "response_code": "51",
    "error": { "code": "51", "message": "…", "retryable": false, "action": "contact_issuer" }
}

{info} El catálogo completo, con todos los códigos y su retryable, se consulta en vivo en GET /api/v1/resources/mit/response-codes. Léelo de ahí en vez de copiar esta tabla: se mantiene sola.

description es el mensaje para el tarjetahabiente y error.message el que nombra la causa para tu log. En los rechazos de esta tabla ambos coinciden con la causa y con error.action: la description pide lo mismo que la acción. Los códigos que no están aquí conservan el mensaje del catálogo de la red.

Código Qué pasó description (tarjetahabiente) error.message (tu log) retryable action
00 Aprobada — — — —
05 El emisor no autorizó, sin causa específica Tu banco no autorizó el pago. Comunícate con tu banco o usa otro medio de pago. Transacción declinada por el emisor sin causa específica. false contact_issuer
14 Tarjeta inválida Esta tarjeta no es válida. Usa otra tarjeta. Transacción declinada. Tarjeta inválida. false use_another_card
41 Tarjeta reportada como extraviada Esta tarjeta no se puede usar. Usa otra tarjeta. Transacción declinada. Tarjeta reportada como extraviada. false use_another_card
43 Tarjeta bloqueada por el emisor Esta tarjeta está bloqueada. Usa otra tarjeta. Transacción declinada. Tarjeta bloqueada por el emisor. false use_another_card
51 Fondos insuficientes Transacción declinada. Fondos insuficientes Transacción declinada. Fondos insuficientes false contact_issuer
54 Tarjeta vencida Tu tarjeta está vencida. Usa otra tarjeta. Transacción declinada. Tarjeta vencida. false use_another_card
57 El emisor no permite este tipo de transacción Esta tarjeta no permite este pago. Usa otra tarjeta. Transacción declinada. Tipo de transacción no permitido para la tarjeta. false use_another_card
61 Excede el límite de monto de la tarjeta El pago supera el límite de tu tarjeta. Comunícate con tu banco o usa otra tarjeta. Transacción declinada. Excede el límite de monto. false contact_issuer
62 Tarjeta restringida Esta tarjeta tiene restricciones. Usa otra tarjeta. Transacción declinada. Tarjeta restringida. false use_another_card
65 Excede la frecuencia de transacciones Superaste el número de pagos permitidos con esta tarjeta. Comunícate con tu banco o usa otra tarjeta. Transacción declinada. Excede la frecuencia de transacciones. false contact_issuer
91 El emisor no está disponible Tu banco no respondió. Intenta de nuevo en unos minutos. Transacción rechazada. El emisor no está disponible. true retry_later
96 Falla del sistema del emisor o de la red No pudimos procesar el pago. Intenta de nuevo en unos minutos. Transacción rechazada. Falla del sistema del emisor o de la red. true retry_later
98 CVV inválido El código de seguridad (CVV) es incorrecto. Verifica los datos de la tarjeta. Transacción declinada. CVV inválido. false check_card_data

error.action toma uno de estos valores (los códigos de reserva de cupo pueden traer su propio texto de acción):

action Qué hacer
contact_issuer El cliente debe hablar con su banco, o usar otro medio
use_another_card Pedir otra tarjeta
retry_later Reintentar más tarde, con espera
check_card_data Pedir al tarjetahabiente que revise número, fecha o CVV

Puedes provocar cada causal en pruebas con las tarjetas por causal.

Qué es seguro reintentar

error.retryable Ejemplos Qué hacer
false 05, 14, 41, 43, 51, 54, 57, 61, 62, 65, 98 (fondos, vencida, bloqueada, límite, CVV) No reintentes. El resultado será el mismo y algunos emisores penalizan la insistencia. Pide otro medio de pago
true 19, 90, 91, 96, E99, S01, S10, S11 (la red o un servicio interno falló), salvo que el catálogo diga otra cosa Reintenta con espera creciente
— T01 y cualquier 202 Estado indeterminado: no reintentes. No sabemos si la operación se procesó. Consulta el estado antes de hacer nada

{danger} La diferencia entre «falló» y «no sé si falló» es la que produce cobros dobles. Ante T01 o un 202, consulta con estado de transacción antes de reenviar nada.

Reserva de cupo

Estos son propios de las reservas de cupo.

Código HTTP Qué pasó
MIT_PRODUCTION_KEY_REQUIRED 422 Intentaste autorizar con llave de prueba; una reserva retiene fondos reales
MIT_AMOUNT_EXCEEDS_AUTHORIZED 422 El cobro final supera lo reservado
MIT_EXPIRED 422 La reserva venció; hay que crear una nueva
MIT_ALREADY_CONFIRMED 409 Esta reserva ya tiene su cobro aplicado
MIT_VOID_WINDOW_CLOSED 422 Ya cerró la ventana para liberar (24 h antes del vencimiento). Deja que venza
MIT_FRANCHISE_NOT_ENABLED 422 Esa franquicia no está habilitada para reservas en tu comercio
MIT_INDETERMINATE 202 No recibimos respuesta de la red. No reintentes
MIT_IN_PROGRESS 429 Ya hay una autorización en curso para esta reserva
M01 409 La reserva ya fue confirmada
M02 422 La operación referenciada no es una reserva pre-autorizada
M04 422 La reserva está vencida en la red
M05 422 La reserva no fue aprobada, así que no hay cupo que cobrar
M06 422 La red no encuentra la operación. Solo conserva 30 días
M08 / M10 / M11 422 La franquicia o el banco emisor no permiten reservas
M12 422 El valor del cobro final no corresponde al reservado
309 422 El tipo de transacción MIT no admite 3DS
310 422 El ECI enviado no es válido: debe ser 05, 06 o 07
319 / 320 422 Enviaste el objeto 3DS de la otra franquicia

Anulación y reversión

Código Qué pasó
A01 Pasó la fecha permitida para anular
A03 – A06 La operación no está en un estado que admita anulación
A07 No se puede anular una operación incremental
V39 No se puede liberar una reserva que ya fue cobrada
V40 La reserva no está en fecha válida para liberarse
V42 La operación indicada no es anulable

Errores de transporte y cifrado

Código HTTP Qué pasó Qué hacer
T01 202 Se agotó el tiempo de espera con la red. No sabemos si la operación quedó registrada No reintentar. Consultar el estado
E99 502 Error interno de la red Reintentable con espera
S10 / S11 500 Fallo de cifrado con la red Contactar a soporte

{danger} T01 y MIT_INDETERMINATE son los dos casos en que reintentar es peligroso: si la operación sí se aplicó, un reintento la duplica. Consulta el estado o espera el webhook.

Catálogo en vivo

En lugar de copiar esta tabla a tu código, consúltala:

GET /api/v1/resources/mit/response-codes

Cada entrada trae:

Campo Para qué
code El código
message El mensaje pensado para el comercio
action Qué hacer
retryable Si tiene sentido reintentar

Qué mostrarle a tu cliente

Tenemos dos mensajes para cada código y sirven para cosas distintas:

Campo Audiencia Ejemplo
Mensaje al pagador (description) El tarjetahabiente «Tu tarjeta está vencida. Usa otra tarjeta.»
Mensaje al comercio (error.message) Tu operador o tu log «Transacción declinada. Tarjeta vencida.»

En la respuesta de una transacción, description trae el mensaje que se le puede mostrar al cliente tal cual. En un rechazo con código catalogado pide lo mismo que error.action (ver Rechazos de la red).

{warning} No le muestres al cliente el código crudo ni el mensaje interno. Un M12 en pantalla no le dice nada a nadie, y los mensajes internos pueden mencionar tu configuración.