Facturiadocs
Emisión

Crear documentos

La emisión mínima, la emisión completa, qué completamos por ti y cómo leer los documentos que ya emitiste.

La emisión mínima

El punto de guardar el perfil emisor en la empresa es que una factura son tres campos.

El receptor lleva giro y address porque el SII no acepta una factura sin ellos: acepta el SOBRE y después rechaza el DOCUMENTO, y el folio se gasta igual. Cuando ya le emitiste una vez a ese RUT, basta con receptor: {"rut": "…"} — el resto lo completamos desde tu documento anterior a ese mismo RUT. Si no hay ninguno, recibes 400 receptor_incomplete nombrando los campos exactos, antes de asignar folio. Las boletas (39/41) y una factura de compra extranjera (46) nunca los necesitan.

curl -sS https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Facturia-Empresa: $EMPRESA" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "tipo_dte": 33,
    "receptor": {
      "rut": "76543212-K",
      "giro": "Venta al por mayor de alimentos",
      "address": { "street": "Los Militares 5001, of. 302", "comuna": "Las Condes", "city": "Santiago" }
    },
    "items": [
      { "description": "Desarrollo a medida — agosto", "quantity": 1, "unit_price": 2400000 }
    ]
  }'

Qué completamos por ti

CampoDe dónde sale
Bloque emisor (RUT, razón social, giro, dirección, comuna, acteco)el perfil de la empresa
folioel siguiente folio del CAF activo para este tipo
issue_datehoy en America/Santiago
Razón social, giro, dirección y comuna del receptorla libreta de direcciones de la empresa, si no el registro de contribuyentes del SII, resuelto por RUT
totalscalculados desde items bajo las reglas tributarias del tipo
payment_terms, currency, indicatorsvalores por defecto sensatos (contado, CLP)

Si el receptor no se puede resolver y el tipo requiere esos campos, recibes 400 receptor_incomplete nombrando exactamente cuáles enviar — nunca un rechazo silencioso del SII tres minutos después.

La emisión completa

Todo lo que el SII te deja decir, dicho explícitamente.

curl -sS https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Facturia-Empresa: $EMPRESA" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "tipo_dte": 33,
    "external_id": "order-8842",
    "issue_date": "2026-08-14",
    "prices_include_tax": false,
    "receptor": {
      "rut": "76543212-K",
      "legal_name": "Comercial Andina Ltda.",
      "giro": "Venta al por mayor de alimentos",
      "address": { "street": "Los Militares 5001, of. 302", "comuna": "Las Condes", "city": "Santiago" },
      "contact": "[email protected]",
      "email_document_to": ["[email protected]", "[email protected]"]
    },
    "items": [
      {
        "description": "Licencia plataforma — plan Enterprise",
        "detail": "Periodo 2026-08-01 a 2026-08-31",
        "quantity": 12, "unit": "UN", "unit_price": 180000,
        "discount_percent": 5,
        "item_code": { "type": "INT1", "value": "PLAT-ENT" }
      },
      { "description": "Servicio exento de capacitación", "quantity": 1, "unit_price": 300000, "exempt": true }
    ],
    "global_discounts": [ { "type": "percent", "value": 2, "applies_to": "afecto", "reason": "Acuerdo comercial" } ],
    "references": [
      { "tipo_dte": 801, "folio": "OC-99213", "date": "2026-08-01", "code": null, "reason": "Orden de compra" }
    ],
    "payment": { "terms": "credito", "due_date": "2026-09-13", "method": "transferencia" },
    "totals": { "net": 2352000, "iva": 446880, "exempt": 300000, "total": 3098880 },
    "pdf": { "format": "carta", "cedible": true, "public": true },
    "xml": { "public": true },
    "notes": "Gracias por su compra.",
    "metadata": { "crm_deal": "D-1182", "cost_center": "LATAM" }
  }'

Los cuatro campos que conviene entender

Trampa

prices_include_tax es el origen del bug del 19%

Por defecto depende del tipo: true para boletas (39/41), false para todo lo demás. Ponlo explícito y normalizamos en cualquier dirección.

Es la fuente número uno de errores de 19% en la facturación chilena. Que sea un campo con nombre en vez de una convención implícita es deliberado: si tu sistema guarda precios brutos y emites una factura, dilo.

