Documents
Issuing DTEs, reading them, PDF and XML artifacts, public links, and actions on a document.
Base: https://api.facturia.cl/v1. Every endpoint in this chapter authenticates with Authorization: Bearer unless stated otherwise, and the error envelope is the one in Errors.
| Method | Path | What it does |
|---|---|---|
GET | /documents | List emitted documents |
POST | /documents | Emit a DTE |
POST | /documents/previews | Price and validate a document without emitting it |
GET | /documents/previews/{preview_id} | Retrieve a stored preview |
GET | /documents/{document_id} | Retrieve a document |
GET | /documents/{document_id}/pdf | Download the PDF |
GET | /documents/{document_id}/xml | Download the signed XML exactly as sent to the SII |
GET | /documents/{document_id}/events | Lifecycle events for one document |
POST | /documents/{document_id}/retry | Retry a document in failed |
POST | /documents/{document_id}/send | Email the document to the receptor |
POST | /documents/{document_id}/cesion | Ask the SII whether this factura is cedible |
POST | /documents/{document_id}/cede | Cede this factura to a factoring company, registering it in the SII's RTC |
POST | /documents/{document_id}/credit_note | Emit a nota de crédito against this document |
POST | /documents/{document_id}/public_links | Create or rotate public links for this document's artifacts |
DELETE | /documents/{document_id}/public_links | Revoke every public link for this document |
GET /documents
List emitted documents
List documents for the empresa, newest first, filterable by tipo_dte, status and date range.
curl -sS https://api.facturia.cl/v1/documents \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
limit | query | — | integer | |
cursor | query | — | string | Opaque cursor from a previous response's next_cursor. |
expand | query | — | string | Comma-separated, applied per returned page. On a LIST the vocabulary is narrower than on GET /documents/\{document_id\}: events, references and received_document only. xml and pdf_base64 inline a whole artifact per row and are 400 invalid_field here — fetch them from the single document, or from /pdf and /xml (§5.4). |
status | query | — | queued · sent · accepted · reparo · rejected · failed · annulled | |
tipo_dte | query | — | 33 · 34 · 39 · 41 · 46 · 52 · 56 · 61 | |
folio | query | — | integer | |
receptor_rut | query | — | Rut | |
external_id | query | — | string | |
issue_date[gte] | query | — | string | |
issue_date[lte] | query | — | string | |
created_at[gte] | query | — | string |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | A list of documents. | lista de Document |
400 | Malformed or missing input. | ErrorResponse |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents
Emit a DTE
Returns 201 with status: queued and folio: null. The folio arrives when the
document reaches sent: it is allocated by the engine after the row exists, so no 201
can carry one. null there is a WAIT, not a failure and not a placeholder — observe it
with document.sent, or by re-reading the document on the cadence
Facturia-Poll-After gives you.
The SII round-trip after that is a state machine you watch the same way. Everything not supplied is defaulted from the empresa profile (emisor block, giro, dirección), the receptor registry, and the tipo's tax rules.
curl -sS -X POST 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,"items":[{"description":"Consultoría","quantity":1,"unit_price":1200000}]}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Idempotency-Key | header | — | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
preview_id | string | — | Emit a previously computed preview (POST /documents/previews). The stored request is replayed through the ordinary create path, so nothing about the emission differs from a direct one. Only external_id and metadata may be supplied alongside it — nothing that could change an amount, a receptor or a date. Emitting the same preview twice returns the original document with 200 rather than a second 201. E.g.: "prev_01K2R9M3D8V5QW7N". |
tipo_dte | 33 · 34 · 39 · 41 · 46 · 52 · 56 · 61 | yes | 33 factura afecta · 34 factura exenta · 39 boleta afecta · 41 boleta exenta · 46 factura de compra · 52 guía de despacho · 56 nota de débito · 61 nota de crédito. Boletas (39/41) travel a different SII transport; that is invisible through this API. |
external_id | string | — | Your own identifier. Unique per empresa; a duplicate returns 409 external_id_exists. |
issue_date | string | — | Defaults to today in America/Santiago. |
prices_include_tax | boolean | — | Defaults to true for 39/41 and false for every other tipo. |
receptor | Party | — | Who the document is issued to. giro here is the SII's GiroRecep, capped at 40 characters — tighter than the emisor's own GiroEmis (80), so the same name of trade can be legal on one side of a document and illegal on the other. legal_name is 100 and address is 70/20/20. Over-long values are refused with 400 field_too_long before a folio is allocated, and never truncated: shortening a razón social or a name of trade would change what the document says about the parties. address must be an object; a bare string is 400 invalid_field, because there is no way to split one into the three components the SII asks for and dropping it silently would emit a document with no address at all. AND THE ADDRESS IS NOT OPTIONAL on 33, 34, 46, 52, 56 and 61: the SII accepts the ENVELOPE and then REJECTS the document whose <Receptor> carries no GiroRecep/DirRecep/CmnaRecep, with the folio already spent. So giro, address.street and address.comuna are resolved from this empresa's own earlier documents to the same RUT and, failing that, refused with 400 receptor_incomplete before a folio is allocated. Three exceptions: a tipo 52 needs no giro (its receptor is often the emisor itself, whose own giro frequently exceeds 40); emission_channel=sfg also needs address.city, which the SII portal's own form demands; and 39/41 and a foreign 46 (foreign: true, or 55555555-5) need none of them. |
items | DocumentItem[] | yes | |
global_discounts | object[] | — | The engine emits a single global discount line — the SII's <DscRcgGlobal> is one hard-coded NroLinDR>1 — so a second entry is refused with 400 invalid_field before a folio is allocated, rather than dropped. MERCADO CHANNEL ONLY: on emission_channel=sfg it is refused with 422 global_discount_not_available_for_channel, because the SII portal's emission form has no discount field at all — its EFXP_PCT_DESC/EFXP_MNT_DESC inputs are commented out in the captured form, and items[].discount_percent is refused there for the same reason. Fold the discount into items[].unit_price on that channel. |
references | DocumentReference[] | — | |
dispatch | object | — | Required for tipo 52, and its transfer_type / dispatch_type are refused on any other tipo. Both emission channels emit a guía; they differ in which fields they can express. destination and dispatch_type are accepted on emission_channel=mercado (the engine writes them into the DTE's IdDoc/TipoDespacho and Transporte block) and refused on emission_channel=sfg with 422 dispatch_not_available_for_channel, because the SII portal's guía form has nowhere to put them: it carries a single address — the receptor's, so put a differing one in receptor.address — and its only traslado select is the kind of movement, with no TipoDespacho input at all. The two channels also differ on amounts: sfg refuses a guía with any line priced at or below 0, or a zero total, with 422 sfg_guia_zero_amount — the portal validates the guía with its factura validations, whatever the traslado kind — while mercado accepts one. |
withholding | object | — | Tipo 46 — you are the withholding agent. |
payment | object | — | |
totals | Totals + any | — | |
pdf | object | — | |
xml | object | — | The XML artifact shares the PDF's public-link mechanism on identical terms — it is the artifact the receptor's accounting system actually ingests, so gating it behind a key would force integrators to proxy a file we have already generated. |
notes | string | — | |
metadata | object | — | Up to 20 keys, ≤500 chars each. Stored and returned untouched; never sent to the SII. |
test | object | — | Test mode only. Sending this with a live key fails with 400 test_field_in_live_mode. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | The preview named by preview_id was ALREADY emitted, by this call's own earlier attempt or by a concurrent one. The original document, unchanged — nothing was created by this call and no second folio was spent (§10.2). Only reachable on a \{"preview_id": "prev_…"\} body. | EmittedDocument |
201 | Document created and queued for transmission. Carries Facturia-Poll-After when the document is not terminal — which the 201 always is on the live lane, since it answers status: "queued". It was attached only by GET /v1/documents/\{document_id\} until 2026-09-04, so an agent following «poll per Facturia-Poll-After» had to issue a first read purely to discover the number it was told to poll with (#522). Same value, same function, both doors. | EmittedDocument |
400 | Malformed or missing input. | ErrorResponse |
401 | Missing, malformed or revoked key. | ErrorResponse |
402 | subscription_required - this live act needs an active subscription for the EMPRESA the request names, and that empresa has none (enforced). The subscription is per empresa: another empresa on the same account is unaffected, and error.details carries empresa, empresa_rut, empresa_name, account_plan, subscription_status, grace_until, blocked_by, a next handoff and a checkout_url pointing at that empresa. Or plan_limit_reached - a plan quota is exhausted (declared, not yet enforced). Test mode is never gated on either. ELIGIBILITY IS EVALUATED FIRST (2026-09-04, #475). This refusal now arrives ONLY when the empresa is otherwise able to emit: while its checklist is unfinished, its puesta en vivo pending or the empresa suspended, the act is refused by 422 empresa_not_emitting with the checklist handoff instead. So this 402 and Empresa.capabilities.blocked_by can no longer name two different next actions for one state — which they did, the object saying «sign the mandato» while the door said «pay». | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
422 | Well-formed, and refused by a domain rule. The codes this operation answers, in the order the door checks them: - empresa_not_emitting — live only: this endpoint requires onboarding.status = emitting, and the empresa has not got there yet. In SANDBOX it is not checked at all: a test-mode empresa emits from the first minute, which is what makes the whole surface exercisable before the SII has issued anything. error.details is an EmissionRefusalDetails and carries the pending_action — who must act, where, and in what words — so the refusal itself is the handoff. - empresa_not_active — the empresa is suspended or archived. - tipo_not_available_for_channel — this tipo_dte is not emitted on this empresa's emission_channel (§6.2). - plus the payload rules: totals_mismatch, receptor_incomplete, reference_required and their siblings — see GET /error_codes. This list named folios_exhausted until 2026-09-04. There is no such code and there never was: running out of folios is not refused at this door at all — the range is drawn from AFTER the request is accepted, so it arrives as a rejected document or, on POST /documents/previews, as the non-blocking warning no_folios_available (#496 found it by cross-checking every code these sentences name against the published dictionary). | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/previews
Price and validate a document without emitting it
Runs the ENTIRE emission validator — the same refusals, in the same words — and then
performs none of the emission. folio is null because none was taken; folio_preview is
the folio that WOULD be used, read from the counter and not reserved.
Stored for an hour and readable with GET /documents/previews/\{preview_id\}, which returns
what was computed rather than re-pricing: totals that moved between being shown and being
approved is the failure this flow exists to prevent.
Emit it by sending \{"preview_id": "prev_…"\} to POST /documents. Emitting the same
preview twice returns the ORIGINAL document with 200 and spends no second folio — a
property enforced in the database, because an agent retrying a tool call is the ordinary
case here and agents rarely send an Idempotency-Key.
curl -sS -X POST https://api.facturia.cl/v1/documents/previews \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"tipo_dte":33,"items":[{"description":"Consultoría","quantity":1,"unit_price":1200000}]}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
preview_id | string | — | Emit a previously computed preview (POST /documents/previews). The stored request is replayed through the ordinary create path, so nothing about the emission differs from a direct one. Only external_id and metadata may be supplied alongside it — nothing that could change an amount, a receptor or a date. Emitting the same preview twice returns the original document with 200 rather than a second 201. E.g.: "prev_01K2R9M3D8V5QW7N". |
tipo_dte | 33 · 34 · 39 · 41 · 46 · 52 · 56 · 61 | yes | 33 factura afecta · 34 factura exenta · 39 boleta afecta · 41 boleta exenta · 46 factura de compra · 52 guía de despacho · 56 nota de débito · 61 nota de crédito. Boletas (39/41) travel a different SII transport; that is invisible through this API. |
external_id | string | — | Your own identifier. Unique per empresa; a duplicate returns 409 external_id_exists. |
issue_date | string | — | Defaults to today in America/Santiago. |
prices_include_tax | boolean | — | Defaults to true for 39/41 and false for every other tipo. |
receptor | Party | — | Who the document is issued to. giro here is the SII's GiroRecep, capped at 40 characters — tighter than the emisor's own GiroEmis (80), so the same name of trade can be legal on one side of a document and illegal on the other. legal_name is 100 and address is 70/20/20. Over-long values are refused with 400 field_too_long before a folio is allocated, and never truncated: shortening a razón social or a name of trade would change what the document says about the parties. address must be an object; a bare string is 400 invalid_field, because there is no way to split one into the three components the SII asks for and dropping it silently would emit a document with no address at all. AND THE ADDRESS IS NOT OPTIONAL on 33, 34, 46, 52, 56 and 61: the SII accepts the ENVELOPE and then REJECTS the document whose <Receptor> carries no GiroRecep/DirRecep/CmnaRecep, with the folio already spent. So giro, address.street and address.comuna are resolved from this empresa's own earlier documents to the same RUT and, failing that, refused with 400 receptor_incomplete before a folio is allocated. Three exceptions: a tipo 52 needs no giro (its receptor is often the emisor itself, whose own giro frequently exceeds 40); emission_channel=sfg also needs address.city, which the SII portal's own form demands; and 39/41 and a foreign 46 (foreign: true, or 55555555-5) need none of them. |
items | DocumentItem[] | yes | |
global_discounts | object[] | — | The engine emits a single global discount line — the SII's <DscRcgGlobal> is one hard-coded NroLinDR>1 — so a second entry is refused with 400 invalid_field before a folio is allocated, rather than dropped. MERCADO CHANNEL ONLY: on emission_channel=sfg it is refused with 422 global_discount_not_available_for_channel, because the SII portal's emission form has no discount field at all — its EFXP_PCT_DESC/EFXP_MNT_DESC inputs are commented out in the captured form, and items[].discount_percent is refused there for the same reason. Fold the discount into items[].unit_price on that channel. |
references | DocumentReference[] | — | |
dispatch | object | — | Required for tipo 52, and its transfer_type / dispatch_type are refused on any other tipo. Both emission channels emit a guía; they differ in which fields they can express. destination and dispatch_type are accepted on emission_channel=mercado (the engine writes them into the DTE's IdDoc/TipoDespacho and Transporte block) and refused on emission_channel=sfg with 422 dispatch_not_available_for_channel, because the SII portal's guía form has nowhere to put them: it carries a single address — the receptor's, so put a differing one in receptor.address — and its only traslado select is the kind of movement, with no TipoDespacho input at all. The two channels also differ on amounts: sfg refuses a guía with any line priced at or below 0, or a zero total, with 422 sfg_guia_zero_amount — the portal validates the guía with its factura validations, whatever the traslado kind — while mercado accepts one. |
withholding | object | — | Tipo 46 — you are the withholding agent. |
payment | object | — | |
totals | Totals + any | — | |
pdf | object | — | |
xml | object | — | The XML artifact shares the PDF's public-link mechanism on identical terms — it is the artifact the receptor's accounting system actually ingests, so gating it behind a key would force integrators to proxy a file we have already generated. |
notes | string | — | |
metadata | object | — | Up to 20 keys, ≤500 chars each. Stored and returned untouched; never sent to the SII. |
test | object | — | Test mode only. Sending this with a live key fails with 400 test_field_in_live_mode. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
201 | The computed preview. Nothing was emitted and no folio was reserved. | DocumentPreview |
400 | Malformed or missing input. | ErrorResponse |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
422 | Well-formed but violates a domain rule — folios exhausted, acuse window closed. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
GET /documents/previews/{preview_id}
Retrieve a stored preview
Returns what was computed, never a re-computation. A preview that has been emitted stays
readable with emitted: true and a consumed_at; an unconsumed one is gone an hour after
it was created and answers 404.
curl -sS https://api.facturia.cl/v1/documents/previews/prev_01K2R9M3D8V5QW7N \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
preview_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | The preview. | DocumentPreview |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
404 | No such object under this key. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
GET /documents/{document_id}
Retrieve a document
One document: its lifecycle status, totals, public_links if any are live, and rejection_reasons if the SII refused it.
curl -sS https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
expand | query | — | string | Comma-separated. One or more of xml, pdf_base64, events, references, received_document. An unrecognised value is 400 invalid_field. received_document is populated when this document was emitted in response to a supplier's — matched at emission by reference (tipo_dte, folio) plus emisor RUT = receptor RUT, within the same empresa, and only when that match is unique (§5.4). |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | The document. | Document |
400 | Malformed or missing input. | ErrorResponse |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
404 | No such object under this key. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
GET /documents/{document_id}/pdf
Download the PDF
The document's rendered PDF (?copy=original or cedible where Ley 19.983 gives one). 409 artifact_not_ready if the SII has not accepted the document yet — the representación impresa is rendered on acceptance, not before.
curl -sS https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/pdf \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
copy | query | — | original · cedible | Which printed copy to serve. cedible is available for facturas — tipo_dte 33 and 34 (Ley 19.983 art. 1; Res. Ex. SII N°14/2005 §1°; Manual de Muestras Impresas §1.4). Any other type returns 422 cedible_not_available. cedible does not exist AT ALL on an emission_channel=sfg empresa, on any tipo: the SII signs and proxies its own PDF for that channel, and its bytes carry no acuse-de-recibo box or CEDIBLE legend to serve as one. copy=cedible there returns 422 cedible_not_available_for_channel; copy=original is unaffected. The two copies are two separate files: the original carries no acuse-de-recibo box and no CEDIBLE legend, the cedible copy carries both. both was withdrawn in wave 9 — the muestras manual allows exactly one page per file (p.12) — and now returns 400 invalid_field, as does any other unrecognised value. format (carta | 80mm) was also withdrawn: no thermal layout exists and none was ever served. Supplying it returns 400 invalid_field. Omitting it does not always mean original. The default is resolved in order: this document's own pdf.cedible (set at emission, §9.3) if it named one; else the empresa's branding.cedible_by_default (§7.6); else original — and never cedible on the SFG channel, regardless of either default. Send ?copy= explicitly to override either default for one read. |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | The rendered PDF. | application/pdf |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
404 | No such object under this key. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
422 | Well-formed but violates a domain rule — folios exhausted, acuse window closed. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
GET /documents/{document_id}/xml
Download the signed XML exactly as sent to the SII
The exact XML Facturia sent to the SII (or will send), for a caller that wants the wire bytes rather than the parsed object.
curl -sS https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/xml \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | The signed EnvioDTE / EnvioBOLETA XML (ISO-8859-1). | application/xml |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
404 | No such object under this key. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
GET /documents/{document_id}/events
Lifecycle events for one document
The document's own event history — every document.* webhook Facturia published for it, in order, independent of whether delivery succeeded.
curl -sS https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/events \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | Events, oldest first. | lista de DocumentEvent |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/{document_id}/retry
Retry a document in failed
Reuses the already-assigned folio. Never creates a second document.
curl -sS -X POST https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/retry \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | The document, back in queued. | Document |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/{document_id}/send
Email the document to the receptor
202 when THIS call created the send; 200 with the SAME job when an identical one
(same document, same addresses, same attachments) already exists — one email, one job id,
mirroring §10.2's "emitting the same preview twice returns the original document". Both
statuses carry the same body, so a client that only handles one of them is handling half
the endpoint.
curl -sS -X POST https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/send \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"to":["[email protected]"],"attach":["pdf","xml"]}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Idempotency-Key | header | — | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
to | string[] | — | |
attach | pdf · xml[] | — | Always the ORIGINAL copy of the PDF — never cedible, whatever this document's or the empresa's default says. §9.6: the cedible copy is an instrument for assigning the debt, not the invoice you send a customer to be paid. Default: ["pdf","xml"]. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | An identical send already exists. The ORIGINAL job — no second email. | SendJob |
202 | Queued for delivery. | SendJob |
401 | Missing, malformed or revoked key. | ErrorResponse |
402 | subscription_required - this live act needs an active subscription for the EMPRESA the request names, and that empresa has none (enforced). The subscription is per empresa: another empresa on the same account is unaffected, and error.details carries empresa, empresa_rut, empresa_name, account_plan, subscription_status, grace_until, blocked_by, a next handoff and a checkout_url pointing at that empresa. Or plan_limit_reached - a plan quota is exhausted (declared, not yet enforced). Test mode is never gated on either. ELIGIBILITY IS EVALUATED FIRST (2026-09-04, #475). This refusal now arrives ONLY when the empresa is otherwise able to emit: while its checklist is unfinished, its puesta en vivo pending or the empresa suspended, the act is refused by 422 empresa_not_emitting with the checklist handoff instead. So this 402 and Empresa.capabilities.blocked_by can no longer name two different next actions for one state — which they did, the object saying «sign the mandato» while the door said «pay». | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/{document_id}/cesion
Ask the SII whether this factura is cedible
READ-ONLY (ticket #47). Queues a query against the SII's own registro
(WSREGISTRORECLAMODTE.consultarDocDteCedible) and caches the answer on the document's
cesion object. Nothing here cedes the invoice, builds an AEC, or writes to the Registro
de Cesión de Créditos — that operation takes no action code and cannot change SII state.
Under Ley 19.983 a factura becomes assignable when the receptor gives acuse de recibo, or when eight días corridos pass without a reclamo; one declared as paid al contado never does (Circular N°35 de 23.06.2017). Only the SII knows which of those applies, so this is a report, never a computation.
202 when this call queued the question, 200 when an identical one is already in
flight. Poll GET /documents/\{document_id\} for the answer; there is no webhook.
Accepts a documents:read key: the caller this exists for is a factoring integration,
which should not need one that can issue invoices.
In test mode the answer is simulated in-process against the empresa's simulated clock and
no SII socket is opened — 24 until eight days after acceptance, 25 after.
curl -sS -X POST https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/cesion \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Idempotency-Key: $(uuidgen)"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Idempotency-Key | header | — | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409. |
Responses
| Code | Meaning | Body |
|---|---|---|
200 | A check against this same answer is already queued (or, in test mode, the simulated answer, already applied). The document, with its current cesion. | Document |
202 | Queued. The document, with cesion still showing the previously known answer — null throughout if this was the first check. | Document |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
422 | Well-formed but violates a domain rule — folios exhausted, acuse window closed. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/{document_id}/cede
Cede this factura to a factoring company, registering it in the SII's RTC
THE WRITE (ticket #60), unlike POST …/cesion above (a read). Assembles an Archivo
Electrónico de Cesión — the emisor's original signed DTE plus the terms of the transfer,
signed by the cedente — and uploads it to the SII's Registro Electrónico de Transferencia
de Crédito. A real, irreversible act.
Works the same regardless of channel (ticket #60, ruled 2026-09-06): the AEC carries the
emisor's original signed envelope verbatim, so it does not care whether the DTE was emitted
through emission_channel=sfg or emission_channel=mercado. cesion_channel_unsupported,
the code the old channel guard raised, is RETIRED from the catalogue.
202 with a job when this call queued a NEW attempt; 200 with the SAME job when one
is already queued/submitted/aec_generated for this document (one attempt in flight
at a time). GET /documents/\{document_id\} carries the newest attempt's outcome under
cesion.status.
A RECEIPT IS NOT A REGISTRATION (ticket #69). The furthest the upload itself can take an
attempt is submitted — the SII answered «Envío recibido OK» and issued a TrackID. Its
validation happens afterwards, and a rejection is notified by e-mail to the cedente hours
later, so this API claims confirmed only when the registry's own
wsRPETCConsulta.getEstEnvio answers EOK («Envío Aceptado») for that TrackID. Webhooks
follow that exactly: document.cesion_submitted means «AEC recibido, anotación
pendiente», document.cesion_confirmed is the one to settle money on, and
document.cesion_failed covers a refusal, a fence, an answer nobody recognises, and a
registration that could never be confirmed.
409 cesion_attempt_may_have_registered when the newest attempt for this document had
already put the signed AEC on the wire when it stopped (ticket #76). Whether it registered
cannot be decided from here, so a second cesión is refused rather than risking the same
credit being assigned twice. error.details carries this system's own last known registry
answer for that attempt (rtc_estado, rtc_track_id) when one was ever recorded — reading
it costs a column, not a new SII call; when neither was ever recorded the details say so.
In test mode this settles in the same request (§4.1) — no socket opened, the attempt
lands simulated, and BOTH document.cesion_submitted and document.cesion_confirmed
fire, in that order, so the event a factoring integration acts on is exercisable in test.
Live submission is owner-gated, and since the 2026-09-06 ruling (R6) also gated per
empresa: the AEC assembly and the registry upload are built and checked against the SII's
own schema — the first live upload (2026-09-03) was accepted by the registry with a real
TrackID and then rejected at validation for a signature defect, since fixed. Opening it
needs BOTH ALLOW_LIVE_CESION_SUBMITS (an operator's own shell, never a deploy) AND the
ceding empresa's own connection reaching onboarding_status=emitting, active,
ambiente=palena and not a sandbox connection. A live attempt missing either one is
accepted and queued like any other and settles failed non-retryably, naming whichever
condition is missing.
curl -sS -X POST https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/cede \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"cesionario":{},"ultimo_vencimiento":"…"}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Idempotency-Key | header | — | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
cesionario | object | yes | |
monto | integer | — | CLP, in [1, total]. Defaults to the document's total. |
ultimo_vencimiento | string | yes | |
otras_condiciones | string | — | |
email_deudor | string | — | |
contacto | object | — | |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | A cesión attempt was already in flight for this document. The same job. | CesionJob |
202 | Queued. | CesionJob |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
422 | Well-formed but violates a domain rule — folios exhausted, acuse window closed. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/{document_id}/credit_note
Emit a nota de crédito against this document
There is no "delete an invoice" in Chile. code: 1 annuls the referenced document,
2 corrects text, 3 corrects amounts (supply items for the corrected values).
curl -sS -X POST https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/credit_note \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"code":1,"reason":"content"}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Idempotency-Key | header | — | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
code | 1 · 2 · 3 | yes | |
reason | string | yes | |
items | DocumentItem[] | — | |
| Responses |
| Code | Meaning | Body |
|---|---|---|
201 | The nota de crédito (tipo 61). | Document |
401 | Missing, malformed or revoked key. | ErrorResponse |
402 | subscription_required - this live act needs an active subscription for the EMPRESA the request names, and that empresa has none (enforced). The subscription is per empresa: another empresa on the same account is unaffected, and error.details carries empresa, empresa_rut, empresa_name, account_plan, subscription_status, grace_until, blocked_by, a next handoff and a checkout_url pointing at that empresa. Or plan_limit_reached - a plan quota is exhausted (declared, not yet enforced). Test mode is never gated on either. ELIGIBILITY IS EVALUATED FIRST (2026-09-04, #475). This refusal now arrives ONLY when the empresa is otherwise able to emit: while its checklist is unfinished, its puesta en vivo pending or the empresa suspended, the act is refused by 422 empresa_not_emitting with the checklist handoff instead. So this 402 and Empresa.capabilities.blocked_by can no longer name two different next actions for one state — which they did, the object saying «sign the mandato» while the door said «pay». | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
422 | Well-formed but violates a domain rule — folios exhausted, acuse window closed. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
POST /documents/{document_id}/public_links
Create or rotate public links for this document's artifacts
Turns a public link on after the fact, or rotates an existing one (which invalidates the previous URL). A link exposes exactly one artifact of one document — never the empresa's other documents, never account data — and cannot be escalated into an API credential.
cedible (ticket #18 wave 10) is NOT in the default set: unlike pdf/xml it does not
exist for every document, so a caller who did not ask for it does not get one minted
silently — mirroring ?copy= on GET …/pdf, which defaults to original rather than
guessing. Requesting it for a tipo outside 33/34 is 422 cedible_not_available; requesting
it on an emission_channel=sfg empresa, on any tipo, is 422 cedible_not_available_for_channel
(#48) — the SII proxies its own PDF for that channel and no cedible copy of it ever exists,
so this refuses the mint rather than handing back a .cedible link that could only ever
resolve to the non-cedible file.
curl -sS -X POST https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/public_links \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"artifacts":["pdf","xml"]}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Idempotency-Key | header | — | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
artifacts | pdf · xml · cedible[] | yes | Default: ["pdf","xml"]. |
expires_in_days | integer,null | — | Null means the link does not expire. Default: 90. |
expires_in_seconds | integer,null | — | The same lifetime in seconds, clamped to 30s–365d. WINS when both units are sent: days cannot express the ten-minute link an agent needs, and the shorter, more specific unit is the safer reading of an ambiguous request. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | The minted links. NOT the document: the secret half of each URL is stored hashed, so this response is the only place a working URL ever exists. Capture it. | PublicLinks |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
409 | State conflict — idempotency reuse, duplicate external_id, artifact not ready. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
DELETE /documents/{document_id}/public_links
Revoke every public link for this document
Revoke every live public link minted for the document. Already-cached copies of the URL keep working until they expire; this only stops new fetches.
curl -sS -X DELETE https://api.facturia.cl/v1/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/public_links \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA"Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
document_id | path | yes | string | |
Facturia-Empresa | header | — | string | Target empresa: canonical RUT (77928532-4) or empresa id (emp_…). Required on every empresa-scoped call unless the key is an empresa token. This header is the ONLY mechanism: there is no ?empresa= query parameter and no empresa body field. Omitted where required → 400 empresa_required; outside the allowlist → 403 empresa_not_allowed. |
Responses
| Code | Meaning | Body |
|---|---|---|
204 | Revoked. Existing URLs stop resolving immediately. | — |
401 | Missing, malformed or revoked key. | ErrorResponse |
403 | Valid key, not permitted — scope, empresa allowlist, or mode. | ErrorResponse |
429 | Too many requests. | ErrorResponse |
default | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |