Facturiadocs
Start here

From sandbox to production

The whole path of a real empresa — origen and channel, mandato, certificate or centralized password, the steps a person performs at the SII, the month approval, the puesta en vivo and the folios.

The quickstart ends with a boleta — Chile's consumer receipt — issued in the sandbox. This page is what sits between that and a factura with real tax standing: a state machine you can drive end to end over the API, with three or four moments where Chilean law demands that a person act and we hand you the URL to give them.

Everything here lives behind a single read resource:

curl -sS https://api.facturia.cl/v1/empresas/emp_01K2R7Q4XW9M3B8ZC5YHTVJD6N/onboarding \
  -H "Authorization: Bearer $FACTURIA_KEY"

The exact shape of the body is in Onboarding, and the shape of the handoff to a person in PendingAction.

The two lanes

An empresa emits through one of two channels, and the channel decides which list of steps it walks. They are not two products: it is the same endpoint, the same fields and the same response.

mercadoSFG (Sistema de Facturación Gratuito)
Who emitsOur own engine, certified before the SII (Chile's tax authority)The SII's free portal, operated by us
CertificationYes: set de pruebas, simulación, intercambio, muestras impresasNone — the SII already enabled the empresa when it enrolled it
Ambientecertificacion until the resolución (the SII's authorizing ruling), produccion after itAlways produccion
FoliosOur own timbraje — a CAF, the SII-signed block of foliosThe SII assigns them on each emission
Steps11 (10 without boletas)7
Copia cedible (the transferable copy used for factoring)Yes (types 33 and 34)No

emission_channel is how the empresa emits today. certification_track is what it is certifying for, and it can be something else, or null: an empresa can emit on SFG while it certifies for mercado, and an empresa happy with the free portal forever holds the track at null. Both fields come on the Empresa object and are read-only except the channel, which you declare yourself.

Render the steps you are handed

onboarding.steps[] carries one list or the other according to the active lane. Do not assume the length or the keys: walk whatever arrives, read each step's actor and show its help. A step with a key your code does not know is rendered all the same; one that disappeared was not required.

The steps, with their actor

actor says whose step it is: customer is yours or your client's, facturia we run, sii means waiting. Three steps of the mercado lane are ours and still do not move without a person — sii_postulacion, cumplimiento and habilitacion: we file them with the custodied certificate, after an approval — and what says so is pending_action.approval, not actor.

execution is the wire's other field (Onboarding): automatic is a queued job and operator is a person of ours at a keyboard, which is the difference between «it lands overnight» and «it lands when somebody sits down to do it».

It is not a fixed fact about the product: execution goes back to operator in the API if that step's fence is closed in production (execution vuelve a operator en el API si la cerca de ese paso se cierra en producción), and the step's eta_business_days and the body's estimated_ready_at go up with it. The table below shows the production state — every fence open, automatic on all eleven rows — so read it off the wire rather than from here. While a step says operator, its eta_business_days never drops below 2 business days: a sitting is scheduled, not queued.

mercado lane — eleven steps, or ten if you did not declare boletas:

#keyactorexecutionWhat it is
1mandatocustomerautomaticThe representante legal (the company's legal representative) authorizes us to act before the SII. Revocable
2certificatecustomerautomaticThe representante legal's .pfx, into the vault
3sii_postulacionfacturiaautomaticWe file it with the custodied certificate; the account holder authorizes the moment
4set_de_pruebasfacturiaautomaticWe emit the set the SII requires for each type
5simulacionfacturiaautomaticThe simulation that precedes authorization
6intercambiofacturiaautomaticWe prove the empresa receives and answers third-party documents
7muestras_impresasfacturiaautomaticWe send the printed samples of each type
8cumplimientofacturiaautomaticWe file it with the custodied certificate; the representante legal approves the act and, if it was not set before, the month
9resolucionsiiautomaticThe SII issues the resolución. Here you only wait
10habilitacionfacturiaautomaticOnly if you declared boletas: we enable them end to end with the custodied certificate; the representante legal decides when to start and approves the final declaración
11foliosfacturiaautomaticWe request the first timbraje (CAF) of each type

SFG lane — seven steps, no certification, plus two if you declare boletas:

#keyactorexecutionWhat it is
1mandatocustomerautomaticThe same mandato, the same record, the same revocation
2credentialcustomerautomaticA custodied certificate or delegation: our RUT (the Chilean tax id) as a usuario autorizado
3clave_centralizadocustomerautomaticThe Sistema de Facturación Centralizado password, through POST /v1/empresas/{empresa_id}/sfg_credential
4usuario_autorizadocustomerautomaticWe check that our certificate can act for the empresa in Portal MIPYME
5emisor_verificadofacturiaautomaticWe confirm the empresa is enrolled and authorized to emit
6puesta_en_vivofacturiaautomaticOurs: the canary and per-empresa enabling of real emission
7emittingfacturiaautomaticDone: we emit and receive through its free-system account
8habilitacionfacturiaautomaticOnly if you declared boletas: the free portal has no boleta form, so our own engine issues them and we get them habilitated at the SII first. Two decisions are yours: when to start, and the final declaración
9foliosfacturiaautomaticOnly if you declared boletas: the first CAF for types 39/41. The free lane's facturas carry no CAF

Trap

A free-lane empresa's boletas go through our own engine

Portal MIPYME never had a boleta form, so a 39 or a 41 does not travel the free system: our own engine builds, signs and sends it, exactly as it does for a mercado empresa. The empresa's emission_channel does not decide this — the type does. What decides whether you can issue one is the SII's boleta habilitación (step 8) and the first CAF (step 9); until both are in, POST /v1/documents answers 422 empresa_not_emitting naming the habilitacion step, and capabilities.tipos lists neither 39 nor 41. Your facturas wait for none of it: they go live as soon as step 7 is done.

Trap

`puesta_en_vivo` is never yours

It is an actor: facturia step with stall_actor: facturia: a wait with an estimate, not an action_required. If your assistant renders it as something the client can press, it will send a person to stare at a page where there is nothing to do. habilitación is reserved for acts of the SII; puesta en vivo is ours, and it happens in both lanes.

Already emitting with another provider?

The SII certifies the taxpayer, not the software. The resolución your empresa already holds stays valid when it changes vendors, and certifying again would mean re-earning something it already has. So you state it when you create the empresa — or later, with PATCH /v1/empresas/{empresa_id} — and we check it.

curl -X POST https://api.facturia.cl/v1/empresas \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rut": "77928532-4",
    "legal_name": "Connect SpA",
    "origen": "mercado",
    "doc_types": [33, 34, 61],
    "resolucion": { "numero": 80, "fecha": "2026-06-21" }
  }'

numero and fecha are the NroResol and FchResol from the carátula: they are printed on every document your empresa has already issued with its current vendor. Nobody has to go to the SII for them.

Stating it skips nothing. The field writes two columns and moves no step. Read it back: resolucion.verified is false until we have checked it, and until then the empresa walks the whole list.

{
  "resolucion": {
    "numero": 80,
    "fecha": "2026-06-21",
    "verified": false,
    "verified_at": null
  }
}

How we check it

By asking the SII for folios. As soon as your certificate is in custody, the machine asks the portal to timbrar one of the document types you declared: the SII refuses a timbraje for a RUT it has not authorized for that type, so a granted range is the proof and a refusal is the answer.

  • It grants them → resolucion.verified turns true, the certification steps report skipped with the note that says why, and estimated_ready_at is today: all that is left is the rest of your folios.
  • It refuses → verified stays false and the sii_postulacion step is yours again, with blocked_reason.resolucion_claim carrying the SII's own sentence. We never leave you without an explanation.

It is an act, not a reading, and there is no other way: no SII consulta separates a mercado resolución from an enrolment in the free system, which is why we check it by using it.

Boletas

The boleta electrónica habilitación is a separate authorization, with its own date and no number — the SII's mail says «habilitados los documentos de boleta electrónica a contar del …» and that is all of it. State it separately, alongside doc_types containing 39 or 41:

{ "resolucion_boletas": { "fecha": "2026-09-01" } }

It is checked the same way, with a 39 or 41 timbraje. If it works, your boletas are enabled without going through our habilitación chain; if not, the ordinary habilitación steps appear.

The casilla de intercambio stays yours

Changing the exchange address in Mi SII is an act of administration on your own account, and the mandato expressly forbids us from touching it. Until you change it, your suppliers keep sending documents to your previous provider: nothing is lost, but nothing arrives here either. Your address with us is published per empresa as reception_email, and the casilla_intercambio act on GET /v1/empresas/{empresa_id}/onboarding carries the instructions for the person who can do it.

The path, step by step

Account and first key

The same as the quickstart, with one change: Facturia-Mode: live instead of test, and the full cold-start scope set.

curl -sS https://api.facturia.cl/dashboard/api_keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Facturia-Mode: live" \
  -H "Content-Type: application/json" \
  -d '{"name":"produccion","scopes":["documents:read","documents:write","empresas:read","empresas:write","webhooks:read","webhooks:write"],"empresas":["*"]}'

Minting an sk_live_ key from an unverified account answers 403 email_verification_required.

Build the whole thing against the sandbox first

An sk_test_ key walks the full cycle — emission, statuses, webhooks, PDF — without touching the SII and at no cost. A sandbox empresa is born emitting: the machine on this page is skipped entirely in test mode, and that is deliberate. Finish the integration there and change the prefix afterwards.

The real empresa: origen and emission channel

curl -sS https://api.facturia.cl/v1/empresas \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "rut": "76000002-7",
    "legal_name": "Ejemplo SpA",
    "origen": "ninguno",
    "doc_types": [33, 34, 61]
  }'

origen is required in live mode (Origen): gratuito if the empresa already emits through the SII's portal, mercado if it already uses a provider, ninguno if it does not emit electronically yet. Omitting it with an sk_live_ key is 400 invalid_field.

origen does not choose the channel and does not decide what can be skipped. It is a statement about the present that routes the wizard's copy; the certification skip is settled against the SII, empirically. The emission channel is a different preference — stay on the free portal or move to the certified engine — and that is why it is declared separately.

ambiente is derived and read-only: SFG is always producción; mercado is certificación until the resolución arrives. Do not send it.

The mandato is signed by the representante legal, who very often is not the person holding the account. So the default path is to mint a link and hand it over:

# Cuerpo vacío = acuñar el enlace de firma.
curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/mandato \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

The response carries the signing_url. It is returned once only — from there on we keep nothing but its hash. To find out how it is going, you read it:

curl -sS https://api.facturia.cl/v1/empresas/$EMPRESA_ID/mandato \
  -H "Authorization: Bearer $FACTURIA_KEY"

which answers status, terms_current and acceptance_method (available from v1.1).

Scopes: minting and reading the mandato wants mandato:write. Posting an acceptance with a body —the case of the integrator who collected the signature in their own flow— wants mandato:attest, is recorded visibly as acceptance_method: api, never enters the default set of a new key, and is never granted by implication: empresas:write implies mandato:write (so a key that already exists can still mint and read the mandato) but not mandato:attest, which has to be asked for explicitly (ADR-0012 §4). See Scopes.

GET /v1/mandato_terms is public (no authentication) and today publishes the current version:

{ "object": "mandato_terms", "version": "1.0", "status": "active", "effective_date": "2026-08-24" }

An acceptance against a version that is not current — yours went stale because the text changed — is refused with 422 mandato_terms_changed, not mandato_terms_draft: there is no draft state blocking live-mode acceptance. GET …/mandato publishes terms_current with the current version and its content_hash; if it does not match what you signed, you need to re-sign with a new signing_url (see «Revoke and re-sign» below).

Revoke and re-sign

The mandato is revocable "at any time, without stating a reason, without penalty or charge" (cláusula 7.1), through two doors:

  • With your key or your session: DELETE /v1/empresas/{empresa_id}/mandato — 204 even when there was no live mandato, because you asked for a state ("I no longer have your authorisation") and that state is the outcome either way.
  • The representante legal, without a key: the signing receipt e-mail carries a perpetual link to .../onboarding/{empresa_id}/mandato_revocation?token=…. The page asks for the representante legal's RUT exactly as recorded on the mandato they signed — the link never expires, so the RUT is what stops a forwarded e-mail from being a stop button — and calls POST https://api.facturia.cl/onboarding/mandato/revoke with {"token": "…", "rep_legal_rut": "…", "revoke": true}. If the link was lost, it is re-minted with POST .../onboarding/mandato/revocation_link (same two factors).

What revoking does: the empresa becomes suspended immediately — the same tick, not up to five minutes later — and stops being able to emit; documents already emitted with tax value keep existing, revocation does not delete them. Certificate custody is a separate decision (cláusula 7.1.1): revoking the mandato does not destroy the .pfx — that is DELETE /v1/empresas/{empresa_id}/certificate.

Re-signing restores the position the empresa held before the revocation (cláusula 7.1.3): if it had already certified, it does not certify again just because it re-signed. The same signing-link flow above is what re-signs.

A terms version change, the other reason to re-sign: if GET /v1/mandato_terms publishes a version different from the one the current mandato accepted, any act that depends on the mandato answers 422 mandato_terms_changed instead of proceeding. The way out is the same: mint a fresh signing_url with POST …/mandato {} and have the representante legal sign it again; the rest of the empresa's position does not move.

Certificate (mercado) or centralized password (SFG)

In the mercado lane, step 2 is the representante legal's digital certificate:

curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/certificate \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -F "[email protected]" -F "password=…"

It is encrypted into a per-empresa vault and never comes back out; it is not an argument of the emission call. It can be purged with DELETE /v1/empresas/{empresa_id}/certificate, which revokes the custody and suspends emission.

In the SFG lane the equivalent step is the portal credential: the password of the SII's centralizado certificate (not the clave tributaria), which Portal MIPYME asks for again at the final step of every emission.

curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/sfg_credential \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"clave_centralizado": "…"}'
{
  "object": "sfg_credential",
  "empresa_id": "emp_01K2R7Q4XW9M3B8ZC5YHTVJD6N",
  "clave_centralizado": { "stored": true, "stored_at": "2026-08-24T12:03:11Z" },
  "purpose": "sfg_emission_centralizado_signing"
}

