API de facturación electrónica — República Dominicana (e-CF / DGII)¶
Guía de integración para enviar comprobantes fiscales electrónicos a la DGII a través de esta API. Usted envía JSON; nosotros construimos el XML del e-CF, lo firmamos digitalmente, lo transmitimos a la DGII, resolvemos su estado y generamos la representación impresa.
No necesita conocer el formato XML de la DGII ni tener certificado digital propio: ambos los resolvemos nosotros. Y si su sistema ya genera el XML e-CF, envíelo tal cual: lo firmamos y lo transmitimos igual. Vea passthrough-xml.md.
Documentos disponibles¶
| Tipo | Documento | Cuándo se usa | Documentación |
|---|---|---|---|
| 31 | Factura de Crédito Fiscal Electrónica | Ventas a contribuyentes (B2B). Sustenta crédito fiscal del comprador. | ecf-31.md |
| 32 | Factura de Consumo Electrónica | Ventas a consumidor final (B2C). | ecf-32.md |
| 33 | Nota de Débito Electrónica | Aumenta el valor de un comprobante ya emitido. | ecf-33.md |
| 34 | Nota de Crédito Electrónica | Anula, corrige o disminuye un comprobante ya emitido. | ecf-34.md |
| 41 | Comprobante de Compras Electrónico | Compras a proveedores que no emiten e-CF, con retención. | ecf-41.md |
| 43 | Gastos Menores Electrónico | Gastos de caja chica sin comprobante del vendedor. | ecf-43.md |
| 44 | Regímenes Especiales Electrónico | Ventas exentas a zonas francas, diplomáticos y similares. | ecf-44.md |
| 45 | Gubernamental Electrónico | Ventas al Estado. | ecf-45.md |
| 46 | Comprobante de Exportaciones Electrónico | Ventas al exterior, gravadas a tasa 0%. | ecf-46.md |
| 47 | Pagos al Exterior Electrónico | Pagos a beneficiarios extranjeros, con retención de ISR. | ecf-47.md |
Los diez tipos se validan con el mismo rigor: cuadratura de totales, totales condicionales según el indicador de facturación, coherencia del ISC y reglas de todos los bloques (subtotales informativos, descuentos y recargos globales, otra moneda, paginación). Si algo no cuadra, la respuesta es un 422 con el detalle por campo: no se crea ningún documento ni se envía nada a la DGII, y su número sigue libre para el comprobante corregido.
Documentos de apoyo:
- campos-comunes.md — nodos y campos compartidos por todos los tipos.
- catalogos.md — valores permitidos (tipos de ingreso, formas de pago, unidades…).
- estados.md — códigos de estado internos y el ciclo de vida del e-CF.
- Cómo responde la API — la forma única de toda respuesta, éxito o error: status, message y una sola carga (document o data), con un ejemplo real de cada caso.
- tokens-y-permisos.md — el token, el RNC del emisor (la empresa
en cuyo nombre actúa), las empresas que tiene habilitadas y los permisos. Léalo si recibe un
401, un 403 o un 422 al empezar.
- consultas-dgii.md — consultas en vivo: estado de un e-CF, validez fiscal de
lo que recibe, y los envíos de un e-CF. Todos los recursos de consulta, juntos, en
«Consultar qué pasó con un documento».
- mis-documentos.md — el detalle de un e-CF con su XML firmado y su PDF en
base64, y el listado paginado (con filtros) de sus e-CF emitidos. Pensado para un portal de consulta o
para una pantalla «Mis documentos» dentro de su propio sistema.
- passthrough-xml.md — enviar su propio XML en vez del JSON canónico.
- entrega-al-receptor.md — cómo llega el e-CF al receptor y qué responde.
- recepcion.md — los e-CF que recibe de sus proveedores: el listado y la aprobación
comercial.
- directorio.md — a quién puede enviarle e-CF y por qué URL.
- numeracion.md — la numeración la lleva su sistema: cómo enviar el e-NCF, qué
validamos del número y qué respondemos si reenvía uno que ya existe.
- anulacion-secuencias.md — anular rangos de e-NCF no utilizados.
- La colección de Postman — lista para importar: todos los endpoints con sus cuerpos de ejemplo. Al emitir guarda el e-NCF y el identificador del documento en variables, así que las consultas funcionan sin copiar y pegar.
- Los JSON de esta documentación, listos para copiar:
| Tipo | Mínimo | Completo / variantes |
|---|---|---|
| 31 Factura de Crédito Fiscal | [mínimo](ejemplos/ecf-31-minimo.json) | [completo](ejemplos/ecf-31-completo.json) |
| 32 Factura de Consumo | [mínimo](ejemplos/ecf-32-minimo.json) | [dos productos](ejemplos/ecf-32-dos-productos.json) · [varios productos con descuento](ejemplos/ecf-32-varios-productos-con-descuento.json) · [con bono](ejemplos/ecf-32-bono.json) |
| 33 Nota de Débito | [mínimo](ejemplos/ecf-33-minimo.json) | |
| 34 Nota de Crédito | [mínimo](ejemplos/ecf-34-minimo.json) | |
| 41 Compras | [mínimo](ejemplos/ecf-41-minimo.json) | |
| 43 Gastos Menores | [mínimo](ejemplos/ecf-43-minimo.json) | |
| 44 Regímenes Especiales | [mínimo](ejemplos/ecf-44-minimo.json) | |
| 45 Gubernamental | [mínimo](ejemplos/ecf-45-minimo.json) | |
| 46 Exportaciones | [mínimo](ejemplos/ecf-46-minimo.json) | |
| 47 Pagos al Exterior | [mínimo](ejemplos/ecf-47-minimo.json) | |
Todos los ejemplos de esta documentación se verifican automáticamente contra la API en cada ejecución de la suite de pruebas: si un ejemplo dejara de ser válido, la compilación falla. Puede copiarlos con la confianza de que funcionan.
Cada ejemplo trae un e-NCF de muestra (
E310000000001…) y la fecha de vencimiento de su secuencia: cámbielos por su siguiente número antes de enviarlo. Si repite un e-NCF que ya envió, no se crea otro documento: se le responde con el que ya existe ("duplicate": true, ver numeracion.md).
Autenticación¶
Cada llamada lleva dos datos: quién llama (el token) y en nombre de qué empresa actúa (el RNC del emisor, que la propia petición declara).
Authorization: Bearer <su-token>— el token es de su integración (su ERP, su plataforma), no de una empresa: una misma integración puede atender a varias. Lleva declarado qué puede hacer: el que factura no tiene por qué poder cambiar el certificado de firma ni anular secuencias. Los permisos y los perfiles están en tokens-y-permisos.md.- El RNC del emisor — el RNC (9 dígitos) o la cédula (11) de la empresa a la que se refiere la petición. No hay ninguna cabecera para esto: se declara donde ya viaja el emisor, y la API comprueba que su token esté vinculado a esa empresa.
| La petición es… | El RNC va en… |
|---|---|
| un cuerpo JSON (emitir, anular secuencias, aprobación comercial, certificado) | emisor.rnc |
| un cuerpo XML (passthrough) | <RNCEmisor> del e-CF |
una consulta sin cuerpo (GET) |
el parámetro emisor_rnc |
la consulta de un documento suyo (GET /documents/{identificador}…) |
el prefijo del identificador de la ruta: no hace falta emisor_rnc (cómo se arma) |
Es obligatorio en todas las llamadas que actúan en nombre de una empresa (todas las de la tabla de
endpoints, salvo GET /api/v1/companies y GET /api/v1/statuses).
POST /api/v1/documents/31 HTTP/1.1
Host: endpoint-test.emision.do
Authorization: Bearer <su-token>
Content-Type: application/json
Accept: application/json
{ "emisor": { "rnc": "133802074" }, "id_doc": { "e_ncf": "E310000000001", … }, … }
GET /api/v1/documents/133802074E310000000001 HTTP/1.1
Host: endpoint-test.emision.do
Authorization: Bearer <su-token>
Accept: application/json
(Aquí no hay emisor_rnc: la empresa es 133802074, el prefijo del identificador. Una consulta que no es
de un documento concreto —el listado, los recibidos…— sí lo lleva: GET /api/v1/documents?emisor_rnc=133802074.)
Qué responde si algo falla (todo ello antes de crear nada):
| Código | Cuándo | Qué hacer |
|---|---|---|
401 |
El token falta, está mal copiado o fue revocado. | Revise la cabecera Authorization. |
422 |
No se declara el RNC del emisor, no tiene forma de RNC, la petición declara dos distintos o el XML no se puede leer (ni admite DOCTYPE): «Falta indicar la empresa.» / «El RNC de la empresa no es válido.» / «La petición declara más de una empresa.» / «No se pudo leer el RNC de la empresa.» | Declárelo una sola vez (emisor.rnc, RNCEmisor o emisor_rnc). |
403 |
«Este token no puede actuar sobre esa empresa.»: esa empresa no está habilitada para ese token. | Revise el RNC; si es correcto, pida a quien le entregó la credencial que habilite esa empresa. |
Los mensajes completos están en tokens-y-permisos.md. Los tres llevan el mismo envelope que el resto de la API y explican el porqué en data.detalle (cómo leerlos).
En una petición con cuerpo, emisor.rnc ya era obligatorio (dice de quién es el comprobante); ahora además
es el dato que identifica la empresa, así que no se manda dos veces. Un sistema que atiende a varias
empresas no puede emitir por descuido bajo una con los datos de otra: el comprobante sale siempre bajo el
RNC que declara, y sólo si su token está vinculado a él.
No hay endpoint de login. El token se lo entrega quien da de alta su integración. Para comprobar que el suyo funciona y ver qué empresas tiene habilitadas,
GET /api/v1/companies: devuelve el RNC, la razón social y el ambiente de cada una, o401si el token no vale.
Ciclo de vida de un e-CF¶
Su sistema Esta API DGII
│ │ │
├── POST /documents/31 ───────────────────►│ (con SU e-NCF en id_doc) │
│ ├─ valida el JSON y el e-NCF │
│◄─ 202 received + identificador ──────────┤ │
│ ├─ construye + firma el XML │
│ ├───── transmite ───────────────►│
│ │◄───── TrackID ─────────────────┤
│ │ in_process_dgii │
│ ├── consulta el resultado ──────►│
│ │◄── aceptado / rechazado ───────┤
│ ├─ genera la representación impresa (PDF)
├── GET /documents/{identificador} ───────►│ │
│◄─ accepted_dgii + archivos ──────────────┤ │
El envío es asíncrono: la respuesta 202 confirma que recibimos y validamos el comprobante y su
número, y que quedó en cola; no que la DGII lo haya aceptado. Esa misma respuesta trae el
identificador del documento: con él lo consulta (GET /api/v1/documents/{identificador}) hasta que
tenga su estado final y, ya aceptado, recoge ahí mismo su XML firmado y su PDF
(más abajo).
Una variante: la factura de consumo bajo RD$250.000 (tipo 32). Se transmite como resumen (RFCE) y
la DGII contesta el resultado en la propia llamada, sin TrackID: el documento pasa de received
directamente a accepted_dgii (o rejected_dgii), sin pasar por in_process_dgii en el caso normal, y su
track_id queda null para siempre. Es lo esperado, no un fallo: se sigue por su identificador. Todos los demás
tipos, y el 32 de RD$250.000 o más, reciben su TrackID al transmitirse —pasan a in_process_dgii con él— y
lo conservan en los estados finales. Detalle en
ecf-32.md.
La numeración la lleva su sistema¶
Esta API no numera: cada ERP o Facturador lleva su propia numeración por tipo de documento y envía el
e-NCF completo en id_doc.e_ncf (E + tipo + 10 dígitos, p. ej. E310000000001), junto con
id_doc.fecha_vencimiento_secuencia en los tipos que la llevan (todos menos el 32 y el 34). Antes de
enviar a la DGII comprobamos lo básico —estructura y tipo, que no esté anulado ni vencido y, si usted
registró su autorización, que caiga dentro de ella— y, si algo falla, responde un 422 sin crear nada.
Un folio tiene un solo documento. Si reenvía uno que ya existe no se crea otro ni se vuelve a
transmitir: se le responde según el estado del que ya está (200 si ya se procesó o está en espera de
la DGII, 409 si fue rechazado, 202 si se reintenta por un fallo nuestro), con "duplicate": true.
Vale lo mismo si el folio lo envió antes otro sistema de la misma empresa: el documento es de la
empresa. Ese reenvío devuelve el detalle del documento sin exigir documents:read (la idempotencia lo
necesita; ver tokens-y-permisos.md). Todo el detalle, en numeracion.md.
El identificador del documento¶
El e-NCF sólo es único dentro de un emisor: cada empresa lleva su propia numeración, así que dos empresas
pueden tener, las dos, su E310000000001. Para consultar un comprobante hace falta un dato que lo identifique
sin ambigüedad: su identificador, que es el RNC (o la cédula) del emisor seguido del e-NCF, pegados y sin
separadores.
| Emisor | e-NCF | Identificador |
|---|---|---|
RNC 133802074 (9 dígitos) |
E320000000303 |
133802074E320000000303 |
Cédula 00112345678 (11 dígitos) |
E310000000042 |
00112345678E310000000042 |
- Forma exacta: 9 u 11 dígitos, la
Een mayúscula y 12 dígitos más (tipo y consecutivo del e-NCF):^(?:[0-9]{9}|[0-9]{11})E[0-9]{12}$. Sin guiones ni espacios:1-33-80207-4E320000000303no es un identificador y la ruta responde404. - Es el mismo esquema con que la DGII nombra los adjuntos de un e-CF (RNC + e-NCF). Por eso es también el
nombre de los archivos que devuelve el detalle (
133802074E320000000303.xml,133802074E320000000303.pdf). - Usted lo conoce antes de enviar: es el RNC de su empresa y el e-NCF que usted mismo numeró. No hay que
esperar ninguna respuesta ni guardar nada para poder consultarlo. Aun así, todas las respuestas que hablan de
un documento lo traen en el campo
identificador: el202de emitir (JSON y XML), el detalle, el reenvío de un folio que ya existe, cada elemento del listado y las respuestas dedgii-statusytrack-ids. - La empresa es el prefijo. Por eso
GET /api/v1/documents/{identificador}y sus dos consultas a la DGII no pidenemisor_rnc. Si lo manda igualmente, tiene que ser el mismo RNC; si es otro —aunque sea otra empresa de su integración— responde422«La petición declara más de una empresa.»: nunca se elige una en silencio. Su token tiene que seguir habilitado para esa empresa (tokens-y-permisos.md).
¿Venía consultando por el e-NCF a secas, con
emisor_rnc? Esa forma ya no existe y responde404: se consulta por identificador, que es el mismo e-NCF con el RNC del emisor delante. Y ya no hay recursos aparte para bajar el XML ni el PDF mediante una URL temporal: vienen en el propio detalle del documento.
Endpoints¶
| Método | Ruta | Descripción |
|---|---|---|
GET |
/api/v1/companies |
Las empresas que su token tiene habilitadas (permiso companies:read). No declara empresa. |
POST |
/api/v1/documents/{tipo} |
Emite un e-CF. {tipo} = 31, 32, 33, 34, 41, 43, 44, 45, 46, 47. |
GET |
/api/v1/documents |
Listado paginado de sus e-CF emitidos, con filtros. |
GET |
/api/v1/documents/{identificador} |
Detalle de un e-CF emitido: su estado y, si ya tiene validez fiscal, su XML firmado y su PDF en base64. |
GET |
/api/v1/received-documents |
e-CF que otros emisores le enviaron. En cada elemento, emisor_rnc es el RNC del proveedor que lo envió; su empresa es la del parámetro emisor_rnc de la consulta. |
POST |
/api/v1/received-documents/{eNCF}/commercial-approval |
Acepta o rechaza comercialmente un e-CF recibido. |
GET |
/api/v1/documents/{identificador}/dgii-status |
Estado del e-CF consultado en vivo a la DGII. |
GET |
/api/v1/documents/{identificador}/track-ids |
Envíos (TrackID) de ese e-CF ante la DGII. |
GET |
/api/v1/received-documents/{eNCF}/dgii-status |
Validez fiscal de un e-CF que usted recibió. |
GET |
/api/v1/directory · /api/v1/directory/{rnc} |
Directorio de emisores y sus URL de recepción. |
GET |
/api/v1/voided-sequences · POST |
Consulta y anulación de rangos de e-NCF. |
GET |
/api/v1/certificate |
Estado de su certificado digital (sólo firma propia). |
POST |
/api/v1/certificate |
Sube o reemplaza su certificado .p12. |
GET |
/api/v1/statuses |
Catálogo de estados internos. No declara empresa. |
Todas las rutas de esta tabla, salvo /api/v1/companies y /api/v1/statuses, actúan en nombre de
una empresa y exigen que se declare su RNC (emisor.rnc, RNCEmisor o emisor_rnc: Autenticación); las
que piden un documento suyo lo declaran con el prefijo de su identificador.
Los permisos que pide cada una están en tokens-y-permisos.md.
Consultar qué pasó con un documento¶
Todos los recursos de consulta son GET, actúan en nombre de una empresa y sólo devuelven datos de esa
empresa. Los de un documento suyo se piden por su identificador y la
empresa es su prefijo; los demás llevan el RNC en el parámetro emisor_rnc (salvo los dos últimos de la
tabla, que no declaran empresa). Un identificador que no existe, o que es de otra empresa, responde siempre
404.
Hay una sola forma de consultar un documento: por su identificador. El detalle trae los archivos del e-CF cuando ya es válido; no hay recursos aparte para descargar el XML ni el PDF, ni URL firmadas, ni un segundo paso.
| Recurso | Para qué sirve | Permiso | Cuándo usarlo |
|---|---|---|---|
GET /api/v1/documents/{identificador} |
Estado y detalle de un e-CF emitido, según nuestro registro: estado, TrackID (si lo hay), errores de la DGII, entrega_al_receptor y correo_al_comprador; y, cuando el e-CF ya tiene validez fiscal, sus archivos en base64 (PDF, XML firmado y respuesta de la DGII). |
documents:read |
El de siempre, tras emitir: sondéelo hasta un estado final y, ya aceptado, recoja ahí sus archivos. No llama a la DGII. mis-documentos.md |
GET /api/v1/documents |
Listado paginado de sus e-CF emitidos, con filtros from, to, status, type, buyer. |
documents:read |
Pantallas «Mis documentos», conciliaciones, buscar lo que quedó sin resolver (status=unresolved). mis-documentos.md |
GET /api/v1/documents/{identificador}/dgii-status |
Le pregunta a la DGII en vivo por ese e-CF y la compara con nuestro registro (discrepancias). Sirve también para el resumen de la factura de consumo. |
documents:read |
Un documento en unresolved, o la duda de si lo que tenemos es lo que la DGII tiene. consultas-dgii.md |
GET /api/v1/documents/{identificador}/track-ids |
Los TrackID que la DGII registra para ese e-CF (más de uno = se transmitió más de una vez). | documents:read |
Auditar envíos duplicados. No aplica al resumen de la factura de consumo, que no tiene TrackID. consultas-dgii.md |
GET /api/v1/received-documents |
Los e-CF que sus proveedores le enviaron (los últimos 100) y si se les acusó recibo. | received:read |
Ver qué ha recibido. recepcion.md |
GET /api/v1/received-documents/{eNCF}/dgii-status |
Si un e-CF recibido tiene validez fiscal ante la DGII (la firma no lo demuestra). | received:read |
Antes de dar por bueno un crédito fiscal. consultas-dgii.md |
GET /api/v1/voided-sequences |
Las anulaciones de rangos de e-NCF (ANECF) y si la DGII las aceptó. | sequences:read |
Comprobar una anulación. anulacion-secuencias.md |
GET /api/v1/statuses |
El catálogo de estados internos. No declara empresa ni exige permiso. | — | Programar contra los status. estados.md |
GET /api/v1/companies |
Las empresas que su token tiene habilitadas. No declara empresa. | companies:read |
Comprobar que el token funciona y qué RNC puede usar. |
Ejemplo, tras emitir el E320000000043 de la empresa 133802074 (su identificador es 133802074E320000000043):
curl -s "https://endpoint-test.emision.do/api/v1/documents/133802074E320000000043" \
-H "Authorization: Bearer <su-token>" \
-H "Accept: application/json"
Cómo sondear. Consulte GET /api/v1/documents/{identificador} cada pocos segundos (por ejemplo, cada 3 a
5 s al principio y espaciando después) hasta que status sea final (estados.md): aceptado
(accepted_dgii, conditionally_accepted_dgii), rejected_dgii, rejected_validation o unresolved. La
DGII suele resolver en segundos. No espere un TrackID: llega en cuanto se transmite en la mayoría de los
tipos (el documento entra en in_process_dgii con él y los estados finales lo conservan), y no existe
nunca en la factura de consumo (32) por debajo de RD$250.000. Mientras el documento no
es válido la respuesta es pequeña (archivos va vacío); al quedar aceptado trae los archivos. La
representación impresa se genera unos segundos después de la aceptación: si archivos aún no trae el
pdf, consulte de nuevo. Cuando ya tiene el XML y el PDF, deje de consultar y guárdelos: cada respuesta
con archivos pesa lo que ellos (el base64 añade un tercio).
Recepción: usted también recibe comprobantes¶
Como emisor electrónico, la DGII le exige poder recibir e-CF de sus proveedores. Eso lo
resolvemos nosotros: exponemos los servicios que el estándar define (/fe/recepcion/api/ecf y
/fe/aprobacioncomercial/api/ecf), respondemos el Acuse de Recibo firmado y registramos lo
recibido. Usted sólo consulta GET /api/v1/received-documents y, si quiere, aprueba o rechaza
comercialmente la transacción:
POST /api/v1/received-documents/E310000000055/commercial-approval
// Authorization: Bearer <su-token>
{
"emisor": { "rnc": "133802074" }, // la empresa que aprueba o rechaza
"estado": 2, // 1 acepta, 2 rechaza
"motivo_rechazo": "La mercancía no corresponde al pedido" // obligatorio si rechaza (máx. 250)
}
El acuse de recibo (automático) sólo dice que el comprobante llegó y es válido en forma. La aprobación comercial es su conformidad con la operación, y es opcional.
El listado, sus campos, los motivos de no recepción y la aprobación comercial, completos: recepcion.md.
Certificado digital¶
Si la empresa firma con su propio certificado, súbalo una vez (por cada empresa):
curl -X POST https://endpoint-test.emision.do/api/v1/certificate \
-H "Authorization: Bearer <su-token>" \
-F "emisor[rnc]=<rnc de la empresa>" \
-F "[email protected]" \
-F "password=<clave del .p12>"
Antes de aceptarlo verificamos que abra con esa contraseña y que esté vigente. El titular del
certificado normalmente no es su empresa, sino la persona física que ustedes registren como "Firmante
Autorizado" en su Oficina Virtual de la DGII (Delegación de Roles); esa autorización la valida la
propia DGII al recibir cada e-CF, no nosotros. El archivo queda cifrado y nunca se devuelve; con
GET /api/v1/certificate consulta su vigencia y cuántos días faltan para que venza.
Si firma con nuestro certificado de proveedor no tiene que subir nada: basta la Delegación de Roles ("Firmante Autorizado") a nuestro favor en su Oficina Virtual de la DGII.
Cómo responde la API¶
Toda respuesta JSON de /api/* tiene la misma forma: la que ya conoce del 202 de emitir. Un éxito y un
error se leen igual, y da igual quién los produzca: nuestro código, la autenticación, el control de
permisos o el servidor. No hay un formato distinto para «cuando falla».
La regla
| Clave | Cuándo está | Qué es |
|---|---|---|
status |
Siempre | "success" si el código HTTP es menor de 400; "error" si es 400 o más. |
message |
Siempre | Una frase en español, nunca vacía: qué pasó, en una línea para mostrar a una persona. |
document |
Cuando la respuesta trata de un e-CF suyo: el 202 de emitir y su rechazo de validación, el detalle, el reenvío de un folio. |
El e-CF: su estado (status_code, status, status_description) y lo que corresponda. |
data |
En todo lo demás: lecturas, operaciones y errores. | La carga de la respuesta o, en un error, su explicación. |
meta |
Sólo en el listado, junto a data. |
La paginación. |
duplicate |
Sólo en el reenvío de un folio que ya existe. | true, junto a document. |
Como máximo una carga —document o data, nunca las dos— y nada más va suelto en la raíz: no hay un
errors aparte, ni un 503 con un formato distinto del de un 404.
Cómo leer cualquier respuesta. Lea siempre
statusymessage. Si es un error, la explicación está endata.detalle; y si la respuesta traedocument(la validación de una emisión, un reenvío), endocument.errors. Para decidir qué hacer, use el código HTTP y losstatusde estados.md, no el texto demessage: las pocas frases que sí puede reconocer se dicen expresamente en esta guía (por ejemplo, «Documento enviado y aprobado anteriormente.»).No confunda el
statusde la raíz (success/error: ¿salió bien la petición?) condocument.status(el estado del e-CF:received,accepted_dgii…).
Qué trae cada respuesta, de un vistazo
| Respuesta | Código | message |
Dónde está el detalle |
|---|---|---|---|
| Lectura correcta | 200 |
«Consulta exitosa.» | data (o document si es un e-CF suyo); meta en el listado |
| Emitir un e-CF | 202 |
«e-CF recibido y encolado para envío a la DGII.» | document |
| Reenvío de un folio que ya existe | 200 / 202 / 409 |
Según el estado del documento | document + "duplicate": true |
| Una operación (anular, subir el certificado, aprobar) | 201 / 202 |
Propio de cada operación | data |
| Token ausente o inválido | 401 |
«No se pudo autenticar la petición.» | data.detalle |
| Empresa no habilitada para el token | 403 |
«Este token no puede actuar sobre esa empresa.» | data.detalle |
| Falta un permiso | 403 |
«El token no tiene permiso para esta operación.» | data.detalle, data.permisos_requeridos, data.permisos_del_token |
| El documento no existe | 404 |
«No se encontró el documento.» | data.detalle |
| La ruta no existe | 404 |
«La ruta solicitada no existe.» | data.detalle |
| Método no admitido | 405 |
«El método no está permitido para esta ruta.» | data.detalle, data.metodos_permitidos (y la cabecera Allow) |
| No se declara la empresa, o el cuerpo no se lee | 422 |
«Falta indicar la empresa.», «El cuerpo de la petición no es un JSON válido.»… | data.detalle |
| Validación de emitir un e-CF | 422 |
«Los datos enviados no son válidos.» o «El e-CF no se pudo recibir.» | document.errors y document.detailed_errors |
| Validación de otra cosa (un filtro, una anulación, el certificado) | 422 |
«Los datos enviados no son válidos.» | data.detalle y data.errores (por campo) |
| Demasiadas peticiones | 429 |
«Demasiadas peticiones.» | data.detalle, data.reintentar_en_segundos (y Retry-After) |
| Error nuestro | 500 |
«Ocurrió un error interno.» | data.detalle |
| La DGII no responde | 503 |
«El servicio de consulta de la DGII no está disponible en este momento.» | data.detalle |
Cada caso, con su respuesta real, a continuación.
Lectura — 200 OK¶
Toda consulta que no es de un e-CF concreto devuelve su carga en data y «Consulta exitosa.» como message. El
listado añade meta con la paginación (mis-documentos.md).
{
"status": "success",
"message": "Consulta exitosa.",
"data": [
{
"rnc": "133802074",
"razon_social": "EMISOR DE PRUEBA",
"nombre_comercial": null,
"ambiente": "testecf",
"habilitada": true
}
]
}
Una consulta que no encuentra nada no es un error si la pregunta tiene respuesta: un listado vacío es
"data": []; el directorio dice "receptor_electronico": false. Lo que no existe de verdad (un documento) es un
404.
Éxito — 202 Accepted¶
{
"status": "success",
"message": "e-CF recibido y encolado para envío a la DGII.",
"document": {
"document_id": 1234,
"ecf_type_code": "31",
"e_ncf": "E310000000001",
"identificador": "133802074E310000000001",
"track_id": null,
"status_code": 1,
"status": "received",
"status_description": "El e-CF fue recibido, validado y encolado para su envío a la DGII."
}
}
identificador es el RNC del emisor seguido del e-NCF (cómo se arma): es el
dato con el que consulta este documento a partir de ahora.
track_idllega siemprenullen el202, en todos los tipos: la transmisión a la DGII ocurre en segundo plano, después de responderle, y en ese instante no puede existir todavía un TrackID. No lo espere para consultar: el documento se identifica por su identificador (GET /api/v1/documents/{identificador}). Cuando se transmite, los tipos que pasan por el servicio de recepción de e-CF reciben su TrackID: el documento entra enin_process_dgiicon él y los estados finales lo conservan (si se transmite más de una vez hay varios, y todos se ven enGET /api/v1/documents/{identificador}/track-ids). En la factura de consumo (32) por debajo de RD$250.000 (resumen RFCE) el TrackID no existe nunca, ni siquiera con el documento ya aceptado:track_idsiguenully es lo normal (ecf-32.md).
Consulta de estado — 200 OK¶
El detalle de un e-CF suyo también es un document (no un data):
{
"status": "success",
"message": "Consulta exitosa.",
"document": {
"document_id": 1234,
"ecf_type_code": "31",
"e_ncf": "E310000000001",
"identificador": "133802074E310000000001",
"track_id": "a1b2c3d4-...",
"status_code": 4,
"status": "accepted_dgii",
"status_description": "La DGII aceptó el e-CF: tiene validez fiscal.",
"is_valid_fiscally": true,
"security_code": "aB12cd",
"issued_at": "27-07-2026",
"signed_at": "27-07-2026 09:15:03",
"total_amount": "1180.00",
"total_itbis": "180.00",
"sequence_reusable": null,
"errors": null,
"entrega_al_receptor": {
"via": "printed",
"descripcion": "El receptor no es electrónico: le corresponde la representación impresa.",
"acuse_recibido": null,
"mensajes": [],
"entregado_en": null
},
"correo_al_comprador": {
"estado": "sent",
"destinatario": "[email protected]",
"motivo": null,
"enviado_en": "27-07-2026 09:15:40"
},
"archivos": [
{
"tipo": "pdf",
"nombre": "133802074E310000000001.pdf",
"mime": "application/pdf",
"contenido_base64": "JVBERi0xLjQK"
},
{
"tipo": "xml",
"nombre": "133802074E310000000001.xml",
"mime": "application/xml",
"contenido_base64": "PD94bWwgdmVyc2lvbj0iMS4wIj8+"
},
{
"tipo": "respuesta_dgii",
"nombre": "133802074E310000000001-respuesta-dgii.json",
"mime": "application/json",
"contenido_base64": "eyJjb2RpZ28iOjEsImVzdGFkbyI6IkFjZXB0YWRvIn0="
}
]
}
}
(Los contenido_base64 del ejemplo están abreviados.)
track_id es null mientras no se haya transmitido y para siempre en la factura de consumo (32) por debajo de
RD$250.000, que va como resumen y no tiene TrackID. En los demás tipos llega al transmitirse —el documento
entra en in_process_dgii con él— y los estados finales lo conservan
(estados.md). entrega_al_receptor y correo_al_comprador se explican
en entrega-al-receptor.md.
archivos es una lista que sólo se llena cuando el documento tiene validez fiscal (accepted_dgii o
conditionally_accepted_dgii); en cualquier otro estado es []. Cada elemento trae tipo, nombre, mime y
contenido_base64, en este orden de tipos: pdf (la representación impresa), xml (el e-CF completo
firmado, lo que usted debe conservar), resumen_rfce (sólo en la factura de consumo bajo RD$250.000: el
resumen que se transmitió a la DGII) y respuesta_dgii (la respuesta final de la DGII, si consta). Todo en
línea, en base64: no hay URL que caduque. Qué trae cada tipo, cuándo falta alguno y qué es
archivos_no_disponibles, en mis-documentos.md.
Reenvío de un folio que ya existe — 200, 202 o 409¶
No es un error de validación: ese número ya tiene su documento. Se le responde con el estado del
documento que existe, marcado con "duplicate": true, sin crear ni transmitir nada nuevo. Lleva document
(el mismo detalle que la consulta de estado, sin los archivos), como cualquier respuesta que habla de un e-CF suyo,
incluso cuando el código es un 409 y el status de la raíz es error:
{
"status": "success",
"message": "Documento enviado y aprobado anteriormente. El e-NCF E310000000001 ya fue procesado: la DGII lo aceptó y tiene validez fiscal. No se envió de nuevo.",
"duplicate": true,
"document": { "e_ncf": "E310000000001", "identificador": "133802074E310000000001", "status_code": 4, "status": "accepted_dgii", "…": "…" }
}
Y si el documento que ya existe fue rechazado (409), el reenvío no lo reactiva: use otro folio.
{
"status": "error",
"message": "El e-NCF E310000000030 ya fue procesado y la DGII lo rechazó (motivos en errors). Un folio tiene un solo documento, así que no se envió de nuevo: use otro folio para el comprobante corregido.",
"duplicate": true,
"document": {
"e_ncf": "E310000000030",
"identificador": "133802074E310000000030",
"status_code": 6,
"status": "rejected_dgii",
"status_description": "La DGII rechazó el e-CF: es nulo para fines tributarios. Revise los mensajes de error. Si sequence_reusable es true, la DGII da la secuencia por no utilizada y puede anularla.",
"…": "…"
}
}
Cuando el documento que ya existe fue aprobado por la DGII (aceptado o aceptado condicional), el mensaje empieza siempre por «Documento enviado y aprobado anteriormente.»: puede usar esa frase para reconocer que el reenvío ya está resuelto. La respuesta es la misma lo haya enviado su sistema, otro sistema de la misma empresa o cualquier otra vía, y no dice quién lo envió.
El reenvío devuelve el detalle sin los archivos: para el XML y el PDF consulte el documento por su identificador.
La tabla de qué se responde por cada estado está en numeracion.md.
El token o la empresa no valen — 401, 403 y 422 de empresa¶
Estos tres cortes ocurren antes de leer el comprobante: no se crea nada y no se consume ningún e-NCF. Se
comprueban en este orden —token, empresa, permiso— y todos explican el porqué en data.detalle.
401 — el token falta, está mal copiado o fue revocado (con WWW-Authenticate: Bearer):
{
"status": "error",
"message": "No se pudo autenticar la petición.",
"data": {
"detalle": "Falta el token. Envíelo en la cabecera «Authorization: Bearer {token}»."
}
}
422 — la petición no declara la empresa (o la declara mal, o dos veces distinta):
{
"status": "error",
"message": "Falta indicar la empresa.",
"data": {
"detalle": "Envíe en la consulta el parámetro «emisor_rnc» con el RNC (9 dígitos) o la cédula (11 dígitos) de la empresa en cuyo nombre actúa, por ejemplo «?emisor_rnc=133802074». El token identifica a quien llama, que puede atender a varias empresas; el RNC de la petición dice a cuál se refiere."
}
}
403 — esa empresa no está habilitada para su token:
{
"status": "error",
"message": "Este token no puede actuar sobre esa empresa.",
"data": {
"detalle": "La empresa indicada no está habilitada para este token. Compruebe el RNC; si es correcto, solicite que habiliten esa empresa para su cuenta."
}
}
403 — la empresa es suya, pero al token le falta un permiso; la respuesta dice cuál:
{
"status": "error",
"message": "El token no tiene permiso para esta operación.",
"data": {
"detalle": "Esta operación exige el permiso «directory:read». Solicite una credencial que lo incluya.",
"permisos_requeridos": ["directory:read"],
"permisos_del_token": ["documents:read"]
}
}
Todos los textos y el porqué de cada uno, en tokens-y-permisos.md.
El documento o la ruta no existen — 404¶
Un identificador que no existe, o que es de otra empresa, responde siempre lo mismo: «No se encontró el documento.»
(y, en un e-CF recibido, «No se encontró el e-CF recibido.»), con el texto largo en data.detalle:
{
"status": "error",
"message": "No se encontró el documento.",
"data": {
"detalle": "No se encontró un e-CF con el identificador [133802074E310000009999] para este emisor."
}
}
Y una ruta que no existe (una versión que no es v1, un identificador mal formado, un tipo que no es uno de los diez, la forma
antigua de consultar por e-NCF solo) también lleva el envelope, en español, con el recurso y el método que pidió:
{
"status": "error",
"message": "La ruta solicitada no existe.",
"data": {
"detalle": "No hay ningún recurso para «GET /api/v2/documents». Revise la dirección y el método; la guía de integración lista los recursos disponibles."
}
}
Método no admitido — 405¶
Dice qué métodos sí admite la ruta, en el cuerpo (data.metodos_permitidos) y en la cabecera Allow:
{
"status": "error",
"message": "El método no está permitido para esta ruta.",
"data": {
"detalle": "Esta ruta no admite el método DELETE. Los métodos permitidos son: GET, HEAD.",
"metodos_permitidos": ["GET", "HEAD"]
}
}
Error de validación al emitir — 422 Unprocessable Entity¶
La validación de un e-CF es la única que responde con document (y no con data): el comprobante tiene un
estado fiscal (rejected_validation) y es lo que se le devuelve. El e-CF no se transmitió y no se creó ningún
documento: su número sigue libre. Corrija y reintente con el mismo e-NCF.
{
"status": "error",
"message": "Los datos enviados no son válidos.",
"document": {
"status_code": 2,
"status": "rejected_validation",
"status_description": "El e-CF no superó las validaciones previas al envío y no se transmitió a la DGII: su e-NCF no llegó a usarse ante ella.",
"errors": [
"El campo tipo de ingresos es obligatorio.",
"El campo cantidad del ítem debe ser mayor que 0."
],
"detailed_errors": {
"id_doc.tipo_ingresos": ["El campo tipo de ingresos es obligatorio."],
"items.0.cantidad": ["El campo cantidad del ítem debe ser mayor que 0."]
}
}
}
errors— lista plana de mensajes, en español. Útil para mostrar al usuario final.detailed_errors— los mismos mensajes indexados por campo. Útil para resaltar campos en un formulario.
Los 422 que tienen que ver con el número —que esté vencido, anulado o fuera de la autorización que usted
registró— llevan el mismo document con errors, y «El e-CF no se pudo recibir.» como message:
{
"status": "error",
"message": "El e-CF no se pudo recibir.",
"document": {
"status_code": 2,
"status": "rejected_validation",
"status_description": "El e-CF no superó las validaciones previas al envío y no se transmitió a la DGII: su e-NCF no llegó a usarse ante ella.",
"errors": ["La secuencia del e-NCF E310000000042 venció el 31-12-2025."]
}
}
(En el modo XML, el message es «El XML e-CF no se pudo recibir.»; el resto es igual.)
Error de validación de otra operación — 422¶
Un filtro inválido del listado, una anulación de secuencias mal armada, un certificado sin contraseña o una
aprobación comercial con un estado que no existe no hablan de un e-CF, así que no llevan document (que
mostraría un estado fiscal que no corresponde): llevan data, con detalle (el primer error y cuántos más hay) y
errores, los mensajes por campo.
{
"status": "error",
"message": "Los datos enviados no son válidos.",
"data": {
"detalle": "El campo fecha desde no corresponde al formato Y-m-d. (y 1 error más: vea «errores».)",
"errores": {
"from": ["El campo fecha desde no corresponde al formato Y-m-d."],
"per_page": ["El campo elementos por página debe ser un número entero."]
}
}
}
Una anulación que la DGII rechaza también es un 422 con data.detalle (el motivo que dio la DGII); ver
anulacion-secuencias.md.
Demasiadas peticiones — 429¶
Si la API o la red que tiene por delante aplican un límite de peticiones, la respuesta dice cuánto esperar, en el
cuerpo (data.reintentar_en_segundos) y en la cabecera Retry-After. Respételo: es una espera, no un fallo.
{
"status": "error",
"message": "Demasiadas peticiones.",
"data": {
"detalle": "Superó el límite de peticiones. Espere 30 segundos antes de reintentar.",
"reintentar_en_segundos": 30
}
}
Errores nuestros — 500 y 503¶
500 — algo falló de nuestro lado. El texto es siempre el mismo: nunca trae el detalle técnico del fallo (una
consulta, una ruta de archivos, una traza); eso queda en nuestro registro, no en su respuesta.
{
"status": "error",
"message": "Ocurrió un error interno.",
"data": {
"detalle": "No se pudo completar la petición por un error de nuestro lado. Ya quedó registrado: reintente más tarde y, si persiste, contáctenos."
}
}
Si el fallo ocurre al registrar un e-CF, la respuesta lo dice con el bloque document (estado system_error): «No se
pudo registrar el e-CF por un error interno. No se consumió ningún e-NCF: reintente más tarde.» Reenvíe el mismo folio.
503 — la DGII no respondió o contestó algo ilegible. No es un error de su petición: reintente en unos minutos. El
texto técnico de lo que dijo la DGII no sale (ni una URL, ni su cuerpo): sólo esto. El message cambia según la consulta
(la del directorio dice «No se pudo consultar el directorio de la DGII en este momento.»); el detalle es el mismo.
{
"status": "error",
"message": "El servicio de consulta de la DGII no está disponible en este momento.",
"data": {
"detalle": "La DGII no respondió o contestó con un error. No es un fallo de su petición: reintente en unos minutos."
}
}
Códigos HTTP¶
| Código | Significado |
|---|---|
200 |
Una lectura correcta, o el reenvío de un folio que ya existe y no necesita nada más: ya se procesó o espera a la DGII ("duplicate": true). |
201 |
Una operación que crea algo: anular secuencias (aceptada por la DGII), instalar el certificado. |
202 |
Recibido y encolado (emitir; también la aprobación comercial) o, en un reenvío, reintentado tras un fallo nuestro. |
400, 413, 415 |
La petición está mal formada, es demasiado grande o su Content-Type no se admite. Con el envelope y data.detalle. |
401 |
Token ausente, inválido o revocado (con WWW-Authenticate: Bearer; el cuerpo dice cuál de los casos: tokens-y-permisos.md). |
403 |
O la empresa no está habilitada para su token («Este token no puede actuar sobre esa empresa.»), o al token le falta un permiso (el cuerpo dice cuál). |
404 |
El documento (o el e-CF recibido) no existe o no es de esa empresa; o la ruta no existe (un identificador mal formado, la forma antigua por e-NCF). |
405 |
Esa ruta no admite el método usado; el cuerpo y la cabecera Allow dicen cuáles sí. |
409 |
Reenvío de un folio cuyo documento fue rechazado ("duplicate": true): use otro folio. |
422 |
Falta el RNC del emisor, está mal formado o se declaran dos distintos (también emisor_rnc junto a un identificador que lleva otro); el cuerpo no es un JSON válido; o los datos no pasaron las validaciones. No se creó nada. |
429 |
Superó un límite de peticiones: espere data.reintentar_en_segundos. |
500 |
Error interno nuestro, con un texto fijo. Si ocurre al registrar un e-CF, no se consumió ningún e-NCF. |
503 |
La DGII no responde. No es un error de su petición: reintente. |
Convenciones del JSON¶
| Tema | Convención |
|---|---|
| Nombres | snake_case en español (precio_unitario, razon_social). |
| Emisor | Se declara en emisor.rnc (obligatorio) y es también la empresa en cuyo nombre se actúa: su token tiene que estar vinculado a ese RNC. |
| e-NCF | Se envía siempre, completo, en id_doc.e_ncf; y id_doc.fecha_vencimiento_secuencia en los tipos que la llevan. La numeración la lleva su sistema (numeracion.md). |
| Identificador | {RNC o cédula del emisor}{e-NCF} (p. ej. 133802074E320000000303): el dato con el que se consulta un documento (cómo se arma). |
| Fechas | dd-MM-aaaa (p. ej. 27-07-2026). Fecha y hora: dd-MM-aaaa HH:mm:ss. |
| Montos | Número JSON con hasta 2 decimales y punto decimal (1180.50). Sin separador de miles ni símbolo de moneda. |
| Códigos de catálogo | Siempre string, respetando ceros a la izquierda ("01", "002"). |
| Campos opcionales | Omítalos. Enviar null es equivalente a omitirlos. |
| Zona horaria | America/Santo_Domingo (UTC−4, sin horario de verano). |
Estructura general del JSON¶
{
"emisor": { "rnc": "133802074" /* obligatorio: identifica la empresa */ },
"comprador": { /* datos del cliente (según el tipo) */ },
"id_doc": { /* identificación del documento: ingresos, pago, plazos */ },
"items": [ /* líneas del detalle: 1 a 1.000 */ ],
"totales": { /* montos gravados, exentos, ITBIS, total */ },
"referencia": { /* SOLO notas de crédito/débito: NCF que modifica */ },
"descuentos_recargos": [ /* opcional: descuentos o recargos globales */ ],
"otra_moneda": { /* opcional: si factura en divisa */ },
"subtotales": [ /* opcional: subtotales informativos */ ],
"informacion_adicional":{ /* opcional */ }
}
Detalle campo por campo en campos-comunes.md y en la página de cada tipo.
Topes y límites¶
| Límite | Valor |
|---|---|
| Líneas de detalle | 1.000; 10.000 en Factura de Consumo (32) por debajo de RD$250.000 |
| Formas de pago | 7 |
| Descuentos/recargos globales | 20 |
| Subtotales informativos | 20 |
| Decimales en montos | 2 |
| Umbral de la Factura de Consumo | RD$250.000 (define el comportamiento del tipo 32) |
Recomendaciones¶
- Guarde el
identificadorque devuelve el202(o arme el suyo: RNC + e-NCF, que usted ya tiene) y, si quiere, eldocument_id. - Si no sabe si su envío llegó (un corte, un timeout), reenvíe el mismo folio: no se duplica. Se le responde según el estado del documento que ya exista, o se recibe si no había llegado.
- Consulte el estado por identificador, con espaciado creciente. La DGII suele resolver en segundos, pero
puede tardar; nosotros consultamos automáticamente con reintentos progresivos. No espere un TrackID
(llega
nullen el202, aparece al transmitir y los estados finales lo conservan; no existe en el resumen de la factura de consumo). - Un
rejected_dgiies definitivo: el comprobante es nulo fiscalmente. Reviseerrorsy emita uno nuevo con otro folio: el rechazado no se puede reenviar. Sisequence_reusableestrue, la DGII da esa secuencia por no utilizada y puede anularla para no dejar un hueco en su numeración. system_errorno es culpa de su JSON: es un fallo nuestro y se reintenta solo.