# Emisión

> Emitir un DTE con la API de Simplo — el endpoint POST /api/v1/billing/dte, la Idempotency-Key obligatoria y el flujo asíncrono hacia el SII.

> **Sandbox primero:**
> Cualquier cuenta emite hoy mismo en el **sandbox** con una key
> `sk_simplo_sandbox_` self-serve — ver [Quickstart](/comienza/quickstart/).
> La emisión en **producción** por API key se habilita por empresa
> ([Pasar a producción](/comienza/pasar-a-produccion/)).

## El endpoint

| Método | Path                        | Descripción                                                                 |
| ------ | --------------------------- | --------------------------------------------------------------------------- |
| `POST` | `/api/v1/billing/dte`       | Emitir un DTE (`Idempotency-Key` obligatoria). Responde `201` con estado `firmado`; el envío al SII es asíncrono. |
| `GET`  | `/api/v1/billing/dte`       | Listar DTEs con paginación por cursor y filtros por tipo, estado, fecha y receptor. |
| `GET`  | `/api/v1/billing/dte/{id}`  | Detalle y estado actual de un DTE (útil para polling).                      |
| `GET`  | `/api/v1/billing/dte/{id}/pdf` | Representación impresa (PDF con timbre PDF417).                          |
| `GET`  | `/api/v1/billing/dte/{id}/xml` | XML firmado del documento.                                               |
| `POST` | `/api/v1/billing/dte/send-boletas` | Enviar un **conjunto** de boletas al SII en **un** envelope EnvioBOLETA (`Idempotency-Key` obligatoria por API key). |

