Documentación de la API

Emite e-CF programáticamente en el ambiente de pruebas de DGII (TesteCF) usando tu API Key.

Autenticación

Todas las llamadas requieren el header X-API-Key con tu llave (se genera desde tu panel, próximamente disponible en el dashboard).

X-API-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Endpoints
POST/v1/emitir-xmlFirma y envía un e-CF a DGII
POST/v1/consultarConsulta el resultado de un envío por track_id
GET/v1/proximo-encfObtiene el siguiente número de secuencia (uso manual — no hace falta si dejas que /v1/emitir-xml lo genere solo)
GET/v1/secuenciasLista tus rangos de e-NCF registrados, con estadísticas de uso
POST/v1/secuenciasRegistra un rango de e-NCF autorizado por DGII
PUT/v1/secuenciasActiva/desactiva un rango o ajusta su contador
DELETE/v1/secuenciasElimina un rango inactivo
GET/v1/estado-certificadoEstado y vencimiento de tu certificado
GET/v1/documentosLista los documentos emitidos

Tipos de Comprobantes Electrónicos (e-CF)

Cada tipo de comprobante tiene campos y reglas específicas según DGII.

E31 Factura de Crédito Fiscal
Ventas entre empresas con RNC. Permite al comprador deducir el ITBIS. Requiere datos completos del comprador.
E32 Factura Consumidor Final
< RD$250,000: no se envía sección Comprador, solo Emisor, Items y Totales. ≥ RD$250,000: requiere datos reales del comprador (como E31). No incluir FechaVencimientoSecuencia. Siempre incluir FechaLimitePago.
E33 Nota de Débito
Cargos adicionales sobre una factura previa. Requiere InformacionReferencia con el eNCF de la factura original.
E34 Nota de Crédito
Anulaciones, devoluciones, descuentos. Requiere InformacionReferencia + IndicadorNotaCredito. Usa montos de la factura original.
E41 Comprobante de Compra
Compras con retenciones de ITBIS e ISR. Los ítems incluyen bloque Retencion. Totales con TotalITBISRetenido.
E43 Gastos Menores
Gastos menores sin datos de comprador. Solo usa MontoExento y MontoTotal. Sin sección Comprador.
E44 Regímenes Especiales
Ventas a zonas francas. Incluye datos bancarios: TipoCuentaPago, NumeroCuentaPago, BancoPago. No incluir IndicadorMontoGravado — DGII lo rechaza.
E45 Gubernamental
Ventas al Estado. Comprador con RNC gubernamental. Puede incluir InformacionesAdicionales y datos de orden de compra.
E46 Exportaciones
Ventas al exterior. ITBIS tasa 0%. Incluye datos logísticos: embarque, peso, bultos, transporte. TablaCodigosItem.
E47 Pagos al Exterior
Pagos a proveedores internacionales. Usa IdentificadorExtranjero en vez de RNCComprador. Bloque OtraMoneda con tipo de cambio. Retención ISR.
Importante para E33 y E34: las notas de débito y crédito solo se pueden emitir si existe una factura previamente registrada. Debe incluir el eNCF de la factura original en el campo NCFModificado dentro de InformacionReferencia — que va fuera de Encabezado, como hermano de Encabezado y DetallesItems a nivel raíz de ECF (confirmado probando en vivo contra DGII — colocarlo dentro de Encabezado lo rechaza). Además, RazonModificacion es obligatorio.
Estado de Pruebas con DGII

Los 11 formatos de esta guía (E32 en sus dos variantes) fueron probados en vivo contra el sandbox TesteCF de DGII a través de nuestra propia API — todos pasaron la validación estructural completa (XSD) y recibieron un trackId real. Ninguno llegó a "Aceptado" porque se probaron con el tenant demo (RNC ficticio, sin delegación real en DGII) — con un tenant real y certificado delegado, el mismo flujo termina en "Aceptado" tal como viste antes con la respuesta de ejemplo.

TipoEstadoDetalle
E31Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E32Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026 (2 variantes: < y ≥ RD$250K)
E33Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E34Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E41Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E43Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E44Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E45Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E46Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026
E47Estructura válidaHTTP 200, trackId real de DGII, verificado en vivo el 17-08-2026

Ejemplos JSON por Tipo de Comprobante

