Ir al contenido

Documentos Tributarios Electrónicos (DTE)

Un DTE es un documento tributario que se emite ante el SII de Chile. La API cubre nueve tipos, desde la factura afecta hasta la liquidación factura, con un mismo endpoint de emisión.

Esta guía es conceptual: explica el flujo, los modos y las restricciones que se descubren en runtime. El detalle exhaustivo de campos, tipos y esquemas de respuesta vive en la especificación OpenAPI — acá no se duplica.

Tres cosas tienen que existir en tu tenant antes del primer POST:

RequisitoQué es¿Por API?
Emisor DTELa empresa que emite, con su certificado digital vigente delegado a la plataforma. Su identificador es el emisorDteId que pide el endpoint de emisión, por tokenización SII
Folios CAFLos rangos de folios que el SII autoriza por tipo de documento. Sin folios disponibles, la emisión fallaNo
dirOrigenLa dirección de origen configurada en el emisor. El SII la exige en el encabezadoNo

El alta de un emisor DTE sí se hace íntegramente por API: el camino es la tokenización SII, donde el titular entrega su certificado en un wizard de Redcumbre y tú recibes el emisor de vuelta. Está documentado en Emisores Tributarios y Emisores DTE. Los folios CAF y la dirOrigen no se resuelven por API: si te faltan, escribe a tu contacto en REDCUMBRE.

CódigotipoDteDocumento
33FACTURA_AFECTAFactura Electrónica Afecta
34FACTURA_EXENTAFactura Electrónica Exenta
39BOLETA_AFECTABoleta Electrónica Afecta
41BOLETA_EXENTABoleta Electrónica Exenta
43LIQUIDACION_FACTURALiquidación Factura
46FACTURA_COMPRAFactura de Compra (cambio de sujeto)
52GUIA_DESPACHOGuía de Despacho
56NOTA_DEBITONota de Débito
61NOTA_CREDITONota de Crédito

El canal de emisión es del emisor, no del tipo de documento

Sección titulada «El canal de emisión es del emisor, no del tipo de documento»

Es lo primero que conviene entender, porque de ahí cuelgan el límite de líneas, el modo de emisión y la forma exacta de la respuesta.

El canal es una propiedad del emisor DTE configurado en tu tenant. Una misma factura afecta se emite por un canal o por otro según cómo esté habilitado tu emisor: no lo decide el tipo de documento ni lo eliges en la petición. Te lo indica tu contacto en REDCUMBRE, junto con el resto de los prerequisitos.

CanalCómo emiteQué cambia para ti
Portal MiPymeLa plataforma opera el portal del SII con el certificado delegadoMáximo de 10 líneas de detalle. Los tres modos de emisión aplican
Firma local (FULL_DTE)La plataforma arma el XML, lo firma y lo envía al SII en loteHasta 60 líneas de detalle. El modo que pidas no aplica: la respuesta siempre viene con modo: "ASINCRONO", con el documento y su folio ya asignados, y con estadoSii: "PENDIENTE_ENVIO"
API Oficial SIISólo las boletas 39 y 41, con CAF y certificado digital propiosHasta 500 líneas de detalle
Integración a medida (API_EXTERNA)La plataforma delega la emisión en el sistema que ya usasEl máximo de líneas lo fija ese sistema. El modo que pidas no aplica: la respuesta viene con modo: "ASYNC_EXTERNO", estado: "PENDIENTE_EXTERNO" y folio: 0 — el folio lo asigna el sistema externo y llega después

Un RUT puede tener dos emisores: facturación y boletas

Sección titulada «Un RUT puede tener dos emisores: facturación y boletas»

Las boletas electrónicas (39 y 41) salen de un emisor propio, el de boletas, distinto del de facturación. Un mismo RUT puede tener los dos, y cada uno tiene su emisorDteId: el que mandas en la petición decide por cuál sale el documento.

Cómo se dan de alta los dos, y cómo llegan sus ids, está en Emisores.

POST /{tenantSlug}/dte

Cuatro campos son obligatorios: emisorDteId, tipoDte, receptor y lineas. El emisorDteId sale del panel web o de la tokenización — ver Antes de emitir. Dentro de receptor, el giro es obligatorio en todos los tipos salvo las boletas 39 y 41: si falta, la petición responde 400 con receptor.El giro del receptor es obligatorio para este tipo de DTE.