Estas seis rutas son, además, **toda** la superficie que una API key
`billing:dte` puede llamar — el resto de `/api/v1/billing/*` es solo-sesión
(ver [la tabla del quickstart](/comienza/quickstart/#qué-puede-hacer-tu-api-key)).
Con el SDK (aún no publicado), todo esto será `simplo.dtes.emit`, `.list`,
`.retrieve`, `.pdf` y `.xml` — ver el [quickstart](/comienza/quickstart/).

## Reglas que hacen correcta una emisión

1. **Los montos son pesos chilenos (CLP) enteros.** Sin centavos, sin
   floats para dinero. En facturas `precio` es **neto** y la API agrega el
   IVA (19%); en boletas (39/41) `precio` es **bruto** (IVA incluido) y el
   motor lo desglosa por dentro.
2. **`emit` retorna antes del veredicto del SII.** Persiste `dte.id`, trata
   `firmado` como "en vuelo" y resuelve el estado final con polling de
   `GET /api/v1/billing/dte/{id}` hasta un estado terminal (`aceptado`,
   `rechazado`, `con_reparos` o `error_envio`). (La entrega de
   [webhooks](/referencia/webhooks/) aún no está activa.)
3. **Las boletas (tipo 39 y 41) exigen `ind_servicio`** — usa `3` (boleta de
   venta y servicios). Omitirlo responde `400 IND_SERVICIO_REQUERIDO`, con
   `action: "set_ind_servicio"` y `context.field`. Las facturas (33) no lo
   llevan.
4. **Guarda `dte.id` y `dte.folio` en tu lado** (ej. en la fila de la orden)
   inmediatamente después de emitir.
5. **La API valida los montos por ti — y nombra el campo.** `precio`
   negativo, `cantidad: 0` y total en cero responden
   `400 MONTO_INVALIDO` con `context.field` (`detalle[0].precio`) y
   `received_value`; **no** se emite ni se quema folio. Medido el 2026-09-04.
   Igual conviene validar en tu lado para no gastar un round-trip.
6. **No envíes `metadata` — ahora te lo decimos con un error.** El campo
   todavía no se persiste, así que no volvería en `GET /api/v1/billing/dte/{id}`.
   Antes se aceptaba y se descartaba en silencio; desde el 2026-09-10 una
   `metadata` con contenido responde **`422 BILLING_METADATA_NOT_SUPPORTED`**
   y **no se emite nada**. Preferimos que pierdas un round-trip hoy a que
   pierdas tu id de correlación sin enterarte. `metadata: {}` y
   `metadata: null` siguen pasando (no pierden nada). Tu correlación vive en
   tu base, keyed por el `dte.id` que devuelve la emisión.

   Si recibes este 422 al **reintentar** una emisión que pudo haber salido
   antes de este cambio: **no mintees una key nueva** — eso emitiría el
   documento dos veces (y quitar `metadata` reintentando con la misma key daría
   `IDEMPOTENCY_BODY_MISMATCH`). Busca primero el documento en
   `GET /api/v1/billing/dte` por folio/monto/fecha; **no hay búsqueda por
   `Idempotency-Key`**, así que es una revisión manual, no una recuperación
   automática. Esta ventana se cierra sola: las keys completadas se retienen 24
   horas.
7. **Boletas de monto alto: identifica al pagador (Res. Ex. SII 44/2025).**
   Una boleta de monto alto debe identificar a quien paga; el consumidor final
   genérico `66666666-6` no basta. **Simplo no valida esta regla**: la emisión
   se acepta igual y la responsabilidad tributaria queda en el emisor. No la
   validamos porque exigiría resolver el umbral en pesos, y un valor vencido o
   no disponible bloquearía emisiones legítimas — negarse a vender es peor
   falla que la que se evita.

   Dónde se identifica al pagador en nuestra API: en `receptor`, que **ya es
   obligatorio** en toda emisión — mandas ahí el **RUT real** y la
   `razon_social` en vez de `66666666-6`. No necesitas ningún campo nuevo.

   Lo que **no** afirmamos: ni el umbral exacto en pesos ni su fecha de
   valorización, ni que identificar al receptor sea **por sí solo** suficiente
   para cumplir la resolución (hay requisitos adicionales para software de
   mercado que nuestro contrato no expone hoy). Confirma el alcance con tu
   asesor tributario o con el texto de la resolución: no publicamos una
   garantía de cumplimiento que no podemos verificar.
8. **Puedes emitir en paralelo.** La carrera de asignación de folios que
   producía un `400 CAF_NO_DISPONIBLE` **espurio** está corregida: el
   allocator espera el lock en vez de saltárselo. Medido el 2026-09-04 contra
   producción: 20 emisiones simultáneas → 20 folios únicos, cero espurios.
   Recuerda que el límite sigue siendo 60 req/min **por empresa**.
9. **No bloquees el flujo del usuario esperando la aceptación del SII** —
   puede tardar. Emite, responde y reconcilia de forma asíncrona.

## Boletas: el conjunto va en UN envelope

Emitir con `auto_send` manda **un envelope por documento**. Cuando necesitas
que un conjunto de boletas viaje al SII en **un solo `EnvioBOLETA`** —el caso
del cierre de turno, y el que exige el Set de Pruebas de certificación— la
emisión y el envío son dos pasos separados:

1. `POST /api/v1/billing/dte` por cada boleta, con `auto_send: false`. Cada
   una responde `201` con su `id` y su `folio`, en estado `firmado`.
2. `POST /api/v1/billing/dte/send-boletas` **una vez**, con todos los `id` del
   paso anterior. Ese es el envelope.

### Ejemplo completo: tres boletas, un envelope

```bash
set -euo pipefail
IDS=()

# Usa TUS identificadores de negocio, iguales en cada reintento del MISMO
# cierre. Si cambias la key reemites; si repites la key con otro payload, 422.
RUN_ID="caja-01-2026-09-10-turno-tarde"

# Paso 1 — emitir cada boleta SIN enviarla (auto_send: false)
for N in 1 2 3; do
  RESP=$(curl -sX POST https://api.simplo.cl/api/v1/billing/dte \
    -H "X-Api-Key: $SIMPLO_API_KEY" \
    -H "Idempotency-Key: $RUN_ID-boleta-$N" \
    -H 'Content-Type: application/json' \
    -d '{
      "tipo_dte": 39,
      "ind_servicio": 3,
      "auto_send": false,
      "receptor": {"rut":"66666666-6","razon_social":"Consumidor final",
                   "giro":"Particular","direccion":"Sin direccion",
                   "comuna":"Santiago","ciudad":"Santiago"},
      "detalle": [{"nombre":"Cafe","cantidad":1,"precio":2500}]
    }')
  ID=$(jq -r '.id // empty' <<<"$RESP")
  # Aborta AQUÍ si una boleta no se emitió. Seguir de largo mete un id vacío
  # en la lista y terminas enviando un envelope incompleto sin darte cuenta.
  [ -n "$ID" ] || { echo "boleta $N falló: $RESP" >&2; exit 1; }
  IDS+=("$ID")
done

# Paso 2 — un SOLO envelope con las tres
ENVIO=$(curl -sX POST https://api.simplo.cl/api/v1/billing/dte/send-boletas \
  -H "X-Api-Key: $SIMPLO_API_KEY" \
  -H "Idempotency-Key: $RUN_ID-envio" \
  -H 'Content-Type: application/json' \
  -d "$(jq -nc '{dte_ids: $ARGS.positional}' --args "${IDS[@]}")")

# Paso 3 — comprueba que viajaron TODAS (ver "Lo que te va a morder")
[ "$(jq -r '.num_dtes' <<<"$ENVIO")" = "${#IDS[@]}" ] \
  || { echo "enviamos MENOS boletas de las pedidas: $ENVIO" >&2; exit 1; }
echo "$ENVIO"
```

Respuesta:

```json
{
  "envio_id": "6f1c0a2e-...",
  "track_id": "918273645",
  "estado": "enviado",
  "num_dtes": 3
}
```

El `track_id` es del **envelope completo**, no de cada boleta. Guárdalo, pero
ten claro que **ninguna ruta pública lo acepta como consulta**: sirve para
soporte y para hablar con el SII. El veredicto de cada boleta lo sigues como
siempre con `GET /api/v1/billing/dte/{id}`.

### Lo que te va a morder a las 3 AM

- **Entre 1 y 500 boletas por envelope.** Con 501 falla el envío **completo**:
  parte la jornada en tandas **antes** de llamar, no después del error.
- **Manda `dte_ids` distintos y compara `num_dtes` contra el largo de tu
  lista.** Con `200` puedes haber enviado menos de las que pediste: los ids que
  no existen en tu empresa se omiten (si faltan **todos**, ahí sí es `400`), y
  los ids **repetidos** colapsan aunque los documentos existan. El paso 3 del
  ejemplo hace exactamente esa comparación.
- **Solo entran boletas** (tipo 39 y 41). Un `id` de factura 33 en la lista
  aborta el envío **completo** con `400`.
- **Solo entran boletas en estado `firmado` o `error_envio`.** Una boleta ya
  enviada aborta el envío **completo** con `400`.
- **El `track_id` es del envelope; ninguna ruta pública lo acepta como
  consulta.** Guárdalo para soporte y para hablar con el SII. El veredicto por
  documento se consulta con `GET /api/v1/billing/dte/{id}` (campos `tracking` /
  `sii_status`). El `estado: "enviado"` de esta respuesta es el resultado de la
  **subida**, no el veredicto final del SII.

### Reintentos: léelo antes de reintentar

Esta es la parte que preferimos decirte incómoda a decírtela tranquilizadora.
**El envío no es atómico.** El motor lee los estados, sube al SII y **recién
después** persiste el resultado y mueve los documentos; los fallos al
actualizar cada documento solo quedan en el log. De ahí salen tres hechos:

- **Un `400` no prueba que no se subió nada.** Un fallo posterior a la subida
  responde `400` con las boletas todavía en `firmado`.
- **Que las boletas sigan en `firmado` tampoco lo prueba**, por lo mismo.
- **Dos llamadas solapadas pueden ambas leer `firmado` y ambas subir.** No hay
  un candado que lo impida, y la `Idempotency-Key` —obligatoria por API key—
  **no** tiene replay en esta ruta: la misma key vuelve a ejecutar.

Por lo tanto, ante cualquier error o timeout: **no reintentes a ciegas.**
Consulta `GET /api/v1/billing/dte/{id}` de las boletas involucradas y, si el
estado no te deja decidir, escala a soporte con tus `dte_ids`, el `envio_id` y
la `Idempotency-Key` que usaste, en vez de volver a enviar. Una boleta enviada
dos veces es un problema tributario; una llamada de soporte, no.

- **`estado: "ambiguo"`** significa que el SII respondió que ya había recibido
  ese envelope (código 99). Las boletas quedan como estaban. La reconciliación
  periódica **puede** resolver el envelope, pero no garantiza propagar un
  estado terminal a cada documento, y sin `track_id` solo agota reintentos:
  **no asumas que se arregla solo**. Guarda `envio_id`, `track_id` y tus
  `dte_ids`, revisa el estado de los documentos y escala si no avanza.
- **Esta ruta no emite nada:** no crea documentos y no quema folios. Solo
  empaqueta y envía boletas que tu empresa ya emitió, y solo las tuyas.

## Idempotencia

La emisión exige un header `Idempotency-Key` — un UUID es lo recomendado,
pero sirve cualquier string único y estable (no hay largo mínimo/máximo
aplicado). Con el SDK:

- `dtes.emit` genera una `Idempotency-Key` (UUID) automáticamente por llamada
  — un reintento en vuelo nunca puede duplicar una factura. **No** construyas
  una capa extra de dedup alrededor de llamadas individuales.
- Para dedup **entre procesos** (un job reintentado por tu cola), pasa tu
  propia key estable:

  ```ts
  await simplo.dtes.emit(params, { idempotencyKey: `order-${orderId}` });
  ```

  La misma key + el mismo body dentro de 24 horas devuelve el resultado
  original. Una emisión repetida (misma key, mismo body) llega marcada con el
  header de respuesta `X-Idempotency-Replay: true`. Las keys completadas se
  retienen por 24 horas.
- Nunca reutilices una idempotency key con un body distinto — eso es un
  `ValidationError` con `code: 'IDEMPOTENCY_BODY_MISMATCH'`.

| `code`                      | Status | Significado                                                                    |
| --------------------------- | ------ | ------------------------------------------------------------------------------ |
| `IDEMPOTENCY_IN_PROGRESS`   | 409    | La misma key se está procesando ahora mismo — espera y consulta el resultado   |
| `IDEMPOTENCY_BODY_MISMATCH` | 422    | La key se reutilizó con un body **distinto** — usa una key nueva               |

Si al emitir recibes un `503` por el servicio de idempotencia temporalmente no
disponible, reintenta con la **misma** key.

## Relacionado

- [Tipos de DTE](/referencia/tipos-de-dte/) — códigos y requisitos por tipo
- [Estados](/referencia/estados/) — el ciclo de vida completo
- [Errores](/referencia/errores/) — clases de error y reintentos
- [Límites](/referencia/limites/) — rate limits por empresa

---

Versión HTML: https://docs.simplo.cl/referencia/emision/ · Índice para agentes: https://docs.simplo.cl/llms.txt