Haga clic en cada tipo para ver el JSON completo que debe enviar (dentro de xml_sin_firmar ya convertido, o como referencia para construir su propio XML) a POST /v1/emitir-xml.

E31 – Factura de Crédito Fiscal

Ventas entre empresas con RNC. Permite deducción de ITBIS al comprador.


      

Probar API en Vivo

Pruebe la API directamente desde el navegador. Complete los campos y envíe el comprobante.

1. API Key
Llave de demostración precargada (RNC ficticio, límite de 5 peticiones/min por IP). Regístrate para tu propia llave sin límite.
2. Seleccionar Tipo
3. JSON del Comprobante
Tip: Modifique el JSON según sus necesidades. Las fechas de emisión y firma se completan automáticamente al enviar. El eNCF no hace falta incluirlo — se genera solo desde el rango de secuencia que tengas registrado y activo para ese tipo/entorno. FechaVencimientoSecuencia debe coincidir con la fecha de vencimiento de ese rango (déjela como venga en la plantilla si usa el tenant demo).
Entorno: TesteCF (sin validez fiscal)
Respuesta
— aún no se ha enviado nada —

Guía de Integración

Cómo conectar tu propio software de facturación (ERP, POS, sistema a medida) para que emita e-CF usando nuestra API como proveedor electrónico.

Credenciales de Prueba
RNC Empresa
000000001
Empresa
EMPRESA DEMO (RNC de prueba)
API Key
sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8
A diferencia de otros proveedores, no hay paso de login por separado: el header X-API-Key autentica cada llamada directamente, sin necesidad de intercambiar un usuario/contraseña por un token primero.
Antes de emitir por primera vez: registra el rango de e-NCF que DGII te autorizó para cada tipo de comprobante, desde Secuencias NCF en tu dashboard o llamando a POST /v1/secuencias. Sin un rango activo para ese tipo/entorno, /v1/emitir-xml devuelve un 400 ("Sin secuencia configurada").
Ejemplos de Integración Completos

Seleccione su lenguaje de programación para ver el ejemplo completo de emisión de un e-CF E31 (Crédito Fiscal).

#!/bin/bash
# ============================================
#  e-CF SaaS API - Ejemplo completo con cURL
# ============================================

BASE_URL="https://cert.ocoasertec.com"
API_KEY="sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"

XML='<ECF xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Encabezado><Version>1.0</Version><IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia><IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc><Emisor><RNCEmisor>000000001</RNCEmisor><RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor><DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia><FechaEmision>'$(date +%d-%m-%Y)'</FechaEmision></Emisor><Comprador><RNCComprador>131703836</RNCComprador><RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador><DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador><ProvinciaComprador>10116</ProvinciaComprador></Comprador><Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1><ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1><MontoTotal>1770.00</MontoTotal></Totales></Encabezado><DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion><NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio><CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem><MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>'

# eNCF es opcional: si no se envia, el servidor lo genera automaticamente
# desde tu secuencia registrada en DGII (requiere jq instalado)
curl -s -X POST "$BASE_URL/v1/emitir-xml" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d "$(jq -n --arg xml "$XML" '{xml_sin_firmar: $xml, entorno: "TesteCF"}')" \
  | python3 -m json.tool

# Respuesta exitosa:
# {
#   "exito": true,
#   "encf": "E310000000012",
#   "estado": "Aceptado",
#   "track_id": "...",
#   "ri_pdf_url": "/v1/documentos/E310000000012/ri",
#   "xml_firmado_url": "/v1/documentos/E310000000012/xml"
# }
<?php
$baseUrl = "https://cert.ocoasertec.com";
$apiKey  = "sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8";

$fecha = date('d-m-Y');
$xml = '<ECF xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
     . '<Encabezado><Version>1.0</Version>'
     . '<IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia>'
     . '<IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc>'
     . '<Emisor><RNCEmisor>000000001</RNCEmisor>'
     . '<RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor>'
     . '<DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia>'
     . "<FechaEmision>$fecha</FechaEmision></Emisor>"
     . '<Comprador><RNCComprador>131703836</RNCComprador>'
     . '<RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador>'
     . '<DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador>'
     . '<ProvinciaComprador>10116</ProvinciaComprador></Comprador>'
     . '<Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1>'
     . '<ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1>'
     . '<MontoTotal>1770.00</MontoTotal></Totales></Encabezado>'
     . '<DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion>'
     . '<NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio>'
     . '<CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem>'
     . '<MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>';

