Errores y rechazos

Errores y rechazos

Hay dos clases de fallo y conviene no confundirlas. Un error de la API llega en la respuesta HTTP: la petición no se acepto y nada se emitió. Un rechazo de la DGII llega después, en el campo error_message del documento, casi siempre con el e-NCF ya consumido.

Errores de la API

No hay un solo formato de error. La API responde con cuatro sobres distintos según de dónde venga el fallo. Un cliente que asuma siempre la misma forma se rompe en el momento menos oportuno. Discrimina por código HTTP y por las claves presentes, no por una estructura fija.

1. Errores de dominio

Los que produce la lógica de documentos. Son los únicos que traen un código legible por máquina.

422 Response
{
  "success": false,
  "error": {
    "code": "INVALID_TOTALS",
    "message": "total (5800.00) does not match subtotal + itbis (5900.00).",
    "field": "totals.total"
  }
}

field aparece solo cuando el error se puede atribuir a un campo concreto.

error.code HTTP Qué significa y qué hacer
INVALID_TOTALS 422 subtotal + itbis no da total, con dos centavos de tolerancia. Revisa tu redondeo por línea.
DUPLICATE_DOCUMENT 409 Ya existe un documento con ese e-NCF en esta empresa y ambiente, o con ese external_ref sin poder recuperar el original. Avanza el número de secuencia.
DOCUMENT_NOT_FOUND 404 El id no existe o pertenece a otra organización. Comprueba que usas el ULID que devolvió la emisión.
INVALID_DOCUMENT_STATE 422 La operación no aplica al estado actual: descartar algo que no está rechazado, reintentar algo ya aceptado, notificar algo que sigue en proceso. El mensaje incluye el estado.
COMPANY_NOT_FOUND 404 El company_rnc de una vista previa no corresponde a ninguna empresa de tu organización.

Dos mensajes de esta familia merecen leerse completos porque te dicen exactamente qué hacer:

422 Response
{
  "success": false,
  "error": {
    "code": "INVALID_DOCUMENT_STATE",
    "message": "The DGII already consumed this e-NCF, so resending it will only be rejected as a duplicate. Discard this document and submit the corrected data instead."
  }
}

Y al descartar un rechazado, la respuesta te recuerda que el reenvío necesita un external_ref nuevo:

200 Response
{
  "success": true,
  "data": { "ulid": "01JZ8K4QM2N5P7R9T1V3W5X7Y9", "encf": "E310000000001", "discarded": true },
  "message": "Rejected document discarded. e-NCF E310000000001 can be submitted again with a distinct external_ref."
}

2. Errores de validación

Formato estandar de Laravel, con todos los campos que fallaron:

422 Response
{
  "message": "The type field is required. (and 2 more errors)",
  "errors": {
    "type": ["The type field is required."],
    "items": ["The items field is required."],
    "totals.total": ["The totals.total field is required."]
  }
}

3. Errores de autenticación y de plan

HTTP Cuerpo Causa
401 {"success": false, "message": "Invalid API key"} Falta la key, es incorrecta o fue revocada.
401 {"success": false, "message": "Organization is suspended or cancelled."} La cuenta está suspendida. Contacta a soporte.
403 {"success": false, "message": "IP not whitelisted", "ip": "..."} Tu IP de salida no está en la lista blanca de la key.
403 {"error": "...", "feature": "webhooks", "plan": "receptor"} Tu plan no incluye esa funcionalidad. Ver Planes y límites.
403 {"error": "Company is paused"} o "Company is cancelled" La empresa no está activa.
403 {"error": "Sandbox company cannot access production environment."} Usaste una key ffs_cert_ o ffs_live_ contra una empresa de pruebas.
429 {"error": "Monthly document limit reached", "limit": 1000} Agotaste la cuota mensual de documentos. Este 429 no trae Retry-After: no lo reintentes en bucle.
429 {"message": "Too Many Attempts."} Superaste el límite por minuto. Este sí trae Retry-After; respetalo.

4. Errores del servidor

500 Response
{
  "message": "Server Error"
}

Reintenta con espera creciente. Si un POST /api/v1/documents devuelve 500 o se corta por timeout, no lo repitas a ciegas: consulta primero por tu external_ref si el documento ya existe. Si lo mandaste con external_ref, repetirlo es seguro y te devolverá el original.

Rechazos de la DGII

