Endpoints
Invoices

Invoices

Tres endpoints alrededor del recurso invoice:

MétodoPathPara qué
POST/invoicesEnviar un nuevo e-CF
GET/invoices/:trackOrIdConsultar estado
GET/invoices/:trackOrId/xmlObtener el XML generado/firmado
GET/invoicesListar historial paginado

Todos requieren Authorization: Bearer <JWT>.

📋

"Invoices" cubre TODA la familia de e-CFs DGII, no solo facturas. El tipo de documento se determina por el campo TipoeCF dentro del XML/JSON que mandas — no por el endpoint o un parámetro de URL.

Tipos de e-CF soportados

POST /invoices acepta cualquiera de los siguientes tipos vía el campo TipoeCF del header (Encabezado.IdDoc.TipoeCF en JSON Digimart):

CódigoTipo de documentoUso típico
31Factura de Crédito FiscalB2B con derecho a crédito ITBIS
32Factura de ConsumoB2C — consumidor final
33Nota de Débito ElectrónicaAumento de un e-CF previamente emitido
34Nota de Crédito ElectrónicaDevolución / descuento sobre un e-CF previo
41ComprasRegistro de compras a no-emisores (gastos informales)
43Gastos MenoresOperaciones < umbral DGII
44Régimen EspecialSectores con tratamiento fiscal específico
45GubernamentalOperaciones con el Estado
46Comprobante Pago ExteriorPagos a proveedores fuera del país
47Régimen Especial Pago ExteriorCombinación de 44 y 46

Otros documentos también soportados

POST /invoices también acepta Resumen de Facturas de Consumo (RFCE) y Aprobaciones Comerciales (ACECF / ANECF). El endpoint del cliente es el mismo — el worker auto-detecta el root element del XML/JSON y rutea al endpoint DGII correcto bajo el capó:

Root elementTipo de documentoEndpoint DGII destino
<ECF>e-CF estándar (tipos 31-47)ecf.dgii.gov.do/{env}/recepcion/api/FacturasElectronicas
<RFCE>Resumen Facturas Consumofc.dgii.gov.do/{env}/recepcionfc/api/recepcion/ecf
<ACECF>Aprobación Comercial (emisor → receptor)ecf.dgii.gov.do/{env}/aprobacionComercial/api/AprobacionComercial
<ANECF>Aprobación de Notasecf.dgii.gov.do/{env}/aprobacionComercial/api/AprobacionComercial

Si mandas format: 'digimart' con un JSON cuyo root es RFCE, el worker lo convierte a XML con el tag correcto, lo firma, y lo manda al host CF de DGII. Tú no tienes que hacer nada distinto — un solo POST, el routing es invisible.


POST /invoices

Envía un comprobante para procesamiento async. Responde 202 con un identificador que puedes usar para consultar estado.

POST /api/v1/fe/invoices
Authorization: Bearer <JWT>
Content-Type: application/json

Scope requerido: invoices:write

Request body

CampoTipoRequeridoDescripción
format"xml" | "digimart"Forma del payload
xmlstringSi format="xml"XML completo del e-CF
payloadobjectSi format="digimart"JSON estructurado
clientRequestIdstringIdempotency key opcional
asUserstring (correo)Emitir con el certificado de un usuario específico (ver Certificado y usuarios)
asUserIdstring (UUID)Igual que asUser pero por ID. Si mandas ambos, gana asUserId

Certificado y usuarios

Antes de emitir en producción necesitas un certificado digital (.p12) cargado. Tienes dos formas de subirlo desde el portal:

  • A nivel de empresa (recomendado) — en Configuración → Certificado digital subes un solo .p12 + su clave. Se usa para todas tus emisiones. Ideal si tienes una sola identidad fiscal.
  • A nivel de usuario — en Usuarios creas actores (p. ej. por sucursal o RNC) y le subes a cada uno su propio .p12. Eliges cuál usar con asUser/asUserId en la solicitud.
🔐

Debes tener al menos un certificado (de empresa o de usuario) para emitir. Si no, la emisión queda en status: "error" con un mensaje indicándote que subas el .p12 en el portal.

¿Cómo elige el worker qué certificado usar?

  1. Si mandas asUser/asUserId → usa el certificado de ese usuario (y su emisorOverride, si tiene). Recomendado cuando manejas varios certificados.
  2. Si NO mandas asUser y tienes un certificado de empresa → lo usa.
  3. Si NO mandas asUser, no hay cert de empresa, y hay un solo usuario con certificado → lo selecciona automáticamente.
  4. Si NO mandas asUser y hay varios usuarios con certificado (y sin cert de empresa) → la emisión falla pidiéndote indicar cuál vía asUser.

¿Solo tienes una identidad fiscal? Sube el .p12 en Configuración y omite asUser. Especifica asUser únicamente cuando manejes múltiples certificados (p. ej. varias sucursales o RNC).