// eNCF es opcional: si no se envia, el servidor lo genera automaticamente
$ch = curl_init("$baseUrl/v1/emitir-xml");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["Content-Type: application/json", "X-API-Key: $apiKey"],
    CURLOPT_POSTFIELDS => json_encode(["xml_sin_firmar" => $xml, "entorno" => "TesteCF"]),
]);
$respuesta = json_decode(curl_exec($ch), true);
print_r($respuesta);

// Respuesta esperada:
// [ "exito" => true, "encf" => "E310000000012", "estado" => "Aceptado", ... ]
import requests
from datetime import datetime

BASE_URL = "https://cert.ocoasertec.com"
API_KEY  = "sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"

fecha = datetime.now().strftime("%d-%m-%Y")
xml = f"""<ECF xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Encabezado><Version>1.0</Version>
<IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia>
<IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc>
<Emisor><RNCEmisor>000000001</RNCEmisor>
<RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor>
<DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia>
<FechaEmision>{fecha}</FechaEmision></Emisor>
<Comprador><RNCComprador>131703836</RNCComprador>
<RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador>
<DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador>
<ProvinciaComprador>10116</ProvinciaComprador></Comprador>
<Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1>
<ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1>
<MontoTotal>1770.00</MontoTotal></Totales></Encabezado>
<DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion>
<NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio>
<CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem>
<MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>"""

# eNCF es opcional: si no se envia, el servidor lo genera automaticamente
resp = requests.post(
    f"{BASE_URL}/v1/emitir-xml",
    headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
    json={"xml_sin_firmar": xml, "entorno": "TesteCF"},
)
print(resp.json())

# Respuesta esperada:
# {'exito': True, 'encf': 'E310000000012', 'estado': 'Aceptado', ...}
const BASE_URL = "https://cert.ocoasertec.com";
const API_KEY  = "sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8";

const pad = n => String(n).padStart(2, '0');
const hoy = new Date();
const fecha = `${pad(hoy.getDate())}-${pad(hoy.getMonth()+1)}-${hoy.getFullYear()}`;

const xml = `<ECF xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Encabezado><Version>1.0</Version>
<IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia>
<IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc>
<Emisor><RNCEmisor>000000001</RNCEmisor>
<RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor>
<DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia>
<FechaEmision>${fecha}</FechaEmision></Emisor>
<Comprador><RNCComprador>131703836</RNCComprador>
<RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador>
<DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador>
<ProvinciaComprador>10116</ProvinciaComprador></Comprador>
<Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1>
<ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1>
<MontoTotal>1770.00</MontoTotal></Totales></Encabezado>
<DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion>
<NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio>
<CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem>
<MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>`;

// eNCF es opcional: si no se envia, el servidor lo genera automaticamente
const resp = await fetch(`${BASE_URL}/v1/emitir-xml`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
  body: JSON.stringify({ xml_sin_firmar: xml, entorno: 'TesteCF' }),
});
console.log(await resp.json());

// Respuesta esperada:
// { exito: true, encf: 'E310000000012', estado: 'Aceptado', ... }
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;

var baseUrl = "https://cert.ocoasertec.com";
var apiKey  = "sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8";
var fecha   = DateTime.Now.ToString("dd-MM-yyyy");

var xml = $@"<ECF xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
<Encabezado><Version>1.0</Version>
<IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia>
<IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc>
<Emisor><RNCEmisor>000000001</RNCEmisor>
<RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor>
<DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia>
<FechaEmision>{fecha}</FechaEmision></Emisor>
<Comprador><RNCComprador>131703836</RNCComprador>
<RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador>
<DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador>
<ProvinciaComprador>10116</ProvinciaComprador></Comprador>
<Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1>
<ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1>
<MontoTotal>1770.00</MontoTotal></Totales></Encabezado>
<DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion>
<NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio>
<CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem>
<MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>";

