Facturiadocs
Start here

Concepts

Empresa, document, test/live mode and the Facturia-Empresa header — the four ideas that make the rest of the API legible.

Four ideas. Get these straight and the rest of this documentation is detail.

1. Empresa

An empresa is a Chilean company — one RUT, Chile's taxpayer id — that Facturia issues for. It is the tenancy boundary for everything: folios (the pre-authorised document numbers), documents, RCV (the Registro de Compras y Ventas the SII keeps for you), webhook payloads and usage. Nothing crosses from one empresa to another.

An account is a billing and access-control container over many empresas. Which means an ERP, a vertical SaaS or an accounting firm can run N clients under a single account, with one key per client if they want.

curl -sS https://api.facturia.cl/v1/empresas \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "rut": "77928532-4",
    "legal_name": "Connect SpA",
    "giro": "Servicios de desarrollo de software",
    "acteco": [620200],
    "address": { "street": "Av. Apoquindo 4700, of. 1201", "comuna": "Las Condes", "city": "Santiago" },
    "contact_email": "[email protected]",
    "origen": "ninguno",
    "doc_types": [33, 34, 39, 61]
  }'

legal_name, giro, address, acteco and contact_email become the emisor block — the issuer identity — on every document. You never repeat them in an issuance payload; that is the whole point of keeping the profile on the empresa.

origen and the emission channel

origen is mandatory with an sk_live_ key and optional in test mode, where it is ninguno. Omitting it in live mode is 400 invalid_field — the hint rescues you, but it is exactly the round trip this example exists to save you.

It is your declaration of how the empresa issues today: gratuito if it already uses the SII's free invoicing system, mercado if it already uses a vendor, ninguno if it does not issue electronically yet. It routes the copy in the go-live wizard and nothing else — it does not pick the lane and it does not decide what can be skipped. Whether an empresa gets to skip certification is settled against the SII, empirically: being enrolled in the free system already makes a RUT an issuer of real DTEs, so a declared mercado we simply believed would skip certification for exactly the party who needs it.

The emission channel is a different thing and is declared separately: it is the preference between going on issuing through the SII's free portal, operated by us, or moving to the certified engine. And ambiente is not declared at all: it is derived and read-only. The two lanes, with their steps side by side, are in From sandbox to production.

DELETE does not exist: issued documents are tax records. A PATCH {"status":"archived"} stops issuance and billing while keeping the history.

Going live

Taking an empresa all the way to emitting is a state machine you can draw as a wizard with GET /v1/empresas/{empresa_id}/onboarding. In test mode the machine is skipped whole.

The steps that need a person are five, not three. A step needs a person when actor: "customer" (they perform it) or when we perform it and it cannot move without a decision of theirs. In order:

  1. Mandato — the representante legal, the company's legal representative, authorises us to act before the SII. Revocable whenever they like.
  2. Digital certificate (mercado lane) or the Sistema de Facturación Centralizado password (SFG lane) — the credential we operate with.
  3. The authorization to postulate — we present the postulación ourselves, with the representante legal's certificate we hold in custody, under clause 4.2 a) of the mandato. What the account holder decides is the moment: if the empresa emits today on the Sistema de Facturación Gratuito, it stops being an active user of that system the instant the SII authorizes it.
  4. The Declaración de Cumplimiento — we file it ourselves, with the same certificate, after the representante legal approves it on a hosted page (act cumplimiento_declaracion, role rep_legal). That same approval also sets the month from which the empresa is operating, if the account holder had not set it separately beforehand.
  5. Boleta habilitation — only if you declared boletas, Chile's consumer-facing receipts: we run it end to end with the same certificate, with two approvals from the representante legal along the way — when to start, and the final declaración.

Acts 3, 4 and 5 are ours to run with the custodied certificate, and we only wait for the right person to approve on their page; none of the three is filed by a person at the SII in person. The first two are yours because nobody else can authorise us or hand us a credential. Everything else — sets de pruebas, libros, simulación, intercambio, muestras impresas, timbraje (the folio stamping) and the go-live — is scripted and unattended, and comes marked actor: "facturia" or actor: "sii".