La normalización no es simétrica. En una factura (33/34/56/61/46) el <PrcItem>/<MontoItem> del SII siempre es NETO (DTE_v10.xsd: neto salvo que venga MntBruto, que nunca mandamos), así que prices_include_tax: true ahí se normaliza HACIA ABAJO en la puerta — los items que devolvemos, el PDF y el <PrcItem> quedan en neto (11990 → 10075.6303), y el total puede quedar un peso distinto del bruto cotizado. En una boleta (39/41), cuyo MontoItem es bruto por esquema, el precio se queda bruto sin importar prices_include_tax.

  • totals es opcional y, cuando viene, es una aserción. Si nuestro cálculo difiere aunque sea en un peso, recibes 422 totals_mismatch con ambas cifras y no se emite nada. Un sistema financiero debería mandarlo siempre.
  • totals.other_taxes es 0 salvo en un tipo 46 con withholding, donde lleva la retención en NEGATIVO (p. ej. -1891 si el IVA retenido es 1891) — es la única forma de que total = net + exempt + iva + other_taxes siga siendo cierto se retenga o no.
  • global_discounts acepta como máximo una entrada. El <DscRcgGlobal> del SII es un solo NroLinDR>1, así que una segunda entrada se rechaza con 400 invalid_field antes de reservar folio — júntalas en una, o pasa el resto a items[].discount_percent.
  • Los descuentos son del canal mercado. En una empresa con emission_channel=sfg se rechazan en la puerta los dos: global_discounts con 422 global_discount_not_available_for_channel, y cualquier items[].discount_percent mayor que 0 con 422 line_discount_not_available_for_channel, nombrando la línea. El formulario de emisión del portal del SII no tiene ningún campo de descuento que hayamos capturado — el formulario capturado trae comentadas tanto la columna «% Desc» por línea como sus entradas EFXP_PCT_DESC/EFXP_MNT_DESC —, así que un descuento aceptado ahí sería un descuento que el portal nunca ve: te cotizaríamos neto 80.000 / total 95.200 y el SII firmaría neto 100.000 / total 119.000. Se rechaza en vez de descartarse en silencio, porque la diferencia se la cobrarías a tu cliente en un documento que solo una nota de crédito puede deshacer. Métele el descuento al unit_price, o emite por emission_channel=mercado. Un discount_percent en 0 se acepta en los dos canales: es exactamente lo que el portal emitiría.
  • references con code: 1|2|3 son los códigos de referencia del SII (1 = anula el documento referenciado, 2 = corrige texto, 3 = corrige montos). Las referencias a documentos que no son DTE (orden de compra, contrato) usan los códigos de tipo_dte del SII en el rango 800 y omiten code. references[].date toma la issue_date del documento si se omite; references[].folio acepta número o string, y un número se convierte a su string decimal (<FolioRef> es xs:string en el esquema del SII).
  • metadata es un mapa de strings arbitrario (≤ 20 llaves, ≤ 500 caracteres cada una) que guardamos y devolvemos intacto. Nunca llega al SII.
  • email_document_to envía el PDF y el XML al receptor cuando el documento llega a accepted.

El objeto documento

{
  "id": "doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N",
  "object": "document",
  "mode": "live",
  "empresa": { "id": "emp_01K2R6ZC4P8QW1VN7T3MHDY9EK", "rut": "77928532-4" },
  "external_id": "order-8842",
  "tipo_dte": 33,
  "folio": 412,
  "issue_date": "2026-08-14",
  "status": "accepted",
  "receptor": { "rut": "76543212-K", "legal_name": "Comercial Andina Ltda." },
  "items": [ { "description": "Licencia plataforma — plan Enterprise", "quantity": 12, "unit_price": 180000, "amount": 2052000 } ],
  "totals": { "net": 2352000, "iva": 446880, "exempt": 300000, "other_taxes": 0, "total": 3098880 },
  "references": [],
  "pdf_url": "https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/pdf",
  "xml_url": "https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/xml",
  "sii": {
    "environment": "produccion",
    "track_id": "0252083112",
    "estado": "DOK",
    "glosa": "Documento Recibido por el SII, datos coinciden",
    "err_code": "0",
    "num_atencion": "8812394",
    "received_at": "2026-08-14T13:06:55Z",
    "last_checked_at": "2026-08-14T13:07:02Z"
  },
  "rejection_reasons": [],
  "metadata": { "crm_deal": "D-1182" },
  "created_at": "2026-08-14T13:04:11Z",
  "accepted_at": "2026-08-14T13:06:55Z"
}

Leer documentos

# Listar, del más nuevo al más antiguo, con filtros
curl -G https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -d status=accepted -d tipo_dte=33 \
  -d "issue_date[gte]=2026-08-01" -d "issue_date[lte]=2026-08-31" \
  -d limit=50

# Un documento
curl https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"

# Por folio (los folios son únicos por empresa+tipo, así que es una búsqueda legítima)
curl -G https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -d tipo_dte=33 -d folio=412

Filtros: status, tipo_dte, folio, receptor_rut, external_id, issue_date[gte|lte], created_at[gte|lte]. El mode es implícito de la llave.

Un status no reconocido devuelve 400 invalid_field nombrando los siete valores válidos — una página vacía se leería como «no tienes ninguno de esos», que es una respuesta distinta y mucho más confusa.

Cada filtro, status incluido, se aplica en la consulta. Una página filtrada es una página completa, y has_more / next_cursor significan lo que dicen: pagina hasta que has_more sea false y tienes el conjunto entero.

Expansión

Los documentos devuelven pdf_url y xml_url por defecto. Para evitar una segunda vuelta:

curl "https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N?expand=xml,pdf_base64,events" \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"

expand acepta una lista separada por comas: xml (string en línea), pdf_base64, events, references, received_document (en documentos emitidos en respuesta a uno recibido). Un valor no reconocido es 400 invalid_field con la lista de lo que sí acepta este recurso — no un 200 en silencio en el que no puedes distinguir un typo de un resultado vacío.

En un listado el vocabulario es más angosto. GET /v1/documents?expand=… acepta events, references y received_document; xml y pdf_base64 son 400 ahí y solo existen en GET /v1/documents/{document_id}. Cada uno de los excluidos incorpora un artefacto completo por fila, así que en una página de cien documentos serían cien lecturas de archivo en una sola respuesta.

received_document se resuelve al emitir: el documento emitido debe referenciar el tipo_dte y folio del recibido, y el RUT emisor del recibido debe ser el RUT receptor del emitido, dentro de la misma empresa. Si calzan dos, el vínculo queda en null — un antecedente equivocado es una afirmación, y una ausencia no lo es.

El objeto documento no trae enlaces públicos. Ni en una lectura, ni en un listado, ni en un webhook: la URL con su secreto ?k= aparece una sola vez, dentro del objeto public_links de la respuesta que la mintea — la emisión que pidió pdf: { "public": true } / xml: { "public": true }, o POST /v1/documents/{document_id}/public_links. Guardamos el secreto hasheado, igual que una llave de API, así que no hay lectura capaz de reconstruirlo. Ver Artefactos y enlaces públicos.

En esta página