// eNCF es opcional: si no se envia, el servidor lo genera automaticamente
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-API-Key", apiKey);
var body = JsonSerializer.Serialize(new { xml_sin_firmar = xml, entorno = "TesteCF" });
var resp = await http.PostAsync($"{baseUrl}/v1/emitir-xml",
    new StringContent(body, Encoding.UTF8, "application/json"));
Console.WriteLine(await resp.Content.ReadAsStringAsync());

// Respuesta esperada:
// {"exito":true,"encf":"E310000000012","estado":"Aceptado", ...}
import java.net.URI;
import java.net.http.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EmitirEcf {
    public static void main(String[] args) throws Exception {
        String baseUrl = "https://cert.ocoasertec.com";
        String apiKey  = "sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8";
        String fecha   = LocalDate.now().format(DateTimeFormatter.ofPattern("dd-MM-yyyy"));

        String xml = "<ECF xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
            + "<Encabezado><Version>1.0</Version>"
            + "<IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia>"
            + "<IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc>"
            + "<Emisor><RNCEmisor>000000001</RNCEmisor>"
            + "<RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor>"
            + "<DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia>"
            + "<FechaEmision>" + fecha + "</FechaEmision></Emisor>"
            + "<Comprador><RNCComprador>131703836</RNCComprador>"
            + "<RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador>"
            + "<DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador>"
            + "<ProvinciaComprador>10116</ProvinciaComprador></Comprador>"
            + "<Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1>"
            + "<ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1>"
            + "<MontoTotal>1770.00</MontoTotal></Totales></Encabezado>"
            + "<DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion>"
            + "<NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio>"
            + "<CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem>"
            + "<MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>";

        // eNCF es opcional: si no se envia, el servidor lo genera automaticamente
        ObjectMapper mapper = new ObjectMapper();
        var body = mapper.createObjectNode();
        body.put("xml_sin_firmar", xml);
        body.put("entorno", "TesteCF");

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + "/v1/emitir-xml"))
            .header("Content-Type", "application/json")
            .header("X-API-Key", apiKey)
            .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
            .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());

        // Respuesta esperada:
        // {"exito":true,"encf":"E310000000012","estado":"Aceptado", ...}
    }
}
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    baseUrl := "https://cert.ocoasertec.com"
    apiKey  := "sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"
    fecha   := time.Now().Format("02-01-2006")

    xml := fmt.Sprintf(`<ECF xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Encabezado><Version>1.0</Version>
<IdDoc><TipoeCF>31</TipoeCF><FechaVencimientoSecuencia>31-12-2028</FechaVencimientoSecuencia>
<IndicadorMontoGravado>0</IndicadorMontoGravado><TipoIngresos>01</TipoIngresos><TipoPago>1</TipoPago></IdDoc>
<Emisor><RNCEmisor>000000001</RNCEmisor>
<RazonSocialEmisor>EMPRESA DEMO (RNC de prueba, no registrado en DGII)</RazonSocialEmisor>
<DireccionEmisor>Santo Domingo</DireccionEmisor><Municipio>10116</Municipio><Provincia>10116</Provincia>
<FechaEmision>%s</FechaEmision></Emisor>
<Comprador><RNCComprador>131703836</RNCComprador>
<RazonSocialComprador>GRUPO MEDIA SOFT TECHNOLOGY GMS SRL</RazonSocialComprador>
<DireccionComprador>Santo Domingo</DireccionComprador><MunicipioComprador>10116</MunicipioComprador>
<ProvinciaComprador>10116</ProvinciaComprador></Comprador>
<Totales><MontoGravadoTotal>1500.00</MontoGravadoTotal><MontoGravadoI1>1500.00</MontoGravadoI1>
<ITBIS1>18</ITBIS1><TotalITBIS>270.00</TotalITBIS><TotalITBIS1>270.00</TotalITBIS1>
<MontoTotal>1770.00</MontoTotal></Totales></Encabezado>
<DetallesItems><Item><NumeroLinea>1</NumeroLinea><IndicadorFacturacion>1</IndicadorFacturacion>
<NombreItem>Servicio de consultoria</NombreItem><IndicadorBienoServicio>2</IndicadorBienoServicio>
<CantidadItem>1</CantidadItem><UnidadMedida>43</UnidadMedida><PrecioUnitarioItem>1500.00</PrecioUnitarioItem>
<MontoItem>1500.00</MontoItem></Item></DetallesItems></ECF>`, fecha)

    // eNCF es opcional: si no se envia, el servidor lo genera automaticamente
    payload, _ := json.Marshal(map[string]string{"xml_sin_firmar": xml, "entorno": "TesteCF"})
    req, _ := http.NewRequest("POST", baseUrl+"/v1/emitir-xml", bytes.NewBuffer(payload))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-API-Key", apiKey)

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    out, _ := io.ReadAll(resp.Body)
    fmt.Println(string(out))

    // Respuesta esperada:
    // {"exito":true,"encf":"E310000000012","estado":"Aceptado", ...}
}
eNCF automático: el campo encf en el body de /v1/emitir-xml es opcional. Si no se envía (ni está presente dentro de <IdDoc> en el XML), el sistema lo genera automáticamente desde la secuencia registrada del tenant en DGII e inserta el elemento en la posición correcta del documento.
Sobre el "exito": true de los ejemplos: es el formato de respuesta que verás con un tenant real, cuyo certificado sí está delegado ante DGII para su propio RNC. Con la API Key demo de arriba, el RNC (000000001) es ficticio, así que DGII siempre rechaza el envío con "estado": "Rechazado" y el mensaje "El RNC del certificado no está delegado para realizar transacciones para el RNC: 000000001" — no es un error de tu código ni de la API, es justamente lo esperado al usar credenciales de demo. La firma, el envío y la generación del eNCF sí ocurren de verdad; solo el paso de autorización final en DGII rechaza el RNC de prueba.