El camino natural para obtenerlo es POST /{tenantSlug}/herramientas/lookup-rut, pero giroGlosa normalmente llega null: el SII solo entrega el giro cuando el RUT consultado es el mismo del certificado que consulta, y al facturarle a un cliente nunca lo es. Hay que resolverlo en cascada:

const { data } = await lookupRut(rutReceptor);
const giro = data.giroGlosa // null salvo lookup auto-referencial
?? data.actividadesEconomicas?.[0]?.descripcion // la actividad principal del SII
?? await pedirGiroAlUsuario(); // último recurso

Los nombres de campo son literales: el array es data.actividadesEconomicas —no data.actividades— y cada elemento trae { codigo, descripcion }. El SII trunca el giro a 40 caracteres al recibir el DTE, así que conviene mandarlo ya recortado y revisado, no una glosa larga cortada a ciegas.

Ventana de terminal
curl -X POST https://api.redcumbre.cl/tu-tenant/dte \
-H "Authorization: Bearer $REDCUMBRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emisorDteId": "clxyz123abc456def",
"tipoDte": "FACTURA_AFECTA",
"formaPago": "CREDITO",
"receptor": {
"rut": "77438768-4",
"razonSocial": "REDCUMBRE SPA",
"giro": "VENTA AL POR MENOR DE PRODUCTOS EN COMERCIOS ESPECIALIZADOS",
"direccion": "LOS PEUMOS ST. 3 LT B",
"comuna": "BULNES",
"email": "contacto@empresa.cl"
},
"lineas": [
{
"nombre": "Servicio de consultoría especializada",
"cantidad": 1,
"precioUnitario": 100000,
"subtotal": 100000
}
],
"idempotencyKey": "orden-12345"
}'

Respuesta (201). El documento va en data.dte y los montos en data.montos — no sueltos en data:

{
"success": true,
"data": {
"modo": "SINC_PARCIAL",
"dte": {
"id": "dte_5fe4ff45-...",
"tipoDte": "FACTURA_AFECTA",
"folio": 25403,
"dhdrCodigo": "2831022103",
"estado": "EMITIDO",
"modoEmision": "SINC_PARCIAL"
},
"montos": {
"montoNeto": 100000,
"montoExento": 0,
"montoIva": 19000,
"montoTotal": 119000
},
"urls": {
"pdfUrl": "https://api.redcumbre.cl/public/documentos/{token}/pdf",
"xmlUrl": "https://api.redcumbre.cl/public/documentos/{token}/xml",
"pdfSiiUrl": null,
"webUrl": "https://app.redcumbre.cl/tu-tenant/dte/dte_5fe4ff45-..."
},
"estadoPdfSii": "PENDIENTE",
"mensaje": "DTE emitido exitosamente"
}
}

Igual que la 33, cambiando el tipo. Sin IVA: montoNeto y montoTotal coinciden.

Ventana de terminal
curl -X POST https://api.redcumbre.cl/tu-tenant/dte \
-H "Authorization: Bearer $REDCUMBRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emisorDteId": "clxyz123abc456def",
"tipoDte": "FACTURA_EXENTA",
"receptor": {
"rut": "77438768-4",
"razonSocial": "REDCUMBRE SPA",
"giro": "VENTA AL POR MENOR DE PRODUCTOS EN COMERCIOS ESPECIALIZADOS",
"direccion": "LOS PEUMOS ST. 3 LT B",
"comuna": "BULNES"
},
"lineas": [
{
"nombre": "Servicio educacional exento de IVA",
"cantidad": 1,
"precioUnitario": 250000,
"subtotal": 250000
}
]
}'

Es el tipo con más particularidades. Se usa cuando un mandatario liquida ventas a su mandante: las líneas describen lo vendido y las comisiones describen lo que el mandatario retiene.