GET on the same resource returns the same shape — only whether we hold it and since when, never the password itself — and DELETE /v1/empresas/{empresa_id}/sfg_credential destroys it (204, even when there was nothing to destroy: you asked for a state, "you hold nothing of mine", and that is the outcome either way).

The clave_centralizado step (available from v1.1) is the one that names that handoff. It is actor: customer, its performed_by.role is usuario_administrador, and its evidence is own_record: it lands in our own tables in the same act as the POST, so we watch it happen without asking the SII:

{
  "blocked_on": "clave_centralizado",
  "pending_action": {
    "object": "pending_action",
    "act": "sfg_clave_centralizado",
    "step": "clave_centralizado",
    "title": "Entregarnos la clave de tu Sistema de Facturación Gratuito",
    "performed_by": {
      "role": "usuario_administrador",
      "label": "El usuario administrador de la empresa ante el SII, o quien tenga la clave del Sistema de Facturación Gratuito."
    },
    "verification": {
      "method": "own_record",
      "recheck_url": "POST /v1/empresas/{empresa_id}/onboarding/steps/clave_centralizado/recheck"
    },
    "agent_actions": [
      {
        "method": "POST",
        "url": "https://api.facturia.cl/v1/empresas/{empresa_id}/sfg_credential",
        "body": { "clave_centralizado": "…" }
      }
    ]
  }
}

