Códigos de error

Códigos de error

Esta página cubre los errores que puedes ver al integrar la API. Separamos en tres capas:

  1. HTTP de la FE API — errores devueltos por nuestros endpoints.
  2. Estados de DGII — respuestas que DGII puede dar al recibir un e-CF.
  3. Errores async del worker — cuando algo falla DESPUÉS de que ya recibiste 202.

1. HTTP de la FE API

StatusMensaje típicoCausaSolución
400format must be one of: xml, digimartBody inválidoRevisa el esquema
400xml is required when format="xml"Falta el XMLIncluye el campo xml
400payload is required when format="digimart"Falta el payloadIncluye payload con JSON Digimart
400apiKeyId must start with "fak_"Format inválido en loginVerifica que copiaste la key completa
401Missing bearer tokenNo mandaste Authorization headerAgrega Authorization: Bearer <token>
401Invalid or expired tokenJWT mal-firmado o expiradoVuelve a hacer POST /login
401API key has been revokedLa key fue revocadaGenera una key nueva y rota
401Invalid credentialsapiKeyId o apiSecret incorrectos en loginRevisa credenciales
403Missing required scope(s): XTu key no tiene el scope que el endpoint requiereCrea key con scope adecuado
404{found: false} (no es error 404 real)trackOrId no corresponde a un envío tuyoVerifica el id
500Internal Server ErrorError inesperado en nuestro ladoReintenta 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 observacionesNada — eres oficialmente emisor
"Aceptado Condicional"⚠️ Aceptado con observacionesRevisa mensajes y corrige para el próximo envío
"Rechazado"❌ DGII rechazóRevisa mensajes, corrige, y vuelve a enviar
"En Proceso"🔄 Aún procesandoVuelve a consultar en ~30s
"No encontrado"⚠️ DGII no encuentra el trackIdEl 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ódigoSignificadoCausa común
0OK(no requiere acción)
1Error de schema XSDTu XML/JSON no cumple el esquema DGII
2RNC inválidoEl RNC del emisor o comprador no existe en DGII
3NCF duplicadoYa enviaste un e-CF con ese eNCF
4Secuencia agotadaLa secuencia NCF del prefijo está consumida
7Vencimiento de secuenciaFechaVencimientoSecuencia ya pasó
8Firma inválidaEl certificado .p12 no firma correctamente
13Total mal calculadoSuma 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.

ErrorCausaSolución
Tenant X missing dgiiSettings.certKeyFalta la contraseña del .p12Configura en panel Digimart
Loaded .p12 did not yield a key+cert pairArchivo .p12 corrupto o vacíoRe-sube el certificado
format=xml but DgiiLogs.originalXml is nullMandaste format=xml sin el campo xmlRevisa tu cliente
DGII rejected with status 400DGII rechazó por contenidoMira 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.

ErrorCausa típica
5xx de DGIIDGII en mantenimiento o degradado
ETIMEDOUTTimeout de red al contactar DGII
ECONNABORTEDConexió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');
}