Errores
La forma
Sección titulada «La forma»Todos los errores del API tienen la misma estructura, incluidos los de validación y los que no vienen de nuestro código:
{ "status": 422, "code": "VALIDATION", "message": "texto legible en español", "errors": [ { "rule": "FAJ43b", "field": "numbering_range_id", "message": "..." } ]}| Campo | Descripción |
|---|---|
status |
El mismo código HTTP de la respuesta. |
code |
Identificador estable. Programa contra esto, no contra el texto. |
message |
Explicación en español, pensada para leerse. Puede cambiar. |
errors |
Detalle por campo. Vacío cuando no aplica. |
Los códigos
Sección titulada «Los códigos»Autenticación y permisos
Sección titulada «Autenticación y permisos»code |
HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
UNAUTHORIZED |
401 | Falta la cabecera, o el token es inválido o venció | Pide otro token |
FORBIDDEN |
403 | La credencial es válida pero no abre esa puerta | Revisa qué credencial estás usando |
Petición
Sección titulada «Petición»code |
HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
VALIDATION |
400 / 422 | Un campo falta, sobra o no cumple | Mira errors[]: dice el campo |
BAD_REQUEST |
400 | La petición no encaja con el estado actual | Léelo: el mensaje dice qué falta |
NOT_FOUND |
404 | No existe, o no es de tu emisor | Comprueba el id |
Emisor mal configurado
Sección titulada «Emisor mal configurado»code |
HTTP | Qué pasó |
|---|---|---|
CERT_MISSING |
— | El emisor no tiene certificado digital cargado |
CERT_EXPIRED |
— | El certificado venció |
CERT_NIT_MISMATCH |
— | El certificado es de otro NIT |
SOFTWARE_NOT_CONFIGURED |
— | Falta registrar el software (id y PIN) ante la DIAN |
Los cuatro se arreglan en la configuración del emisor, no en tu código.
Numeración
Sección titulada «Numeración»code |
HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
RANGE_EXHAUSTED |
422 | Se acabó el rango autorizado | Pedirle otro a la DIAN |
RANGE_EXPIRED |
422 | La resolución venció | Pedir una nueva |
RANGE_INACTIVE |
422 | La resolución se retiró de circulación a mano | Usar otra, o reactivarla con PATCH /v1/numbering-ranges/{id}/actividad |
Los rangos de notas crédito y débito se salen de los dos primeros consejos:
no llevan vigencia, así que RANGE_EXPIRED no les puede pasar, y cuando
se agotan no hay nada que pedirle a la DIAN — se registra el tramo siguiente
con el mismo prefijo. Ver rangos de numeración.
Emitir es asíncrono: la DIAN no contesta dentro de la petición, así que su
respuesta nunca llega como error HTTP. Un «no» de la DIAN y una caída
de la DIAN son estados del documento, no un code:
| Situación | Estado | Dónde está el motivo |
|---|---|---|
| La DIAN rechazó el documento | REJECTED |
dian_errors[] — son los suyos, literales — y dian_status_code |
| La DIAN no respondió tras cinco intentos | CONTINGENCY |
dian_errors[] |
Ambos llegan por webhook (document.rejected, document.contingency) o
consultando el documento. Ver Estados de un
documento.
Idempotencia
Sección titulada «Idempotencia»code |
HTTP | Qué pasó |
|---|---|---|
IDEMPOTENCY_CONFLICT |
409 | Ese reference_code ya existe para otro tipo de documento |
Repetir un reference_code del mismo tipo no es un error: devuelve el
documento que ya existe.
code |
HTTP | Qué pasó |
|---|---|---|
PLAN_REQUIRED |
— | El emisor no tiene plan activo |
PLAN_EXHAUSTED |
— | Se agotó el cupo de documentos |
PLAN_EXPIRED |
— | El plan venció |
Interno
Sección titulada «Interno»code |
HTTP | Qué pasó |
|---|---|---|
INTERNAL |
500, 501, 429, 404… | Algo se rompió de nuestro lado, o el error no tiene un code propio |
INTERNAL es el único code que no identifica una sola situación: además
de los fallos nuestros, es el valor de relleno que reciben todos los
errores que no pasan por nuestra lista de códigos, conservando cada uno su
HTTP real.
El fallo interno de verdad, el 500, se ve así:
{ "status": 500, "code": "INTERNAL", "message": "Error interno del servidor", "errors": [] }Si un 500 se repite, escríbenos con el reference_code y la hora.
Límite de peticiones
Sección titulada «Límite de peticiones»300 por minuto y por IP por defecto. Al superarlo, 429 — con
code: "INTERNAL", no un código propio, y con el message en inglés:
{ "status": 429, "code": "INTERNAL", "message": "ThrottlerException: Too Many Requests", "errors": [{ "message": "ThrottlerException: Too Many Requests" }]}Reconócelo por status, no por code.
Las rutas de autenticación de personas tienen cupos propios, bastante más estrictos:
| Ruta | Límite |
|---|---|
POST /v1/auth/login |
10 por minuto |
POST /v1/auth/refresh |
30 por minuto |
POST /v1/auth/password/reset |
10 por minuto |
POST /v1/auth/password/forgot |
3 por minuto |
Siguen siendo por minuto y por IP. El resto del API —incluidas
POST /v1/auth/logout y /logout-all— va con el límite por defecto.
Cómo tratarlos
Sección titulada «Cómo tratarlos»Reintentables — la operación puede salir bien después: INTERNAL
cuando status es 500 o 429. Reintenta con espera creciente y
el mismo reference_code: la idempotencia impide que se duplique.
No reintentables — hay que arreglar algo primero: VALIDATION,
BAD_REQUEST, NOT_FOUND, FORBIDDEN, los cuatro de certificado y
software, los de plan, y el INTERNAL que llega con status 501 o
404. Reintentar sin cambiar nada da el mismo error.
Caso aparte: RANGE_EXHAUSTED y RANGE_EXPIRED no los arregla tu
código ni el nuestro — hay que pedirle numeración a la DIAN, salvo en las
notas, que numeran sin resolución. Vale la pena alertar antes de llegar al
final del rango.