Trap

We never ask for the clave tributaria

The whole tax suite runs on a certificate-authenticated session against the SII's own data area. The Sistema de Facturación Centralizado password is not the clave tributaria — the taxpayer's own SII password — and does not replace it.

The human steps: the handoff pattern

When a step needs a person, the response does not say "blocked". It says who, where, what to do and how we are going to find out — the same shape with which OAuth hands over authentication.

{
  "object": "onboarding",
  "status": "action_required",
  "blocked_on": "sii_postulacion",
  "pending_action": {
    "object": "pending_action",
    "act": "sii_postulacion",
    "step": "sii_postulacion",
    "title": "Autorizar la postulación al sistema de facturación de mercado",
    "action_url": "https://app.facturia.cl/onboarding/…",
    "sii_url": "https://www.sii.cl/…",
    "performed_by": {
      "role": "titular_cuenta",
      "label": "El titular de la cuenta de Facturia",
      "why_not_delegable": "…",
      "legal_basis": ["Mandato Facturia, cláusula 4.2 a) «Certificación como emisor electrónico»", "…"]
    },
    "approval": {
      "act": "postulacion_autorizacion",
      "role": "titular_cuenta",
      "headline": "¿Presentamos la postulación ahora?",
      "consequences": ["Si hoy emites en el Sistema de Facturación Gratuito, tu empresa pierde su calidad de usuario activo…"],
      "button_label": "Sí, preséntenla",
      "reject_label": "No todavía"
    },
    "instructions": [{ "order": 1, "text": "…" }],
    "verification": {
      "method": "sii_probe",
      "recheck_url": "POST /v1/empresas/{empresa_id}/onboarding/steps/sii_postulacion/recheck",
      "recheck": {
        "method": "POST",
        "url": "https://api.facturia.cl/v1/empresas/{empresa_id}/onboarding/steps/sii_postulacion/recheck"
      },
      "sii_latency": "…"
    },
    "evidence": "captured",
    "caveat": null
  }
}

