Códigos de error
Esta página cubre los errores que puedes ver al integrar la API. Separamos en tres capas:
- HTTP de la FE API — errores devueltos por nuestros endpoints.
- Estados de DGII — respuestas que DGII puede dar al recibir un e-CF.
- Errores async del worker — cuando algo falla DESPUÉS de que ya recibiste 202.
1. HTTP de la FE API
| Status | Mensaje típico | Causa | Solución |
|---|---|---|---|
400 | format must be one of: xml, digimart | Body inválido | Revisa el esquema |
400 | xml is required when format="xml" | Falta el XML | Incluye el campo xml |
400 | payload is required when format="digimart" | Falta el payload | Incluye payload con JSON Digimart |
400 | apiKeyId must start with "fak_" | Format inválido en login | Verifica que copiaste la key completa |
401 | Missing bearer token | No mandaste Authorization header | Agrega Authorization: Bearer <token> |
401 | Invalid or expired token | JWT mal-firmado o expirado | Vuelve a hacer POST /login |
401 | API key has been revoked | La key fue revocada | Genera una key nueva y rota |
401 | Invalid credentials | apiKeyId o apiSecret incorrectos en login | Revisa credenciales |
403 | Missing required scope(s): X | Tu key no tiene el scope que el endpoint requiere | Crea key con scope adecuado |
404 | {found: false} (no es error 404 real) | trackOrId no corresponde a un envío tuyo | Verifica el id |
500 | Internal Server Error | Error inesperado en nuestro lado | Reintenta con backoff. Si persiste, contáctanos. |
2. Estados de DGII
Cuando DGII responde, su estado queda persistido en
response.trackStatus.estado. Valores posibles:
| Estado | ¿Qué significa? | ¿Qué hacer? |
|---|---|---|
"Aceptado" | ✅ DGII aceptó sin observaciones | Nada — eres oficialmente emisor |
"Aceptado Condicional" | ⚠️ Aceptado con observaciones | Revisa mensajes y corrige para el próximo envío |
"Rechazado" | ❌ DGII rechazó | Revisa mensajes, corrige, y vuelve a enviar |
"En Proceso" | 🔄 Aún procesando | Vuelve a consultar en ~30s |
"No encontrado" | ⚠️ DGII no encuentra el trackId | El envío no llegó. Reintenta. |
Códigos comunes de mensajes de DGII
Los response.mensajes traen un codigo y un valor (texto).
Los más frecuentes:
| Código | Significado | Causa común |
|---|---|---|
0 | OK | (no requiere acción) |
1 | Error de schema XSD | Tu XML/JSON no cumple el esquema DGII |
2 | RNC inválido | El RNC del emisor o comprador no existe en DGII |
3 | NCF duplicado | Ya enviaste un e-CF con ese eNCF |
4 | Secuencia agotada | La secuencia NCF del prefijo está consumida |
7 | Vencimiento de secuencia | FechaVencimientoSecuencia ya pasó |
8 | Firma inválida | El certificado .p12 no firma correctamente |
13 | Total mal calculado | Suma de items ≠ Totales.MontoTotal |
Si recibes un código que no está en esta tabla, mira el valor (texto
descriptivo) o consulta el catálogo oficial de DGII.
3. Errores async del worker
Después de que recibiste 202 queued, el procesamiento ocurre en
background. Si algo falla ahí, el estado de tu envío refleja el error:
{
"dgiiLogId": "a3f...",
"status": null,
"response": {
"status": "error",
"error": "Tenant ... missing dgiiSettings.certKey (.p12 passphrase)",
"dgiiResponse": null
}
}Errores permanentes
Causan response.status = "error". NO se reintentan automáticamente.
| Error | Causa | Solución |
|---|---|---|
Tenant X missing dgiiSettings.certKey | Falta la contraseña del .p12 | Configura en panel Digimart |
Loaded .p12 did not yield a key+cert pair | Archivo .p12 corrupto o vacío | Re-sube el certificado |
format=xml but DgiiLogs.originalXml is null | Mandaste format=xml sin el campo xml | Revisa tu cliente |
DGII rejected with status 400 | DGII rechazó por contenido | Mira dgiiResponse para detalles específicos |
Errores transientes
Se reintentan automáticamente con exponential backoff (max 8 intentos sobre ~8 minutos). Si todos los retries fallan, el mensaje va al Dead Letter Queue y un operador investiga.
| Error | Causa típica |
|---|---|
5xx de DGII | DGII en mantenimiento o degradado |
ETIMEDOUT | Timeout de red al contactar DGII |
ECONNABORTED | Conexión interrumpida mid-request |
Patrón recomendado de manejo de errores
async function emitInvoice(payload) {
const resp = await fetch('/api/v1/fe/invoices', { /* ... */ });
if (!resp.ok) {
if (resp.status === 401) {
// JWT expirado o key revocada
await refreshJWT();
return emitInvoice(payload); // 1 retry
}
if (resp.status === 400) {
// Error de cliente — no reintentes, arregla el payload
const { message } = await resp.json();
throw new InvalidPayloadError(message);
}
if (resp.status >= 500) {
// Error del servidor — reintenta con backoff
throw new RetryableError(`Server error: ${resp.status}`);
}
}
const { dgiiLogId } = await resp.json();
return dgiiLogId;
}
async function pollStatus(dgiiLogId, maxAttempts = 12) {
for (let i = 0; i < maxAttempts; i++) {
const resp = await fetch(`/api/v1/fe/invoices/${dgiiLogId}`, { /* ... */ });
const data = await resp.json();
if (data.status === 'Aceptado' || data.status === 'Aceptado Condicional') {
return data; // ✅ Éxito
}
if (data.status === 'Rechazado') {
throw new RejectedByDgiiError(data.response);
}
if (data.response?.status === 'error') {
throw new PermanentError(data.response.error);
}
// queued / "En Proceso" / null — espera y reintenta
await sleep(2000 * Math.pow(1.5, i)); // backoff
}
throw new TimeoutError('No final state after polling');
}