format: "xml"

Le pasas el XML del e-CF tal cual lo construiste. Lo firmamos y enviamos.

{
  "format": "xml",
  "clientRequestId": "factura-001-2026-06-15",
  "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ECF xmlns:xsi=\"...\">...</ECF>"
}

format: "digimart"

JSON estructurado que sigue el esquema DGII. Tienes dos modos de uso del mismo formato:

Modo A — auto-fill (recomendado)

Omites 4 campos y nosotros los rellenamos por ti:

Campo omitidoDe dónde sale
Encabezado.IdDoc.TipoeCFLos 2 dígitos después del prefix E en tu eNCF (ej. E31..."31")
Encabezado.IdDoc.FechaVencimientoSecuenciaLookup de tu config NCF (la fecha que DGII te autorizó). Configúrala vía los endpoints de NCF de esta API o manualmente en el portal
Encabezado.EmisorConstruido de los datos fiscales de tu empresa (RNC, razón social, dirección, etc.) configurados en el portal
Encabezado.TotalesCalculado de DetallesItems.Item[] agrupando por IndicadorFacturacion
Modo B — payload completo (tal cual DGII)

Si prefieres control total — porque ya tienes el cálculo de Totales por tu lado, quieres fijar un Emisor distinto al del tenant, o estás migrando desde un sistema que ya construye el shape DGII — manda los 4 campos explícitamente. Los respetamos sin cuestionar.

Ambos modos son el mismo format: 'digimart' — la diferencia es solo si incluyes esos 4 campos o los omites. Puedes mezclar: enviar Emisor explícito y dejar que calculemos Totales, o cualquier combinación.

{
  "format": "digimart",
  "clientRequestId": "factura-001-2026-06-15",
  "payload": {
    "Encabezado": {
      "Version": "1.0",
      "IdDoc": {
        "eNCF": "E310000000001",
        "IndicadorMontoGravado": "0",
        "TipoIngresos": "01",
        "TipoPago": "1"
      },
      "Comprador": {
        "RNCComprador": "101672919",
        "RazonSocialComprador": "Cliente B2B SRL"
      }
    },
    "DetallesItems": {
      "Item": [
        {
          "NumeroLinea": 1,
          "IndicadorFacturacion": "1",
          "NombreItem": "Servicio profesional",
          "CantidadItem": 1,
          "PrecioUnitarioItem": 10000,
          "MontoItem": 10000
        }
      ]
    }
  }
}

Puedes sobrescribir cualquiera de los 4 auto-derivados si los envías explícitamente — respetamos tu valor. Útil cuando quieres fijar un Emisor diferente al del tenant o ya calculaste los Totales por tu lado.

⚠️

IndicadorFacturacion de cada item se valida strict. Solo aceptamos: "1" (ITBIS 18%), "2" (ITBIS 16%), "3" (ITBIS 0%), "4" (Exento). Cualquier otro valor → 400 con la línea exacta del item ofensivo. Si lo omites en un item, lo tratamos como exento.

clientRequestId (idempotency)

Si tu sistema reintenta la misma factura (timeout de red, retry interno, etc.), envía el mismo clientRequestId en cada intento. Detectamos la duplicación y devolvemos el envío original en vez de procesar dos veces.

  • Recomendación: usa un id determinístico desde TU sistema (ej. tu_factura_id + fecha).
  • Longitud: 1-120 caracteres.
  • Si lo omites, no hay protección contra retries duplicados.

Response — 202 Accepted

{
  "dgiiLogId": "a3f7b8c9-d1e2-3f4g-5h6i-7j8k9l0m1n2o",
  "status": "queued",
  "environment": "ecf",
  "billable": true,
  "message": "Invoice queued for processing"
}
CampoDescripción
dgiiLogIdUUID del registro. Úsalo para consultar estado (ver abajo).
statusSiempre "queued" inicialmente. Cambia a "Aceptado", "Rechazado", etc. después de la respuesta de DGII.
environmentAmbiente al que se envía (determinado por la key).
billabletrue solo si environment === "ecf". Pruebas no facturan.

Response — 400 Bad Request

{
  "message": "xml is required when format=\"xml\"",
  "statusCode": 400
}

Otros errores 400 comunes:

  • payload is required when format="digimart"
  • format must be one of: xml, digimart

Idempotency response

Si reutilizas un clientRequestId:

{
  "dgiiLogId": "a3f...",
  "status": "queued",
  "deduped": true,
  "message": "Existing submission returned (idempotency key matched)"
}

El deduped: true te dice que NO procesamos de nuevo — te devolvimos el envío original.


GET /invoices/:trackOrId

Consulta el estado de un comprobante. Acepta tanto el dgiiLogId (que te dimos al hacer POST) como el trackId (que DGII asigna después).