`recheck_url` is deprecated

recheck_url still ships, unchanged, so nothing reading it today breaks. It is a relative "POST <path>" string — you have to parse the method apart and prefix the host yourself. Use recheck above instead: the same endpoint, as {method, url}, absolute. The same applies to error.details.onboarding_url below: error.details.onboarding is the structured version.

Three fields decide what your agent does with this:

  • performed_by.role — who you hand it to. representante_legal, usuario_administrador, titular_certificado or titular_cuenta. A handoff addresses a role, never a person: we do not know which of them your principal is.
  • verification.method — how we find out. sii_probe (we re-read the SII), own_record (it lands in our tables) or no_producer, which means that nothing in this version observes it and that here a person has to confirm it. Do not tell anyone you are watching a no_producer.
  • evidence and caveat — how well backed the click path is. If evidence is not captured, caveat is not null and says what we are unsure of. Pass it on.

instructions[] comes in Spanish and is passed on verbatim: do not paraphrase it and do not add steps to it.

pending_action.action_url (and steps[].action_url) is the URL of a live link —not expired, not revoked— if one exists for that step. If none exists, it is null. It is never a URL that opens on "the link is missing".

To mint one, the handoff itself says how (available from v1.1):

"link": {
  "mint": {
    "method": "POST",
    "url": "https://api.facturia.cl/v1/empresas/{empresa_id}/onboarding/links",
    "body": { "step": "sii_postulacion" }
  },
  "ttl_seconds": 2592000,
  "reusable": true
}