Ventana de terminal
curl -X POST https://api.redcumbre.cl/tu-tenant/dte \
-H "Authorization: Bearer $REDCUMBRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emisorDteId": "clxyz123abc456def",
"tipoDte": "LIQUIDACION_FACTURA",
"receptor": {
"rut": "77438768-4",
"razonSocial": "REDCUMBRE SPA",
"giro": "VENTA AL POR MENOR DE PRODUCTOS EN COMERCIOS ESPECIALIZADOS",
"direccion": "LOS PEUMOS ST. 3 LT B",
"comuna": "BULNES"
},
"lineas": [
{
"nombre": "NETO FACTURAS ELECTRONICAS",
"cantidad": 8,
"precioUnitario": 18000,
"subtotal": 144000,
"tipoDocumentoLiquidar": 33
},
{
"nombre": "EXENTO FACTURAS ELECTRONICAS",
"cantidad": 3,
"precioUnitario": 12000,
"subtotal": 36000,
"esExento": true,
"tipoDocumentoLiquidar": 33
}
],
"comisiones": [
{ "tipoMovimiento": "C", "glosa": "Comisión por venta", "valorNeto": 5000 }
]
}'

tipoDocumentoLiquidar identifica qué tipo de documento se está liquidando en esa línea. Es exclusivo del 43:

CódigoDocumento
0Sin especificar (es el valor que se envía si omites el campo)
33Factura electrónica
34Factura exenta
39Boleta electrónica
48Pago de cheque
52Guía de despacho

esExento en una línea permite mezclar afectos y exentos en el mismo documento, cosa que los demás tipos no admiten.

El modo aplica a los emisores que emiten por Portal MiPyme. Con firma local se ignora, y la respuesta viene siempre con modo: "ASINCRONO" y el documento ya emitido.

modoQué haceCuándo usarlo
SINC_COMPLETOEmite y genera el PDF del SII antes de responderNecesitas el PDF en la misma petición
SINC_PARCIALDefault. Emite en forma síncrona; el PDF del SII se genera en segundo plano con reintentosEl caso general
ASINCRONOSolo valida y encola. Todo ocurre en segundo planoEstás emitiendo desde una petición web y no quieres que el usuario espere al SII

El modo de la respuesta informa cómo operó el carril; no anuncia la forma de la respuesta. Un emisor de firma local responde modo: "ASINCRONO" y trae el documento igual, porque lo que ocurre en segundo plano es el envío al SII, no la emisión.

La regla que sí se cumple en todos los canales es una sola:

if (respuesta.data.dte) {
// El documento existe y tiene folio. Sigue con tu flujo.
} else {
// Quedó encolado: consulta respuesta.data.intentoId.
}

Un cliente escrito como if (modo === "ASINCRONO") poll(intentoId) busca un intentoId que no existe en la mayoría de los canales.

Cuando la emisión queda encolada, la respuesta trae un intentoId en lugar del documento:

{
"success": true,
"data": {
"modo": "ASINCRONO",
"intentoId": "dtei_3f2a1b8c-...",
"estado": "PENDIENTE",
"mensaje": "Solicitud de emisión registrada. El DTE se procesará en segundo plano."
}
}

El estado del intento recorre PENDIENTE, PROCESANDO, REINTENTANDO y termina en EMITIDO o FALLIDO. Se consulta con GET /{tenantSlug}/dte/intento/{intentoId}. Para enterarte sin hacer polling, usa webhooks.

Con "saveAsDraft": true el documento se guarda sin emitir. La respuesta es plana —no trae dte ni montos— y no consume folio:

{
"success": true,
"data": {
"id": "dte_3b4e9827-...",
"estado": "BORRADOR",
"lineas": [{ "nombre": "Servicio de consultoría", "cantidad": 1, "precioUnitario": 100000, "subtotal": 100000 }],
"trazaPrecios": []
}
}

Se puede editar y se emite después con POST /{tenantSlug}/dte/{id}/emitir.

La emisión puede avisarle al receptor por email y por SMS. Son dos carriles independientes, y en ambos los destinatarios salen de la suma de tres orígenes, deduplicada:

CampoQué hace
receptor.emailEmail del receptor. Recibe el PDF del documento
receptor.telefonoTeléfono del receptor. Recibe el SMS con el enlace al PDF si notificarSmsDte está activo. Para el SMS tiene que ser un móvil chileno (569XXXXXXXX)
emailsAdicionalesHasta 20 destinatarios extra de email — el contador del cliente, por ejemplo
telefonosSmsAdicionalesHasta 10 destinatarios extra de SMS, móviles chilenos
notificarSmsDteActiva el SMS. Si no lo mandas, queda activo cuando tu tenant tiene el servicio de SMS habilitado
emailVoid / smsVoidExcluyen destinatarios de esta emisión. No borran nada de la agenda

