Comges DTE

Buscar en la documentación

Escribí para filtrar guías, endpoints y webhooks.

Guía

Modelo asíncrono y polling

La emisión responde antes de que el SII conteste. Esta guía explica por qué, qué recibís en ese momento y cómo averiguar en qué terminó el documento.

Cómo funciona

No hay modos que elegir: no existen parámetros sendMode ni responseFormat. Pero la emisión tiene dos respuestas exitosas posibles, y tu código tiene que manejar las dos:

RespuestaCuándoQué trae
201 CreatedHabía folio disponible. Es el caso normal en régimen.El documento completo: id, folio, estadoSii.
202 AcceptedNo había folio: el documento es válido, quedó encolado y ya se pidieron folios al SII.Un ticket: ticketId. No trae id ni folio.

1. La emisión responde al instante

Reservamos el folio del CAF, armamos el XML, lo firmamos, generamos el timbre y respondemos 201. El folio que recibís es definitivo. Si no hay folio, en vez de fallar respondemos 202 con un ticket.

2. El envío al SII ocurre después

El documento viaja al SII en segundo plano. Su estadoSii converge a Aceptado o Rechazado sin que hagas nada.

El ciclo completo

1. Emitir

POST /api/public/v1/dte
curl -X POST https://api-publica.dtecomges.cl/api/public/v1/dte \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tipoDte": 39,
    "transactionId": "e2f0b1c4-77aa-4f2b-9d31-8c5a0e6b1d20",
    "receptor": { "rut": "66666666-6", "razonSocial": "Cliente" },
    "detalles": [
      { "nroLinea": 1, "nombreItem": "Producto retail", "cantidad": 1, "precioUnitario": 9990 }
    ],
    "indServicio": 3
  }'

2. Respuesta inmediata

201 Created
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "8b4c2f1e-9a3d-4e8f-b21c-5d6e7f8a9b0c",
  "tipoDte": 39,
  "folio": 1049,
  "ambiente": "Certificacion",
  "fechaEmision": "2026-07-27",
  "montoNeto": 9990,
  "montoExento": 0,
  "montoIva": 1898,
  "montoTotal": 11888,
  "montoNF": 0,
  "estadoSii": "Pendiente",
  "trackId": null,
  "creadoEn": "2026-07-27T14:30:15Z",
  "offsetUtcHoras": -4
}

2b. Partiendo del precio al público

El mismo producto, pero al revés: querés que la boleta totalice $9.990 exactos. Enviás el neto — round(9990 / 1,19) = 8395 — y la respuesta trae montoNeto: 8395, montoIva: 1595 y montoTotal: 9990.

POST /api/public/v1/dte — desde el precio al público
# Objetivo: que la boleta totalice $9.990 en la caja.
# precioUnitario va NETO -> se envía round(9990 / 1,19) = 8395.
curl -X POST https://api-publica.dtecomges.cl/api/public/v1/dte \
  -H "X-Api-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tipoDte": 39,
    "receptor": { "rut": "66666666-6", "razonSocial": "Cliente" },
    "detalles": [
      { "nroLinea": 1, "nombreItem": "Producto retail", "cantidad": 1, "precioUnitario": 8395 }
    ],
    "indServicio": 3
  }'

La otra respuesta: 202 y un ticket

Cuando no hay folio disponible, la emisión no falla: el documento ya fue validado, se encola y se piden folios al SII automáticamente. La respuesta es un 202 con un ticketId.

Le pasa sobre todo a las empresas recién dadas de alta: el SII autoriza timbrajes de a pocos folios al principio, así que es el caso que más probablemente te toque en tu primera integración real.

202 Accepted
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "ticketId": "3f21a9c8-0e6b-4d51-8a77-1c2b3d4e5f60",
  "estado": "Pendiente",
  "tipoDte": 33,
  "ambiente": "Certificacion",
  "posicionEnCola": 1,
  "intentos": 0,
  "documentoId": null,
  "codigoError": null,
  "mensaje": "No hay folios disponibles; se solicitaron al SII.",
  "esErrorTerminal": false,
  "resumen": "Factura 33 — Cliente Demo S.A.",
  "solicitadoEn": "2026-07-27T14:30:12Z",
  "terminadoEn": null,
  "offsetUtcHoras": -4
}

Resolver el ticket

Consultá GET /api/public/v1/dte/pendientes/{ticketId} (scope dte:read) hasta que estado sea terminal. Con Completado llega el documentoId, y desde ahí el flujo sigue igual que un 201.

estadoSignificado¿Terminal?
PendienteEn la cola, esperando que el SII entregue folios.No
ProcesandoHay folio y se está emitiendo el documento en este momento.No
CompletadoEl documento se emitió. El campo documentoId trae su id.
ErrorLa emisión falló. Mirá codigoError y esErrorTerminal para saber si conviene actuar o esperar.
CanceladoEl ticket fue cancelado y no va a emitirse.
GET /api/public/v1/dte/pendientes/{ticketId}
curl https://api-publica.dtecomges.cl/api/public/v1/dte/pendientes/3f21a9c8-0e6b-4d51-8a77-1c2b3d4e5f60 \
  -H "X-Api-Key: $API_KEY"

# Cuando el SII entrega el folio:
# { ..., "estado": "Completado", "documentoId": "8b4c2f1e-..." }
#
# Y con ese documentoId ya podés consultar el DTE como cualquier otro:
# GET /api/public/v1/dte/8b4c2f1e-...

