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
1. Errores de dominio
Los que produce la lógica de documentos. Son los únicos que traen un código legible por máquina.
{
"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:
{
"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:
{
"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:
{
"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
{
"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ón | Nunca reintentes. Corrige el payload. |
| 409 duplicado | Nunca reintentes igual. Avanza el e-NCF. |
| 429 por minuto | Espera lo que diga Retry-After y reintenta. |
| 429 de cuota mensual | No reintentes. Es un problema de plan, no de ritmo. |
| 5xx o timeout | Reintenta con espera creciente, siempre con el mismo external_ref para no duplicar. |
Documento rejected | Depende 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
{
"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.