Estos no vienen en la respuesta HTTP: aparecen en error_message cuando el documento pasa a rejected, y llegan por webhook o al consultarlo. El texto es casi siempre el de la DGII, palabra por palabra.

Mensaje Qué pasa de verdad
Este número de secuencia ya ha sido utilizado Ese e-NCF ya se emitió bajo ese RNC. Casi siempre porque otro sistema factura sobre el mismo rango, o porque un reenvío sin external_ref repitió el número. Avanza a un bloque libre; el número no se recupera.
Fecha de vencimiento de secuencia inválida (código 145) El expiration_date que enviaste no es el vencimiento que la DGII tiene autorizado para tu rango. Cópialo de la Oficina Virtual. Nada en el mensaje menciona el campo.
El formato del XML no es valido Un dato no cumple el esquema. La causa número uno es una fecha en yyyy-mm-dd donde el esquema exige dd-mm-yyyy, típicamente expiration_date o la fecha de una nota de crédito o débito. La segunda es unit_measure con texto en vez del código numérico de la DGII.
Esta factura no es valida por este canal. Enviar por resumen B2C (código 11105) Una E32 se envío por el canal normal cuando debía ir por RFCE, o al revés. Lo decide el monto: por debajo de RD$250,000 va por resumen. FiscalFlow lo enruta solo, así que este mensaje suele indicar que el total cambio entre el cálculo y el envío.

Rechazos que genera FiscalFlow antes de llegar a la DGII

Cuando FiscalFlow detecta que el documento no puede ser aceptado, lo rechaza el mismo, sin gastar el envío. En ese caso error_code viene relleno:

error_code Qué significa
ENCF_REQUIRED Falta encf y el ambiente no es de pruebas. No se consumió nada: agrega el número y envía de nuevo.
XSD_VALIDATION_FAILED El documento no puede formar un XML valido: falta expiration_date en un tipo que lo exige, hay una fecha ilegible, o el XML no pasa el esquema. No se reintenta, porque el resultado sería idéntico. Corrige y envía un documento nuevo.
ENVIRONMENT_MISMATCH El ambiente del prefijo de la key no coincide con el ambiente DGII configurado en la empresa. No se reintenta y no se consumió nada: nada se firmó ni salió hacia la DGII. El mensaje nombra los dos valores. Usa una key del ambiente de la empresa, o cambia el ambiente de la empresa en el panel. Nuevo en 2.0.0.

Estos mensajes son largos a propósito y dicen qué configurar. El de expiration_date, por ejemplo, nombra el campo, el formato y la pantalla de Odoo donde se configura, precisamente porque el rechazo 145 que produciría la DGII no diría nada de eso.

Cuando se agotan los intentos

{
  "status": "rejected",
  "attempts": 5,
  "error_message": "Max attempts reached. Last error: DGII returned HTTP 503: ...",
  "error_code": null
}

Esto no es un rechazo de la DGII: es que no se pudo entregar tras cinco intentos repartidos en unas siete horas. Comprueba el estado de la DGII con GET /api/v1/dgii/status y, cuando esté disponible, descarta el documento y envíalo de nuevo.

Estrategia de reintentos

Situación Qué hacer
422 de validaciónNunca reintentes. Corrige el payload.
409 duplicadoNunca reintentes igual. Avanza el e-NCF.
429 por minutoEspera lo que diga Retry-After y reintenta.
429 de cuota mensualNo reintentes. Es un problema de plan, no de ritmo.
5xx o timeoutReintenta con espera creciente, siempre con el mismo external_ref para no duplicar.
Documento rejectedDepende de sequence_consumed. Ver Secuencias y fechas.

Estado de la DGII

Antes de dar por perdida una integración, comprueba si el problema es de la DGII. Este endpoint es público y no necesita key:

curl https://api.fiscalflowsupport.com/api/v1/dgii/status
200 Response
{
  "success": true,
  "data": {
    "environments": {
      "testecf": { "status": "online", "latency_ms": 143, "checked_at": "2026-08-14T15:00:00+00:00" },
      "certecf": { "status": "online", "latency_ms": 150, "checked_at": "2026-08-14T15:00:00+00:00" },
      "ecf": { "status": "offline", "latency_ms": null, "checked_at": "2026-08-14T15:00:00+00:00" }
    },
    "overall": "degraded"
  }
}

Las caídas de la DGII son habituales, sobre todo en ventanas de mantenimiento. FiscalFlow reintenta solo, así que un documento en processing durante una caída no está perdido.