Todo contacto que mandas en una emisión —los cuatro campos de arriba— queda guardado en la agenda de destinatarios DTE del cliente dentro de tu empresa, normalizado y sin duplicar. Desde la emisión siguiente al mismo receptor no hace falta repetirlo: ya está en la agenda y se notifica igual.

Eso vale también para los borradores: si guardas un borrador con contactos, la agenda se puebla ahí mismo, sin haber emitido.

Tres detalles que conviene tener presentes:

  • El teléfono se normaliza al guardarlo (+56 9 8430 624456984306244) y sólo entran móviles chilenos. Un fijo se acepta en el documento pero no llega a la agenda ni recibe SMS.
  • Las boletas a público general (66666666-6) no alimentan la agenda. La ficha sería compartida por todos los compradores anónimos.
  • emailVoid y smsVoid no borran de la agenda. Excluyen del envío de esa emisión y nada más; para sacar un destinatario definitivamente están los endpoints de la agenda del cliente.

El detalle de campos, largos y validaciones está en la especificación OpenAPI.

metadataAdicional es un objeto de pares clave-valor que se guarda como JSON junto al documento. No viaja al SII: no forma parte del XML firmado ni entra en el timbre. Sirve para alimentar la plantilla PDF de tu empresa con datos que el formato tributario no tiene dónde poner —el número de orden de compra, el centro de costo, el período facturado— y para dejarlos consultables después.

{
"emisorDteId": "clxyz123abc456def",
"tipoDte": "FACTURA_AFECTA",
"receptor": { "rut": "77438768-4", "razonSocial": "REDCUMBRE SPA", "giro": "VENTA AL POR MENOR", "direccion": "LOS PEUMOS ST. 3 LT B", "comuna": "BULNES" },
"lineas": [{ "nombre": "Servicio de consultoría", "cantidad": 1, "precioUnitario": 100000, "subtotal": 100000 }],
"metadataAdicional": {
"NumOrdenCompra": "OC-12345",
"CentroCosto": "CC-001",
"PeriodoFacturado": "FEB de 2026"
}
}

Cuatro cosas que conviene saber antes de usarlo:

  • Las claves las define tu plantilla PDF, no la API. No hay lista de nombres válidos ni tope de cantidad: lo que mandes se guarda tal cual. Si tu empresa no tiene plantilla personalizada, el dato queda almacenado y consultable, pero no se imprime en ninguna parte.
  • Manda los valores como string. Es lo que declara el schema (additionalProperties: string) y lo que la plantilla espera recibir.
  • No lo uses para datos que sí tienen campo propio. Un dato que el SII debe ver —referencias, descuentos, glosa del ítem— puesto acá no llega al documento tributario.
  • Vuelve en el detalle del documento. GET /{tenantSlug}/dte/{id} lo devuelve en metadataAdicional, y null si no mandaste nada.

idempotencyKey (máximo 128 caracteres) protege contra reintentos por timeout o doble click. Si ya existe un documento emitido para la misma combinación de emisor, ambiente, tipo y clave, se devuelve el original sin crear uno nuevo ni consumir folio.

Dos cosas que conviene tener claras antes de integrar:

  • El ambiente forma parte del alcance. La misma clave puede usarse una vez en certificación y una vez en producción. Cuando pases tu emisor a producción, las claves que gastaste probando no te devuelven los documentos de prueba: emiten los reales.
  • Anular libera la clave. Un documento anulado deja de ocuparla y la misma clave vuelve a emitir uno nuevo.

Un reintento sobre una clave ya usada responde 201 con "idempotent": true y el documento original en data.dte — con el folio, el receptor y los montos de la primera emisión, no los del payload que acabas de mandar. El campo a mirar para distinguir “emití” de “ya estaba emitido” es idempotent, no el status.

Ver Reglas transversales.

Estas son las que se descubren con un 400 en runtime si no se saben de antemano.

El precio que mandas es el precio final: las promociones no se aplican por API

Sección titulada «El precio que mandas es el precio final: las promociones no se aplican por API»

Una venta emitida por la API de integración no recibe las promociones vigentes del tenant. El precioUnitario que envías en cada línea es el que se factura, sin descuentos automáticos.

Es deliberado, no una limitación pendiente: quien integra por API ya calculó lo que va a cobrar. Un ERP que manda una línea de $10.000 y recibe una boleta por $6.666 lo trataría como un error — con razón, porque su propio cálculo dejó de ser el que salió en el documento.

