# Changelog (/en/docs/changelog)

`/v1` evolves **additively**: new endpoints, new optional fields, new response fields, new enum values, new event types. Your client must ignore what it does not recognise. A removal or a change of behaviour gets **12 months** of notice, a `Sunset` header on the affected endpoints, and an entry here (§5.8).

There is an RSS feed at [`https://docs.facturia.cl/en/feed.xml`](/en/feed.xml). Subscribing to it is part of a good integration.

## 2026-10-06 — `GET /v1/contribuyentes/\{rut\}`: razón social, activities and a giro that fits the factura, from the SII's public nómina [#2026-10-06--get-v1contribuyentesrut-razón-social-activities-and-a-giro-that-fits-the-factura-from-the-siis-public-nómina]

**Where it comes from.** The SII's public nómina of personas jurídicas ([https://www.sii.cl/sobre\_el\_sii/nominapersonasjuridicas.html](https://www.sii.cl/sobre_el_sii/nominapersonasjuridicas.html)), with no captcha and no login. Facturia reloads it when the SII publishes a new edition, and `source.published_at` says which one answered. Personas naturales are not in it: their RUT answers 404, like a RUT that does not exist.

**The suggested giro.** The official descriptions rarely fit `GiroRecep`'s 40 characters (the one for buying and selling vehicles has 81), and cutting them would change what the factura says about the receptor. So `suggested_giro` uses a reviewed short text for the most common activities (`basis: curated`), the official description when it fits as it stands (`sii_description`), or nothing.

**How to use it.** Show it to the person and, once confirmed, store it with `PATCH /v1/empresas/\{id\}/counterparties/\{rut\}`: that is what the next emission reads. No `Facturia-Empresa` needed; it requires `empresas:read` and is limited to 120 requests a minute per account.

**New error code.** `503 contribuyentes_unavailable` while the nómina has not been loaded; retry after `Retry-After`.

## 2026-09-23 — A natural person as a boleta receptor: the name reaches the document [#2026-09-23--a-natural-person-as-a-boleta-receptor-the-name-reaches-the-document]

**What happened.** `POST /v1/documents` stores the receptor in its public shape (`legal_name`, `address`) and the boleta builder reads another. The factura and SFG-portal lanes translated between the two; the boleta lane did not, so a boleta to «María José Pérez Soto» went out with her RUT in `&lt;RUTRecep&gt;` and `Sin RUT` in `&lt;RznSocRecep&gt;` and in the timbre. The SII accepts it — it is a valid document — but it does not name the person, and an Isapre or seguro complementario reimbursement reads exactly the name.

**What changes.** The name and address you send reach the boleta, and the timbre says what the document says. The anonymous boleta is unchanged: no receptor, `66666666-6` and «Sin RUT». There is no new field and no company flag: a natural person is a `Party` like any other.

**And the credit note.** A 61 to that same person is a factura-family document and carries `giro`, `address.street` and `address.comuna` like every 61, even when it annuls a boleta. `POST …/credit_note` copies the receptor from the original boleta, which has none of them: store them once in the address book (`PATCH /v1/empresas/\{id\}/counterparties/\{rut\}`) before annulling, or the note is refused with `400 receptor_incomplete` before a folio is spent.

## 2026-09-23 — «Already emitting with another provider?»: a declared resolución is verified when an operator confirms it, and `resolucion.verification_status` says where it stands [#2026-09-23--already-emitting-with-another-provider-a-declared-resolución-is-verified-when-an-operator-confirms-it-and-resolucionverification_status-says-where-it-stands]

**What happened.** An empresa that already invoices through another provider declares its resolución (`resolucion: \{numero, fecha\}`), and we verify it by asking the SII for one folio range of a declared factura tipo: the SII will not timbrar for a RUT it has not authorized. That request went out with the empresa's own ambiente, which starts in certification, and since 2026-09-08 an emission timbraje in certification is blocked before a connection opens. The local block was read as the SII's refusal, so no declared resolución could ever be verified, and the empresa fell into the full certification without the SII being asked anything.

**What changes.** Verification is made against the production SII, the only honest question. Because registering a range in production is irreversible, it never happens implicitly: not when the empresa is created, not by the onboarding process, not in certification. A Facturia operator confirms it, empresa by empresa, with `POST /v1/empresas/\{id\}/resolucion/verify` and `confirm: true`. That operation authenticates with the operator's secret; no API key can call it, root keys included.

**What you see.** `resolucion` gains three fields. `verification_status` is `pending_verification` (declared; waiting for the confirmation, in flight, or an attempt the SII did not answer about the RUT), `verified` (the SII granted the range: certification is skipped and the empresa moves to production) or `refused` (the SII answered without a range). `verification_request` is the exact request: tipo, quantity and ambiente. `refusal` carries the SII's words when it refused. Re-declaring the resolución with `PATCH` returns a `refused` claim to `pending_verification`. `verified` and `verified_at` keep their meaning.

**New error codes.** `400 confirmation_required`, `401 operator_authentication_required` and `409 resolucion_verification_not_allowed` (the reason in `error.details.reason`). Only a caller of the operator operation receives them.

## 2026-09-10 — `GET /v1/folios` counts the folios you actually hold: queued ranges stop being invisible [#2026-09-10--get-v1folios-counts-the-folios-you-actually-hold-queued-ranges-stop-being-invisible]

**What happened.** Ever since the allocator learned to ROLL — when `next_folio` passes `caf_hasta` it takes the next active range starting above it and emits from there — the platform held two different truths: the emitter's, which saw every range, and the readers', which kept subtracting `caf_hasta - next_folio + 1` over a single one. This endpoint was one of those readers, and its own header promised the opposite: «it reads the same tables `allocate_dte_folio` allocates from, so what it reports is what the next emission will actually get». The response even listed both ranges in `ranges[]` while reporting `available: 1`.

**What changes in the object.** `available` is the sum of the CHAIN the allocator will consume: what is left of the current range, plus each active range starting above the previous one, in order. A chain and not a sum of everything present: an overlapping range is never reached, so it is never counted. `next_folio` is the folio the next emission takes, and it is `null` when no emission of that type could succeed right now — it used to report the out-of-range number the counter was parked on. `status` keeps its meaning but is computed over the correct count: a company that already held its replenishment stops showing as `low`.

**And a new block, `stock`.** `stock.chain` carries every range that will be consumed, in order, with `from`, `to`, `remaining` and `expired`; `stock.stops_at_expired_range` says whether the walk ended on a lapsed range rather than on «nothing more was granted». That is not the same as `available == 0`: a chain can carry folios usable today and still stop behind them at a range whose vigencia has expired. Res. Ex. SII N° 58/2017 §1° makes such a range unusable for the documents that grant crédito fiscal, the allocator rolls onto it anyway and the emission path refuses it — so counting what sits behind it would promise folios no emission can reach.

**The same mistake was costing ranges.** Automatic replenishment read the same subtraction, so it saw `low` on a company that already had its replacement range — and it answers `low` by asking for a timbraje. An unused range has to be annulled and five of them block further timbraje for that document type, so the old arithmetic pushed a company toward that block one refill at a time. Replenishment now uses the same chain, so a waiting range counts and the request is not made.

**Nothing changes about emission.** The allocator was already right and was not touched: anyone holding queued ranges could always emit against them. What was wrong was what the API counted, and therefore what an agent or a dashboard repeated. The `status` enum also now documents `sfg_managed`, which this endpoint has returned since the SFG channel existed and the contract never declared.

