Emite e-CF desde tu sistema con un solo POST
Tu sistema manda la factura en JSON. ncf.do asigna el e-NCF, la firma con el certificado de tu empresa, la entrega a la DGII y al comprador, y te devuelve al instante todo lo que necesitas para imprimirla.
{
"encf": "E310009100101",
"codigoSeguridad": "TbtHcs",
"fechaHoraFirma": "30-10-2026 21:04:00",
"montoTotal": 2950.00,
"urlTimbre": "https://ecf.dgii.gov.do/…",
"dgii": { "estado": "Pendiente" },
"comprador": { "estado": "Pendiente" }
}
Inicio rápido
En tres pasos emites tu primera factura en el ambiente de pruebas. Sin firmar XML, sin certificados en tu servidor y sin hablar con los servicios de la DGII.
Prepara tu empresa
En el portal sube tu certificado digital y carga los rangos de e-NCF en Emisión → Secuencias.
Crea una clave de API
En Emisión → Claves de API. Se muestra una sola vez: guárdala como secreto.
Envía tu factura
Un POST /api/v1/ecf con el JSON. La respuesta trae el e-NCF y el QR.
curl -X POST https://test.ncf.do/api/v1/ecf \
-H "Authorization: Bearer $NCF_CLAVE" \
-H "Idempotency-Key: FACT-000123" \
-H "Content-Type: application/json" \
-d @factura.json
Cómo funciona
Tu sistema solo habla con ncf.do. ncf.do se encarga de la DGII y del comprador, y reintenta solo si algo falla.
- Asigna el e-NCF
- Arma y firma el XML
- Valida el formato DGII
- Reintenta si algo falla
- Asigna el e-NCF del rango que la DGII autorizó a tu empresa.
- Arma el XML, lo valida contra el formato oficial de la DGII y lo firma con el certificado digital de tu empresa.
- Lo entrega a la DGII y consulta el resultado. La factura de consumo menor de RD$250,000 se envía como resumen (RFCE), como pide la DGII.
- Lo entrega al comprador si es receptor electrónico y guarda el acuse de recibo que devuelve.
Ambientes
Integra primero en pruebas. Para pasar a producción cambias solo la URL base y la clave de API: el JSON y las respuestas son iguales.
| Ambiente | URL base | Ambiente de la DGII | Validez fiscal |
|---|---|---|---|
| Pruebas | https://test.ncf.do | TesteCF (pre-certificación) | No |
| Producción | https://ncf.do | eCF (producción) | Sí |
En cada ambiente, un administrador de tu empresa deja listo en el portal (/portal):
- Certificado digital vigente, en Certificado digital. El titular debe estar delegado para tu empresa en la Oficina Virtual de la DGII, con permiso para emitir.
- Secuencias: los rangos de e-NCF que autorizó la DGII, en Emisión → Secuencias, escritos exactamente como los autorizó. Los tipos 32 y 34 no llevan fecha de vencimiento.
- Clave de API, en Emisión → Claves de API. Crea una por sistema para poder revocarlas por separado.
En pruebas, la DGII autoriza a cada empresa habilitada los e-NCF del 1 al 10,000,000 por tipo (la 32, hasta 50,000,000), con vencimiento 31-12-2028. Por ejemplo, E310000000001 a E310000001000. En producción, solo los rangos que la DGII autorizó a tu empresa.
Autenticación
Cada petición lleva la clave de API en el encabezado Authorization. La clave identifica a tu empresa, así que ninguna ruta lleva el RNC. También se acepta X-Api-Key.
Authorization: Bearer ncf_XXXXXXXXXXXX_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
| Código | Cuándo |
|---|---|
401 | Falta la clave, no existe, fue revocada o la empresa está inactiva. |
403 | «Servicio suspendido»: el servicio de tu empresa está suspendido. Comunícate con ncf.do para reactivarlo. |
429 | Más de 300 peticiones por minuto con la misma clave. Espera lo que indica Retry-After. |
Guarda la clave como secreto (variable de entorno o almacén de secretos), nunca en el código ni en el navegador. Si se expone, revócala en el portal: deja de funcionar al instante.
Emitir un e-CF
/api/v1/ecfUn POST por factura. La respuesta llega al instante con el e-NCF asignado y los datos para imprimir. La entrega a la DGII sigue en segundo plano.
POST /api/v1/ecf
Authorization: Bearer ncf_…
Idempotency-Key: FACT-000123
Content-Type: application/json
{ "ecf": { "Encabezado": { … }, "DetallesItems": { "Item": [ … ] } } }
El contenido de ecf
ecf es el elemento ECF del Formato e-CF v1.0 de la DGII, escrito en JSON. Sirve para los diez tipos: 31, 32, 33, 34, 41, 43, 44, 45, 46 y 47.
- Mismos nombres de campo que el XML de la DGII (
TipoeCF,RNCComprador,MontoGravadoI1…), sin distinguir mayúsculas. El orden no importa: lo pone ncf.do. - Mismos valores y códigos que el XML: fechas
dd-MM-yyyyy códigos de la DGII (TipoIngresos: "01",TipoPago: 1). No haytrue/false: van los códigos. - Montos como texto (
"1180.00") para conservar los decimales exactos; también se aceptan números. - Lo que se repite va como lista:
"Item": [ … ],"FormaDePago": [ … ]. Si hay uno solo, puede ir como objeto. - Lo que no aplica se omite: la DGII no admite etiquetas vacías. Un campo vacío o
nullcuenta como omitido. - Tú mandas los montos y totales. ncf.do no los recalcula; la DGII valida los cálculos y rechaza el e-CF si no cuadran.
- Factura de consumo (32) desde RD$250,000: identifica al comprador con
RNCComprador(oIdentificadorExtranjerosi es extranjero sin RNC ni cédula) yRazonSocialComprador.
Campos que pone ncf.do y no debes enviar (si vienen, la respuesta es 400):
| Campo | Lo pone ncf.do con |
|---|---|
Encabezado.IdDoc.eNCF | El siguiente número del rango activo del tipo que vence primero. |
Encabezado.IdDoc.FechaVencimientoSecuencia | La fecha de vencimiento de ese rango (32 y 34 no la llevan). |
FechaHoraFirma | El momento de la firma, en hora de República Dominicana. |
Encabezado.Version y Encabezado.Emisor.RNCEmisor son opcionales; si los envías, deben ser 1.0 y el RNC de tu empresa.
Idempotency-Key, obligatoria
Es el identificador único de la factura en tu sistema, por ejemplo su número interno: hasta 100 caracteres visibles, sin espacios.
- Si repites la petición con la misma clave y el mismo contenido, recibes la misma factura (
200y el encabezadoIdempotent-Replayed: true). No se gasta otro e-NCF. - La misma clave con otro contenido responde
409. - Ante un error de red o un tiempo de espera agotado, repite siempre con la misma clave. Nunca generes una nueva para reintentar.
La respuesta
201 Created, con el encabezado Location: /api/v1/ecf/{id}. Caso real del ambiente de pruebas:
{
"id": "aa81bdd3-6f78-4e09-9f9a-993b6d30170e",
"encf": "E310009100101",
"tipoEcf": 31,
"tipoNombre": "Factura de Crédito Fiscal Electrónica",
"fechaEmision": "2026-10-30",
"fechaHoraFirma": "30-10-2026 21:04:00",
"codigoSeguridad": "TbtHcs",
"montoTotal": 2950.00,
"totalItbis": 450.00,
"rncComprador": "131880681",
"razonSocialComprador": "DOCUMENTOS ELECTRONICOS DE 03",
"resumen": false,
"urlTimbre": "https://ecf.dgii.gov.do/testecf/consultatimbre?rncemisor=130964874&rnccomprador=131880681&encf=E310009100101&fechaemision=30-10-2026&montototal=2950.00&fechafirma=30-10-2026%2021%3A04%3A00&codigoseguridad=TbtHcs",
"dgii": { "estado": "Pendiente", "trackId": null, "mensajes": [], "secuenciaUtilizada": null, "intentos": 0, "enviadoEn": null, "finalizadoEn": null },
"comprador": { "estado": "Pendiente", "url": null, "intentos": 0, "error": null, "motivoNoRecibido": null, "entregadoEn": null },
"aprobacionComercial": null,
"creadoEn": "2026-10-30T21:04:00-04:00"
}
| Campo | Para qué te sirve |
|---|---|
id | Consultar la factura después. Guárdalo junto a tu factura. |
encf, codigoSeguridad, fechaHoraFirma | Van en la factura impresa. |
urlTimbre | El contenido del código QR. |
resumen | true si es una factura de consumo menor de RD$250,000 (se envió como RFCE). |
dgii | El estado ante la DGII. En la respuesta del POST siempre viene Pendiente. |
comprador | La entrega al receptor electrónico del comprador. |
aprobacionComercial | La aprobación o el rechazo comercial del comprador, cuando llegue. |
Consultar el resultado
La DGII suele responder en menos de 10 segundos. Consulta la factura con su id y mira dgii.estado; el formato es el mismo de la respuesta del POST.
| Ruta | Qué devuelve |
|---|---|
GET /api/v1/ecf/{id} | La factura con su estado actual. |
GET /api/v1/ecf?encf=E310000000037 | La factura de ese e-NCF. |
GET /api/v1/ecf?desde=2026-10-01&hasta=2026-10-31&tipo=31&estado=Rechazado&pagina=1&tamano=25 | Una página de facturas, las más recientes primero: { total, pagina, tamano, items }. Todos los filtros son opcionales; buscar filtra por comprador. |
Si la factura no existe, la respuesta es 404.
Estado ante la DGII
dgii.estado | Qué significa | Qué hace tu sistema |
|---|---|---|
| Pendiente | Firmada; ncf.do la está entregando a la DGII y reintenta si no responde. | Esperar y volver a consultar. |
| EnProceso | La DGII la recibió (trackId) y la está validando. | Esperar y volver a consultar. |
| Aceptado | Válida. | Marcarla como aceptada. |
| AceptadoCondicional | Válida, con observaciones en mensajes. | Marcarla como aceptada y revisar los mensajes. |
| Rechazado | Sin valor fiscal; el motivo está en mensajes. | Corregir y emitir una factura nueva. |
| Error | No se pudo entregar ni confirmar en 72 horas, el plazo de contingencia de la DGII. | Avisar a soporte. |
- Los mensajes de la DGII vienen como
"código texto", por ejemplo"135 Número de secuencia no autorizada". - En un rechazo,
secuenciaUtilizada: falsesignifica que la DGII no dio por usado ese e-NCF. - La factura de consumo menor de RD$250,000 (
resumen: true) pasa dePendientea su resultado sintrackId: la DGII valida el RFCE en el acto.
Entrega al comprador
comprador.estado | Qué significa |
|---|---|
| Pendiente | Se envía cuando la DGII la acepta; si falla, ncf.do reintenta durante unas horas. |
| Recibido | El comprador devolvió su acuse de recibo (descárgalo en /acuse). |
| NoRecibido | El comprador respondió que no la recibió. Motivo en motivoNoRecibido: 1 especificación, 2 firma, 3 duplicado, 4 RNC no corresponde. |
| NoAplica | No hay a quién enviarla: tipos 43 y 47, consumo menor de RD$250,000, o el comprador no es receptor electrónico. |
| Error | No se pudo entregar después de los reintentos. |
| Cancelada | No se envió porque la DGII no la aceptó. |
Si tu empresa recibe sus e-CF en ncf.do, la aprobación comercial que mande el comprador aparece en aprobacionComercial: { estado: "Aprobado" | "Rechazado", fechaHora, motivo, recibidaEn }.
Cada cuánto consultar
No hay avisos automáticos (webhooks). Este ritmo basta:
- Consulta a los 5 segundos de emitir.
- Si sigue
PendienteoEnProceso, consulta cada 30 segundos durante 5 minutos. - Después, revisa cada 15 minutos las que sigan sin resultado final, hasta 72 horas.
Representación impresa
Tu sistema imprime la factura con sus propios datos más cuatro que vienen en la respuesta del POST. No hace falta esperar a la DGII para imprimir.
| En la factura impresa | Campo de la respuesta |
|---|---|
| e-NCF | encf |
| Código de seguridad | codigoSeguridad (6 caracteres) |
| Fecha de firma digital | fechaHoraFirma |
| Código QR, abajo a la izquierda, de al menos 22 × 22 mm | urlTimbre, tal cual |
- Si
resumenestrue,urlTimbreya apunta a la consulta del RFCE (consultatimbrefc), como pide la DGII. - El QR abre la verificación de la DGII; cuando la DGII acepta la factura, esa página muestra «Estado Aceptado».
- La DGII indica la versión 8 del QR; la URL de un e-CF completo no cabe en ella, así que usa la menor versión en que quepa.
- Si tu sistema no puede imprimir una factura, en el portal la imprimes con su QR: Emitidos → la factura → Imprimir.
Documentos y secuencias
| Ruta | Qué devuelve |
|---|---|
GET /api/v1/ecf/{id}/xml | El e-CF firmado: lo que recibieron la DGII y el comprador. |
GET /api/v1/ecf/{id}/rfce | El resumen RFCE firmado (solo consumo menor de RD$250,000). |
GET /api/v1/ecf/{id}/acuse | El acuse de recibo (ARECF) que devolvió el comprador. |
GET /api/v1/secuencias | Los rangos cargados con el siguiente e-NCF, cuántos quedan, el vencimiento y el estado. |
ncf.do guarda estos documentos por ti; descárgalos si tu sistema también debe conservarlos. Usa /api/v1/secuencias para avisar antes de que un rango se agote o venza: sin un rango activo y vigente del tipo, POST /api/v1/ecf responde 409.
[
{ "id": 1, "tipoEcf": 31, "desde": "E310000000001", "hasta": "E310000001000", "siguiente": "E310000000038",
"usados": 37, "disponibles": 963, "fechaVencimiento": "2028-12-31", "estado": "Activa" }
]
Anular e-NCF que no usarás
/api/v1/anulacionesAnula ante la DGII los números que no vas a usar: un rango vencido o el sobrante de uno que dejas de usar. ncf.do arma el documento de anulación (ANECF), lo firma, lo envía y responde el resultado de la DGII.
{ "rangos": [ { "desde": "E310000000038", "hasta": "E310000000500" } ] }{
"id": "8a4e45f7-ad12-4dab-86f6-3c14ff036abd",
"estado": "Aceptada",
"rangos": ["E310009100002-E310009100020"],
"cantidad": 19,
"mensajes": ["Las secuencias fueron anuladas correctamente."],
"creadoEn": "2026-10-30T20:40:19-04:00"
}estado:Aceptada;Rechazada, cuando la DGII dijo que no (motivo enmensajes); oError, cuando no se pudo entregar y hay que repetir la petición.- No se puede anular un e-NCF ya emitido (400).
- De un rango cargado en ncf.do solo se anula lo que falta por asignar completo, desde el siguiente hasta el final. Si la DGII acepta, ese rango deja de asignar esos números.
Errores
Todo error llega como application/problem+json con title, status y detail. En los 400, errors dice qué campo falla con su ruta en tu JSON, para mostrarlo junto al dato que lo causó. Un error en los datos no gasta ningún e-NCF.
{
"title": "Revisa los datos",
"status": 400,
"detail": "Es obligatorio.",
"errors": {
"ecf.Encabezado.IdDoc.TipoPago": ["Es obligatorio."],
"ecf.Encabezado.IdDoc.TipoIngresos": ["«99» no es un valor válido para este campo. Valores permitidos: 01, 02, 03, 04, 05, 06."],
"ecf.DetallesItems.Item[1].CantidadItem": ["«-1» no es un valor válido para este campo (formato Decimal18D1or2ValidationTypeMayorCero del XSD de la DGII)."],
"ecf.Encabezado.Emisor.Telefono": ["Este campo no existe en el formato de la DGII para este documento."]
}
}
| Código | Cuándo | Qué hacer |
|---|---|---|
200 | Repetiste la misma Idempotency-Key con el mismo contenido. | Usar la factura que devuelve. |
400 | JSON inválido, falta Idempotency-Key, un campo no cumple el formato de la DGII o enviaste un campo que pone ncf.do. | Corregir los campos de errors y reenviar. |
401 | Clave de API ausente, revocada o inválida. | Revisar la clave. |
403 | El servicio de la empresa está suspendido («Servicio suspendido»). | Comunicarse con ncf.do. |
404 | El id o el e-NCF no existe. | Revisar el identificador. |
409 | No quedan e-NCF del tipo, el certificado no está vigente o la Idempotency-Key ya se usó con otro contenido. | Ver detail; el administrador carga un rango o el certificado en el portal. |
429 | Más de 300 peticiones por minuto. | Esperar Retry-After y reintentar. |
5xx o sin respuesta | Falla temporal. | Reintentar con la misma Idempotency-Key. |
Ejemplos por tipo
Estos cuatro casos fueron aceptados por la DGII en el ambiente de pruebas. Cambia los datos del emisor por los de tu empresa. El comprador 131880681 es el contribuyente de prueba de la DGII y solo sirve en pruebas.
{
"ecf": {
"Encabezado": {
"IdDoc": { "TipoeCF": 31, "IndicadorMontoGravado": 0, "TipoIngresos": "01", "TipoPago": 2, "FechaLimitePago": "29-11-2026", "TerminoPago": "30 días" },
"Emisor": {
"RazonSocialEmisor": "Tu Empresa SRL",
"NombreComercial": "Tu Empresa",
"DireccionEmisor": "Av. Winston Churchill 1099, Santo Domingo",
"NumeroFacturaInterna": "FACT-000123",
"FechaEmision": "30-10-2026"
},
"Comprador": { "RNCComprador": "131880681", "RazonSocialComprador": "DOCUMENTOS ELECTRONICOS DE 03" },
"Totales": { "MontoGravadoTotal": "2500.00", "MontoGravadoI1": "2500.00", "ITBIS1": 18, "TotalITBIS": "450.00", "TotalITBIS1": "450.00", "MontoTotal": "2950.00" }
},
"DetallesItems": {
"Item": [
{ "NumeroLinea": 1, "IndicadorFacturacion": 1, "NombreItem": "Servicio de mantenimiento preventivo", "IndicadorBienoServicio": 2, "CantidadItem": 1, "PrecioUnitarioItem": "1500.00", "MontoItem": "1500.00" },
{ "NumeroLinea": 2, "IndicadorFacturacion": 1, "NombreItem": "Filtro de aire", "IndicadorBienoServicio": 1, "CantidadItem": 2, "PrecioUnitarioItem": "500.00", "MontoItem": "1000.00" }
]
}
}
}{
"ecf": {
"Encabezado": {
"IdDoc": {
"TipoeCF": 32, "IndicadorMontoGravado": 0, "TipoIngresos": "01", "TipoPago": 1,
"TablaFormasPago": { "FormaDePago": [ { "FormaPago": 1, "MontoPago": "500.00" }, { "FormaPago": 3, "MontoPago": "90.00" } ] }
},
"Emisor": { "RazonSocialEmisor": "Tu Empresa SRL", "DireccionEmisor": "Av. Winston Churchill 1099, Santo Domingo", "FechaEmision": "30-10-2026" },
"Totales": { "MontoGravadoTotal": "500.00", "MontoGravadoI1": "500.00", "ITBIS1": 18, "TotalITBIS": "90.00", "TotalITBIS1": "90.00", "MontoTotal": "590.00" }
},
"DetallesItems": {
"Item": [
{ "NumeroLinea": 1, "IndicadorFacturacion": 1, "NombreItem": "Café americano", "IndicadorBienoServicio": 1, "CantidadItem": 2, "PrecioUnitarioItem": "150.00", "MontoItem": "300.00" },
{ "NumeroLinea": 2, "IndicadorFacturacion": 1, "NombreItem": "Sándwich de pollo", "IndicadorBienoServicio": 1, "CantidadItem": 1, "PrecioUnitarioItem": "200.00", "MontoItem": "200.00" }
]
}
}
}{
"ecf": {
"Encabezado": {
"IdDoc": { "TipoeCF": 32, "IndicadorMontoGravado": 0, "TipoIngresos": "01", "TipoPago": 1, "TablaFormasPago": { "FormaDePago": [ { "FormaPago": 2, "MontoPago": "295000.00" } ] } },
"Emisor": { "RazonSocialEmisor": "Tu Empresa SRL", "DireccionEmisor": "Av. Winston Churchill 1099, Santo Domingo", "FechaEmision": "30-10-2026" },
"Comprador": { "RNCComprador": "131880681", "RazonSocialComprador": "DOCUMENTOS ELECTRONICOS DE 03" },
"Totales": { "MontoGravadoTotal": "250000.00", "MontoGravadoI1": "250000.00", "ITBIS1": 18, "TotalITBIS": "45000.00", "TotalITBIS1": "45000.00", "MontoTotal": "295000.00" }
},
"DetallesItems": {
"Item": [ { "NumeroLinea": 1, "IndicadorFacturacion": 1, "NombreItem": "Equipo de cómputo", "IndicadorBienoServicio": 1, "CantidadItem": 1, "PrecioUnitarioItem": "250000.00", "MontoItem": "250000.00" } ]
}
}
}{
"ecf": {
"Encabezado": {
"IdDoc": { "TipoeCF": 34, "IndicadorNotaCredito": 0, "IndicadorMontoGravado": 0, "TipoIngresos": "01", "TipoPago": 1 },
"Emisor": { "RazonSocialEmisor": "Tu Empresa SRL", "DireccionEmisor": "Av. Winston Churchill 1099, Santo Domingo", "FechaEmision": "30-10-2026" },
"Comprador": { "RNCComprador": "131880681", "RazonSocialComprador": "DOCUMENTOS ELECTRONICOS DE 03" },
"Totales": { "MontoGravadoTotal": "500.00", "MontoGravadoI1": "500.00", "ITBIS1": 18, "TotalITBIS": "90.00", "TotalITBIS1": "90.00", "MontoTotal": "590.00" }
},
"DetallesItems": {
"Item": [ { "NumeroLinea": 1, "IndicadorFacturacion": 1, "NombreItem": "Devolución: filtro de aire", "IndicadorBienoServicio": 1, "CantidadItem": 1, "PrecioUnitarioItem": "500.00", "MontoItem": "500.00" } ]
},
"InformacionReferencia": { "NCFModificado": "E310000000001", "FechaNCFModificado": "30-10-2026", "CodigoModificacion": 3, "RazonModificacion": "Devolución de mercancía" }
}
}Código
Emitir y leer la respuesta en los lenguajes más comunes. Los nombres de campo no distinguen mayúsculas, así que el camelCase que generan muchas librerías también sirve.
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var http = new HttpClient { BaseAddress = new Uri("https://test.ncf.do") };
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("NCF_CLAVE"));
var factura = new
{
ecf = new
{
Encabezado = new
{
IdDoc = new { TipoeCF = 31, IndicadorMontoGravado = 0, TipoIngresos = "01", TipoPago = 1 },
Emisor = new { RazonSocialEmisor = "Tu Empresa SRL", DireccionEmisor = "Av. Winston Churchill 1099, Santo Domingo", FechaEmision = "30-10-2026" },
Comprador = new { RNCComprador = "131880681", RazonSocialComprador = "DOCUMENTOS ELECTRONICOS DE 03" },
Totales = new { MontoGravadoTotal = "1000.00", MontoGravadoI1 = "1000.00", ITBIS1 = 18, TotalITBIS = "180.00", TotalITBIS1 = "180.00", MontoTotal = "1180.00" },
},
DetallesItems = new
{
Item = new[] { new { NumeroLinea = 1, IndicadorFacturacion = 1, NombreItem = "Servicio", IndicadorBienoServicio = 2, CantidadItem = 1, PrecioUnitarioItem = "1000.00", MontoItem = "1000.00" } },
},
},
};
using var pedido = new HttpRequestMessage(HttpMethod.Post, "/api/v1/ecf") { Content = JsonContent.Create(factura) };
pedido.Headers.Add("Idempotency-Key", "FACT-000123"); // el número de la factura en tu sistema
using var respuesta = await http.SendAsync(pedido);
var cuerpo = await respuesta.Content.ReadAsStringAsync();
if (!respuesta.IsSuccessStatusCode)
throw new InvalidOperationException($"ncf.do respondió {(int)respuesta.StatusCode}: {cuerpo}");
using var json = JsonDocument.Parse(cuerpo);
var id = json.RootElement.GetProperty("id").GetString(); // guárdalo para consultar
var encf = json.RootElement.GetProperty("encf").GetString(); // a la factura impresa
var qr = json.RootElement.GetProperty("urlTimbre").GetString(); // contenido del QRconst base = "https://test.ncf.do";
const encabezados = { Authorization: `Bearer ${process.env.NCF_CLAVE}` };
async function emitir(factura, numeroInterno) {
const r = await fetch(`${base}/api/v1/ecf`, {
method: "POST",
headers: { ...encabezados, "Idempotency-Key": numeroInterno, "Content-Type": "application/json" },
body: JSON.stringify(factura),
});
const cuerpo = await r.json();
if (!r.ok) throw new Error(`${r.status} ${cuerpo.detail} ${JSON.stringify(cuerpo.errors ?? {})}`);
return cuerpo; // id, encf, codigoSeguridad, fechaHoraFirma, urlTimbre
}
async function esperarDgii(id) {
for (let i = 0; i < 12; i++) {
await new Promise((listo) => setTimeout(listo, i === 0 ? 5000 : 30000));
const ecf = await (await fetch(`${base}/api/v1/ecf/${id}`, { headers: encabezados })).json();
if (!["Pendiente", "EnProceso"].includes(ecf.dgii.estado)) return ecf; // Aceptado, Rechazado…
}
return null; // sigue en proceso: revisarla más tarde
}import json
import os
import requests
base = "https://test.ncf.do"
encabezados = {"Authorization": f"Bearer {os.environ['NCF_CLAVE']}"}
with open("factura.json", encoding="utf-8") as archivo:
factura = json.load(archivo)
r = requests.post(
f"{base}/api/v1/ecf",
json=factura,
headers={**encabezados, "Idempotency-Key": "FACT-000123"},
timeout=30,
)
cuerpo = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code}: {cuerpo.get('detail')} {cuerpo.get('errors')}")
print(cuerpo["encf"], cuerpo["codigoSeguridad"], cuerpo["urlTimbre"])# Emitir (la factura en factura.json)
curl -X POST https://test.ncf.do/api/v1/ecf \
-H "Authorization: Bearer $NCF_CLAVE" \
-H "Idempotency-Key: FACT-000123" \
-H "Content-Type: application/json" \
-d @factura.json
# Consultar su estado con el id de la respuesta
curl https://test.ncf.do/api/v1/ecf/aa81bdd3-6f78-4e09-9f9a-993b6d30170e \
-H "Authorization: Bearer $NCF_CLAVE"Lista para pasar a producción
Cuando todo esto funcione en pruebas, cambia la URL base a https://ncf.do y usa una clave de API de producción.
- Emitiste en pruebas los tipos que usa tu negocio y la DGII los aceptó.
- Tu sistema guarda el
idy elencfde cada factura. - La factura impresa lleva el e-NCF, el código de seguridad, la fecha de firma y el QR con
urlTimbre. - Muestras los errores de
errorsjunto al campo que los causó. - Ante errores de red o respuestas 5xx, reintentas con la misma
Idempotency-Key. - Consultas el resultado de la DGII y avisas cuando una factura queda
RechazadooError. - Revisas las secuencias y avisas antes de que se agoten o venzan.
- La clave de API de producción está guardada como secreto, fuera del código.
- En el portal de producción están el certificado vigente y los rangos que autorizó la DGII.
¿Listo para integrar?
Escríbenos y te damos acceso al ambiente de pruebas. Para dudas de una factura, incluye su id o su e-NCF.