Registro de Secuencias e-NCF

Antes de emitir comprobantes, cada empresa debe registrar los rangos de e-NCF autorizados por DGII. El sistema usa estos rangos para generar el eNCF automáticamente al procesar una factura.

Autorización DGII: los rangos deben estar previamente autorizados por DGII antes de registrarlos aquí.
Continuidad obligatoria: cada rango nuevo debe comenzar exactamente en (último `hasta` + 1) del rango anterior del mismo tipo. No se permiten saltos.
Sin solapamiento: el sistema valida que no existan rangos duplicados ni solapados del mismo tipo/entorno.
Un solo rango activo por tipo: debe desactivar el actual (PUT estado:false) antes de crear o reactivar otro.
E32 sin vencimiento: el tipo E32 (Consumo Electrónico) no requiere fecha_vencimiento.
Eliminar solo inactivos: para eliminar un rango debe desactivarlo primero.
1. Consultar continuidad

Antes de registrar un rango nuevo, averigua en qué número terminó el anterior del mismo tipo.

# Antes de crear un rango nuevo, consulta en que numero termino el anterior
curl -s "https://cert.ocoasertec.com/v1/secuencias?prefijo=E31" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"

# Respuesta:
# { "max_hasta": 1000000, "rango_activo": { "id": 3, "tipo": "E31", "desde": 1, "hasta": 1000000, ... } }
# El proximo rango debe comenzar en: max_hasta + 1 = 1000001
2. Crear un rango

Registra un rango autorizado por DGII. Si ya existe un rango previo del mismo tipo/entorno, `desde` debe ser exactamente `max_hasta + 1`.

curl -s -X POST "https://cert.ocoasertec.com/v1/secuencias" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8" \
  -H "Content-Type: application/json" \
  -d '{
    "tipo": "E31",
    "entorno": "TesteCF",
    "desde": 1,
    "hasta": 50000,
    "fecha_vencimiento": "31-12-2028"
  }'

# Respuesta:
# {
#   "id": 3, "tipo": "E31", "entorno": "TesteCF", "desde": 1, "hasta": 50000,
#   "secuencia_actual": 1, "fecha_vencimiento": "31-12-2028", "estado": true
# }

# Nota E32: el tipo E32 (Consumo Electronico) no exige fecha_vencimiento -- puede omitirse.
Este ejemplo tal cual (E31/TesteCF, desde 1) fallará con la API Key demo — ese tenant ya tiene un rango de E31/TesteCF registrado (lo usamos para las pruebas de esta misma guía), así que verás el error de continuidad: "El número inicial debe ser 1000001 para continuar la secuencia". Con un tenant real recién registrado, sin rangos previos de ese tipo, sí obtienes la respuesta de éxito mostrada arriba.
3. Listar rangos

