Errores
Todos los errores de /v1 tienen la misma forma, sin importar el endpoint ni el
código HTTP:
{
"code": "PRODUCT_NOT_FOUND",
"message": "No product mapping found for external id SKU-WEB-HOSTING-01."
}
code— estable y legible por máquina. Programa contra esto.message— explicación para humanos. Útil en logs y en tu pantalla de soporte, pero su redacción puede cambiar sin aviso. No lo parsees.
Cómo interpretar el HTTP status
| Status | Significado | ¿Reintentar? |
|---|---|---|
400 | Tu solicitud está mal. Arregla el body y vuelve a mandar. | No, tal cual |
401 | Credencial ausente o inválida. | No |
403 | Credencial válida, pero la empresa no tiene la integración habilitada. | No |
404 | No existe dentro de tu empresa. | No |
409 | Conflicto: duplicado, key reutilizada con otro body, o configuración incompleta de la empresa. | No |
422 | Solicitud bien formada pero pide algo que v1 no hace. | No |
500 | Falla nuestra. | Sí, con backoff y la misma Idempotency-Key |
Regla general: solo los 5xx y los timeouts se reintentan.
Catálogo de códigos
Autenticación y habilitación
code | HTTP | Qué hacer |
|---|---|---|
MISSING_API_KEY | 401 | Manda Authorization: Bearer <key>. |
INVALID_API_KEY | 401 | La key no existe, fue revocada o expiró. Emite otra. |
INTEGRATION_NOT_ENABLED | 403 | Tu empresa no tiene la integración activada. Contacta a Contadito. |
Configuración de la empresa (no lo arreglas tú)
Estos significan que la integración está encendida pero incompleta o
desactualizada del lado de la configuración. Todos son 409 y todos se
resuelven en la app de Contadito, no en tu código.
code | Qué falta |
|---|---|
INTEGRATION_MISSING_EMISSION_POINT | Punto de emisión por defecto. |
INTEGRATION_MISSING_CREATOR_USER | Usuario emisor por defecto. |
INTEGRATION_MISSING_PRICE_TYPE | Lista/tipo de precio por defecto. |
INTEGRATION_MISSING_PAYMENT_TERMINAL | Configuración de cobro por defecto. Solo lo exige la emisión real, no la previsualización. |
INTEGRATION_EMISSION_POINT_NOT_FOUND | El punto de emisión configurado ya no existe. |
INTEGRATION_PAYMENT_TERMINAL_NOT_FOUND | La configuración de cobro ya no existe. |
Si los ves en producción, la acción correcta es alertar a tu equipo de soporte y dejar la venta en cola: reintentar no ayuda hasta que alguien corrija la configuración.
Productos y precios
code | HTTP | Qué pasó |
|---|---|---|
PRODUCT_NOT_FOUND | 400 | Ese externalProductId no está mapeado. El error más común en integraciones nuevas. Pide el mapeo. |
PRICE_NOT_FOUND | 409 | El producto existe pero no tiene precio en la lista configurada. |
ITEM_MISSING_PRODUCT_REFERENCE | 400 | Una línea no referencia ningún producto. |
ITEM_AMBIGUOUS_PRODUCT_REFERENCE | 400 | Una línea referencia más de un producto. |
INVALID_TAX_RATE | 409 | El producto tiene una tasa de ISV inválida en el catálogo. |
DISCOUNT_POLICY_NOT_FOUND | 409 | La política de descuento aplicable no existe. |
DISCOUNT_POLICY_UNAVAILABLE | 409 | La política existe pero no está disponible ahora. |
INVALID_DISCOUNT_POLICY | 409 | La política está mal configurada. |
La solicitud
code | HTTP | Qué pasó |
|---|---|---|
VALIDATION_FAILED | 400 | El body no pasó validación (falta un campo, tipo equivocado, o mandaste un campo que no existe), o un query param como page/limit no es un entero ≥ 1. message dice cuál. |
MISSING_IDEMPOTENCY_KEY | 400 | Falta el header Idempotency-Key en POST /v1/invoices. POST /v1/invoices/preview no lo pide. |
IDEMPOTENCY_KEY_REQUEST_MISMATCH | 409 | Reusaste una key con un body diferente. Ver Idempotencia. |
EXTERNAL_INVOICE_NO_ITEMS | 400 | items vacío. Se requiere al menos una línea. |
EMPTY_ITEMS | 400 | Igual que el anterior, detectado por el motor de totales. |
INVALID_NUMBER | 400 | Un número no es válido (cantidad cero o negativa, por ejemplo). |
INVALID_REQUEST | 400 | La solicitud es inconsistente. Lee message. |
EXTERNAL_INVOICE_CLIENT_NOT_FOUND | 400 | Ese clientId no existe en tu empresa. |
EXTERNAL_INVOICE_EXONERATION_REQUIRES_CLIENT | 400 | Una venta exonerada necesita clientId. |
EXTERNAL_INVOICE_EXONERATION_REQUIRES_RTN | 400 | El cliente de una venta exonerada debe tener RTN (legalId). |
INVALID_RTN_FORMAT | 400 | El RTN debe ser exactamente 14 dígitos. |
UQ_CLIENT_CODE_COMPANY | 409 | Ya existe un cliente con ese código en tu empresa. |
UQ_IHCAFE_CODE_CLIENT | 409 | Ya existe un cliente con ese código IHCAFE. |
EXTERNAL_INVOICE_NOT_FOUND | 404 | Esa factura no existe dentro de tu empresa. |
EXTERNAL_INVOICE_CREDIT_NOT_SUPPORTED | 422 | isCredit: true está reservado para v2. |
Del lado nuestro
code | HTTP | Qué hacer |
|---|---|---|
INTERNAL_ERROR | 500 | Algo falló en Contadito. Reintenta con backoff y la misma Idempotency-Key. Si persiste, avísanos con la hora y la key. |
Cómo manejarlos en código
const NO_REINTENTABLES = new Set([
'MISSING_API_KEY',
'INVALID_API_KEY',
'VALIDATION_FAILED',
'PRODUCT_NOT_FOUND',
'IDEMPOTENCY_KEY_REQUEST_MISMATCH',
'EXTERNAL_INVOICE_CREDIT_NOT_SUPPORTED',
]);
const REQUIEREN_SOPORTE = (code) =>
code === 'INTEGRATION_NOT_ENABLED' || code.startsWith('INTEGRATION_');
function manejar(status, body) {
if (status >= 500) return 'reintentar';
if (REQUIEREN_SOPORTE(body.code)) return 'encolar_y_alertar';
if (NO_REINTENTABLES.has(body.code)) return 'fallar_y_registrar';
return 'fallar_y_registrar';
}
aviso
Nunca hagas if (message === '...')
message es texto para humanos y su redacción cambia. Toda tu lógica va sobre
code y el status HTTP.