Test helpers
Endpoints that only exist for test keys, and move the pieces the real world normally moves.
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 |
|---|---|---|
POST | /test_helpers/received_documents | Inject a supplier DTE as if it had arrived at the reception address |
POST | /test_helpers/rcv/seed | Seed RCV rows for a period so F29 propuestas have data |
POST | /test_helpers/clock | Advance this empresa's simulated clock |
POST | /test_helpers/empresas/{empresa_id}/onboarding | Drive an empresa's onboarding to any state |
POST | /test_helpers/empresas/{empresa_id}/onboarding/steps | Force one onboarding step to a status |
POST | /test_helpers/documents/{document_id}/estado | Force the raw SII estado of an already-emitted sandbox document |
POST | /test_helpers/f29/proposals | Inject a computed F29 propuesta |
POST | /test_helpers/f29/validations | Force the cross-validation verdict for a period |
POST /test_helpers/received_documents
Inject a supplier DTE as if it had arrived at the reception address
Sandbox only: inject a supplier DTE as if it had just arrived at the reception address, without waiting for a real counterparty to send one.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/received_documents \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"tipo_dte":33,"emisor":{"rut":"96790240-3","legal_name":"Proveedor Austral SpA"}}'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 |
|---|---|---|---|
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. |
folio | integer | — | |
emisor | Party | yes | A counterparty — a company OR a natural person. There is no company flag, and none is needed: a natural person (rut + legal_name, no giro, no address) is a valid receptor for a boleta (39/41). Its name is written to <RznSocRecep> and to the timbre's <RSR>, its RUT to <RUTRecep>/<RR> — the boleta a patient presents to an Isapre or a seguro complementario. Without legal_name the boleta names the RUT beside «Sin RUT»; without a receptor it is issued to the anonymous consumer 66666666-6. A nota de crédito (61) to a natural person is valid too, but it is a factura-family document: like every 61 it needs giro, address.street and address.comuna, even when it annuls a boleta. POST /v1/documents/\{id\}/credit_note copies the receptor from the original boleta, so store those three once with PATCH /v1/empresas/\{id\}/counterparties/\{rut\} before annulling, or the note is refused with 400 receptor_incomplete before any folio is spent. |
issue_date | string | — | |
totals | Totals | — | |
items | DocumentItem[] | — | |
| Responses |
| Code | Meaning | Body |
|---|---|---|
201 | The injected received document; received_document.created fires. | ReceivedDocument |
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 /test_helpers/rcv/seed
Seed RCV rows for a period so F29 propuestas have data
Sandbox only: seed the purchases/sales ledgers with synthetic entries, so RCV-dependent flows (characterization, F29) can be tested without a real SII sync.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/rcv/seed \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"period":"2026-07"}'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 |
|---|---|---|---|
period | Period | yes | Tax period YYYY-MM (America/Santiago). E.g.: "2026-07". |
purchases | integer | — | Default: 10. |
sales | integer | — | Default: 10. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
201 | The rows were written, in this request. No job, and nothing to poll: the sandbox has no worker to wait for (§4.1), so a seed that answered 202 with a job would be a promise of an asynchrony that does not exist here. | RcvSeed |
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 /test_helpers/clock
Advance this empresa's simulated clock
Exercises the 8-day acuse window, folio expiry and F29 deadlines without waiting.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/clock \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"advance_days":9,"set_to":"…"}'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 |
|---|---|---|---|
advance_days | integer | — | |
set_to | string | — | |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | The empresa's new simulated date. | object |
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 /test_helpers/empresas/{empresa_id}/onboarding
Drive an empresa's onboarding to any state
Sandbox only: force the empresa's onboarding spine to a named onboarding_status, skipping every step between here and there.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"draft"}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
empresa_id | path | yes | string |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
status | draft · mandato_pending · mandato_signed · certificate_pending · certificate_verified · sii_postulacion_pending · certification_in_progress · cumplimiento_pending · resolucion_pending · folios_pending · emitting · action_required · blocked · suspended | yes | |
step | string | — | |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | The forced onboarding state. | Onboarding |
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 /test_helpers/empresas/{empresa_id}/onboarding/steps
Force one onboarding step to a status
The finer of the two onboarding levers. POST .../onboarding moves the empresa's
STATE; this moves ONE STEP, which is how you produce the parts of §7.4 a state
cannot express: a step at action_required with its action_url, a certification
gate that failed for one tipo_dte while the others passed, a step skipped
because the RUT was already an authorized emisor.
The four certification steps (set_de_pruebas, simulacion, intercambio,
muestras_impresas) exist per document type. Omit tipo_dte to force every
declared type at once; supply it to force one.
Returns the full onboarding object and fires the same empresa.onboarding.*
webhook the real machine would.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/steps \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Content-Type: application/json" \
-d '{"step":"mandato","status":"pending"}'Parameters
| Parameter | In | Required | Type | Notes |
|---|---|---|---|---|
empresa_id | path | yes | string |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
step | mandato · certificate · sii_postulacion · set_de_pruebas · simulacion · intercambio · muestras_impresas · cumplimiento · resolucion · habilitacion · folios | yes | |
status | pending · in_progress · action_required · completed · failed · skipped | yes | |
tipo_dte | 33 · 34 · 39 · 41 · 46 · 52 · 56 · 61 | — | Only meaningful on a certification step. Must be one of the empresa's declared doc_types. |
blocked_reason | object | — | Structured reason to attach to a failed or action_required step. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | The onboarding object after the forced step. | Onboarding |
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 /test_helpers/documents/{document_id}/estado
Force the raw SII estado of an already-emitted sandbox document
test.outcome on POST /documents forces an outcome AT EMISSION. This forces one
AFTERWARDS, and it is the only deterministic way to exercise the transition a webhook
consumer actually cares about: without it, sent → accepted means racing the sandbox's own
~1 s settle, and stuck → accepted is unreachable.
IT WRITES THE ESTADO, NOT THE STATUS, on purpose. The public status is then derived by
the ordinary mapping, so a test that drives a document to DNK proves the reparo mapping
works rather than asserting it. The same document.* lifecycle event the real transition
would fire is published, so a consumer under test receives exactly what production sends.
Sandbox only, checked three times: the key's mode, the empresa's flag, and the DOCUMENT's
own is_sandbox — which is stamped at insert and immutable, so a real document stays real
even if the empresa flag were flipped afterwards.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/documents/doc_01K2R7Q4XW9M3B8ZC5YHTVJD6N/estado \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"estado":"DOK"}'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. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
estado | DOK · DNK · FAU · FNA · FAN · EMP · RCT · EPR · SOK · RPR · RLV | yes | The SII estado to write, exactly as the poll executor would. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
200 | The document, with its derived status. | 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 |
POST /test_helpers/f29/proposals
Inject a computed F29 propuesta
Computing a propuesta is a WORKER job, and the worker is not part of the sandbox's promise (§4.1: test mode is "a complete in-process simulation"). Without this helper the sandbox can seed an RCV and then wait for ever for a propuesta only a running worker produces, which makes the whole §12.3/§12.4 surface untestable in test mode.
It writes the same row the real computation writes, so everything downstream — the
serializer, the upload-file link, and above all the FILING GATE — reads an ordinary
propuesta. The gate is not bypassed: it still reads total_payable and observations from
this row, and it still demands a validation (see /test_helpers/f29/validations).
Re-seeding the same period updates it rather than creating a second one.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/f29/proposals \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"period":"2026-07"}'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 |
|---|---|---|---|
period | Period | yes | Tax period YYYY-MM (America/Santiago). E.g.: "2026-07". |
summary | object | — | Any omitted field defaults to 0, and total_payable to max(0, iva_debito - iva_credito) + iva_retenido + ppm. |
codes | object[] | — | The F29 code rows. Omit for a plausible default set. |
observations | object[] | — | An observation with severity: error lands the propuesta review_required, which is what the filing gate refuses. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
201 | The seeded propuesta. | object |
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 /test_helpers/f29/validations
Force the cross-validation verdict for a period
The real cross-validation SKIPS the SII fetch for a sandbox empresa and always records
unavailable — correctly, because a sandbox empresa has no SII to contrast against. But
unavailable is not validated, so without this helper the filing gate refuses EVERY
sandbox filing with not_validated and the rest of §12.4 can never be reached.
Forcing the VERDICT rather than the filing is what keeps it honest: a test that drives a
proposal to validated and then files proves the gate lets a validated proposal through,
and one that leaves it unavailable proves the gate stops one that is not. The gate itself
is untouched either way.
curl -sS -X POST https://api.facturia.cl/v1/test_helpers/f29/validations \
-H "Authorization: Bearer $FACTURIA_KEY" \
-H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"period":"2026-07"}'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 |
|---|---|---|---|
period | Period | yes | Tax period YYYY-MM (America/Santiago). E.g.: "2026-07". |
verdict | validated · discrepant · unavailable | — | Default: "validated". |
material_count | integer | — | Only on discrepant; how many material differences to record. |
| Responses |
| Code | Meaning | Body |
|---|---|---|
201 | The recorded validation. | object |
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 |