factadesarrolladores API 0.1.0-esqueleto

Emitir un DTE

POST /v1/dte reserva el correlativo, construye y valida el documento contra el esquema oficial, lo firma con el certificado de la empresa y lo transmite al Ministerio — todo en una sola llamada. Es la ruta normal: use preparar y firmar por separado solo cuando necesite revisar el documento antes de firmarlo (vea Preparar y firmar por separado).

Lo mínimo para emitir

Tres cosas, siempre: el tipoDte, la lista de items y, según el tipo, un receptor. El emisor, el ambiente, el establecimiento y el punto de venta salen de la base a partir de la llave — usted no los escribe nunca.

curlTerminal
curl -X POST "$FACTA_API_BASE_URL/v1/dte" \
  -H "X-Facta-Key: $FACTA_API_KEY" \
  -H "X-Facta-Sign-Key: $FACTA_SIGN_KEY" \
  -H "Idempotency-Key: venta-2026-09-02-00417" \
  -H "Content-Type: application/json" \
  -d '{
        "tipoDte": "01",
        "items": [
          { "descripcion": "Café", "cantidad": 2, "precioUni": 1.5 }
        ]
      }'

El precio de la línea depende del tipo

El servidor no adivina: en una factura (01) precioUni incluye el IVA; en un crédito fiscal (03) y en las notas (05/06) lo excluye; en una exportación (11) no hay IVA que incluir; en un comprobante de sujeto excluido (14) el precio es el precio, sin tocarlo. Así lo definen los esquemas oficiales del Ministerio, y mezclar uno con otro produce un total equivocado que el esquema no puede atrapar por sí solo.

Con un cliente ya guardado

Si el receptor ya existe en Facta, nómbrelo por customerId en vez de escribir sus datos de nuevo. Lo explícito gana sobre lo guardado, así que puede combinar los dos: un correo distinto para esta venta no obliga a editar la ficha del cliente.

jsoncuerpo · crédito fiscal
{
  "tipoDte": "03",
  "receptor": { "customerId": "374114b6-e957-4c7a-8911-dd6381b1e0ea" },
  "items": [
    { "descripcion": "Integración de la API de facturación electrónica", "cantidad": 1, "precioUni": 25 }
  ]
}

Un crédito fiscal exige además numDocumento (el NIT), nrc, nombre, codActividad, direccion y correo — completos, en el receptor guardado o en el que se escribe en la petición.

Sin guardar el cliente en Facta

No es necesario crear una ficha de cliente para facturar. Puede enviar los datos fiscales del receptor directamente en receptor; la API los usa para este DTE y no crea ni actualiza el catálogo de clientes. Tampoco necesita FACTA_UNLOCK_KEY ni sincronizar el catálogo si todos los datos viajan en la petición.

Una factura (01) a consumidor final puede omitir receptor por completo. Si necesita identificar a un comprador en una FE, puede enviar sus campos inline. Un crédito fiscal (03), en cambio, exige los datos completos del contribuyente receptor; nombre no se puede omitir. El customerId solo es un atajo opcional del SDK para resolver una ficha que ya esté incluida en el snapshot de esa llave.

typescriptSDK · crédito fiscal con receptor inline, sin ficha de cliente
const dte = await facta.issue({
  tipoDte: "03",
  receptor: {
    nombre: "Comercial Ejemplo, S.A. de C.V.",
    tipoDocumento: "36",
    numDocumento: "06141234567890",
    nrc: "1234567",
    codActividad: "46510",
    descActividad: "Venta al por mayor de equipo",
    direccion: {
      departamento: "06",
      municipio: "20",
      complemento: "Colonia Centro, San Miguel",
    },
    correo: "compras@example.com",
  },
  items: [{ descripcion: "Equipo", cantidad: 1, precioUni: 125 }],
}, { idempotencyKey: "pedido-ERP-1042" });

Si se trata de una FE anónima, la petición mínima no lleva receptor:

typescriptSDK · factura a consumidor final
const dte = await facta.issue({
  tipoDte: "01",
  items: [{ descripcion: "Café", cantidad: 2, precioUni: 1.50 }],
}, { idempotencyKey: "venta-POS-000417" });

La llamada HTTP directa envía el mismo cuerpo y las cabeceras de API y firma. En HTTP no envíe customerId: ese identificador solo lo resuelve localmente el SDK; incluya los campos del receptor que el tipo de DTE exige. Vea el modelo completo de Receptor y la guía del SDK TypeScript.

curlHTTP · CCF con receptor inline
curl -X POST "$FACTA_API_BASE_URL/v1/dte" \
  -H "X-Facta-Key: $FACTA_API_KEY" \
  -H "X-Facta-Sign-Key: $FACTA_SIGN_KEY" \
  -H "Idempotency-Key: pedido-ERP-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "tipoDte": "03",
    "receptor": {
      "nombre": "Comercial Ejemplo, S.A. de C.V.",
      "tipoDocumento": "36",
      "numDocumento": "06141234567890",
      "nrc": "1234567",
      "codActividad": "46510",
      "descActividad": "Venta al por mayor de equipo",
      "direccion": {
        "departamento": "06",
        "municipio": "20",
        "complemento": "Colonia Centro, San Miguel"
      },
      "correo": "compras@example.com"
    },
    "items": [{ "descripcion": "Equipo", "cantidad": 1, "precioUni": 125 }]
  }'