GET /api/v1/fe/invoices/{trackOrId}
Authorization: Bearer <JWT>

Scope requerido: invoices:read

Response — 200 OK (procesado)

{
  "dgiiLogId": "a3f7b8c9-...",
  "trackId": "DGII-2026-06-15-12345",
  "environment": "ecf",
  "status": "Aceptado",
  "qrCodeUrl": "https://ecf.dgii.gov.do/ConsultaTimbre?...",
  "securityCode": "abc123",
  "ncfExpiration": "31-12-2026",
  "fechaHoraFirma": "2026-06-15T14:23:11Z",
  "response": { /* respuesta completa de DGII */ },
  "submittedAt": "2026-06-15T14:23:10Z",
  "updatedAt": "2026-06-15T14:23:13Z",
  "clientRequestId": "factura-001-2026-06-15"
}
CampoDescripción
status"Aceptado", "Aceptado Condicional", "Rechazado", "En Proceso", o null (todavía no procesado)
qrCodeUrlURL del QR oficial para incluir en tu PDF de factura
securityCodeCódigo de seguridad asignado por DGII
ncfExpirationFecha en que expira la secuencia NCF
responseRespuesta cruda de DGII (para debugging)

Estados posibles

EstadoSignificado¿Es facturable?
"Aceptado"DGII aceptó el e-CF sin observaciones.✅ Sí
"Aceptado Condicional"DGII aceptó con observaciones (revisa mensajes).✅ Sí
"Rechazado"DGII rechazó. Mira response.mensajes para detalles.❌ No
"En Proceso"DGII todavía procesando. Vuelve a consultar en ~30s.❌ Todavía no
null o "queued"Aún no enviado a DGII. Vuelve a consultar en ~5s.❌ Todavía no

Response — 200 OK (no encontrado)

{
  "found": false
}

Significa que el trackOrId no corresponde a ningún envío tuyo.


GET /invoices/:trackOrId/xml

Devuelve el XML que generamos y/o firmamos para un envío específico. Sirve para dos cosas concretas:

  1. Debug de rechazos "XML Inválido" — DGII responde con código 400 y un mensaje genérico cuando el XSD no valida. Pidiendo este endpoint ves exactamente el documento que mandamos por ti y puedes detectar el campo problemático (formato de fecha mal, monto fuera de rango, NCF con prefijo equivocado, etc.).
  2. Archivar el comprobante firmado — el XML con la sección <Signature> es la prueba legal del e-CF. Para tus copias internas o un sistema documental, este endpoint te devuelve esa pieza.
GET /api/v1/fe/invoices/{trackOrId}/xml
Authorization: Bearer <JWT>

Scope requerido: invoices:read

Path params

CampoTipoDescripción
trackOrIdstringdgiiLogId (UUID que devolvió POST) o trackId (id de DGII).

Response — 200 OK

{
  "dgiiLogId": "9e5c8010-fa9a-4190-9c3c-d727aa99b26c",
  "environment": "testecf",
  "status": "error",
  "source": "signed",
  "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ECF ...>...</ECF>"
}
CampoDescripción
source"signed" si el worker ya firmó (caso normal); "original" si mandaste format="xml" y aún no se ha firmado; null si todavía está queued.
xmlEl documento XML completo como string. null cuando source = null.
statusMismo status que devuelve GET /invoices/:trackOrId, replicado por conveniencia.
🔐

Para format="digimart" sólo existe signedXml (el JSON lo convertimos a XML y lo firmamos en un solo paso). Si todavía está en cola (status="queued") el xml será null — vuelve a pedirlo en unos segundos.

Response — 200 OK (no encontrado)

{
  "found": false
}

GET /invoices

Lista paginada del historial de envíos del tenant. Solo retorna envíos hechos a través de esta API pública (source = 'fe-api').

GET /api/v1/fe/invoices?page=1&limit=50&from=2026-06-01&to=2026-06-30
Authorization: Bearer <JWT>

Scope requerido: invoices:read

Query params

ParamTipoDefaultDescripción
pagenumber1Página (1-indexed)
limitnumber50Items por página (max 200)
fromISO dateFiltro: createdAt >= from
toISO dateFiltro: createdAt <= to
statusstring(reservado para uso futuro)

Response — 200 OK

{
  "page": 1,
  "limit": 50,
  "total": 247,
  "items": [
    {
      "dgiiLogId": "a3f...",
      "trackId": "DGII-...",
      "environment": "ecf",
      "status": "Aceptado",
      "qrCodeUrl": "...",
      "submittedAt": "2026-06-15T14:23:10Z",
      "updatedAt": "2026-06-15T14:23:13Z",
      "clientRequestId": "factura-001-2026-06-15"
    }
    /* ... más items ... */
  ]
}

Ordenado por submittedAt DESC (más recientes primero).