Facturiadocs
Authentication

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

ScopeGrants
documents:readRead emitted documents, PDF, XML and events
documents:writeEmit documents, retry, send by email
received:readRead the electronic tax documents (DTE) received from suppliers
received:writeAccept / claim / acknowledge receipt
empresas:readRead empresas, onboarding, configuration, folios (the SII-authorised document numbers)
empresas:writeCreate empresas, upload certificates, change configuration
folios:writeAsk the SII (Chile's tax authority) for a CAF, the signed block of folios; upload a CAF
tax:readRCV (the SII's ledger of purchases and sales), F29 (the monthly return) proposals and filings, suggestions
tax:writeCharacterise, file F29, confirm suggestions, enter expenses
webhooks:read / webhooks:writeWebhook endpoints and event replay
usage:readUsage ledger
keys:writeMint, 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:write

Not 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:

ScopeGrants
mandato:writeMint the signing link for the mandato — the power of attorney an empresa grants Facturia — and read it (GET …/mandato)
mandato:attestPost 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:writeMint links for human steps, ask for a re-read, mint the go_live approval link, upload the certificate
onboarding:attestPost 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

ShapeScopesEmpresasWhat for
Backend of your own productdocuments:*, received:*, tax:*["*"]A single key that operates every empresa in the account
One key per customer (ERP, vertical SaaS)the minimum that customer needsa single RUTThe customer's system cannot address anything else
Read-only for BI or reconciliationdocuments:read, tax:read, usage:read["*"]A key that cannot emit even if it leaks
Rootkeys:writewhatever it is going to delegateMint and revoke keys. Nothing else
Exploration and demosanything, but sk_test_anythingA 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: test

The 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_keys with {"root": true} (no scopes, no empresas) returns a sk_live_root_… / sk_test_root_… without opening any dashboard. A key — root or ordinary — that asks for root: true gets 403 privilege_escalation: delegation goes one level deep, and a root key cannot mint another root key. See Root keys and rotation.

On this page