The certificate you give us does identify its holder

A Chilean digital certificate is issued to a natural person — the representante legal — and a certificate session at the SII authenticates that person. That is why the postulación and the declaración de cumplimiento are filed by Facturia with that certificate, after the right person approves them on a page. Where we still ask you to do something yourself — adding an authorised user, granting the «Enviar Doctos.» permission, changing the intercambio mailbox — the reason is one of authority, not law: those acts happen in the session of your SII account's usuario administrador, and the certificate we custody lacks that authority in exactly the cases where the act is missing. Never «the certificate does not work».

onboarding.status never sits at action_required without an action_url you can put in front of a person, or without a pending_action saying who to hand it to and what to do with it. The whole pattern is in From sandbox to production.

2. Document

A document is an issued DTE (documento tributario electrónico, Chile's electronic tax document). Every API resource is a JSON object with id, object, mode and created_at. Ids are opaque, prefixed, URL-safe and sortable by creation date (ULID underneath). Never parse an id.

PrefixResource
emp_empresa
doc_issued document
rdoc_received document
caf_CAF (the SII-signed block of folios)
whe_ / whd_webhook endpoint / delivery
evt_event
job_asynchronous job
rcv_RCV entry
f29_F29 proposal or filing
sug_tax suggestion
exp_recorded expense
con_connector connection (Fintoc)
key_API key (its id, not its secret)
usg_usage record

Money, quantities and dates

  • Amounts are integers in CLP. No decimals, no strings, no cents. 29990 is twenty-nine thousand nine hundred and ninety pesos.
  • Unit prices may carry up to 4 decimals (unit_price: 1234.5678), because the SII allows it. Line amounts and document totals are always integers, rounded half-up the way the SII specifies.
  • issue_date, period and other civil dates are YYYY-MM-DD / YYYY-MM in America/Santiago. A DTE's date is a date on the Chilean calendar, not an instant.
  • Timestamps (created_at, accepted_at, …) are RFC 3339 in UTC: 2026-08-14T13:04:11Z.
  • RUTs are canonical ########-D: no dots, one hyphen, uppercase K. We accept 77.928.532-4 and 779285324 on input and always return the canonical form.

3. Mode: test and live

The mode is decided by the key prefix, and it is immutable.

PrefixModeTalks to the SII?Cost
sk_test_test — a fully simulated sandboxNeverFree forever
sk_live_live — real issuanceYes (the SII's certification or production servers, depending on the empresa)The single plan: $19.990 CLP + IVA (Chile's VAT) a month per empresa, unlimited documents

Trap

Mode and environment are two different axes

The mode comes from the key. The environment (certificacion / produccion) comes from the empresa. A test key against a production empresa is still completely simulated: it does not touch the SII. And the other way round, a live key against an empresa on ambiente: certificacion does talk to the SII, but to its test servers (maullin / apicert).

Every object carries its "mode" stamped on it. Test objects are invisible to live keys and vice versa: they share no ids, no folio counters and no usage ledger.

The mode is inherited and cannot be escalated: an sk_test_root_ key only mints test keys. There is no path from a test credential to a live one.

4. The Facturia-Empresa header

Every empresa-scoped call names its target empresa in a header:

Facturia-Empresa: 77928532-4

The value is the RUT in canonical form (no dots, a hyphen, uppercase K — 77928532-4, 76543212-K) or the Facturia id (emp_…). We recommend the RUT: it is the natural key your own systems already hold.

Trap

This header is the only mechanism

There is no ?empresa= parameter and no empresa field in the body, and there will not be one inside v1. A second way of saying the same thing is a second way of saying it differently, and on a resource whose identity determines which tax records you are writing into, that ambiguity is not worth the convenience. If your HTTP client cannot set a header, it cannot call this API.

The rules:

SituationBehaviour
Required on/documents, /received_documents, /tax/*, /folios, /cafs, /connections, /test_helpers/*
Ignored on/empresas/{empresa_id}/... — the empresa comes from the path
Forbidden on/empresas (list/create), /webhook_endpoints, /events, /usage, /account, /api_keys
Omitted where it is required400 empresa_required, unless the key is an empresa token
Present but outside the allowlist403 empresa_not_allowed. Never a 404: we tell you the empresa is out of your reach instead of pretending it does not exist, because you already know its RUT

Empresa token: when the header is redundant

A key whose allowlist holds exactly one empresa is an empresa token. For an empresa token the header is optional; if you do send it, it has to match, or you get 400 empresa_mismatch.

# token de empresa — sin header Facturia-Empresa
curl https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer sk_live_qT8vN2wLmR5xKpJ7hYdCbA3s" \
  -H "Content-Type: application/json" \
  -d '{"tipo_dte":39,"items":[{"description":"Café","quantity":2,"unit_price":2500}]}'

It is the recommended shape when you integrate a single company (the common case) or when you hand a key to the customer's own system: the key cannot address anything but that RUT, and your code never has to think about empresas.

Cross-cutting conventions

Lists and pagination

Every list endpoint returns the same envelope and uses cursor pagination.

{
  "object": "list",
  "url": "/v1/documents",
  "data": [ { "object": "document", "…": "…" } ],
  "has_more": true,
  "next_cursor": "cur_01K2R7Q4XW9M3B8ZC5YHTVJD6N"
}

Parameters: limit (1–100, 25 by default) and cursor (opaque, out of next_cursor). The default ordering is newest to oldest by created_at. There are no offsets and no page numbers: cursors stay stable while new documents are being issued.

Idempotency

Every POST accepts an Idempotency-Key header. Use a fresh UUID per logical operation and reuse it on the retries.

-H "Idempotency-Key: 0f9f3f4e-6a3d-4f0a-9d70-9b5f19d4b0e6"
  • Keys are scoped to (account, empresa, endpoint) and retained for 24 hours.
  • A replay returns the original status code and body, plus Facturia-Idempotent-Replay: true.
  • The same key with a different body → 409 idempotency_key_reused.
  • A request still in flight under the same key → 409 idempotency_in_progress; retry in a moment.
  • "In flight" is a lease, not a flag: the claim is honoured for 120 seconds and then treated as abandoned. If the process serving your request dies — a deploy, a replaced machine — the next retry with the same key resumes the operation instead of hitting a 409 for the rest of the 24 hours. The retry has to carry the same body: a different one, inside the retention window, is still 409 idempotency_key_reused.
  • Requests that fail with a 4xx do not burn the key (fix it and retry with the same one). 5xx failures are safe to retry with the same key.

Underneath that lives a stronger guarantee: the issuance engine holds a unique constraint on (empresa RUT, tipo_dte, folio). A folio is never issued twice, and once it is assigned to a document, any retry of that same logical issuance reuses the assigned folio instead of burning a new one. That is what really protects you from the expensive failure mode — a duplicated factura, the business-to-business invoice, with a folio thrown away — and it holds even if you forget the header.

Your own identifier

curl "https://api.facturia.cl/v1/documents?external_id=order-8842" \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"

external_id is unique per (empresa, external_id); a second issuance with the same value returns 409 external_id_exists with the existing document's id in error.existing_id.

Versioning

  • The contract is the /v1 prefix. Breaking changes go to /v2; /v1 is not quietly reshaped.
  • Inside /v1 we evolve additively: new endpoints, new optional request fields, new response fields, new enum values, new event types.
  • So your client has to: ignore unknown response fields, tolerate unknown enum values (map them to a default branch instead of throwing) and ignore unknown webhook types. A client that falls over on an unknown field has a bug, and that is the only compatibility rule we ask of you.
  • Removals and behaviour changes get 12 months of notice, a Sunset header on the affected endpoints and a changelog entry.
  • There is no dated version header in v1. The path prefix is the whole contract, which keeps the mental model down to a single moving part.

On this page