Devuelve todos los rangos del tenant con estadísticas: usadas, disponibles, % de uso, días hasta vencimiento.

curl -s "https://cert.ocoasertec.com/v1/secuencias" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"

# Respuesta (array de rangos con estadisticas):
# [{
#   "id": 3, "tipo": "E31", "entorno": "TesteCF", "desde": 1, "hasta": 1000000,
#   "secuencia_actual": 12, "total_secuencias": 1000000, "secuencias_usadas": 11,
#   "secuencias_disponibles": 999989, "porcentaje_uso": 0.0,
#   "fecha_vencimiento": "31-12-2028", "dias_vencimiento": 866, "estado": true
# }, ...]

# Filtrar por tipo y/o entorno:
curl -s "https://cert.ocoasertec.com/v1/secuencias?tipo=E31&entorno=TesteCF" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"
4. Consultar disponibilidad

Total de secuencias libres en el rango activo de un tipo — útil para alertar cuando quedan pocas.

curl -s "https://cert.ocoasertec.com/v1/secuencias?disponibilidad_prefijo=E31" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8"

# Respuesta:
# { "disponibilidad_total": 999989, "rangos_activos": 1 }
# Si disponibilidad_total < 1000, la respuesta incluye "advertencia"
5. Actualizar un rango

Desactivar, reactivar o ajustar el contador de un rango existente.

# Desactivar un rango (requerido antes de poder eliminarlo)
curl -s -X PUT "https://cert.ocoasertec.com/v1/secuencias" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8" \
  -H "Content-Type: application/json" \
  -d '{ "id": 3, "estado": false }'

# Ajustar el contador actual (por ejemplo, al migrar desde otro sistema)
curl -s -X PUT "https://cert.ocoasertec.com/v1/secuencias" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8" \
  -H "Content-Type: application/json" \
  -d '{ "id": 3, "secuencia_actual": 4500 }' 
6. Eliminar un rango

Solo rangos inactivos pueden eliminarse — desactive primero con PUT.

# Solo se pueden eliminar rangos inactivos (estado:false)
curl -s -X DELETE "https://cert.ocoasertec.com/v1/secuencias" \
  -H "X-API-Key: sk_6q2BM1UrfJekODVEf-TjjEtPIBWhXhGRudcELVjRcr8" \
  -H "Content-Type: application/json" \
  -d '{ "id": 3 }' 
Errores Comunes
MensajeCausa / Solución
El número inicial debe ser X para continuar la secuenciaEl `desde` no es consecutivo al rango anterior. Consulta `?prefijo=` para obtener el valor correcto.
El rango se solapa con otro existenteLos números `desde`-`hasta` ya están cubiertos por otro rango del mismo tipo/entorno.
Ya existe un rango activo para este tipo de comprobanteDesactive el rango actual (PUT `estado:false`) antes de crear o reactivar otro.
La secuencia actual debe estar dentro del rango definido`secuencia_actual` debe estar entre `desde` y `hasta`.
fecha_vencimiento es obligatoria salvo para E32Todos los tipos exigen fecha de vencimiento excepto E32 (Consumo Electrónico).
No se puede eliminar un rango activoPrimero desactívelo con PUT `estado:false`, luego ejecute DELETE.
Rango no encontrado o no pertenece a su empresaEl `id` no existe o pertenece a otro tenant.

Catálogo de Errores DGII

Errores comunes al procesar comprobantes electrónicos y cómo resolverlos.

Errores de Nuestra API

Ocurren antes de llegar a DGII — validaciones propias de ecf-saas.