Lo mismo aplica al endpoint XML (dte_xml_format1).

Cada línea debe tener precioUnitario >= 1. Un 0 se rechaza:

{ "statusCode": 400, "message": "El precio unitario debe ser al menos 1 en cada línea. 1 línea(s) tienen precio inválido." }

Hay tres excepciones, donde sí se aceptan líneas en 0:

  • Guía de despacho (52) sin precios, cuando se envía marcada como tal
  • Nota de crédito o débito (61 / 56) de corrección o anulación sin monto — las que sólo corrigen un texto o anulan, con referencia de código 1 o 2. El SII las certifica con total 0
  • Liquidación factura (43), que admite líneas glosa con precio 0 legítimo

El DTO además rechaza valores negativos en precioUnitario.

No es un número fijo. El máximo lo determina el canal de integración del emisor: 10 líneas de detalle por Portal MiPyme, 60 con firma local y 500 por la API Oficial SII de boletas. Si te pasas:

{ "statusCode": 400, "message": "El documento tiene 12 líneas de detalle, pero el emisor ... permite máximo 10. Reduzca la cantidad de líneas." }

El mensaje te dice el máximo aplicable a tu emisor. No lo asumas desde el código.

Largo del nombre de ítem: se trunca, no se rechaza

Sección titulada «Largo del nombre de ítem: se trunca, no se rechaza»

El campo nombre acepta hasta 255 caracteres y no falla si te pasas de 80. Lo que ocurre es que al enviarlo al SII se trunca a 80, que es el límite del organismo. El dato completo queda guardado en la plataforma y aparece íntegro en el PDF interno; el que viaja al SII va cortado.

Si el nombre importa entero en el documento tributario, mantenlo bajo 80 caracteres. Para detalle largo existe descripcionExtendida (hasta 1.000 caracteres), que no viaja al SII.

Un DTE 43 no se anula con nota de crédito ni de débito. Si lo intentas:

{ "statusCode": 400, "message": "Las Liquidaciones Factura (DTE 43) no pueden anularse con Nota de Crédito. Emita una nueva Liquidación Factura con valores negativos referenciando el documento original." }

La vía correcta es emitir otra liquidación con valores negativos, referenciando la original.

Y acá hay una asimetría que conviene entender, porque contradice la intuición:

  • Las líneas de detalle no admiten valores negativos. El esquema XML del SII lo prohíbe.
  • Las comisiones sí conservan el signo. Una comisión negativa —una devolución o un reverso— debe ir con su signo. Si se enviara en valor absoluto, la suma por línea contradiría el resumen de totales y el SII rechazaría el documento.

O sea: el signo de la reversión vive en las comisiones, no en las líneas.

ParaEndpoint
Listar documentosGET /{tenantSlug}/dte
Detalle de unoGET /{tenantSlug}/dte/{id}
PDF de la plataformaGET /{tenantSlug}/dte/{id}/pdf
PDF timbrado del SIIGET /{tenantSlug}/dte/{id}/pdf-sii
Estado ante el SIIPOST /{tenantSlug}/dte/{id}/verificar-estado-sii
Archivos asociados (XML, PDF)GET /{tenantSlug}/dte/{id}/archivos
Historial de eventosGET /{tenantSlug}/dte/{id}/logs

El índice completo de rutas del dominio está en /api-docs/index.txt.

CódigoCausa típica¿Reintentar?
400Payload inválido, precio bajo el mínimo, exceso de líneas, tipo de referencia no permitidoNo. Corrige el payload
403La API key no alcanza el endpoint, o el servicio de DTE no está habilitado en el tenantNo
404El emisorDteId no existe en tu tenantNo. Verifícalo en el panel (Configuración → Emisores, campo ID) o en la respuesta de tu tokenización
409Tu emisor tiene conflictos de sincronización pendientesNo. Se resuelve sobre el emisor; escribe a tu contacto en REDCUMBRE
422Sin folios CAF disponibles, o límite de crédito del cliente excedidoNo. Ver el aviso de abajo
5xxFalla de la API o del SII, con backoff. Usa idempotencyKey para que el reintento no duplique

Un rechazo del SII no es un 5xx: el documento se emite y queda con estadoSii en estado de rechazo. Se consulta con verificar-estado-sii, no se reintenta el POST.