POST /v1/empresas/{empresa_id}/onboarding/links accepts any actor: customer step in the empresa's active catalog, whether from the mercado lane or from SFG. The one exception is mandato, which has its own endpoint (POST …/mandato with an empty body), which is why its link.mint points there with body: {}. Minting wants onboarding:write.

The url is returned once only; minting again revokes the previous one. You revoke it with DELETE /v1/empresas/{empresa_id}/onboarding/links/{step}, which answers 204 whether or not one existed.

The way back: recheck

When the person is done, your agent tells us and we go and look:

STEP=sii_postulacion

curl -sS -X POST \
  https://api.facturia.cl/v1/empresas/$EMPRESA_ID/onboarding/steps/$STEP/recheck \
  -H "Authorization: Bearer $FACTURIA_KEY"

This accepts a claim and produces no fact. It carries no body, it writes no step's status and it does not move the empresa. It enqueues the producer that reads the SII and returns the state exactly as it stood at the moment of the call. If you post about a step in action_required and read action_required back, that is the endpoint working: verified comes in the response and it is always false. The full shape is in OnboardingRecheck.

It is not mandatory: sii_probe and own_record reach us on their own. recheck is there so you do not have to wait for the next sweep.

When the step is OURS and all that is missing is a yes: approve

Three steps are ours to run and still do not start on their own, because each has a consequence someone has to decide first: the postulación (an empresa on the Sistema de Facturación Gratuito loses its active-user status the instant the SII authorizes it), the Declaración de Cumplimiento (it closes certification and, from the month it is filed for, opens the Información Electrónica de Compras y Ventas obligation) and the boleta habilitación (it opens a 24-hour SII clock). Those steps carry an approval block in their pending_action:

"approval": {
  "act": "postulacion_autorizacion",
  "role": "titular_cuenta",
  "headline": "¿Presentamos la postulación ahora?",
  "consequences": ["…lo que hay que leer ANTES de apretar…"],
  "button_label": "Sí, preséntenla",
  "reject_label": "No todavía"
}

The canonical way to take that decision is the hosted page (ADR-0012 §1): mint the step's link and hand it to the account holder. If your integration collected the decision in its own flow, the door is:

curl -sS -X POST \
  https://api.facturia.cl/v1/empresas/$EMPRESA_ID/onboarding/steps/$STEP/approve \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"act": "postulacion_autorizacion", "decision": "approve"}'

It wants onboarding:attest — never implied by empresas:write — the act must be the one the step is currently asking for (otherwise 409 onboarding_approval_not_offered or 400 invalid_field), and decision: "reject" is recorded exactly like an approval: «not yet» is an answer, and executors read the MOST RECENT one.

The Declaración de Cumplimiento: we file it

The Declaración de Cumplimiento closes mercado certification. Once it is filed, the empresa is operating in the electronic invoicing system and owes the Información Electrónica de Compras y Ventas from the month it is filed for, whether or not it emits that month. The SII allows postponing it, so the month stays a decision rather than an automatic effect of the rest of certification.

We file it ourselves, with the representante legal's certificate we hold in custody — the same one we sign your documents with. You do not have to log in to the SII site or fill out anything there.

All that is left is for the representante legal to approve, on their own page. The role that can approve this act is rep_legal, not just any titular_cuenta:

"approval": {
  "act": "cumplimiento_declaracion",
  "role": "rep_legal",
  "headline": "¿Presentamos la Declaración de Cumplimiento?",
  "consequences": [
    "Tu empresa queda operando en el sistema de facturación electrónica desde el mes indicado abajo.",
    "Desde ese mes debes la Información Electrónica de Compras y Ventas, emitas o no."
  ],
  "button_label": "Sí, preséntenla",
  "reject_label": "No todavía"
}

That one click approves two things at once: the act before the SII, and — if the account holder had not already set the month through the next section — the month itself. Before filing we check, against the SII itself, that the holder of the custodied certificate is the same representante legal the empresa has authorized there: that is what Res. Ex. SII N° 81 de 2005, resolutivo CUARTO d) requires — that the representante legal be the one to declare "con su certificado digital" — and the same check we already run for the boleta habilitación.

Setting the month ahead of time, separately: go_live

If the account holder wants to decide the month before there is anything to approve — or is a different person from the representante legal — they can set it in advance:

# Cuerpo vacío = acuñar el enlace de aprobación para el titular.
curl -sS -X POST https://api.facturia.cl/v1/empresas/$EMPRESA_ID/onboarding/go_live \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

The response carries { "approval_url": "…" } (available from v1.1): the page where the account holder sees exactly what they are approving — the month's consequences above, the authorize button below. Minting wants onboarding:write.

There has to be something to decide. If the empresa has not reached the declaración yet we do not mint a link that would open onto an empty page:

  • 409 go_live_not_ready — the mercado lane has not reached the decision. error.details.blocked_on names the step holding it up and error.details.next is the {method, url} that reports when it moves. Retry: it will be ready.
  • 409 go_live_not_applicable — this empresa is not certifying for mercado, so it has no declaración de cumplimiento at all. What gates its emission is puesta_en_vivo, which is ours.

Neither applies to {"authorized": false}: a decision that exists can always be withdrawn.