HTTPMensajeCausa / Solución
401Falta el header X-API-KeyIncluir el header X-API-Key en la petición (no usamos Bearer/JWT en /v1/*).
401API key inválida o inactivaVerificar que la llave esté bien copiada y no haya sido desactivada.
403El tenant asociado a esta API key no está activoLa cuenta está pendiente de pago o suspendida.
429Límite de la llave de demostración alcanzado (5/min)La API Key demo pública tiene rate limit por IP. Regístrate para tu propia llave sin límite.
400El tenant no tiene un certificado activo cargadoSube tu certificado P12 desde el dashboard antes de emitir.
400Sin secuencia configurada para {tipo} en entorno {entorno}Registra el rango autorizado por DGII en Secuencias NCF o POST /v1/secuencias antes de emitir ese tipo.
400Secuencia {tipo} agotada (máx X)Se consumió todo el rango. Registra el siguiente rango que DGII te autorice.
400XML inválido: no se encontró <IdDoc>El xml_sin_firmar enviado no tiene la estructura mínima esperada.
Errores de DGII (rechazos)

Estos ya llegaron a DGII — el XML se firmó y se envió, pero DGII lo rechazó. Los marcados verificado los reprodujimos nosotros mismos probando en vivo contra el sandbox de DGII durante el desarrollo de esta plataforma.

Mensaje DGIICausa / Solución
verificadoinvalid child element 'eNCF'. List of possible elements expected: 'eNCF'eNCF no estaba inmediatamente después de TipoeCF dentro de IdDoc. Ya no debería pasarte — nuestra API lo posiciona automáticamente.
verificado'Encabezado' has invalid child element 'InformacionReferencia'Se intentó colocar InformacionReferencia dentro de Encabezado. Va como hermano de Encabezado y DetallesItems, a nivel raíz de ECF — ver la nota de E33/E34 más abajo.
verificadoEl RNC del certificado no está delegado para realizar transacciones para el RNC: XEl certificado firmante no corresponde al RNC declarado como RNCEmisor. Pasa siempre con la API Key demo (RNC ficticio) — no es un error de tu código.
típicoFecha de vencimiento de secuencia inválidaEl rango de secuencia venció en DGII. Debes solicitar/renovar la autorización en el portal de DGII y registrar el nuevo rango.
típicoeNCF no válido o ya utilizadoEse eNCF ya fue enviado antes (duplicado) o está fuera del rango autorizado. No reenviar el mismo eNCF.
típicoRNC del comprador no encontradoEl RNCComprador no existe en el padrón de DGII. Verificar que esté bien escrito.
típicoMonto total no coincide con el detalleLa suma de MontoItem de todos los ítems no cuadra con Totales. Recalcular MontoGravadoTotal, TotalITBIS y MontoTotal.

Las filas marcadas "típico" son patrones de rechazo documentados y ampliamente conocidos del e-CF dominicano — no los provocamos nosotros mismos en esta plataforma, así que el mensaje exacto puede variar ligeramente.

Notas Importantes por Tipo
TipoRestricción
E32No incluir FechaVencimientoSecuencia. Siempre incluir FechaLimitePago. Monto < RD$250,000: no enviar sección Comprador (solo Emisor, Items y Totales). Monto ≥ RD$250,000: datos reales del comprador obligatorios.
E33/E34InformacionReferencia va fuera de Encabezado, como hermano de Encabezado y DetallesItems a nivel raíz de ECF — confirmado probando en vivo contra DGII (colocarlo dentro de Encabezado lo rechaza). RazonModificacion es obligatorio.
E43No incluir sección Comprador. Solo usar MontoExento y MontoTotal.
E44No incluir IndicadorMontoGravado en IdDoc. DGII lo rechaza.
E47No incluir IndicadorMontoGravado. Usar IdentificadorExtranjero en vez de RNCComprador.

Catálogos de Referencia DGII

Tablas de códigos oficiales utilizados en los comprobantes electrónicos.

Unidades de Medida (UnidadMedida)
CódigoDescripciónUso común
43Servicio / UnidadServicios, licencias, ítems unitarios
NIUUnidad (alternativa)Productos por unidad
15LitroCombustibles, líquidos
21KilogramoPeso, productos agrícolas
25CajaProductos empacados
27Metro cúbicoVolúmenes, embarques
MTRMetroLongitud, telas
DPCDocena de piezasProductos por docena
GLLGalónCombustibles, pinturas
LBRLibraPeso alternativo
YRDYardaTelas, construcción
SETJuego / SetKits, juegos de herramientas
Formas de Pago (FormaPago)
CódigoDescripción
1Efectivo
2Cheque / Transferencia / Depósito
3Tarjeta de Crédito / Débito
4Compra a Crédito
5Permuta
6Nota de Crédito
7Mixto
Tipo de Ingresos (TipoIngresos)
CódigoDescripción
01Ingresos por operaciones (no financieros)
02Ingresos financieros
03Ingresos extraordinarios
04Ingresos por arrendamientos
05Ingresos por venta de activos depreciables
06Otros ingresos
Indicador de Facturación (IndicadorFacturacion)
CódigoDescripciónTasa ITBIS
1Gravado con ITBIS Tasa 118%
2Gravado con ITBIS Tasa 216%
3Gravado con ITBIS Tasa 30% (exportaciones)
4Exento de ITBISN/A
Tipo de Bien o Servicio (IndicadorBienoServicio)
CódigoDescripción
1Bien
2Servicio
3Bien y Servicio
4Otros
Código de Modificación (CodigoModificacion — para E33/E34)
CódigoDescripción
1Cancelación total de la factura
2Corrección de texto / error de escritura
3Devolución de bienes
4Descuento / bonificación
5Cambio de precio
Provincias de República Dominicana
CódigoProvincia
010000Distrito Nacional
020000Azua
030000Bahoruco
040000Barahona
050000Dajabón
060000Duarte
070000Elías Piña
080000El Seibo
090000Espaillat
100000Independencia
110000La Altagracia
120000La Romana
130000La Vega
140000María Trinidad Sánchez
150000Monte Cristi
160000Pedernales
CódigoProvincia
170000Peravia
180000Puerto Plata
190000Hermanas Mirabal
200000Samaná
210000San Cristóbal
220000San Juan
230000San Pedro de Macorís
240000Sánchez Ramírez
250000Santiago
260000Santiago Rodríguez
270000Valverde
280000Monseñor Nouel
290000Monte Plata
300000Hato Mayor
310000San José de Ocoa
320000Santo Domingo
Municipios Principales
CódigoMunicipioProvincia
010100Santo Domingo de GuzmánDistrito Nacional
320100Santo Domingo EsteSanto Domingo
320200Santo Domingo OesteSanto Domingo
320300Santo Domingo NorteSanto Domingo
320400Boca ChicaSanto Domingo
320500San Antonio de GuerraSanto Domingo
320600Los AlcarrizosSanto Domingo
320700Pedro BrandSanto Domingo
250100Santiago de los CaballerosSantiago
230100San Pedro de MacorísSan Pedro de Macorís
120100La RomanaLa Romana
180100San Felipe de Puerto PlataPuerto Plata
130100La VegaLa Vega
210100San CristóbalSan Cristóbal
110100HigüeyLa Altagracia
060100San Francisco de MacorísDuarte
Códigos de Moneda (TipoMoneda — ISO 4217)
CódigoMoneda
DOPPeso Dominicano
USDDólar Estadounidense
EUREuro
GBPLibra Esterlina
CADDólar Canadiense
CHFFranco Suizo

Referencia Rápida

Fórmulas y formatos que se usan constantemente al construir tu comprobante.

Cálculo del ITBIS
IndicadorMontoGravado = "0"
El ITBIS no está incluido en los precios de los ítems.
MontoGravadoTotal = suma(ítems) TotalITBIS = MontoGravadoTotal * 0.18 MontoTotal = Gravado + ITBIS + Exento
IndicadorMontoGravado = "1"
El ITBIS ya está incluido en los precios de los ítems.
MontoGravadoTotal = suma(ítems) / 1.18 TotalITBIS = suma(ítems) - Gravado MontoTotal = suma(ítems) + Exento
Formato de fechas (DGII exige siempre dd-mm-aaaa)
const pad = n => String(n).padStart(2,'0'); const hoy = new Date(); `${pad(hoy.getDate())}-${pad(hoy.getMonth()+1)}-${hoy.getFullYear()}` // "17-08-2026"
from datetime import datetime datetime.now().strftime("%d-%m-%Y") # "17-08-2026"
date('d-m-Y'); // "17-08-2026"
DateTime.Now.ToString("dd-MM-yyyy"); // "17-08-2026"
Validación de RNC/Cédula (9 o 11 dígitos)
/^\d{9}$|^\d{11}$/.test(rnc)
import re re.match(r'^\d{9}$|^\d{11}$', rnc)
preg_match('/^\d{9}$|^\d{11}$/', $rnc)