Scopes and empresas
What each scope grants, how it combines with the empresa allowlist, and which error you get when one is missing.
The scope table
| Scope | Grants |
|---|---|
documents:read | Read emitted documents, PDF, XML and events |
documents:write | Emit documents, retry, send by email |
received:read | Read the electronic tax documents (DTE) received from suppliers |
received:write | Accept / claim / acknowledge receipt |
empresas:read | Read empresas, onboarding, configuration, folios (the SII-authorised document numbers) |
empresas:write | Create empresas, upload certificates, change configuration |
folios:write | Ask the SII (Chile's tax authority) for a CAF, the signed block of folios; upload a CAF |
tax:read | RCV (the SII's ledger of purchases and sales), F29 (the monthly return) proposals and filings, suggestions |
tax:write | Characterise, file F29, confirm suggestions, enter expenses |
webhooks:read / webhooks:write | Webhook endpoints and event replay |
usage:read | Usage ledger |
keys:write | Mint, rotate and revoke API keys. Root keys only; never combined with the scopes above |
A missing scope gets you 403 insufficient_scope, with the required scope named in
error.message. You do not have to guess which one it was.
The starter set
An account's first key is minted with these six, which are exactly what the quickstart walks through end to end:
documents:read, documents:write, empresas:read, empresas:write, webhooks:read, webhooks:writeNot one more. keys:write is left out because it is a root key's power, and the four onboarding
scopes are left out because they grant acts with legal effect.
The onboarding scopes
Four narrow scopes govern the acts that authorise Facturia before the SII (available from v1.1). None of them belongs to the starter set, not by default and not for convenience, and they are requested one at a time:
| Scope | Grants |
|---|---|
mandato:write | Mint the signing link for the mandato — the power of attorney an empresa grants Facturia — and read it (GET …/mandato) |
mandato:attest | Post an acceptance with a body (the integrator collected the signature in its own flow; recorded as acceptance_method: api) and revoke the mandato with DELETE …/mandato — revocation is the act with the largest effect in the contract, and since v1.1 empresas:write no longer grants it |
onboarding:write | Mint links for human steps, ask for a re-read, mint the go_live approval link, upload the certificate |
onboarding:attest | Post the go_live authorisation with a body |
empresas:write implies the two :write scopes (mandato:write, onboarding:write) — so a key
that already exists can still mint the signing link and the human-step links. It implies neither
:attest scope: those are granted explicitly or not at all (ADR-0012 §4). A key holding only
empresas:write that tries POST …/mandato with a body gets 403 insufficient_scope: mandato:attest, exactly as if it held no onboarding scope at all. The difference between :write
and :attest is the difference between asking a person to approve and asserting that they
already approved somewhere else: the second is an attestation, it is recorded visibly as one, and
that is why it lives behind its own scope that is never granted by implication or by default. The
full path is in From sandbox to production.
Trap
A root key cannot read documents
keys:write does not combine with any other scope. A root key (sk_live_root_… /
sk_test_root_…) cannot emit, cannot read documents and cannot touch tax data: its only power
is minting and revoking other keys.
It is the most common confusion when starting out: you mint the root key, try it against
/v1/documents and get a 403. It is not broken — it is doing exactly what it should. Use it to
mint an ordinary key, and use that one.
The empresa allowlist
On top of its scopes, every key carries a list of empresas:
["*"]— every empresa in the account, including ones created later.- An explicit list of RUT, Chile's taxpayer id numbers —
["77928532-4", "78281439-7"].
A key with exactly one RUT is an empresa token, and
the Facturia-Empresa header becomes optional.
The two axes apply together: a key with documents:write over ["77928532-4"] can emit for that
empresa and for no other. Naming a different empresa returns 403 empresa_not_allowed — not a
404, because you already know its RUT and pretending it does not exist protects nobody.
Combinations worth keeping in your head
| Shape | Scopes | Empresas | What for |
|---|---|---|---|
| Backend of your own product | documents:*, received:*, tax:* | ["*"] | A single key that operates every empresa in the account |
| One key per customer (ERP, vertical SaaS) | the minimum that customer needs | a single RUT | The customer's system cannot address anything else |
| Read-only for BI or reconciliation | documents:read, tax:read, usage:read | ["*"] | A key that cannot emit even if it leaks |
| Root | keys:write | whatever it is going to delegate | Mint and revoke keys. Nothing else |
| Exploration and demos | anything, but sk_test_ | anything | A test key is a capability boundary, not a policy |
Session mode: Facturia-Mode
The /dashboard/* surface authenticates with an account JWT instead of an sk_ key, because a
browser must never hold an sk_ key. A session has no prefix to carry its mode, so the mode travels
in a header:
Facturia-Mode: testThe default is live. The backend enforces it exactly as it would enforce a key's mode: in live
mode a sandbox empresa does not exist, and vice versa. An unrecognised value is a 400, never a
silent fall back to live.
Two rules apply to a session that do not apply to a key:
- A session can mint keys AND read data. A root key cannot do the second. The difference is deliberate: a root key is a delegated credential, and a session is the account owner in person.
- An account session is the only credential that mints a root key:
POST /dashboard/api_keyswith{"root": true}(noscopes, noempresas) returns ask_live_root_…/sk_test_root_…without opening any dashboard. A key — root or ordinary — that asks forroot: truegets403 privilege_escalation: delegation goes one level deep, and a root key cannot mint another root key. See Root keys and rotation.