Si el ticket termina en Error, mirá esErrorTerminal: si es true no se arregla esperando (por ejemplo, el SII no autoriza folios de ese tipo para la empresa) y hay que actuar. El codigoError te dice cuál es el caso.

emitir-y-resolver-ticket.js
async function emitir(cuerpo, apiKey) {
  const res = await fetch('https://api-publica.dtecomges.cl/api/public/v1/dte', {
    method: 'POST',
    headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' },
    // transactionId SIEMPRE: es lo que hace seguro cualquier reintento.
    body: JSON.stringify(cuerpo),
  })

  if (res.status === 201) return { emitido: await res.json() }

  if (res.status === 202) {
    // El documento es válido y está encolado. NO reemitir.
    const { ticketId } = await res.json()
    return { ticketId }
  }

  throw new Error(`Emisión fallida: ${res.status}`)
}

// Resolver el ticket: pollear hasta un estado terminal.
async function resolverTicket(ticketId, apiKey) {
  const TERMINALES = ['Completado', 'Error', 'Cancelado']

  for (const espera of [5000, 10000, 20000, 40000, 60000]) {
    await new Promise((r) => setTimeout(r, espera))

    const res = await fetch(
      `https://api-publica.dtecomges.cl/api/public/v1/dte/pendientes/${ticketId}`,
      { headers: { 'X-Api-Key': apiKey } },
    )
    const ticket = await res.json()

    if (ticket.estado === 'Completado') return ticket.documentoId
    if (TERMINALES.includes(ticket.estado)) {
      // esErrorTerminal: true significa que no se arregla esperando.
      throw new Error(`${ticket.codigoError}: ${ticket.mensaje}`)
    }
  }

  return null // sigue en cola: reintentá el polling más tarde
}

3. Consultar el estado

GET /api/public/v1/dte/{id}
curl https://api-publica.dtecomges.cl/api/public/v1/dte/8b4c2f1e-9a3d-4e8f-b21c-5d6e7f8a9b0c \
  -H "X-Api-Key: $API_KEY"

4. Estado final

200 OK
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "8b4c2f1e-9a3d-4e8f-b21c-5d6e7f8a9b0c",
  "tipoDte": 39,
  "folio": 1049,
  "estadoSii": "Aceptado",
  "trackId": 2394857620,
  "montoTotal": 11888,
  "creadoEn": "2026-07-27T14:30:15Z",
  "offsetUtcHoras": -4
}

Valores de estadoSii

El estado es un texto, tanto en el DTE nacional como en exportación (GET /api/public/v1/exportacion/{id}). Compará siempre contra la cadena, nunca contra un número.

estadoSiiSignificado¿Final?
PendienteFirmado y persistido, todavía no salió al SII. Es el estado con el que responde la emisión.No
EnviadoEl SII recibió el sobre y asignó trackId. Falta el veredicto.No
AceptadoEl SII lo aceptó conforme.
RechazadoEl SII lo rechazó. Revisá el detalle en el portal.
AceptadoConReparosAceptado con observaciones. Es válido, pero conviene revisarlo.
AnuladoAnulado mediante una nota de crédito posterior.
ErrorEnvioFalla técnica de envío (timeout, 5xx del SII). Se reintenta automáticamente desde la cola.No

Polling recomendado

Primer intento a los 5 s y después backoff: 10 s, 20 s, 40 s, 60 s. Frená en cuanto llegues a un estado final. Cuidá el rate limit si tenés muchos documentos en vuelo: conviene un worker que recorra los pendientes en lote, y no un poll agresivo por documento.

esperar-estado-final.js
const FINALES = ['Aceptado', 'AceptadoConReparos', 'Rechazado']

async function esperarEstadoFinal(id, apiKey) {
  const esperas = [5000, 10000, 20000, 40000, 60000, 60000]

  for (const espera of esperas) {
    await new Promise((r) => setTimeout(r, espera))

    const res = await fetch(`https://api-publica.dtecomges.cl/api/public/v1/dte/${id}`, {
      headers: { 'X-Api-Key': apiKey },
    })
    const dte = await res.json()

    if (FINALES.includes(dte.estadoSii)) return dte
    // "Pendiente" y "Enviado" son estados de tránsito: seguir esperando.
    // "ErrorEnvio" es reintentable del lado nuestro; también se sigue esperando.
  }

  throw new Error('El documento no alcanzó un estado final en el tiempo esperado')
}

Descargar XML y PDF

Ambos están disponibles desde la emisión, sin esperar al SII. Estos dos endpoints piden la misma API Key, así que son para tu backend. Para hacérselos llegar al receptor está la otra vía: los enlaces firmados que la propia emisión devuelve, acá abajo.

Descargas
# El XML y el PDF existen desde la emisión: no hace falta
# esperar el veredicto del SII para descargarlos.
curl https://api-publica.dtecomges.cl/api/public/v1/dte/{id}/xml \
  -H "X-Api-Key: $API_KEY" -o dte.xml

curl "https://api-publica.dtecomges.cl/api/public/v1/dte/{id}/pdf?formato=80mm" \
  -H "X-Api-Key: $API_KEY" -o dte.pdf

¿Siguiente? → Errores y reintentos para manejar bien el caso ambiguo del timeout. El detalle completo de cada cuerpo de respuesta está en la referencia de la API, que es la fuente de verdad.