The form with a body —{"authorized": true} posted by an integrator who collected the approval in their own flow— sits behind onboarding:attest, the same treatment as mandato:attest. Sending {"authorized": false} withdraws the authorization as long as nothing has been filed; once it is filed there is nothing left to withdraw and the answer is 409 cumplimiento_already_filed.

This authorization is free (#530). It was one of the acts that required an active subscription until 2026-09-04, and only on this door: the hosted cumplimiento page for the representante legal answered 200 for the same unsubscribed empresa. It is a decision, not an emission. The subscription is required where the value is: emitting, the credit note, requesting folios and sending. An authorized empresa that has not paid emits nothing.

Facturia files it; the representante legal approves it

Since ADR-0013 (2026-09-06) we file the act before the SII with the custodied certificate — never the representante legal in person. What they do is approve, on their own page, an act that already identifies them: Res. Ex. SII N° 81 de 2005 requires that the representante legal be the one to declare "con su certificado digital", and that is exactly the certificate we hold in custody under the mandato.

Puesta en vivo

Once the lane has finished its part, one step of ours is left: enabling real emission for that empresa. In SFG it is the puesta_en_vivo step, after the canary; in mercado it is what follows the folios. It is shown as a wait with an estimate and never as something the client has to do.

In the meantime, Empresa.capabilities tells the truth in a single read (available from v1.1):

"capabilities": {
  "can_emit": false,
  "tipos": [],
  "blocked_by": "puesta_en_vivo",
  "next": { "who": "facturia" }
}

It is the same function the emission gate uses to answer 422 empresa_not_emitting, so the block and the refusal cannot contradict each other. blocked_by takes onboarding, subscription, status, puesta_en_vivo or null, and next.who is agent, human or facturia.

Folios

In the mercado lane the folios — the pre-authorized numbers each document has to carry — are a CAF we request from the SII and that lives on the empresa:

curl -sS https://api.facturia.cl/v1/folios \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"

auto_request_folios ships on by default — it is the only automation that is — and requests a fresh timbraje when availability drops below the threshold. You can also ask for one yourself with POST /v1/folios/requests, or upload your own CAF with POST /v1/cafs.

In the SFG lane there is no timbraje: the SII assigns the folio on each emission. GET /v1/folios answers with the sfg_managed shape and folio_preview is null on drafts. That is not missing data; it is how that channel works.

Details in Folios and CAF.

Webhooks

The last thing worth having ready before the first real emission, because it is what saves you from polling:

curl -sS https://api.facturia.cl/v1/webhook_endpoints \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.miempresa.cl/hooks/facturia",
    "enabled_events": ["document.accepted","document.rejected","document.reparo"]
  }'

The secret (whsec_…) is returned once only. How to verify the signature is in Webhooks · Signature.

When the gate closes anyway

If you emit ahead of time, the refusal carries the handoff inside it:

{
  "error": {
    "type": "unprocessable_error",
    "code": "empresa_not_emitting",
    "message": "…",
    "hint": "…",
    "retriable": false,
    "details": {
      "onboarding_status": "sii_postulacion_pending",
      "onboarding_url": "/v1/empresas/{empresa_id}/onboarding",
      "onboarding": { "method": "GET", "url": "https://api.facturia.cl/v1/empresas/{empresa_id}/onboarding" },
      "pending_action": { "…": "…" }
    }
  }
}

onboarding_url is the same address as always, but relative — you have to prefix the host yourself. onboarding (#419) carries the identical endpoint absolute, as {method, url}, the same shape as pending_action.link.mint. Both still ship together; onboarding_url is marked deprecated in the contract and is the one to stop reading.

It is the same PendingAction object the onboarding returns, so your error handling and your wizard can share the code. The codes you will see: 422 empresa_not_emitting, 422 mandato_required, 422 postulacion_required, 422 onboarding_requirement_unmet and, if the empresa is otherwise able to emit and has no subscription, 402 subscription_required. Eligibility is evaluated first (#475): while the checklist is unfinished the refusal is 422 empresa_not_emitting with its handoff, never «pay». Each has its own entry in Errors.

Next step

On this page