## 2026-09-09 — The address book: record a customer’s address once and stop typing it on every document [#2026-09-09--the-address-book-record-a-customers-address-once-and-stop-typing-it-on-every-document]

**Why it exists.** On 8 September 2026 seven exempt facturas went out with a receptor carrying nothing but a RUT. `GiroRecep`, `DirRecep` and `CmnaRecep` are optional in the SII’s schema, so the envelope passed validation (`EPR`, with a TrackID) and the SII refused every document inside it: ACEPTADOS 0 / RECHAZADOS 1, seven times. Seven production folios burned silently. The two sources the resolver had that day — the empresa’s own address, only when the receptor is the emisor, and earlier documents to the same RUT — are no help for the first invoice to a new customer, which is exactly the case that burned the folios.

**How you use it.** `PATCH /v1/empresas/\{empresa_id\}/counterparties/\{counterparty_rut\}` with `legal_name`, `giro` and `address` is an upsert: the first call creates the entry and later ones edit it. It is partial — a field you do not send keeps its value — and `null` clears, which is how a value recorded by mistake is removed. No earlier document is required: recording the address of a RUT you have not invoiced yet is precisely the point. Every value is held to the same SII schema limit a value sent on a document is held to (`giro` 40 characters, `address.street` 70, `address.comuna` and `address.city` 20, `legal_name` 100) and to the same ISO-8859-1 rule, and is refused over the limit rather than truncated: silently shortening a name of trade would change what every document to that counterparty says about who they are.

**The order rules, and it runs from most deliberate to most inferred.** What you send on the document wins; then your own empresa’s address, only when the receptor is you (the internal traslado guía); then the address book; and last an earlier document to the same RUT. The book beats the earlier document on purpose: it is where you go to CORRECT an address we have been getting wrong, and if last month’s won, the correction would be ignored until some emission happened to carry the new one. `GET /v1/empresas/\{empresa_id\}/counterparties/\{counterparty_rut\}` answers with what we actually know and `source` says which of the two it was: `api` if you recorded it, or the earlier document if we inferred it.

**And the list is still derived.** `GET /v1/empresas/\{empresa_id\}/counterparties` reads the receivers of the documents already emitted and joins whatever contacts and addresses exist; nothing copies a receiver into a row. Every entry now also carries `legal_name`, `giro`, `address`, `source` and `updated_at`, and on that list `source: null` means "nothing recorded", not "nothing known" — the single-counterparty read is the one that falls back to earlier documents.

## 2026-09-08 — An empresa that already invoices with another provider stops certifying twice: state the resolución and we check it by using it [#2026-09-08--an-empresa-that-already-invoices-with-another-provider-stops-certifying-twice-state-the-resolución-and-we-check-it-by-using-it]

**The SII certifies the taxpayer, not the software.** An empresa that already invoices electronically through another provider holds a resolución the SII issued to its RUT, and that resolución does not stop being valid because it changed vendors. Until today no surface could state it — the only door that ever wrote those two fields had been deleted as unsafe — so every empresa arriving from another provider walked all eleven certification steps.

**The numbers are on your own invoices.** `numero` and `fecha` are the `NroResol` and `FchResol` from the carátula of every documento tributario electrónico, so they can be read off any recent invoice. Nobody has to go to the SII for them.

**Stating is not skipping.** The field writes two columns and moves no step. `resolucion.verified` comes back `false` until we have checked it and the empresa walks its whole list meanwhile; branch on `verified`, never on the number being present.

**We check it by using it.** With your certificate in custody, the machine asks the SII's 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: `verified` turns `true`, the certification steps report `skipped` with the note that says why, and `estimated_ready_at` is today. There is no consulta that answers this — no SII page separates a mercado resolución from an enrolment in the free system — which is why we check it by acting.

**A refusal is never silent.** If the SII refuses the timbraje, `verified` stays `false`, the full certification lane is offered, and the `sii_postulacion` step's `blocked_reason.resolucion_claim` carries the SII's own sentence, its code and the type it was asked about.

**Boletas, with a date of their own.** The boleta electrónica habilitación is a separate authorization and carries no number: the SII's mail says «habilitados los documentos de boleta electrónica a contar del …». State it in `resolucion_boletas`; it is checked with a 39 or 41 timbraje, and when it works the boletas are enabled without going through the habilitación chain.

**The casilla de intercambio stays yours.** The mandato forbids us from touching your empresa's contact details at the SII, so until you change the casilla in Mi SII your suppliers keep sending documents to your previous provider. Your address with us is published per empresa as `reception_email`.

## 2026-09-08 — A period nobody read is not an empty period: the `sync` block speaks of the month you asked for, `POST /tax/sync` really forces, the job settles, and the MCP gets `sync_rcv` [#2026-09-08--a-period-nobody-read-is-not-an-empty-period-the-sync-block-speaks-of-the-month-you-asked-for-post-taxsync-really-forces-the-job-settles-and-the-mcp-gets-sync_rcv]

**The RCV mirror is not the SII.** What `GET /v1/tax/sales`, `GET /v1/tax/purchases`, `GET /v1/tax/summary` and the F29 proposal read is a copy filled by a daily sweep, and that sweep reads only the last three months. An empresa connected in August never had February read. Until today the `sync` block on those answers said "synced today at 05:25, `fresh`" for February anyway — it described the empresa's latest run, not the month asked for — and an agent read that as "February is current and empty" and told a person their sale did not exist. It existed; nobody had asked for it.

**`sync` speaks of the month you asked for.** With `period`, `last_synced_at` is the completion of the newest sync THAT READ THAT MONTH, `status: syncing` is one in flight for that month, and `null` is "never read". Without `period` the block still describes the whole empresa. The status vocabulary is now the real queue's too: `degraded` rests on `failed_permanent`, which is what the queue writes, instead of on two values that never existed.

**`POST /v1/tax/sync` really forces.** The queue row carried the same per-day key as the nightly sweep, so a call after 05:23 "collapsed" onto a run that had already finished and answered a `job` pointing at nothing. It now collapses only onto a sync for the same period that is still going to run or is running; if that one is done, it queues a fresh read. `period` defaults to the previous month in Santiago time and always rides in `result.period`.

**The `job` settles.** No `rcv_sync` job had ever moved past `queued`: nineteen in production on the day this was written, every one with its sync finished minutes earlier. The worker now moves it to `running` when it starts, to `succeeded` with `purchases`, `sales`, `created`, `updated`, `withdrawn_at_sii` and `partial_errors` when it lands, and to `failed` only when the queue stops retrying.