Líneas gravadas, exentas y no sujetas

Cada línea paga IVA por omisión (gravada). Si una línea no lo paga, usted lo dice con tipoVenta: "exenta" o "no_sujeta". Facta nunca lo deduce de la descripción ni del precio: una línea mal clasificada es un documento fiscal equivocado, y esa decisión es suya, con su contador.

  • gravada: la línea lleva IVA del 13 %. Es el valor por omisión.
  • exenta: la operación entra en el campo del IVA, pero la ley la libera (Ley del IVA, arts. 45 y 46). Va a ventaExenta y no lleva IVA. Un caso frecuente son los intereses de un préstamo, que están exentos solo cuando los cobra un banco u otra institución supervisada por la Superintendencia del Sistema Financiero, o una cooperativa de ahorro y crédito (art. 46, letra f). Quien presta sin esa condición cobra IVA, y su línea es gravada.
  • no_sujeta: la operación está fuera del hecho generador del impuesto. Va a ventaNoSuj y no lleva IVA.

El IVA se calcula solo sobre las líneas gravadas. En una factura (01) el precio ya lo incluye, así que una línea exenta o no sujeta se factura por su precio, sin IVA dentro. En un crédito fiscal (03) y en las notas el precio es neto y el 13 % se suma únicamente a lo gravado. Los totales salen separados (totalGravada, totalExenta, totalNoSuj) y montoTotalOperacion suma los tres más el IVA de lo gravado. Una factura con todas sus líneas exentas es válida y no lleva IVA.

El campo vale en 01, 03, 05 y 06. En una nota, use la clase que tenía la línea del documento que corrige. En una exportación (11), que es siempre de tasa cero, y en un sujeto excluido (14), que documenta una compra, el campo se rechaza con 422 validation_failed aunque diga gravada. Si manda productId, la clase del producto de su catálogo todavía no llega a la API: escriba tipoVenta en la línea.

jsoncuerpo · factura con tres clases de línea
{
  "tipoDte": "01",
  "items": [
    { "descripcion": "Equipo de oficina", "cantidad": 1, "precioUni": 113 },
    { "descripcion": "Intereses de préstamo", "cantidad": 1, "precioUni": 50, "tipoVenta": "exenta" },
    { "descripcion": "Reembolso de gastos", "cantidad": 1, "precioUni": 30, "tipoVenta": "no_sujeta" }
  ]
}

Con esos precios el documento suma totalGravada 113.00 (13.00 de IVA ya incluido: 113 ÷ 1.13 = 100), totalExenta 50.00, totalNoSuj 30.00 y un total a pagar de 193.00.

curlHTTP · factura con una línea exenta
curl -X POST "$FACTA_API_BASE_URL/v1/dte" \
  -H "X-Facta-Key: $FACTA_API_KEY" \
  -H "X-Facta-Sign-Key: $FACTA_SIGN_KEY" \
  -H "Idempotency-Key: venta-POS-000418" \
  -H "Content-Type: application/json" \
  -d '{
    "tipoDte": "01",
    "items": [
      { "descripcion": "Equipo de oficina", "cantidad": 1, "precioUni": 113 },
      { "descripcion": "Intereses de préstamo", "cantidad": 1, "precioUni": 50, "tipoVenta": "exenta" }
    ]
  }'

El tipo de línea del SDK de TypeScript (versión 0.3.0) todavía no declara tipoVenta: para estas líneas use la llamada HTTP directa.

Una limitación que debe conocer: la retención de renta. aplicarReteRenta (solo en el 14) junto a líneas exentas o no sujetas se rechaza con 422 retention_mixed_class_unsupported, sin gastar correlativo. La retención del 10 % del art. 156 del Código Tributario no depende del IVA, pero la base para esa mezcla todavía no está confirmada con un contador, y Facta prefiere no firmar una cifra posiblemente corta. Emita esas líneas en otro documento o no aplique la retención.

Retención y percepción de IVA, solo si usted las pide. aplicarRetencionIva (en el 01 y el 03) retiene el 1 % del monto neto gravado, y aplicarPercepcionIva (solo en el 03) agrega la percepción del 1 % sobre las líneas de bienes (tipoItem 1) después de descuentos. Las dos son opcionales y valen false si no las envía: quién retiene o percibe depende del comprador, algo que la API no puede ver. En el crédito fiscal, el total a pagar es montoTotalOperacion + ivaPerci − ivaRete; la percepción no forma parte de montoTotalOperacion. Enviarlas en otro tipo es 400 validation_failed. El tipo de línea del SDK de TypeScript todavía no las declara: use la llamada HTTP directa.

Qué puede contestar

Un 200 trae selloRecibido: es un documento fiscal. Un 202 significa que quedó firmado y Hacienda no respondió — no se reintenta, se retransmite en contingencia. Un 422 puede ser cuatro cosas distintas y solo una gasta correlativo; la referencia de POST /v1/dte las detalla una por una.

Archive siempre el jws, nunca el documento. Volver a serializar el JSON no reproduce los bytes cuya firma validó Hacienda — es la única forma de probar después que el documento es el que se emitió.

Otras guías

Escriba para buscar Por ejemplo: idempotencia, contingencia, 422, emitir.

↑ ↓ moverse Enter abrir Esc cerrar