# Concepts (/en/docs/empezar/conceptos)

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

## 1. Empresa [#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.

```bash
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": "facturacion@connect.cl",
    "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-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](/en/docs/empezar/produccion).

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

### Going live [#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](/en/docs/empezar/produccion).

## 2. Document [#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). &#x2A;*Never parse an id.**

| Prefix          | Resource                             |
| --------------- | ------------------------------------ |
| `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 [#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 [#3-mode-test-and-live]

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

| Prefix     | Mode                                 | Talks to the SII?                                                             | Cost                                                                                          |
| ---------- | ------------------------------------ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `sk_test_` | **test** — a fully simulated sandbox | Never                                                                         | Free forever                                                                                  |
| `sk_live_` | **live** — real issuance             | Yes (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 [#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:

| Situation                         | Behaviour                                                                                                                                                        |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **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 required      | `400 empresa_required`, unless the key is an empresa token                                                                                                       |
| Present but outside the allowlist | `403 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 [#token-de-empresa]

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`.

```bash
# 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 [#cross-cutting-conventions]

### Lists and pagination [#lists-and-pagination]

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

```json
{
  "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 [#idempotency]

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

```bash
-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 [#your-own-identifier]

```bash
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 [#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.

- [The sandbox in depth](/en/docs/empezar/sandbox): Determinism, how to force a rejection and the test helpers.

- [From sandbox to production](/en/docs/empezar/produccion): The two lanes, `origen`, the mandato and the steps a person performs at the SII.

- [Authentication](/en/docs/autenticacion): Scopes, allowlists, rotation and the cold start.