**In the MCP: `sync_rcv`.** One period, or `from`/`to` of up to ten months (the endpoint's bucket is ten per minute per empresa; if it trips mid-range the answer says exactly which periods were not queued and how long to wait), and `job_id` to check on one. `query_rcv` and `get_periodo_summary` now lead with the warning when the period was never read, and the server instructions carry rule 7: a period nobody read is not an empty period.

## 2026-09-08 — The MCP does everything the API does: 53 new tools, `folios:write` enters the vocabulary, and the RCV backfills twelve months on its own when a company connects [#2026-09-08--the-mcp-does-everything-the-api-does-53-new-tools-folioswrite-enters-the-vocabulary-and-the-rcv-backfills-twelve-months-on-its-own-when-a-company-connects]

**What comes in.** Tax: `characterize_purchases`, `revert_characterization`, `set_estado_contable`, `list_f29_propuestas`, `recompute_f29_propuesta`, `list_f29_filings`, `list_expenses`/`add_expense`, `list_statements`/`upload_statement`, `dismiss_tipo46_suggestion`, and bank connections (`list_bank_connections`, `link_bank_connection`, `sync_bank_connection`, `delete_bank_connection`). Documents: `send_document`, `emit_credit_note`, `retry_document`, `get_document_events`, `check_cesion`, `cede_document`, `revoke_document_links`. Folios and CAFs, under the new `folios:write` scope: `request_folios`, `list_cafs`, `upload_caf`, `deactivate_caf`. Reception: `acknowledge_received_document` (the acuse de recibo, which is not acceptance). Account: `get_account`, `get_job` (generic, narrates every job type), `list_usage_records`, `get_event`, `get_mandato_terms`, `list_webhook_endpoints`, `list_webhook_deliveries`. Empresa: `get_empresa`, `update_empresa` (the door to declare a resolución you already hold from another provider and to change the channel), `get`/`update_automation_settings`, `get`/`update_branding`, counterparties and their contacts. Onboarding: `get_certification`, `set_certification_inputs`, `list_onboarding_evidence`, `get_certificate`, `get_sfg_credential`, `delete_certificate`, `delete_sfg_credential`, `revoke_onboarding_link`, `request_mandato_revocation_link`.

**What stays out, and why.** The person's acts (`onboarding:attest`, `mandato:attest`: signing, approving, authorizing the go-live) have no MCP scope; key minting (`keys:write`) would create a second authority behind a grant; webhook writes make no sense for an agent with no endpoint of its own (the reads are in); `DELETE /empresas` has no use case. And one rule of the MCP's own: `update_automation_settings` refuses to turn `require_confirmation` off — that flag exists so that the person, not the agent, is the record of approval.

**The irreversible acts that do not pass through the approval page.** The confirmation gate replays exactly four actions (`emit_document`, `file_f29`, `emit_tipo46`, `reclaim_received_document`). `emit_credit_note` is not one of them, so on an empresa with `require_confirmation` it REFUSES (`confirmation_path_missing`) rather than bypass the control; elsewhere it requires `confirm_emit: true`. `cede_document`, `deactivate_caf`, `delete_certificate`, `delete_sfg_credential` and `delete_counterparty_contact` require an explicit confirmation argument and never call the API without it. Each carries the `destructiveHint` that matches what it does, not what it is called.

**`folios:write` is a new scope.** A grant older than today does not hold it: if `request_folios` answers `insufficient_scope` naming it, reconnect the agent and consent again. The consent screen shows it as consequential (a timbraje spends a finite SII-authorized resource) and leaves it out of the one-click preset.

**The RCV backfills itself.** The daily sweep read the last three months and nothing else. Now, the first time an empresa has a successful (or partial) sync, the sweep queues the twelve months before that — once, derived from the queue itself (a row for a period older than the window, in any status, means the history was already asked for), honouring `rcv_sync_frequency: manual`, in one portal session. Both live empresas already had their history requested by hand, so neither is touched; the next one to connect gets it without anyone asking. `sync_rcv` remains the door for anything older.

**The history emitted in the SII's free portal, once the portal has closed.** The «Documentos emitidos» import (#45) ran only on the transition to `emitting`, and an empresa that got there by hand never had it. The daily sweep now queues it once for every empresa with an SFG footprint and a certificate in custody — including one that already migrated to mercado. And when the Portal MIPYME no longer shows the empresa (which is what graduating to mercado does — checked live today with both house empresas), the import does not fail loudly: it retires without an alert and takes the SII's other source, the RCV sales register, from which it brings every emitted document with receptor, legal name and the net/IVA/exempt split the portal never gave. When the receptor is itself a Facturia empresa, its intercambio copy of the signed `EnvioDTE` is already in our custody: the import links it, `GET /documents/\{id\}/xml` serves the real bytes and `items` carries the line detail. For an external receptor there is no XML or PDF we can hold: `/xml` and `/pdf` answer `409 artifact_not_available`, permanent and honest, and the document lives at the SII. #46's claim ("the per-document download may re-send the DTE by email") remains untestable: no house document is reachable in the portal any more.

**And `query_rcv` says everything the row carries.** It used to narrate "neto $0 IVA $0" for a $13,916,387 exempt invoice and hide the receptor's RUT behind the legal name; an agent went looking on the internet for a RUT the SII had already given us. Every row now carries date, legal name with RUT, net, IVA, exempt and total.

## 2026-09-07 — A free-system empresa can issue boletas now — through our own engine, with the channel decided per type [#2026-09-07--a-free-system-empresa-can-issue-boletas-now--through-our-own-engine-with-the-channel-decided-per-type]

**The channel belongs to the document, not to the empresa.** One function decides it everywhere (`channelForTipo`): types 39 and 41 → Facturia's own engine, any other type → the empresa's `emission_channel`, as before. Nobody has to switch channel to issue boletas, and doing so would be exactly the wrong move: it would take that empresa's facturas off the free lane.

**What changes at `POST /v1/documents`.** A boleta from an `sfg` empresa is no longer refused at parse time, and it is validated with our own engine's grammar (line discounts, global discounts, references) instead of the portal form's restrictions. The one type the free lane still refuses with `tipo_not_available_for_channel` is the factura de compra (46), where the SII declines to open its own form.

**What gates it, and how you find out.** Issuing a boleta needs the SII's boleta habilitación and the first CAF for 39/41. Until both are in, `GET /v1/empresas/\{id\}` lists neither 39 nor 41 in `capabilities.tipos`, and `POST /v1/documents` answers `422 empresa_not_emitting` — the published code — naming the `habilitacion` step and carrying the `pending_action` with its two approvals. It does **not** answer `tipo_not_available_for_channel`: it is not that the lane cannot, it is that the SII has not authorized it yet.

**Two new steps on the free lane's checklist, and only if you declared boletas.** `habilitacion` and `folios` join `SFG_ONBOARDING_STEPS` with `only_for_tipos: [39, 41]`, so an empresa that only invoices still sees exactly the seven it saw before. They are the same two steps the mercado lane has — same actor, same evidence, the same two approvals by the titular and the representante legal on pages of ours — and they come AFTER `emitting`: **your facturas do not wait for the boleta habilitación**, they go live as soon as the puesta en vivo is done.

**One new per-empresa fact.** `boleta_emission_enabled_at` (migration 155) records the SII's «habilitados los documentos de boleta electrónica a contar del …» plus the first CAF. The onboarding machine writes it itself, exactly as it writes the puesta en vivo, and it is what the emission door and `capabilities` both read — one function, one fact, no way for two surfaces to answer differently.

## 2026-09-07 — The Python recipe we publish now generates the whole client: the `/account/*` response types are back, and so are the five schemas the generator deleted [#2026-09-07--the-python-recipe-we-publish-now-generates-the-whole-client-the-account-response-types-are-back-and-so-are-the-five-schemas-the-generator-deleted]

**Nothing changes on the wire.** Same JSON, same fields, same names, same values. What changes is how the document DESCRIBES what it already sent: an additive correction under the `/v1` evolution rule (§5.8), and a client you already generated stays correct — regenerate it and it simply gains the types it used to lose.

**The five `/account/*` responses have a type again.** The envelope declared `data: [object, "null"]` and every operation redeclared its own typed `data` on top of it, through `allOf`. Those are two incompatible declarations of one property: «an object that is also null». The generator answered «`EnvelopeDataType0 | None` can't be merged with `RegisterAccountResponse201Data`, response will be omitted» and lost the register response, which is the only place the `sk_test_` secret is ever returned. `Envelope` no longer declares `data`, and each operation declares it exactly once.

**`SendJob`, `StatementImportJob`, `RcvEntry`, `RcvEntryList` and `AutomationSettings` are back.** Two causes. One: `allOf` over a scalar `$ref` — the way a `Clp` or an `EstadoContable` got a description — which a generator reads as «I cannot intersect something that is not an object» and answers by deleting the schema and everything that returns it. In OpenAPI 3.1 a `$ref` may carry its description BESIDE it, and that is what it does now. Two: `const: document_send` on top of `Job.type`'s `enum`, the same restriction written in a form that does not merge; it is `enum: [document_send]` now. With them come back `PATCH /v1/empresas/\{empresa_id\}/automation_settings` and `POST /v1/test_helpers/empresas/\{empresa_id\}/onboarding/steps`, which were not generated at all.

**`openapi-generator-cli -g python` finishes on both documents.** It aborted outright on two properties typed the bare `"null"` (`data` on the `/account/*` error, `folio` on a document preview). They are still always `null` — their description says so — and are now declared `[object, "null"]` and `[integer, "null"]`, which the tool knows how to read.

**And the contract cannot break this way again.** `test/contract-shapes.test.ts` walks both published documents and fails on any of the four shapes: a property typed the bare `"null"`, an `allOf` over a non-object schema, a `const` narrowing an inherited `enum`, or an `allOf` member redeclaring a base property incompatibly. The same rules run against a fixture document carrying all four, so the scan cannot pass by looking at nothing.

**The one caveat left, said on the page.** The four binary artifacts (`/documents/\{id\}/pdf` and `/xml`, and their twins under `/received_documents`) answer `application/pdf` and `application/xml`, and `openapi-python-client` only types JSON bodies: the method is generated all the same and the bytes arrive in `response.content`. We leave it alone in the document because the declared `Content-Type` is the one the server really sends.

## 2026-09-07 — The catalog publishes the declaración de cumplimiento as ours, and the step table can no longer contradict it [#2026-09-07--the-catalog-publishes-the-declaración-de-cumplimiento-as-ours-and-the-step-table-can-no-longer-contradict-it]

**`cumplimiento` is `actor: facturia`.** We file it at the SII with the representante legal's certificate held in custody — the one Res. Ex. SII N° 81 de 2005 CUARTO d) asks for — after that person approves the act `cumplimiento_declaracion` on the hosted page. `stall_actor` is `customer`, because what can hold the step for ever is that approval: the declaración's date fixes the month from which the Información Electrónica de Compras y Ventas obligation runs. The step's `help` says exactly that. The approval, the link and the `pending_action.approval` block are unchanged: they still reach the representante legal.

**The step table in [From sandbox to production](/en/docs/empezar/produccion) is corrected and gains a column.** `habilitacion` still said `customer` («the representante legal enables them at the SII») after it became ours; it now says `facturia`, with the same sentence the catalog carries. And both tables — mercado and SFG — publish `execution`, the field that separates a queued job (`automatic`) from a person of ours at a keyboard (`operator`).

**`actor` stops being «the only field worth branching on».** Three mercado steps are ours and still cannot finish without a person: `sii_postulacion`, `cumplimiento` and `habilitacion`. What says a decision is pending is `pending_action.approval`, not `actor`; the contract and the description of `POST /v1/empresas/\{empresa_id\}/onboarding/links` now say so, and a link is minted for `actor: customer` **or** `stall_actor: customer`.

**The docs check diffs the catalog against the table, row by row.** Position, `actor` and `execution`, in Spanish and in English, against `ONBOARDING_STEPS`, `SFG_ONBOARDING_STEPS` and `STEP_EXECUTION`. A row that moves in the code without moving on the page no longer builds the documentation.

## 2026-09-07 — A running step says what the last SII read saw and when we look again; uploading the certificate can no longer end with no probe [#2026-09-07--a-running-step-says-what-the-last-sii-read-saw-and-when-we-look-again-uploading-the-certificate-can-no-longer-end-with-no-probe]

**`steps[].next_poll_at` (new, on every step).** An RFC 3339 instant — or `null` — for the next SII read on that step. A schedule, not a promise: it is when the probe's own cadence falls due, and the reconciler notices within about five minutes. The panel prints it on the row: «Volvemos a mirar a las 16:47».

**`blocked_reason` on an `in_progress` step too.** The field said «present on a `failed` or `action_required` step»; a running step now publishes the last probe outcome while that probe did not settle it. Three new dictionary codes: `sii_unreachable` (neither the session nor the consulta page answered), `sii_propagation` (the SII shows the grant and has not applied it yet — the \~1 business day lag, and it must NOT be granted again) and `requirement_unmet` (its user table does not yet grant the empresa's sender what it needs; `details.key` names the permiso). All three carry a Spanish `hint` and `retriable: true`, none blocks the empresa, and none asks anything of a person.

**Uploading the certificate always triggers its verification.** Opening the hosted `certificate` page enqueued the two probes keyed `\{job\}:\{connection\}:\{5-min window\}`; the upload seconds later took the same key and created nothing, so the certificate just stored was read by nobody and the step sat «in progress» with a four-day estimate. The upload now enqueues with a key that names the credential, and the five-minute sweep rescues any empresa with a certificate in the vault and no verdict on record — at most one open probe per empresa, so no SII session budget is spent.

## 2026-09-07 — The intercambio and the printed samples run on the queue, and you approve the samples before they go [#2026-09-07--the-intercambio-and-the-printed-samples-run-on-the-queue-and-you-approve-the-samples-before-they-go]

**The intercambio runs on the queue, end to end.** `OnboardingIntercambioExecutor` downloads the Set de Intercambio, builds and signs the three responses (RecepcionEnvio, EnvioRecibos, ResultadoDTE) and uploads them, in one session and one handshake. The invariant that makes the paso meaningful stays where it was: the responses must answer the set the SII holds NOW, and every download mints a new one, so uploading responses to a superseded set is refused. A paso already accepted is never re-run. The instructions that asked a person to upload three files by hand are retired.

**The printed samples file themselves, but only after somebody has looked at them.** The engine assembles the kit — every document of the set de pruebas and one sample per tipo of the simulación — the hosted page lists them one by one with a link to open each, and the button reads «Aprobar y enviar al SII». Only then do we upload the PDFs and press «Enviar al SII». The act is `muestras_revision`, role `titular_cuenta`, recorded in `sii_onboarding_step_approvals`. A «not yet» is recorded too, and sends nothing.

**A `//OK` from the SII means FILED, not approved.** The revisión reads POR REVISAR and the SII resolves it on its own clock; the step stays in progress until it answers, and `onboarding_verdict_poll` reads that answer on its own. A revisión that already reads POR REVISAR is not filed again: it would spend a review round on a batch already under review.

**Two new environment fences, closed in every build.** `ALLOW_LIVE_INTERCAMBIO_UPLOAD` and `ALLOW_LIVE_MUESTRAS_UPLOAD` are read from the worker's environment instead of being source constants. With the fence shut the step opens no socket and says so in those words, instead of answering «no set has been ingested» about a set it was never allowed to fetch.

## 2026-09-07 — `execution` stops being hand-written: every step says how it runs TODAY, and the ETA says what a sitting costs [#2026-09-07--execution-stops-being-hand-written-every-step-says-how-it-runs-today-and-the-eta-says-what-a-sitting-costs]

**`execution` is derived from the step's fence.** Five mercado steps reach the SII only with their fence open: the postulación, the intercambio, the printed samples, the Declaración de Cumplimiento and the boleta habilitación. Until yesterday the catalog hardcoded `operator` for all five, with a comment saying the fences were shut «everywhere this repository runs» — true when written and false from the day all five started reading the environment. The catalog now pairs each step with the NAME of its variable (`STEP_FENCE_ENV_VARS`) and `stepExecution()` reads it at serialize time, so both ends read exactly the same fact.

**It is not a constant of the product: read it off the wire.** The same step can answer `operator` on one deployment and `automatic` on another, and can change from one day to the next. Branch on the value you were given; never cache it as a fact about a step key.

**The step's `help` tells the story that is actually true.** The catalog carries both sentences and the serializer picks one: with the fence shut, «lo corre una persona de Facturia y te avisamos por correo y por evento cuando quede»; with it open, «lo hace el motor solo». The wire still carries ONE `help` string. The sentence «esta parte todavía no va sola» is retired from every surface — it was a permanent claim about the product, and today it changes with an environment variable.

**The ETA says what a sitting costs.** `eta_business_days` for a step with `execution: operator` never drops below 2 business days, and `estimated_ready_at` sums those projected numbers. The catalog's figures were decomposed for a queue — 0 means «the SII answers this envelope in minutes» — and none of them described a person of ours finding an hour. `intercambio` published 0 days with nothing scheduled at all. When the fence opens, the ETA goes back to the base number.

**The printed-samples page can be minted now.** `muestras_approval_required` joins `CUSTOMER_REMEDY_REASON_CODES`, the one piece that was missing: without it `POST /v1/empresas/\{empresa_id\}/onboarding/links` refused the `muestras_impresas` step, and the person who has to LOOK at the kit had nowhere to go.

**Where it is configured.** The five variables (`ALLOW_LIVE_POSTULACION_WRITES`, `ALLOW_LIVE_INTERCAMBIO_UPLOAD`, `ALLOW_LIVE_MUESTRAS_UPLOAD`, `ALLOW_LIVE_CUMPLIMIENTO_WRITES`, `ALLOW_LIVE_BOLETA_ENROLMENT`) belong on `facturia-worker` AND on `facturia-api` with the same value: the worker reads them to act, the API to publish. `docs/deploy-fly.md` carries the table and the `fly secrets set` line for both apps.

## 2026-09-07 — Onboarding estimates stop being a decomposition of a marketing sentence and start being measurements [#2026-09-07--onboarding-estimates-stop-being-a-decomposition-of-a-marketing-sentence-and-start-being-measurements]

**The numbers are observations.** The SII answers a set de pruebas in minutes (an SOK verdict within minutes of the declaración), a boleta review request in 26 seconds, and a Declaración de Cumplimiento with a mail one minute fifty-two later. A whole boleta habilitación — set, folios, envelopes, request, V°B° and declaración — ran end to end in 33 minutes. So `set_de_pruebas`, `cumplimiento` and `habilitacion` go from 1 business day to 0.

**One went up, and it is the honest one.** `resolucion` goes from 0 to 1: it is the only step that is an act of the SII's on its own calendar with nothing of ours in flight, and it has never arrived the same day. A 0 there read as «the resolución comes this afternoon».

**What is left in the mercado lane's sum is three waits on the SII**: the postulación being registered (read on a later poll, not in the submit's response), the printed-samples revisión, and the resolución. Total: 3 business days, which is why the surfaces say «around 2 to 3 business days».

**The Sistema de Facturación Gratuito lane sums to 0.** `credential` is an upload on a page of ours, `usuario_autorizado` is one read of the Portal MIPYME's list, and `puesta_en_vivo` is a predicate over facts we already hold — not the accompanied test emission its `help` promised and the engine never performed. You are emitting the same day.

**«Avanzar Siguiente Paso» becomes a queued act.** The POST to `pe_avance4` that moves a postulación out of a certification paso sat behind a hand-written constant, so no automatic job could ever make it: the queue read the paso and a person pressed the button in a browser, while `set_de_pruebas` and `simulacion` published as `automatic`. The fence is now the `ALLOW_LIVE_STAGE_ADVANCE` variable and the lock register names the two steps it holds, so both steps' `execution`, `help` and estimate derive from it the way the other five do. The engine polls first and clicks only if the portal is waiting on it with every set conforme.

**What to do about it.** If you stored `estimated_ready_at` or `eta_business_days` as a constant, read them again: they came down. They are still p50s rather than promises, and whatever the SII takes beyond them is not something the catalog can put a number on.

## 2026-09-06 — The declaración de cumplimiento is approved on a page and filed by Facturia; cesión opens to mercado empresas; retries belong to the machine [#2026-09-06--the-declaración-de-cumplimiento-is-approved-on-a-page-and-filed-by-facturia-cesión-opens-to-mercado-empresas-retries-belong-to-the-machine]

**The Declaración de Cumplimiento is an approval, not an errand at the SII (ADR-0013).** `steps[].pending_action.approval` for `cumplimiento` carries the act `cumplimiento_declaracion`, role `rep_legal`: the representante legal reads the consequences — the empresa goes into production and the Información Electrónica de Compras y Ventas obligation runs from that month — and approves on the page; Facturia files the declaración with the custodied certificate, which is the representante's own and the one Res. Ex. SII 81/2005 requires. The same click records the month authorization (`go_live_authorized_by = rep_legal_approval`). Three code gates: the approval, certificate holder = representante legal per the SII itself, and the env-driven `ALLOW_LIVE_CUMPLIMIENTO_WRITES` fence with a mandato-version requirement for any empresa that is not a house one. New error code `cumplimiento_approval_required`.

**A hand-placed empresa is offered the decisions its steps wait on.** With an `emitting` spine and reason `operator_grant`, a step parked on `action_required` is no longer projected as completed: `pending_action` and its `approval` block reach the API, the panel and the MCP, and `get_pending_human_action` reads `pending_action` before saying nobody is waiting.

**At the first production CAF the empresa switches channel by itself.** An empresa certifying for mercado while emitting on the Portal MIPYME moves to `emission_channel: mercado` when it receives its first folios; between the resolución and that moment new emissions wait in the queue with reason `channel_switch_pending`.

**`POST /v1/documents/\{id\}/cede` accepts mercado empresas.** The channel guard and the `cesion_channel_unsupported` code are retired; the 409 for a previous attempt reports the Registro's last answer (`rtc_estado`, TrackID). The `ALLOW_LIVE_CESION_SUBMITS` fence is read from the environment and the submit is authorized per empresa (`emitting`, active, production).

**What stays the client's says so for the real reason.** The «Enviar Doctos.» permissions, the free portal's authorised user and the intercambio mailbox are set in the session of the account's usuario administrador at the SII, an authority the custodied certificate lacks exactly where the act is missing; the copy no longer speaks of «a legal decision of ours».

**Dead-lettered jobs requeue themselves, and the mail says so.** After an SII outage they retry in an hour; after an error of ours, once per deploy; at most three times per job. Operations alerts describe what the machine does and ask a person only for irreversible decisions.

## 2026-09-05 — A root key that finally inherits its reach, a sandbox that reserves no RUT, and an approval with a way back [#2026-09-05--a-root-key-that-finally-inherits-its-reach-a-sandbox-that-reserves-no-rut-and-an-approval-with-a-way-back]

**A root key minted from a session finally does something.** (#449) `POST /dashboard/api_keys \{root:true\}` used to return a root with `empresas:[]`, and any key minted with it ended up with that same empty reach or a `403 privilege_escalation` when asking for `empresas:["*"]` — a dead end documented as the main path for an integrator to stop holding a human's password. The root now inherits the session's real reach, and minting with it works.

**A sandbox empresa reserves nothing.** (#450) A test empresa's RUT no longer counts against the global uniqueness that does apply to a live empresa — the same account can hold the same RUT once in sandbox and once live — and `DELETE /v1/empresas/\{empresa_id\}` exists for sandbox ones (a live one answers `409 empresa_delete_not_allowed`, naming the mandato revocation instead). The reception alias `&lt;rut&gt;@dte.facturia.cl` is never assigned to a sandbox empresa.

**The subscription is charged after eligibility, never before.** (#475, #530) `capabilities.blocked_by` may now say `subscription`, but only once the empresa could otherwise emit; the go-live authorization is free on every door, because it is the titular's decision, not an emission.

**A revoked mandato is read off the real wire, and leaves a way back.** (#515, #465) The panel used to read its own version of the revocation instead of the empresa's real state; the two now agree, and an empresa with a revoked mandato has a way to re-activate instead of being stranded.

**Every human act has a generic hosted door, with its own record.** A `pending_action` may now carry an `approval` block — who decides, what it costs, the two buttons — that calls `POST /onboarding/steps/:step/approve`; a row lands in `sii_onboarding_step_approvals` (migration 127) naming who decided what, when, and through which channel.

**Boleta habilitación runs as Facturia, not a person clicking through it.** (#452, #538, #543, #548) The whole sequence — set, CAF, round, review request, verdict, declaración — runs as a machine with exactly two human moments: the titular authorizes the 24-hour clock's start on a hosted page, and the declaración is a page for the representante legal with the form pre-filled.

**The SII postulación is filed by Facturia, after the titular approves it.** (#453) `sii_postulacion` becomes `actor: facturia`, always preceded by a titular approval on a hosted page that shows the consequence of losing the free facturador and lets them choose the moment; the production switch (`ALLOW_LIVE_POSTULACION_WRITES`) still defaults to `false`.

**MCP tools read the attested contract, and `tools/list` shows only what the grant reaches.** (#454-456, #550) `tools/list` used to offer the whole catalog regardless of the connection's real reach; a client now sees exactly the tools its scopes permit. The consent that authorizes that connection is in Spanish, with a label and a one-line explanation per permission, and a "Recomendado para emitir con tu agente" preset that checks nothing by default.

**`/aprobar` speaks Spanish, names who asked, and the agent stops guessing.** (#470, #471) The page used to show the summary in English and the tool's raw name (`emit_document`); it now drafts the summary in Spanish once and shows which OAuth client asked. On the other side, an agent can call `get_pending_confirmation` to learn whether the person has decided, instead of the only way to find out being a blind repeat of the original call.

**Recovering a password no longer needs an e-mail to us.** (#524) `POST /account/password/reset_request` sends a single-use link, valid 60 minutes, without confirming or denying whether the account exists; `/recuperar` and `/restablecer` in the panel complete the flow.

## 2026-09-04 — CONNECT is enabled for boleta electrónica, certified from code in 33 minutes [#2026-09-04--connect-is-enabled-for-boleta-electrónica-certified-from-code-in-33-minutes]

**The test set ran and was approved from code.** Sending set 0257510898 reached `EPR` with all five boletas; RCOF 0257510910 confirmed conforming receipt.

**The portal's review request is an automatic act, not a two-week wait.** `certBolElectDteInternet` — the procedure an August e-mail had left waiting 22 days in silence — turned out to be a portal action the SII resolves in seconds: the V°B° arrived in 26s, and set 4911005 landed SOK.

**The declaración de cumplimiento closed the certification.** Filed at 20:35:54; the SII's confirmation e-mail arrived at 20:37:46 — "habilitados los documentos de boleta electrónica a contar del 01-09-2026".

**The whole sitting ran with no person in the loop.** 33 minutes end to end, zero manual e-mails, zero clicks — the same discipline that already governed factura electrónica, now extended to the boleta.

## 2026-09-03 — Agent-first: an approval is a page, registration is one call, and the CAF stops promising what it does not keep [#2026-09-03--agent-first-an-approval-is-a-page-registration-is-one-call-and-the-caf-stops-promising-what-it-does-not-keep]

**`confirm_action` retires; a human approval is always a page, never the agent's word (ADR-0012).** An act that used to be confirmed with a boolean in the same call now parks a `pending_confirmation` and hands over a hosted `/aprobar/\{id\}` page: the person decides there, and no tool can claim on its own that they said yes.

**Registration is one call.** `POST /account/register` accepts `bootstrap: \{sandbox_key, empresa\}` and the same `201` carries the account, the session, the first `sk_test_` key and a sandbox empresa ready to emit — the three calls it used to take are now one.

**`puesta_en_vivo` runs on a single eligibility predicate.** Before this, two different surfaces could ask "can it emit live?" and answer differently; now there is one function both of them consult.

**Bilingual documentation, end to end.** Every page on `docs.facturia.cl` has its twin under `/en`, with code-block and table parity checked on every build — not just the surrounding prose.

**The CAF states its own vigencia.** (#191) Res. Ex. SII N° 58/2017 sets 6 calendar months from the Fecha de Autorización; that date is now persisted and the vigencia is derived from it or absent — never invented — and an expired folio is refused at reception exactly as the SII would refuse it.

**A cesión is assembled and registered natively.** (#60) The AEC (Anexo Cesión de Créditos) is assembled with its three signatures — the cedente's, the cesionario's, and ours — and uploaded to the SII's Registro (RTC) via `POST /v1/documents/\{document_id\}/cede`; a cesión's status stops depending on a query that never said "cedido".

**Printed samples speak the SII's protocol in raw HTTP.** (#58) The upload (`pdfdteInternet`) and the review that follows it resolve in one handshake, with no browser session and no person in the loop.

## 2026-09-02 — The error envelope now carries what to do, and onboarding carries who to tell [#2026-09-02--the-error-envelope-now-carries-what-to-do-and-onboarding-carries-who-to-tell]

**The error envelope now carries `hint` and `retriable`.** Both come out of the same dictionary that serves `GET /v1/error_codes`, looked up by `code` and never hand-written at the throw site, so the response and the dictionary cannot contradict each other. An agent no longer needs a second 75 KB call to know what to do with a code: the response it already has in hand carries it. They are only missing if the code is newer than your client — treat that as "read `message` and `doc_url`", never as a bug. `hint` is in English on purpose — the Spanish for each code lives on this site's own code page and in the dashboard.

**Onboarding publishes the full handoff.** `GET /v1/empresas/\{empresa_id\}/onboarding` carries `pending_action` on every step that needs a human, and hoists it to the top level for the step `blocked_on` names: exactly who has to act and why it cannot be delegated, what to have on hand, ordered instructions, the SII page when we can name one that does not 404, what sets the act in motion, and how we are going to find out it happened. That last field also publishes its own limit: `verification.method: no_producer` means nothing in this version observes that step, and it says so rather than hiding it. The same object also travels inside `error.details.pending_action` when a write is refused for a pending step.

**New: `POST /v1/empresas/\{empresa_id\}/onboarding/steps/\{step\}/recheck`.** The other half of the handoff: the person finished, and this asks us to go look. **It accepts a claim and produces no fact** — it takes no body, writes no step's state, and returns the state exactly as it was before anything was queued. `verified` comes back in the response and is always `false`. No API call can mark a human step done, this one included.

**In the MCP: `get_pending_human_action` and `get_folio_status`.** The first answers "what do I tell my human?" with the same `pending_action` as above. The second lets you see the wall before you hit it: how many folios are left per document type and what happens when they run out. On top of that, **every tool in the catalog now carries annotations derived from what its handler actually does, not from its name** — and two of them run against what the name suggests: `get_document_file` and `get_f29_propuesta` are **not** read-only, because they mint an unauthenticated public link to a signed tax document. A client that auto-approves reads would be handing out invoice links without asking anyone.

**The SII now tells us which IVA shape your empresa is in, and we now store it.** The empresa's new `iva_shape` reports whether the SII classified it as `afecta` or `exenta`, with what evidence and which test set applies to it. It is read-only: it is the SII's own answer about your empresa, not a setting. `null` means we have not established it yet, and `afecta` is never assumed — assuming it would build eleven documents the SII will never review.

**`/v1/cafs` exists.** The contract declared `GET`, `POST` and `DELETE /v1/cafs`, and production answered `404 unknown_endpoint` to all three: the code had merged and the deploy had not. From now on no path reaches the published OpenAPI without having been probed against the real router — if the contract declared an endpoint the API does not mount, this site's build fails before publishing it, and the other way round too.

## 2026-08-24 — One plan, and a `402` at the door [#2026-08-24--one-plan-and-a-402-at-the-door]

**One plan: $19,990 + VAT a month, per empresa, unlimited documents.** The subscription belongs to the EMPRESA, not the account, so every action is decided against the subscription of the RUT the request names: one expired empresa never stops another one on the same account.

**Exactly five actions are blocked**, and they are written in one place: `POST /v1/documents`, `POST /v1/documents/\{document_id\}/credit_note`, `POST /v1/documents/\{document_id\}/send`, `POST /v1/folios/requests` and `POST /v1/empresas/\{empresa_id\}/onboarding/go_live`. Everything else stays open, and the list of what is NOT blocked is as explicit as the list of what is: every read, **all of test mode, forever**, the commercial response to a received document (the 8-day window is legal and we are not going to shorten it over an invoice of ours), and tax filings (same argument, with bigger consequences).

**The `402` comes with what to fix it.** `error.details` carries the empresa, its RUT, its name, the account's plan, the subscription's status, the grace instant if one was running, and a `checkout_url` that points at **that** empresa. An agent that gets a `402` does not have to improvise where to send its human to pay. A subscription that lapses keeps issuing through a 7-day grace period before any of this kicks in.

**A note for anyone who already coded against the error-type table.** `402` is not a tenth family: it is the one case where an `invalid_request_error` carries a status other than `400`, because "you owe us money" is not a malformed request. `plan_limit_reached` stays published and nothing emits it — the plan has no quota to run out — and its page says so.

## 2026-08-21 — `dispatch.dispatch_type` is now the SII's TipoDespacho [#2026-08-21--dispatchdispatch_type-is-now-the-siis-tipodespacho]

**`dispatch.dispatch_type` changes meaning — honestly, because it never did anything.** The published vocabulary ("whose vehicle it is") did not match the SII XML's `&lt;TipoDespacho&gt;` element, so both channels rejected it. As of today the field is optional, guía-only (type 52), and takes exactly the SII's three values: `receiver_account` (1, on the receiver's account), `issuer_to_customer_site` (2, on the issuer's account to the customer's premises), `issuer_to_other_site` (3, on the issuer's account to other premises). The old names answer `400 invalid_field` naming the replacement.

**SII channel (the free issuer): still rejected.** The portal's own form has no such field — we verified it against the capture — so sending it on that channel answers `422 dispatch_not_available_for_channel`. On any type other than 52 it answers `422 dispatch_not_applicable`, the same as `transfer_type`.

## 2026-08-21 — A zero-amount guía no longer goes through the SII channel [#2026-08-21--a-zero-amount-guía-no-longer-goes-through-the-sii-channel]

**What happened.** We issued the first real guía de despacho over the SII channel (the free issuer) with a $0 line and an internal transfer: a movement that generates no output VAT and that, until yesterday, both our documentation and the engine treated as a legitimate document. The portal rejected it, in these words: "Precio del primer item del detalle debe ser mayor a 0 En fila 1 no se cumple validacion \[El precio del detalle está en blanco y debe ser mayor a 0] Valor Total Factura debe igual a la suma del Monto neto + IVA y mayor que 0." A retry with a price of 1,000 was accepted (folio 1, net 1,000 / IVA 190 / total 1,190). The portal validates its guía form with the invoice's own validations — the guía form **is** the invoice form under a different code — so the rule does not look at the transfer class.

**What changes.** In an empresa with `emission_channel=sfg`, a guía (type 52) with any `unit_price` line at or below 0, or whose total is 0, now answers `422 sfg_guia_zero_amount` naming the line. It is rejected at the door, before the document is created and before anything is queued, and the message quotes the portal so it does not read as a limit of ours.

**Nothing changes on the mercado channel.** The same $0 document was accepted by the SII the same day through our own engine (guía t52 folio 1, internal transfer, accepted). The SII's own XSD and validator carry no such rule: it belongs to the portal's form. Both channels still issue a guía; now it is documented where they also differ on amounts.

**While we were in there: SII-channel empresa drafts no longer warn `no_folios_available`.** On that channel folios are assigned by the SII at issuance and `POST /documents/previews` has no counter to consult, so the warning fired on every draft and pointed at a management screen that channel refuses. `folio_preview` is still `null`, and it still means the same thing.

## 2026-08-19 — Cedibility you can query, a guía on both channels, and public links only when minted [#2026-08-19--cedibility-you-can-query-a-guía-on-both-channels-and-public-links-only-when-minted]

**New: cedibility lookup (Ley 19.983).** `POST /documents/\{document_id\}/cesion` queues a read-only query against the SII's registry and the result lands in the document's new `cesion` object: `\{cedible, sii_status_code, checked_at\}` (`null` until the first query). It applies to the types the SII's own service supports (33, 34 and 43); on any other type it answers `422 cesion_not_applicable`. The endpoint only asks for `documents:read` — it is a question, not an action — and in test mode it simulates the 8-calendar-day rule against the empresa's own clock. Facturia does not execute cesiones: to actually cede one, the channel is still the SII's own Registro (RPETC).

**The guía de despacho (type 52) now issues over both channels.** Until now the mercado channel rejected it at parse time (`422 tipo_not_available_for_channel`); now our own engine builds it with the same `dispatch` contract already published for the SII channel: `transfer_type` required (the SII's 9 transfer types), `carrier`, `requester_rut`. Transfers that are a sale (types 1 and 9) carry invoice totals; the rest may go out at zero amount. While we were in there, RUTs inside `dispatch` are now normalised on request acceptance — a RUT with dots used to be accepted at the door and die after spending the folio — and `dispatch.destination` is now validated as an object instead of silently truncated.

**Retired: `pdf_public_url` and `xml_public_url` on the document object.** They never worked: the link's secret is stored hashed and those fields were built without it, so they had always answered 404. Same rule as `format=80mm`: we retire what was published and did not exist rather than build it new behaviour on the spot. The real contract: the URL carrying `?k=` is handed out **exactly once** — in the response of `POST /documents/\{document_id\}/public_links`, or in the `public_links` object of the issuance response when you asked for `pdf: \{"public": true\}` / `xml: \{"public": true\}`. Save it when you get it. `document.created` webhooks no longer carry secret-bearing URLs at all.

## 2026-08-16 — The cedible copy, on by default, and shareable [#2026-08-16--the-cedible-copy-on-by-default-and-shareable]

**`pdf.cedible` at issuance is now stored and used.** It used to be read off the request body and go nowhere. It now sets THIS document's default for `GET /documents/\{document_id\}/pdf` when the read does not send `?copy=`: `true` makes it cedible by default, `false` pins it to the original explicitly. `true` on a type other than 33/34 is `422 cedible_not_available`, rejected before a folio is spent, not silently ignored.

**`branding.cedible_by_default` is now enforced.** It is the EMPRESA's own default for when a document does not set its own. The full precedence for `GET …/pdf` with no `?copy=`: the read's own explicit `?copy=`, if any; otherwise the document's `pdf.cedible`; otherwise the empresa's `cedible_by_default`; otherwise `original`. Fixed along the way: it applies to invoice types 33 **and 34**, not only 33.

**`branding.default_pdf_format` is now validated.** `PATCH /empresas/\{empresa_id\}/branding` used to accept any value unchecked, `80mm` included — the same value retired from `?format=` in the previous wave. Any value other than `carta` is now `400 invalid_field`.

**Public links can now name `cedible`.** `POST /documents/\{document_id\}/public_links` accepts `cedible` inside `artifacts` — a real, separate artifact, with its own `cedible_public_url`, not the `pdf` link under another name. It is not in the default set: asking for it requires naming it explicitly, and it only exists for invoices (`422 cedible_not_available` on any other type).

## 2026-08-15 — What was published and did not exist [#2026-08-15--what-was-published-and-did-not-exist]

**Retired: `format` (`carta` | `80mm`) on the PDF.** No handler ever read it. `?format=80mm` returned the same letter-size PDF as everything else, so a POS integration wired to it printed a full letter page on a thermal roll with nothing in the response saying so. No thermal layout exists, and we are not building one. Supplying the parameter — on the query string or as `pdf: \{ "format": … \}` at issuance — now answers `400 invalid_field`.

**Retired: `copy=both`.** The SII's own Manual de Muestras Impresas allows exactly one page per file (p.12), so the two copies are two files and one HTTP response carries one. `both` never named anything that could exist. `copy` accepts `original` and `cedible`; any other value is `400`.

**`copy=cedible` now actually picks something.** There used to be a single file per document, and for an invoice that file already had the acknowledgement-of-receipt box and the `CEDIBLE` legend drawn on top of it — so `copy=original` returned a cedible copy and the published parameter selected nothing. Both copies are now rendered and stored when the document is accepted. Documents rendered before today still have a single file: `copy=cedible` is served correctly off it, and `copy=original` returns those same bytes while a re-render is queued.

**`pdf_public_url` and `xml_public_url` on reads are now `null`, and that is the fix.** They used to return a URL built from the slug alone, without the `?k=` secret `/d/\{slug\}` requires — meaning every one of them had always answered 404. The secret is stored hashed, the same as an API key, so we cannot reconstruct it: the working URL appears in the response that mints it (issuance with `public: true`, or `POST …/public_links`) and nowhere else. `null` is a value you can branch on; a broken URL is not.

**`GET /v1/mandato_terms` exists.** §7.3 has said since the first draft to present the mandato's text before posting acceptance, and the endpoint answered `404`. It returns the text, its version, its SHA-256 hash and a `status` that today says `draft`: the contract's legal review is not finished, and saying so beats serving text that reads as binding when it is not. Unauthenticated, like the error dictionary.

**`expand` on list endpoints is no longer ignored.** `GET /v1/documents?expand=…` accepts `events`, `references` and `received_document`, and expands the whole page with one extra query, not one per row. `xml` and `pdf_base64` are deliberately left out and answer `400`: they would pull a whole artifact into every row.  `GET /v1/received_documents` accepts no expansion at all, for the same reason.

**`expand=received_document` is no longer always empty.** An issued document links to a received one when it references it by `tipo_dte` and `folio` and the received document's issuer RUT is the issued document's receiver RUT, inside the same empresa, and only when that match is unique. If there are two candidates the link stays `null`: a wrong match is a claim, and an absence is not one.

**The general rate limit is 300 req/min, as the table always said.** The limiter was counting up to 100. The documentation was the correct one.

**Rotating and revoking a key now respect the session's mode.** A dashboard session in test mode used to see only its test keys when listing, yet could still rotate or revoke a `live` key by id. All three operations now apply the same filter.

**CORS now exposes the headers the documentation promises.** `Facturia-Request-Id`, the `RateLimit-*` headers, `Retry-After`, `Facturia-Idempotent-Replay` and `Facturia-Poll-After` had always been sent, and the browser was discarding them.

## 2026-08-14 — v1 — the published surface [#2026-08-14--v1--the-published-surface]

**Issuance.** Eight DTE types (33, 34, 39, 41, 46, 52, 56, 61). `POST` answers `201` with `status: queued` and `folio: null`; the engine assigns the folio when the document moves to `sent`, and the trip to the SII is an observable state machine (`document.sent`, `Facturia-Poll-After`). `POST /v1/documents/previews` quotes and validates without issuing; `preview_id` issues what was approved, and issuing the same preview twice returns the original document, never a second folio.

**Artifacts.** PDF and signed XML, either by key or by a signed public link that expires in days or seconds. The XML is served as ISO-8859-1, byte for byte identical to what the SII received, on both the authenticated route and the public link.

**The cedible copy, for invoice types 33 and 34.** Ley 19.983 grants the third copy to the invoice without distinguishing taxed from exempt, and the SII's own Manual de Muestras Impresas requires it for both. `copy=cedible` on any other type answers `422 cedible_not_available`.

**Reception.** Your suppliers' DTEs, the commercial response (ACD, RCD, RFP, RFT, ERM) and the 8-calendar-day window anchored on the SII's own reception mark, on Santiago's calendar. No anchor, no deadline: `422 commercial_response_anchor_pending` instead of a made-up date.

**Tax.** Purchases and sales RCV, individual and bulk characterisation, an F29 proposal with an explanation and per-code traceability, the SII's own upload file, and filing as a job with honest states. Code 048 is declared as an external input, and every response says so whenever it is missing.

**Webhooks.** HMAC signatures over the raw bytes, at-least-once delivery, published retries, and an event log you can query and replay for 12 months.

**Sandbox.** Free forever, with not a single socket toward the SII: virtual folios, forceable outcomes, and the same rate limits as production, so a load test actually means something.
