Facturiadocs
API reference

Empresas

The tenancy boundary: creation, the mandato (the power of attorney an empresa grants Facturia), certificate custody, onboarding and its automations.

Base: https://api.facturia.cl/v1. Every endpoint in this chapter authenticates with Authorization: Bearer unless stated otherwise, and the error envelope is the one in Errors.

This chapter is ordered the way it makes sense to read it, starting with the mandato: it is step 1 of both onboarding tracks, and the certificate (or the portal credential) is step 2. Uploading the certificate first works, but the step stays pending until the machine reaches certificate_pending. The full path is in From sandbox to production.

MethodPathWhat it does
GET/empresasList empresas visible to this key
POST/empresasCreate an empresa
GET/empresas/{empresa_id}Retrieve an empresa
PATCH/empresas/{empresa_id}Update an empresa
DELETE/empresas/{empresa_id}Delete a SANDBOX empresa
GET/empresas/{empresa_id}/mandatoWhether the representante legal has signed, against which terms, and how
POST/empresas/{empresa_id}/mandatoRecord the mandato authorizing Facturia to act before the SII
DELETE/empresas/{empresa_id}/mandatoRevoke the mandato
GET/empresas/{empresa_id}/certificateRetrieve certificate metadata
POST/empresas/{empresa_id}/certificateUpload (or reuse) the empresa's digital certificate
DELETE/empresas/{empresa_id}/certificatePurge the certificate from the vault (revokes custody, suspends emission)
GET/empresas/{empresa_id}/sfg_credentialRetrieve the SFG centralizado clave's custody metadata
POST/empresas/{empresa_id}/sfg_credentialStore (or replace) the SFG centralizado clave
DELETE/empresas/{empresa_id}/sfg_credentialDestroy the SFG centralizado clave (ends custody)
POST/empresas/{empresa_id}/mandato/revocation_linkMint the hosted page where the representante legal revokes
POST/empresas/{empresa_id}/resolucion/verifyVerify a declared resolución with one production timbraje (Facturia operators only)
GET/empresas/{empresa_id}/counterpartiesCounterparties you have invoiced, with the address and contacts on file for each
GET/contribuyentes/{rut}What the SII publishes about a company, by RUT, and a giro that fits a factura
GET/empresas/{empresa_id}/counterparties/{counterparty_rut}What you know about one counterparty, and whether you stated it or we inferred it
PATCH/empresas/{empresa_id}/counterparties/{counterparty_rut}Put a counterparty's razón social, giro and address on file
GET/empresas/{empresa_id}/counterparties/{counterparty_rut}/contactsThe contacts on file for one counterparty
POST/empresas/{empresa_id}/counterparties/{counterparty_rut}/contactsPut a contacto responsable on file
PATCH/empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts/{contact_id}Edit a contact, promote it, or record an opt-out
DELETE/empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts/{contact_id}Remove a contact from the address book
GET/empresas/{empresa_id}/onboardingOnboarding lifecycle, steps and blocking human actions
POST/empresas/{empresa_id}/onboarding/linksMint a link to a hosted representante-legal page
DELETE/empresas/{empresa_id}/onboarding/links/{step}Revoke a hosted representante-legal link
POST/empresas/{empresa_id}/onboarding/go_liveAsk the titular to approve the go-live, or state the decision on their behalf
POST/empresas/{empresa_id}/onboarding/steps/{step}/recheckTell us a human step was performed, and we go and look
POST/empresas/{empresa_id}/onboarding/steps/{step}/attestRecord that a human performed a step this build cannot probe (#395)
POST/empresas/{empresa_id}/onboarding/steps/{step}/approveRecord the titular's authorization for a step Facturia then performs (#452, #453)
GET/empresas/{empresa_id}/onboarding/certificationThe set de pruebas - what was uploaded, what is missing, what the SII said
POST/empresas/{empresa_id}/onboarding/certification/set_fileUpload the set de pruebas the SII issued for this empresa
POST/empresas/{empresa_id}/onboarding/certification/inputsSupply the facts the set de pruebas does not carry
GET/empresas/{empresa_id}/onboarding/evidenceThe rendicion de cuentas - what we did on the empresa's behalf
GET/empresas/{empresa_id}/onboarding/evidence/exportThe rendicion de cuentas as one CSV file
GET/empresas/{empresa_id}/automation_settingsRetrieve per-empresa automation settings
PATCH/empresas/{empresa_id}/automation_settingsUpdate per-empresa automation settings
GET/empresas/{empresa_id}/brandingRead logo, default print format and footer text
PATCH/empresas/{empresa_id}/brandingUpdate logo, default print format and footer text

GET /empresas

List empresas visible to this key

List every empresa this key can reach — its own allowlist resolved to real rows, or the whole account for an unrestricted key.

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

Parameters

ParameterInRequiredTypeNotes
limitquery—integer
cursorquery—stringOpaque cursor from a previous response's next_cursor.
statusquery—onboarding · active · suspended · archived

Responses

CodeMeaningBody
200A list of empresas.lista de Empresa
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas

Create an empresa

In test mode the empresa is born emitting: no mandato, no certificate, no certification. In live mode it starts at mandato_pending and progresses through the onboarding state machine.

origen IS REQUIRED IN LIVE MODE and this operation refuses without it — 400 invalid_field with param: "origen" (#495). It is not in required above because the mode is a property of the CREDENTIAL, not of the body, and a schema cannot say «required when the bearer token starts with sk_live_»: declaring it unconditionally would make every sandbox call — where it defaults to ninguno — fail a generated client's own validation before it left the machine. So the rule lives on the 400 below, where a codegen'd client can at least see it, and in the field's own description.

curl -sS -X POST https://api.facturia.cl/v1/empresas \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"rut":"77928532-4","legal_name":"Connect SpA"}'

Parameters

ParameterInRequiredTypeNotes
Idempotency-Keyheader—stringClient-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with Facturia-Idempotent-Replay: true. Same key + different body → 409.

Body

FieldTypeRequiredNotes
rutRutyesCanonical RUT — no dots, one hyphen, uppercase K. E.g.: "77928532-4".
legal_namestringyes
girostring—
actecointeger[]—
addressAddress—
codigo_sucursalinteger,null—SII branch code (CdgSIISucur). Omit, or send null, for casa matriz.
contact_emailstring—
emission_channelmercado · sfg—How this empresa will emit. mercado runs Facturia's certified engine and the eleven-step certification; sfg drives the SII's free Sistema de Facturación Gratuito for an empresa that is already enrolled there, and runs a short checklist with no certification in it. Choosing sfg for an empresa that is NOT enrolled does not enrol it: the emisor_verificado step will say so. Choosing mercado for one that is happy on the free portal starts a certification it does not need. ambiente is DERIVED from this and is not accepted here — see Empresa.ambiente. Default: "mercado".
doc_types33 · 34 · 39 · 41 · 46 · 52 · 56 · 61[]—WHAT THIS EMPRESA INTENDS TO EMIT — the declaration that drives certification and which folios are requested; capabilities.tipos is what it can emit NOW. Defaults to [33, 34, 61]; an explicit [] is 400 invalid_field, because an empresa with zero tipos can never finish certification. Declaring 39 or 41 (boletas) on emission_channel: sfg is 422 doc_type_not_supported_on_channel: the SII's free Portal MIPYME exposes no boleta form. Boletas need the mercado channel, where they also add the habilitacion step to the checklist. Default: [33,34,61].
origengratuito · mercado · ninguno—REQUIRED IN LIVE MODE — an sk_live_ key that omits it gets 400 invalid_field with param: "origen", which is the first empresa of every real customer. It is not in this schema's required list because the mode is a property of the credential and not of the body; the operation's 400 declares the refusal (#495). Optional in test mode, where it defaults to ninguno because a sandbox empresa is born emitting and never meets the wizard this field routes. See the Origen schema for what it does and does not decide.
resolucionResolucionClaimRequest—THE RESOLUCIÓN THIS EMPRESA ALREADY HOLDS (ruling 2026-09-07). Send it when the empresa already invoices electronically through another provider; send null to clear it. Both halves are printed on the carátula of every documento tributario electrónico the empresa emits today (NroResol, FchResol), so an agent can read them off a recent invoice rather than sending anybody to Mi SII. IT IS A CLAIM, NOT A SKIP. It writes two columns and moves nothing else: the empresa is still created at mandato_pending, still needs the certificate and the mandato, and still certifies unless our own production timbraje proves the resolución. That timbraje is made only when a Facturia operator confirms it for this empresa; until then the claim reads resolucion.verification_status: pending_verification. Read resolucion.verified on the empresa object for the outcome.
resolucion_boletasResolucionBoletasClaimRequest—THE BOLETA HABILITACIÓN THIS EMPRESA ALREADY HOLDS (ruling 2026-09-07). Send it alongside doc_types containing 39 or 41; send null to clear it. It carries no numero because the SII issues none for boletas: the habilitación arrives as «habilitados los documentos de boleta electrónica a contar del …», and that date is the whole of it. Verified by a tipo-39/41 production timbraje, exactly as the resolución claim is; refused, the ordinary habilitación steps appear on the checklist instead.
Responses
CodeMeaningBody
201The created empresa.Empresa
400Malformed input. The codes this operation answers: * invalid_rut — rut missing, or its check digit is wrong. * invalid_field with param: "legal_name" — absent or empty. * invalid_field with param: "origen" — absent, on a live key. The first empresa of every real customer hits this if the caller treated the field as optional because the schema does. See the operation description. * invalid_field with param: "doc_types" — not an array of supported tipos, or an explicit []. * invalid_field with param: "emission_channel" / "acteco" / "codigo_sucursal" — a value outside what the field admits. * invalid_field with param: "ambiente" — ambiente is derived and is refused rather than ignored.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
422doc_type_not_supported_on_channel — doc_types names a tipo the resolved emission_channel cannot build (39/41 on sfg). error.param is doc_types.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}

Retrieve an empresa

One empresa: legal identity, ambiente, emission_channel/certification_track, onboarding spine and capabilities.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200The empresa.Empresa
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

PATCH /empresas/{empresa_id}

Update an empresa

Update legal_name, giro, contact_email, acteco, origen, codigo_sucursal, doc_types, emission_channel, certification_track or status. ambiente is derived and rejected if sent (422); origen is the titular's own claim about how the empresa emits today, correctable, never a canal preference.

Two of these move the onboarding machine and say so in their own descriptions: doc_types (adding a tipo re-opens certification for it; 39/41 need the mercado channel) and certification_track (which checklist governs the empresa).

THE PANEL MIRROR IS NARROWER. PATCH /dashboard/empresas/\{empresa_id\} — the same handler behind an account session — accepts only doc_types and certification_track, and refuses every other field with 400 invalid_field naming it. Those two are what the puesta en marcha wizard has to record; the rest are the taxpayer identity each document is printed from, and a browser session is not the credential that rewrites what the SII sees on an invoice. An sk_ key on THIS path is unaffected.

curl -sS -X PATCH https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"legal_name":"Connect SpA","giro":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
legal_namestring—
girostring—
actecoarray,null—SII activity codes, most specific first; the first one becomes <Acteco> on the emisor block of every document. An empty array — or null — clears it. Omitting the field leaves the stored value alone.
addressAddress—
codigo_sucursalinteger,null—SII branch code (CdgSIISucur); null clears it back to casa matriz. Omitting the field leaves the stored value alone — a distinction that matters here, because an SFG empresa's Portal MIPYME preview refuses a document whose emisor origin is incomplete.
contact_emailstring—
doc_types33 · 34 · 39 · 41 · 46 · 52 · 56 · 61[]—WHAT THIS EMPRESA INTENDS TO EMIT — the declaration that drives certification and which folios are requested. Read capabilities.tipos for what it can emit NOW. An empty array is 400 invalid_field: an empresa with zero tipos can never finish certification. Omitting the field leaves the stored value alone. ADDING a tipo to an empresa already emitting re-opens its checklist at certification_in_progress — certification and CAF are per tipo, and the SII authorizes only the types you certified. Adding 39 or 41 is what makes the habilitacion step appear, ON EITHER CHANNEL. BOLETAS ARE ALLOWED ON THE sfg CHANNEL, and declaring one is how you ask for the habilitación. The SII's free Portal MIPYME exposes no boleta form to anybody, so a 39/41 always leaves through our own engine whatever the empresa emits through — which means an empresa can keep issuing its facturas on the free portal and have us run the boleta certification for it in the background. The habilitacion and folios steps appear on the free lane's checklist the moment you declare a boleta, and capabilities.tipos gains 39/41 once the SII habilitates them. TIPO 46 ON THE sfg CHANNEL IS 422 doc_type_not_supported_on_channel: unlike a boleta, it is not a document our engine builds on that lane, so it needs emission_channel: "mercado". The same refusal fires when emission_channel is moved to sfg while a 46 is already declared.
certification_trackmercado · sfg · null—WHICH CHECKLIST GOVERNS THIS EMPRESA — settable since #474, because the code has always read it and no surface could state it: PATCH \{emission_channel: "sfg"\} moved the tipos, the ambiente and the driver and left the empresa on the ten-step mercado checklist with blocked_on: mandato, a list an SFG emisor never runs. null means «not pursuing certification» and is a real, permanent answer. Setting it to null or to sfg HIDES the mercado checklist WITHOUT deleting anything: the step records stay exactly as they are and setting the track back to mercado shows them again, unchanged. It is a projection, not a destructive write.
origengratuito · mercado · ninguno—Correctable, and the only way an empresa created before the field existed can acquire one.
emission_channelmercado · sfg—Correctable, like origen: an empresa can move onto the SFG lane or off it. Moving to sfg sets ambiente to produccion in the same write, because an empresa in the free system is a production emisor. Moving to mercado leaves ambiente alone — the resolución is the only thing that moves it the other way. It does NOT change certification_track on its own — that is a separate field on this same request body (#474). Those are two facts and each is stated where it belongs.
resolucionResolucionClaimRequest—THE RESOLUCIÓN THIS EMPRESA ALREADY HOLDS (ruling 2026-09-07). Send it when the empresa already invoices electronically through another provider; send null to clear it. Both halves are printed on the carátula of every documento tributario electrónico the empresa emits today (NroResol, FchResol), so an agent can read them off a recent invoice rather than sending anybody to Mi SII. IT IS A CLAIM, NOT A SKIP. It writes two columns and moves nothing else: the empresa is still created at mandato_pending, still needs the certificate and the mandato, and still certifies unless our own production timbraje proves the resolución. That timbraje is made only when a Facturia operator confirms it for this empresa; until then the claim reads resolucion.verification_status: pending_verification. Read resolucion.verified on the empresa object for the outcome.
resolucion_boletasResolucionBoletasClaimRequest—THE BOLETA HABILITACIÓN THIS EMPRESA ALREADY HOLDS (ruling 2026-09-07). Send it alongside doc_types containing 39 or 41; send null to clear it. It carries no numero because the SII issues none for boletas: the habilitación arrives as «habilitados los documentos de boleta electrónica a contar del …», and that date is the whole of it. Verified by a tipo-39/41 production timbraje, exactly as the resolución claim is; refused, the ordinary habilitación steps appear on the checklist instead.
statusactive · archived—
Responses
CodeMeaningBody
200The updated empresa.Empresa
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

DELETE /empresas/{empresa_id}

Delete a SANDBOX empresa

Throw away a scratch empresa. Sandbox only.

A sandbox empresa holds nothing that is a tax record: its documents are mocked, it never reached the SII, and it reserves nothing — not the RUT (uniqueness is per mode) and not the <rut>@dte.facturia.cl reception alias, which live empresas alone carry. So it can be deleted, and the empresa plus everything hanging off it goes in one transaction.

A LIVE empresa is 409 empresa_delete_not_allowed, always. §7.1: emitted documents are tax records. The two real exits are DELETE /v1/empresas/\{empresa_id\}/mandato (ends the authorization, cláusula 7.1.3) and PATCH /v1/empresas/\{empresa_id\} with \{"status": "archived"\} (stops emission and billing); both keep the history.

curl -sS -X DELETE https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
204Deleted. No body.—
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
409empresa_delete_not_allowed — the empresa is live. Revoke the mandato and/or archive it.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/mandato

Whether the representante legal has signed, against which terms, and how

The read half of the hosted mode. Mint a signing_url with an empty POST, send it to the representante legal, then poll this - completion is observable by the agent that asked for it, without subscribing to a webhook or inferring it from steps[].status.

Two fields answer questions the onboarding checklist cannot. terms_current: false on a signed mandato means the text has moved on and the pipeline has already stopped trusting the acceptance - POST again to collect a fresh one. And acceptance_method distinguishes an aprobacion (hosted: a human of the required role completed a page that showed them what they were signing) from an atestacion (api: an integrator's claim that one did). Those are different pieces of evidence.

signing_url is ALWAYS null here: only a SHA-256 of the token is stored, so no read can reproduce it. POST with an empty body re-mints, revoking the previous link.

status: "none" for an empresa nobody has asked yet. It is a state, not a missing resource - the empresa exists and you are authorized for it.

Scope: mandato:write (which empresas:write implies). The scope that mints the link is the scope that reads whether it was used.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200The mandato, or a none status when nobody has been asked yet.Mandato
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/mandato

Record the mandato authorizing Facturia to act before the SII

Post the acceptance you collected yourself, or omit the body to receive a hosted signing_url to send the representante legal.

We record that an acceptance happened, by whom, from where, and against which exact bytes of the mandato text. We do NOT verify a firma electrónica avanzada: whether one is required is an open legal question, and until it is answered this endpoint claims no more than the evidence it holds.

While GET /v1/mandato_terms reports status: "draft", a live-mode acceptance is refused with 422 mandato_terms_draft — the same warning §7.3 prints, enforced.

Two forms, two scopes (ADR-0012 §4). No body — mint the enlace de firma for the representante legal — needs mandato:write, which empresas:write implies. A body — asserting a signature you collected in your own flow — is an atestación and needs mandato:attest itself: empresas:write does NOT imply it (corrected 2026-09-03), so a key that used to post a body under empresas:write alone now gets 403 insufficient_scope naming mandato:attest.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/mandato \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requested_by":"…","representante_legal":{}}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
requested_byRequestedBy + any—
representante_legalobject—
accepted_atstring—When the representante legal accepted. Clamped to now if it is in the future.
accepted_ipstring—
accepted_user_agentstring—
terms_versionstring—
evidenceobject—The same facts in the shape a signing page naturally produces. Accepted as an alternative to the flat fields above, and kept verbatim in the rendición de cuentas alongside whatever we recorded ourselves.
Responses
CodeMeaningBody
201The mandato.Mandato
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

DELETE /empresas/{empresa_id}/mandato

Revoke the mandato

Cláusula 7.1: at any time, without cause, without penalty, and without ending the contract. The empresa moves to suspended immediately, everything already queued is cancelled in the same transaction, and the mandato step returns to action_required.

This is an atestación, not a write (ADR-0012 §4, ticket #465). It is the act with the largest effect in the contract, so empresas:write is NOT enough: send it with mandato:attest, which is granted explicitly, is never implied by empresas:write and is never in the MCP vocabulary. A dashboard session may also call it — a session is the account holder in person. Anything else gets 403 insufficient_scope, whose error.details carries required_scope and the \{method, url\} that mints the hosted revocation page for the representante legal (POST /empresas/\{empresa_id\}/mandato/revocation_link).

It does NOT destroy the certificate — that is DELETE .../certificate, a separate revocation with its own deadline. And it does not undo what preceded it: the empresa's position is remembered, so a fresh acceptance returns it to where it was rather than restarting onboarding.

The empresa stays READABLE while suspended (ticket #515): GET /empresas/\{id\}, GET .../onboarding and GET .../mandato all answer, onboarding.blocked_on names mandato, and onboarding.pending_action is the mandato_revocado handoff whose link.mint re-signs. active is untouched — a right the contract calls reversible is never implemented as a disappearance.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
204Revoked. The empresa moves to suspended, and stays readable.—
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/certificate

Retrieve certificate metadata

Metadata only. The .pfx and its password are never readable through the API.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200Certificate metadata.Certificate
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/certificate

Upload (or reuse) the empresa's digital certificate

Upload the empresa's .pfx (multipart/form-data with file and password) or reuse another empresa's valid certificate (reuse_from_empresa). An expired certificate is refused at ingest.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/certificate \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -F "file=…" \
  -F "password=…"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body (multipart/form-data)

FieldTypeRequiredNotes
filestring—PKCS#12 (.pfx) file.
passwordstring—The .pfx password.
reuse_from_empresastring—Instead of uploading, reference a certificate already held for another empresa on this account. Valid only when that certificate is a SII-authorized user of this empresa's RUT.
Responses
CodeMeaningBody
201Certificate stored and verified.Certificate + object
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

DELETE /empresas/{empresa_id}/certificate

Purge the certificate from the vault (revokes custody, suspends emission)

Remove the stored certificate metadata. The empresa returns to needing one before it can sign anything the SII will accept.

curl -sS -X DELETE https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/certificate \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
204Purged.—
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/sfg_credential

Retrieve the SFG centralizado clave's custody metadata

Metadata only — whether we hold the clave and when it was stored. The value itself is never readable through the API: there is no code path in this endpoint that decrypts it.

Only an emission_channel: sfg empresa in live mode has one. A mercado empresa signs with the certificate in custody and never asks the SII to sign for it, and a test-mode empresa has no Portal MIPYME at all; both answer 422 naming the fix.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200The custody record.SfgCredential
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/sfg_credential

Store (or replace) the SFG centralizado clave

The value goes straight to the per-tenant vault and its id to the empresa's credential row; there is no plaintext column. Replacing destroys the outgoing secret first, so the destruction is in the custody record rather than a side effect nobody can point at.

The response is the same metadata GET returns. The clave is not echoed back.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/sfg_credential \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"clave_centralizado":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
clave_centralizadostringyesThe clave of the empresa's certificado CENTRALIZADO at the SII. ONE FIELD NAME, NO SYNONYMS: clave, password and clave_tributaria are refused with 400 invalid_field rather than coerced. The one mistake this endpoint exists to make impossible is a customer handing us their SII portal password under a field name we were relaxed about.
Responses
CodeMeaningBody
201Stored.SfgCredential + object
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

DELETE /empresas/{empresa_id}/sfg_credential

Destroy the SFG centralizado clave (ends custody)

Destroys the bytes. 204 even when there was nothing to destroy — you asked for a state ("you hold nothing of mine"), and that state is the outcome either way.

Unlike DELETE .../certificate it does NOT walk the empresa's onboarding back: the next SFG emission refuses for want of a signing clave, which the emit path already reports honestly, and a clave can be re-supplied in one call.

curl -sS -X DELETE https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/sfg_credential \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
204Destroyed.—
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/mandato/revocation_link

Mint the hosted page where the representante legal revokes

Cláusula 7.1 belongs to the person who signed, and this is how you put them in front of it (ticket #465). The acceptance already hands them this address once, and the receipt e-mail carries it; this endpoint is for every later moment — the e-mail was lost, or never went out, or the customer is asking their integrator to help them stop.

Holding the link is not holding the power: the page demands the representante legal's RUT as recorded on the mandato they signed, which this response deliberately does not echo. Minting revokes any previous revocation link, which is what single-use means when a link is reissued.

The URL is returned EXACTLY ONCE — only a SHA-256 of the token is stored. It does not expire: the right it addresses does not either.

409 mandato_not_signed when nothing is in force, because an address for a right that cannot be exercised is worse than a refusal that says so.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
201The link. Returned once.MandatoRevocationLink
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/resolucion/verify

Verify a declared resolución with one production timbraje (Facturia operators only)

OPERATOR ONLY — NOT CALLABLE WITH AN API KEY (sii-api#581 D1, founder decision 2026-09-23). Documented so an integrator knows what moves resolucion.verification_status and who moves it.

A resolución stated on POST/PATCH /v1/empresas is a claim. It is verified by asking the PRODUCTION SII for ONE folio range of one declared factura tipo: the SII refuses a timbraje for a RUT it has not authorized for that tipo, so a granted range is the proof. That request is irreversible (a registered range must be used or annulled), so it is made only when a Facturia operator confirms it for this empresa. Nothing is requested implicitly, nothing during POST /v1/empresas, and nothing against the certification environment.

Without confirm: true the call refuses with 400 confirmation_required and names the exact request in the message and in error.details.request. With it, the confirmation (who, when, why) is recorded and ONE folio_request job is queued; the SII's answer then moves resolucion.verification_status from pending_verification to verified or refused. An attempt that gets no answer about the RUT (the SII's edge block, an unusable certificate) stays pending_verification.

409 resolucion_verification_not_allowed names the blocking fact in error.details.reason: sandbox, sfg_channel, no_claim, already_verified, refused, in_flight, range_registered, not_ready (the empresa is not at certificate_verified) or no_factura_tipo.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/resolucion/verify \
  -H "X-Admin-Secret: $FACTURIA_OPERATOR_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"confirm":true,"authorized_by":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
confirmbooleanyesMust be true. Anything else is 400 confirmation_required and nothing is sent.
authorized_bystringyesThe person confirming. Recorded beside the SII's answer.
reasonstring,null—Why, in a sentence (the ticket, the customer). Recorded.
Responses
CodeMeaningBody
202Confirmed. The one production request is queued; the SII's answer lands on resolucion.ResolucionVerification
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
404No such object under this key.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/counterparties

Counterparties you have invoiced, with the address and contacts on file for each

A DERIVED list, not a stored one: it reads the receivers of the documents this empresa has already emitted (tipos 33, 34, 56 and 61) and joins whatever contacts and address book entries exist, so the screen opens as a checklist rather than a blank form. A counterparty that has an address or a contact but no document yet is included.

The address fields come from the address book only. Where none is on file they are null and so is source, which on this list means "nothing recorded" rather than "nothing known" — the single-counterparty read is the one that falls back to what earlier documents said.

has_more is always false. The window bounds the SCAN, not a page of results, and promising a cursor this shape cannot honour would be worse than saying so.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200The counterparties, newest first.lista de Counterparty
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /contribuyentes/{rut}

What the SII publishes about a company, by RUT, and a giro that fits a factura

The razón social and economic activities the SII publishes for a persona jurídica, and a suggested giro that fits the receptor of a factura (at most 40 characters, ISO-8859-1). For prefilling a customer before the first invoice to them.

The source is the SII's public nómina of personas jurídicas (https://www.sii.cl/sobre_el_sii/nominapersonasjuridicas.html), which the SII republishes every few months and does not guarantee to be complete or current. source.published_at says which edition answered. Personas naturales are not in it: their RUTs answer 404, like a RUT that does not exist.

A SUGGESTION, not a statement: nothing here is written into a document on its own. Show it to the person, and once confirmed, put it on file with PATCH /v1/empresas/\{id\}/counterparties/\{rut\} — that is what the next emission reads.

suggested_giro.basis is curated when the text comes from Facturia's short wording for a common activity code (the official descriptions are usually longer than 40 characters, and a cut one would change what the factura says), sii_description when the official description fits as it stands. Null when neither applies: ask the person.

Not empresa-scoped: the data is public and about a third party, so no Facturia-Empresa is needed. Limited to 120 requests a minute per account.

curl -sS https://api.facturia.cl/v1/contribuyentes/{rut} \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
rutpathyesRutAny persona jurídica's RUT, canonical (76000002-7) or with dots, or without the hyphen.

Responses

CodeMeaningBody
200The contribuyente.Contribuyente
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404The nómina does not list this RUT (a persona natural, or no such company).ErrorResponse
429Too many requests.ErrorResponse
503The nómina has not been loaded yet (contribuyentes_unavailable). Retry after Retry-After.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/counterparties/{counterparty_rut}

What you know about one counterparty, and whether you stated it or we inferred it

Your address book entry for this RUT — the razón social, giro and domicilio we will put on the next document to it.

Two sources, in the order the emission resolver uses them. If you have put an address on file, that is the answer and source is api. If you have not, but you have already emitted a document to this RUT, the answer is read off the newest one and source says so — that is inference, not a statement, and it is the difference worth knowing before you spend a folio. If neither exists, this is a 404.

The counterparty has no id: there is exactly one identity per RUT, so the RUT is the key.

curl -sS https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/counterparties/{counterparty_rut} \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
counterparty_rutpathyesRutThe counterparty's RUT, canonical (76000002-7). It is the key of the relationship and of the address book entry — a counterparty has no id of its own, because there is exactly one of each per RUT.

Responses

CodeMeaningBody
200The counterparty.CounterpartyIdentity
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404Nothing is on file for this RUT and no document has ever been emitted to it.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

PATCH /empresas/{empresa_id}/counterparties/{counterparty_rut}

Put a counterparty's razón social, giro and address on file

The SII will not file a factura whose receptor has no giro, dirección and comuna: it accepts the ENVELOPE and then refuses the DOCUMENT, and the folio is spent either way. Recording the address here answers that once instead of on every emission — and it is the only source that can answer for a counterparty you have never invoiced, which is exactly when the question bites.

An upsert: the first call creates the entry, later ones edit it. There is no POST and no DELETE, because the entry has no lifecycle of its own.

Partial. A field you do not send keeps its value; a field sent as null is cleared. 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. Over the limit is refused, never truncated: a silently shortened name of trade would change what every document to this counterparty says about who they are.

No earlier document is required. Recording an address for a RUT you have not invoiced yet is the point.

curl -sS -X PATCH https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/counterparties/{counterparty_rut} \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"legal_name":"Connect SpA","giro":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
counterparty_rutpathyesRutThe counterparty's RUT, canonical (76000002-7). It is the key of the relationship and of the address book entry — a counterparty has no id of its own, because there is exactly one of each per RUT.

Body

FieldTypeRequiredNotes
legal_namestring,null—RznSocRecep, at most 100 characters.
girostring,null—GiroRecep, at most 40 characters — half of what the emisor's own giro is allowed, and the cap that bites most often. Refused over the limit, never truncated.
addressCounterpartyAddress—The receptor's domicilio, as the SII's own elements: street is DirRecep, comuna is CmnaRecep, city is CiudadRecep. Always present as an object, even when every part of it is null, so a client never has to guard the parent before reading a part.
Responses
CodeMeaningBody
200The stored counterparty.CounterpartyIdentity
400A malformed RUT, a body naming no field at all, a value longer than the SII element it becomes (field_too_long) or one carrying a character ISO-8859-1 cannot represent (unencodable_character).ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts

The contacts on file for one counterparty

The primary contact comes first — it is the only one that is notified.

curl -sS https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/counterparties/{counterparty_rut}/contacts \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
counterparty_rutpathyesRutThe counterparty's RUT, canonical (76000002-7). It is the key of the relationship and of the address book entry — a counterparty has no id of its own, because there is exactly one of each per RUT.

Responses

CodeMeaningBody
200The contacts.lista de CounterpartyContact
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts

Put a contacto responsable on file

A contact with no attestation is a legitimate state: it is stored, it is shown, and it is never messaged. Sending opt_in: true without opt_in_method is refused, because consent recorded without recording how it was obtained is not a record.

The first contact for a counterparty becomes the primary unless you say otherwise.

A number that has opted out cannot be re-added under a new id: that is 422 contact_opted_out, and it is what stops delete-and-recreate from undoing a STOP.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/counterparties/{counterparty_rut}/contacts \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"cliente — Comercial Andina","phone":"+56912345678"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
counterparty_rutpathyesRutThe counterparty's RUT, canonical (76000002-7). It is the key of the relationship and of the address book entry — a counterparty has no id of its own, because there is exactly one of each per RUT.

Body

FieldTypeRequiredNotes
namestringyes
phonestringyesE.164. Anything that cannot be brought to that form is 400 invalid_phone. E.g.: "+56912345678".
counterparty_namestring,null—Overrides the razón social the documents carried, for display only.
primaryboolean—Defaults to true for the first contact of a counterparty, false afterwards.
opt_inboolean—Your attestation that this person agreed to receive WhatsApp notifications from you. Omitting it is a legitimate state: the contact is stored as an address-book entry and is never messaged (skip reason no_optin).
opt_in_methoden_persona · telefono · email · escrito · otro · null—How the person actually agreed to be messaged. en_persona = in person or at the point of sale; telefono = by phone, recorded by whoever took the call; email = by email or a web form; escrito = on paper, signed; otro = something else, said plainly rather than forced into the nearest fit. WhatsApp's policy is that holding a number is not consent — "an affirmative, disclosed opt-in action is required" — so an attestation without a method is refused with 400 optin_method_required.
opt_in_attested_bystring—Who collected the consent. Defaults to the credential that made the call.
Responses
CodeMeaningBody
201The contact.CounterpartyContact
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

PATCH /empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts/{contact_id}

Edit a contact, promote it, or record an opt-out

primary: true demotes whoever held it, in the same call — exactly one contact per counterparty may be primary and the database is what enforces it.

opted_out: true records that the person asked to stop by some other channel. opted_out: false does not exist: an opted-out contact is frozen and cannot be edited, re-attested or re-enabled. Someone who asked to be left alone is not un-asked by a colleague fixing a typo in their name.

curl -sS -X PATCH https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/counterparties/{counterparty_rut}/contacts/{contact_id} \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"cliente — Comercial Andina","phone":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
counterparty_rutpathyesRutThe counterparty's RUT, canonical (76000002-7). It is the key of the relationship and of the address book entry — a counterparty has no id of its own, because there is exactly one of each per RUT.
contact_idpathyesstringThe cnt_… id.

Body

FieldTypeRequiredNotes
namestring—
phonestring—
counterparty_namestring,null—
primaryboolean—true demotes whoever currently holds it, in the same call. Two primaries cannot exist even momentarily.
opt_inboolean—false clears the attestation — a consent record that can only ever be added to is worse for the person it exists to protect.
opt_in_methoden_persona · telefono · email · escrito · otro · null—How the person actually agreed to be messaged. en_persona = in person or at the point of sale; telefono = by phone, recorded by whoever took the call; email = by email or a web form; escrito = on paper, signed; otro = something else, said plainly rather than forced into the nearest fit. WhatsApp's policy is that holding a number is not consent — "an affirmative, disclosed opt-in action is required" — so an attestation without a method is refused with 400 optin_method_required.
opted_outboolean—true records that the person asked to stop by another channel. false is not accepted: an opt-out is not reversible through this API.
Responses
CodeMeaningBody
200The updated contact.CounterpartyContact
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

DELETE /empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts/{contact_id}

Remove a contact from the address book

204 whether or not it was there. The exception is an opted-out contact, which is kept: its row is precisely what makes re-adding the same number refusable.

curl -sS -X DELETE https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/counterparties/{counterparty_rut}/contacts/{contact_id} \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
counterparty_rutpathyesRutThe counterparty's RUT, canonical (76000002-7). It is the key of the relationship and of the address book entry — a counterparty has no id of its own, because there is exactly one of each per RUT.
contact_idpathyesstringThe cnt_… id.

Responses

CodeMeaningBody
204Removed.—
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/onboarding

Onboarding lifecycle, steps and blocking human actions

Everything a wizard needs to render progress and tell the user what to do next.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200Onboarding state.Onboarding
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/links

Mint a link to a hosted representante-legal page

The person who has to act is often not the person holding the account: the account belongs to the accountant, the certificate belongs to a socio, and the SII steps that need a person are the socio's. So the hosted pages are reached by a link, not by a login.

step is any step of THIS empresa's checklist that needs a person - actor: customer (the step they perform) OR stall_actor: customer (a step of OURS that cannot finish until they decide). Which ones exist depends on its certification_track: certificate, sii_postulacion, cumplimiento and habilitacion on the mercado catalog; credential, clave_centralizado and usuario_autorizado on the SFG one. A step from the other catalog is refused with a 400 naming the ones this empresa does have. The mandato's link is single-use and comes from POST /empresas/\{empresa_id\}/mandato with an empty body instead.

The url is returned exactly once - only its SHA-256 is stored. The link is reusable and lives thirty days, and re-minting revokes the previous one.

The response also carries the step's pending_action, with action_url set to the URL just minted: the mint is the one moment both halves exist at once, so an agent gets the live address and the instructions to relay with it in a single call.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/links \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"step":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
stepstringyesWhich hosted page the link opens: any step of THIS empresa's checklist that needs a human - actor: customer (they perform it) or stall_actor: customer (we perform it and it waits on their decision). mandato is deliberately absent: its link is single-use and comes from POST /empresas/\{empresa_id\}/mandato with an empty body. Read the valid values off GET /empresas/\{empresa_id\}/onboarding.
requested_byRequestedBy—Who is asking for this link, as YOU describe yourself (2026-09, #459). Optional, and rendered to the person who receives the link: "Este enlace lo pidio Claude Desktop el 4 de septiembre" instead of "lo genero una integracion o un agente en nombre de tu cuenta", which was the only sentence available for every agent that has ever existed. A CLAIM, NOT A CREDENTIAL. It changes nothing about authorization and is never trusted for a decision; the credential your request actually arrived on is recorded separately. Send \{"kind": "agent", "name": "&lt;your client name&gt;"\} when you are an agent acting for a person.
Responses
CodeMeaningBody
201The link. url carries the token and is never shown again.OnboardingLink
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

Revoke a hosted representante-legal link

204 whether or not one existed. A revoked link answers exactly like one that never existed, so a token cannot be used to learn whether we ever asked a given company to sign something.

curl -sS -X DELETE https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/links/{step} \
  -H "Authorization: Bearer $FACTURIA_KEY"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
steppathyesstringThe step whose link is being revoked. Any step either catalog can mint a link for - the wider set on purpose, so a link minted under a checklist the empresa has since left can still be taken back.

Responses

CodeMeaningBody
204Revoked.—
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/go_live

Ask the titular to approve the go-live, or state the decision on their behalf

The Declaracion de Cumplimiento is a decision about a month, not a progression. On declaring, the empresa "queda operando automaticamente en el sistema de facturacion electronica" and owes the Informacion Electronica de Compras y Ventas FROM THE MONTH OF THE DECLARACION, whether or not it emits anything. The SII's own page says the step may be postponed.

So the month is the customer's to choose and a custody mandate does not carry it.

TWO FORMS, and the body is which one.

  • No body mints an enlace de aprobacion for the titular and answers 201 with approval_url and expires_at. The human opens the page, sees the month and the IECV consequence in their own language, and decides. Single use, seven days, and the URL is returned exactly once - only its SHA-256 is stored. Scope onboarding:write.
  • \{"authorized": true|false\} states the decision on the titular's behalf: an atestacion, for an integrator that collected it in its own flow. Answers 200 with the authorization. Scope onboarding:attest.

empresas:write implies onboarding:write — the no-body form — but NOT onboarding:attest (corrected 2026-09-03): an atestación is a human granting that power on purpose, every time, never a side effect of a wider grant a key already held. A key that used to send \{"authorized": true|false\} under empresas:write alone now gets 403 insufficient_scope naming onboarding:attest.

This changed in 2026-09. An empty body used to DEFAULT to authorized: true, so a caller who sent no body silently authorized a tax consequence. An omitted field is not a decision; if you were relying on that, send \{"authorized": true\} explicitly.

\{"authorized": false\} withdraws while nothing has been filed. Once the declaracion is filed there is nothing left to withdraw and the call is refused with 409 cumplimiento_already_filed.

Refusals arrive together. The status and code are the first blocker's own - 422 mandato_revoked, for instance - and error.details.blockers[] lists every one, so you do not learn them one round trip at a time.

THE AUTHORIZATION IS FREE (2026-09-04, #530). It used to be gated on the subscription, and only on this door: the hosted cumplimiento page for the representante legal answered 200 for the same unsubscribed empresa, so the rule lived in one channel and an agent that sent the human to the other one walked around it. It is a DECISION, not an emission — the titular saying «start my obligation this month» — and the gate stands where the value is: every act that spends a folio or leaves the building is still gated, so an authorized empresa that has not paid emits nothing. This operation no longer answers 402.

There has to BE a decision (2026-09, #451). Minting an approval link for an empresa that has not reached the Declaracion de Cumplimiento handed a human a page with nothing on it. Two refusals come first, before the blockers and before the mint:

  • 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. Retriable: it WILL be ready.
  • 409 go_live_not_applicable - this empresa is not certifying for mercado, so it has no Declaracion de Cumplimiento at all. What gates an SFG empresa's emission is puesta_en_vivo, which is ours. Not retriable.

Neither applies to \{"authorized": false\}: a decision that exists can always be withdrawn, whatever the lane has done since.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/go_live \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"authorized":true}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
authorizedboolean—Present at all, this states the decision on the titular's behalf - an atestacion, behind onboarding:attest. false withdraws an authorization that has not been acted on yet; once the declaracion is filed the call is refused with 409 cumplimiento_already_filed. No default. Omit the whole body to mint an approval link for the titular instead. It used to default to true, which meant a caller who sent no body silently authorized a tax consequence.
Responses
CodeMeaningBody
200The authorization, as it now stands. Answered for the body form.GoLiveAuthorization
201The approval link, answered for an empty body. approval_url is shown once.GoLiveApproval
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/steps/{step}/recheck

Tell us a human step was performed, and we go and look

The resume half of the pending_action handoff. A person has performed one of the acts Chilean law reserves to them (see pending_action.performed_by), and this asks us to find out whether the SII agrees.

This endpoint accepts a claim and produces no fact. It takes no request body, writes no step status and moves no empresa. What it does is enqueue the producer that READS the SII, plus the tick that folds whatever that producer finds, and return the step's status as it stood the moment you called — read before the enqueue and returned unchanged. If you post a recheck on a step that is action_required and read action_required back, that is the endpoint working: the step advances when we see the fact, not when you say so. verified is on the response and it is always false.

evidence_source says how (or whether) the step can be confirmed at all. sii_probe means we go and read the SII. own_record means the act lands in our own tables and we will see it. no_producer means this build genuinely cannot observe that step yet — the boleta habilitacion is the one — and contact names the door out: POST .../onboarding/steps/\{step\}/attest (#395), which is a DIFFERENT endpoint that writes a fact rather than merely recording a claim. Do not tell a user this call alone is watching a no_producer step; nothing is.

queued and already_queued split the jobs this call created from the ones that were already scheduled for the same five-minute window. Both mean "we are about to look".

For a sandbox empresa nothing is enqueued and evidence_source is sandbox: a test empresa's onboarding never opens a session against the SII, so there is nothing to re-read. Move its steps with the test helpers instead.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
steppathyesstringAn onboarding step key from this empresa's own checklist.

Responses

CodeMeaningBody
202The claim was accepted for verification. Nothing has been verified yet.OnboardingRecheck
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/steps/{step}/attest

Record that a human performed a step this build cannot probe (#395)

The endpoint recheck's own contact field points at when evidence_source: 'no_producer'. Unlike recheck — which accepts a claim and produces no fact — this endpoint writes one: a sii_onboarding_evidence row the next onboarding tick reads as the fact that lets the step complete.

Refused with 422 invalid_field on any step whose evidence_source is not no_producer — attesting a step the SII probe or our own records already confirm would be a second, competing source of truth for a fact we can already verify. habilitacion (boleta habilitación at certBolElectDteInternet, an uncaptured SII SPA) is the only step this map names no_producer today.

Requires onboarding:attest itself: never in the bootstrap default set, never implied by empresas:write alone (ADR-0012 §3-4). attested_by is required — who told you this happened.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/steps/{step}/attest \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"attested_by":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
steppathyesstringAn onboarding step key from this empresa's own checklist.

Body

FieldTypeRequiredNotes
attested_bystringyesA name, or the representante legal's RUT. Who told you this happened.
notestring—
Responses
CodeMeaningBody
202The attestation was recorded. The step folds on the next tick.OnboardingAttestation
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/steps/{step}/approve

Record the titular's authorization for a step Facturia then performs (#452, #453)

The sibling of attest, for the opposite situation. attest says «a person did something we cannot see»; this says «a person authorized something WE are about to do».

Two acts in this pipeline are ours to perform once somebody chooses the moment. The postulacion is presented with the representante legal's own custodied certificate under mandato 4.2 a), and it costs an empresa on the Sistema de Facturacion Gratuito its "calidad de usuario activo" the instant the SII authorizes it. The boleta habilitacion opens a 24-hour SII clock. Neither is a decision a custody mandate carries, so each waits for a row in sii_onboarding_step_approvals.

act must be the one the step is currently asking for. Read steps[].pending_action.approval on GET .../onboarding: it names the act, whose decision it is, the consequences to show first, and both button labels. A step asking for nothing answers 409 onboarding_approval_not_offered; a mismatched act answers 400 invalid_field. One step carries different decisions at different points of its life, and an approval recorded against the wrong one is consent that was never given.

A rejection is recorded, not ignored. decision: "reject" writes a row exactly as an approval does, so the product can say "you left it for later" rather than "nobody answered". Executors read the LATEST row for the act and branch on decision.

Requires onboarding:attest itself — never in the bootstrap default set, never implied by empresas:write (ADR-0012 §3-4). The canonical form of an approval is the hosted page (POST /onboarding/steps/\{step\}/approve with the link token); this is the integrator door for a decision collected in their own flow.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
steppathyesstringAn onboarding step key from this empresa's own checklist.

Body

FieldTypeRequiredNotes
actstringyesThe act being decided, exactly as pending_action.approval.act names it.
decisionapprove · rejectyes
notestring—
Responses
CodeMeaningBody
201The decision was recorded. On approve, the fold tick is enqueued.OnboardingStepApproval
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
404No such object under this key.ErrorResponse
409State conflict — idempotency reuse, duplicate external_id, artifact not ready.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/onboarding/certification

The set de pruebas - what was uploaded, what is missing, what the SII said

One read, not four. A certification is a bounded thing - one set file, one input document, a handful of envios across at most three rounds - and splitting it would make the obvious screen four round trips whose answers can disagree.

TWO FIELDS THAT LOOK ALIKE AND ARE OPPOSITE. envios[].estado is the ENVIO estado, where SOK means "envio recibido" - arrived, not judged. envios[].verdict is the SET verdict, where SOK means the set was APPROVED. Same three letters, opposite consequences, which is why they are two fields and never merged.

envios[].dry_run: true means the envelope was built and NOT sent. submittable is null until a build has happened at all, which is not the same as false.

problems[].message may be null: the sentences live in the build artifact beside the record, and when it cannot be read the response degrades to codes rather than failing. The code is always there.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200The certification, as it now stands.Certification
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/certification/set_file

Upload the set de pruebas the SII issued for this empresa

The SII issues the set .txt per taxpayer when it accepts the postulacion. Nothing downloads it for you; this is how the bytes get in.

MULTIPART, NOT BASE64 IN JSON. The file is ISO-8859-1 and its accents are part of the document - Cajon and Panuelo are two of the items the SII reported as reparos when theirs were lost. Multipart hands us the bytes; a JSON string would already have been through somebody's idea of a text encoding by the time we saw it. The bytes are stored verbatim and never re-encoded. Limit: 1 MB.

Bytes that are not a set de pruebas are refused with 422 set_file_unrecognized and NOTHING is stored - a portal login page parsed as an empty set would report the empresa as having no casos, which is a different thing from having no set file.

A set that IS recognised is stored even when parts of it were not understood; those appear in problems[].

This endpoint does not run a round, does not open a socket to the SII and does not spend a folio. The round is the engine's, on its own clock.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/certification/set_file \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -F "file=…"

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body (multipart/form-data)

FieldTypeRequiredNotes
filestringyesThe .txt as the SII issued it. ISO-8859-1, at most 1 MB.
Responses
CodeMeaningBody
201The set as we read it.CertificationSetFile
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
422Well-formed but violates a domain rule — folios exhausted, acuse window closed.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

POST /empresas/{empresa_id}/onboarding/certification/inputs

Supply the facts the set de pruebas does not carry

The set states several things by implication and never spells them out: who issued each purchase in the libro de compras, what the CodRef-2 correction says, which factura invoiced a guia the set marks "facturada", the factor de proporcionalidad, the SII-issued folio de notificacion, and - for a boleta round - the abbreviated unidades and ten everyday boletas. Each one is a deliberate refusal in the engine: the round reports what it needs rather than inventing a value and spending folios on a document the SII will reject.

THE MERGE IS PER TOP-LEVEL SLOT: absent keeps, null clears, present replaces WHOLE. Restating proveedores restates all of them - which is what a form submits and the only way to REMOVE one. A deep merge would leave the row you deleted in the next libro.

NOTHING IS WRITTEN WHEN ANYTHING IS INVALID, not even the valid half: a partial write leaves the empresa in a state nobody asked for and a round that refuses for a reason you believe you already fixed. 400 certification_input_invalid carries EVERY problem in error.details.problems, not the first. It may also refuse over a value you did not send, when the stored document your patch merges into is itself invalid.

ONE SLOT IS PREFILLED: libro_guias.folio_notificacion defaults to 3, the number continuing the 1 and 2 the libro de ventas and the libro de compras carry. LibroGuia_v10.xsd makes the element mandatory where the IECV's is optional, so it cannot simply be omitted, and a round that goes out on the default says so in a notice. libro_boleta_folio_notificacion has no default and will not get one: it is an SII-issued number answering a specific notificacion and nothing can derive it.

curl -sS -X POST https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/onboarding/certification/inputs \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"receptor":{"rut":"76543212-K"},"giro_recep_emisor":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
receptorobject,null—The party the test documents are issued to. giro is measured against GiroRecep (40), which is shorter than GiroEmis (80) - an emisor's own name of trade is frequently illegal in this slot.
giro_recep_emisorstring,null—A ≤40-character abbreviation of the EMISOR's own giro, for the ONE place a traslado interno guia writes it back: the receptor is the emisor itself (a traslado interno moves goods between the empresa's own premises), so its giro is what would be written into GiroRecep - which caps at 40 where the emisor's own GiroEmis allows 80. Absent, and the emisor's giro fits within 40 characters anyway: written verbatim, same as always. Absent, and it does not fit: GiroRecep is optional in the schema, so the element is OMITTED rather than truncated, and the round raises giro_recep_omitted so the omission is visible rather than silent - this is the exact reparo the SII drew over the pilot's own 65-character giro. Present: written verbatim, and refused - never truncated - if it does not fit either.
text_correctionobject,null—The CodRef-2 correction. Written as "Dice: {says} y debe decir: {shouldSay}" into RazonRef, which caps at 90 - 21 of them are the scaffolding, so the two halves share 69. Measured on the composition, and never truncated for you.
proveedoresobject,null—Who issued each purchase, keyed &lt;tipoDte&gt;:&lt;folio&gt; - the two things the set prints for every row. A RUT whose verifier digit fails modulo 11 is one of the two documented causes of an LRH "Descuadrado", found after the folios are spent. razonSocial is measured against the LIBRO's RznSoc (50), not the DTE's 100.
iva_no_rec_codesobject,null—Why a purchase loses its credito. LibroCV_v10.xsd enumerates 1,2,3,4,9.
libro_treatmentsobject,null—The IVA treatment of a purchase the set does not classify. "unknown" is the planner's word for "the set said nothing" and "exento_sin_iva" is arithmetic, so neither is an input.
libro_guiasobject,null—
factor_proporcionalidadnumber,null—The set prints it as "EL FACTOR DE PROPORCIONALIDAD DEL IVA ES DE 0.60". It decides how much credito is claimed, so it is never guessed.
libro_boleta_folio_notificacioninteger,null—The SII-issued "Folio de la Notificacion con que se Solicita el Libro Especial". NO DEFAULT, and there will not be one - nothing can derive it, and a boleta round refuses without it.
boleta_unidadesobject,null—UnmdItem by &lt;casoId&gt;:&lt;nroLinDet&gt;, for the one thing a set can demand that its own schema forbids - a unidad longer than four characters. Refused, never truncated: "mens" is not a unit.
boleta_muestrasarray,null—The ten everyday boletas the SII's boleta review asks for, describing what this empresa actually sells at prices it actually charges. Fewer than ten is an incomplete request and is refused; more is fine.
permiso_usuario_rutstring,null—The natural person the permisos probe asks "Mantencion de usuarios" about, when the custodied certificate encodes no RUT of its own. THE ONLY SLOT HERE THAT IS NOT A DOCUMENT FACT, and the only one no builder reads. Estado 106 "Usuario sin permiso de envio" is about the RUT the SII token was minted for, so the probe reads it out of the certificate first and falls back to this. Absence does NOT refuse a round: the round still builds and signs, and it is the permisos gate that parks, reporting no_target with the remedy attached. Validated modulo 11 like every other RUT here. null clears it. Never the empresa's own RUT - that table lists natural persons, so an empresa RUT would find nobody and report the sender as unlisted.
Responses
CodeMeaningBody
200The inputs as they now stand on the empresa.object
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/onboarding/evidence

The rendicion de cuentas - what we did on the empresa's behalf

Clausula 4.4 of the mandato obliges us to render account of what we did on the customer's behalf. Every act the onboarding pipeline performs is kept as an artifact, and this reads them back, newest first.

retention_until is on every row and is six years from the act - the Codigo Tributario's prescripcion extraordinaria plus its conservation duty, which is also the retention purpose under Ley 21.719. On revocation or traspaso the record freezes read-only and is offered as an export; it is never purged early.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
limitquery—integer
cursorquery—stringOpaque cursor from a previous response's next_cursor.
stepquery—stringOnly artifacts of this onboarding step.
kindquery—stringOnly artifacts of this kind.

Responses

CodeMeaningBody
200The artifacts.lista de OnboardingEvidence
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/onboarding/evidence/export

The rendicion de cuentas as one CSV file

The same rows the list endpoint serves, complete in one response, as a file you can hand to a customer, an accountant or a lawyer. The retention ruling says that on revocation or traspaso we offer the customer a full export as the formal clausula 4.4 rendicion; this is that export, and it is available at any time, not only on the way out.

RFC 4180, comma-separated, UTF-8 with NO byte-order mark, CRLF line endings. The columns are the JSON object's own fields in the same order - id, empresa_id, step, tipo_dte, kind, captured_at, retention_until, sha256, bytes, content_type, detail - so storage_path is absent here exactly as it is absent there. detail travels as its compact JSON, quoted.

Paged internally and capped at 50,000 rows. When the cap is reached the response says so in Facturia-Export-Complete: false rather than serving a truncated file that looks whole; step and kind are how you narrow an export that hit it.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring
stepquery—stringOnly artifacts of this onboarding step.
kindquery—stringOnly artifacts of this kind.

Responses

CodeMeaningBody
200The rendicion, as a CSV attachment.text/csv
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/automation_settings

Retrieve per-empresa automation settings

The empresa's automatic-emission toggles (auto_characterize_purchases, auto_emit_46, …), each defaulted where unset.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200Automation settings.AutomationSettings
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

PATCH /empresas/{empresa_id}/automation_settings

Update per-empresa automation settings

Every automation defaults to OFF except auto_request_folios. auto_emit_46 and auto_file_f29 are opt-in per empresa and capped.

curl -sS -X PATCH https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/automation_settings \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"object":"…","empresa_id":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
objectstring—
empresa_idstring—
auto_emit_46boolean—Default: false.
auto_emit_46_max_amountClp—Amount in CLP. Integer pesos, never decimals or strings.
auto_file_f29boolean—Default: false.
auto_file_f29_dayinteger—Default: 18.
auto_file_f29_max_payableClp—Default 0 — out of the box only a zero or refund position files itself, and any month you actually owe IVA waits for a human. Raise deliberately, per empresa. Default: 0.
auto_request_foliosboolean—Default: true.
auto_request_folios_thresholdinteger—Default: 50.
auto_accept_receivedboolean—Default: false.
auto_accept_received_after_daysinteger,null—
require_confirmationboolean—When true, money-moving actions initiated from an agent surface — emitting a document, filing an F29, registering a reclamo through the MCP server's tools — require a draft → confirm two-step instead of executing on the first call. It exists primarily for the MCP surface (see mcp-server-spec.md); the REST API described here is unaffected, and the flag lives on this resource so one settings object governs the empresa on every surface. Default: false.
rcv_sync_frequencyhourly · daily · weekly · manual—Default: "daily".
honor_estado_contable_overrideboolean—Whether the F29 propuesta obeys the LOCAL estado_contable_override set through POST /tax/purchases/\{entry_id\}/estado_contable instead of the estado_contable the SII's own register supplied. Off by default, and the direction of that default is the point: only REGISTRO purchases carry a crédito fiscal, so a local PENDIENTE → REGISTRO flip claims IVA the SII does not agree exists — and the SII's value is the one an audit reads. Turned on, every affected propuesta carries an explicit observation naming the documents, and every write is recorded on the document's event log. Default: false.
updated_atstring—
Responses
CodeMeaningBody
200Updated settings.AutomationSettings
400Malformed or missing input.ErrorResponse
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

GET /empresas/{empresa_id}/branding

Read logo, default print format and footer text

Every field is filled in from the platform defaults where the empresa has set nothing, so the object always describes what a PDF will actually look like rather than only what was overridden.

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

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Responses

CodeMeaningBody
200The empresa's branding.Branding
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

PATCH /empresas/{empresa_id}/branding

Update logo, default print format and footer text

Update the panel and hosted-page branding fields (logo, colors, display name). An unknown field name is refused (422 unknown_setting), never silently ignored.

curl -sS -X PATCH https://api.facturia.cl/v1/empresas/emp_01K2R6ZC4P8QW1VN7T3MHDY9EK/branding \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"object":"…","logo_url":"…"}'

Parameters

ParameterInRequiredTypeNotes
empresa_idpathyesstring

Body

FieldTypeRequiredNotes
objectstring—
logo_urlstring—
default_pdf_formatcarta—carta is the only legal value — 80mm was removed in wave 9 along with the format parameter, and no thermal layout exists. PATCH refuses any other value with 400 invalid_field (ticket #18 wave 10). There is nothing further for it to select: it already names the only layout this API renders, so validating it is what "applying" it means. Default: "carta".
cedible_by_defaultboolean—Applies to facturas — tipo 33 and 34 (Ley 19.983; not "tipo 33 only", corrected in ticket #18 wave 10). Consumed by GET /documents/\{document_id\}/pdf's default-copy resolution: a per-document pdf.cedible (§9.3) overrides it; an explicit ?copy= overrides both. See that endpoint's copy parameter for the full precedence. Default: false.
footer_textstring—
Responses
CodeMeaningBody
200Updated branding.Branding
401Missing, malformed or revoked key.ErrorResponse
403Valid key, not permitted — scope, empresa allowlist, or mode.ErrorResponse
429Too many requests.ErrorResponse
defaultAn error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on error.code, never on the HTTP status alone or on error.message — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human.ErrorResponse

On this page

GET /empresasPOST /empresasGET /empresas/{empresa_id}PATCH /empresas/{empresa_id}DELETE /empresas/{empresa_id}GET /empresas/{empresa_id}/mandatoPOST /empresas/{empresa_id}/mandatoDELETE /empresas/{empresa_id}/mandatoGET /empresas/{empresa_id}/certificatePOST /empresas/{empresa_id}/certificateDELETE /empresas/{empresa_id}/certificateGET /empresas/{empresa_id}/sfg_credentialPOST /empresas/{empresa_id}/sfg_credentialDELETE /empresas/{empresa_id}/sfg_credentialPOST /empresas/{empresa_id}/mandato/revocation_linkPOST /empresas/{empresa_id}/resolucion/verifyGET /empresas/{empresa_id}/counterpartiesGET /contribuyentes/{rut}GET /empresas/{empresa_id}/counterparties/{counterparty_rut}PATCH /empresas/{empresa_id}/counterparties/{counterparty_rut}GET /empresas/{empresa_id}/counterparties/{counterparty_rut}/contactsPOST /empresas/{empresa_id}/counterparties/{counterparty_rut}/contactsPATCH /empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts/{contact_id}DELETE /empresas/{empresa_id}/counterparties/{counterparty_rut}/contacts/{contact_id}GET /empresas/{empresa_id}/onboardingPOST /empresas/{empresa_id}/onboarding/linksDELETE /empresas/{empresa_id}/onboarding/links/{step}POST /empresas/{empresa_id}/onboarding/go_livePOST /empresas/{empresa_id}/onboarding/steps/{step}/recheckPOST /empresas/{empresa_id}/onboarding/steps/{step}/attestPOST /empresas/{empresa_id}/onboarding/steps/{step}/approveGET /empresas/{empresa_id}/onboarding/certificationPOST /empresas/{empresa_id}/onboarding/certification/set_filePOST /empresas/{empresa_id}/onboarding/certification/inputsGET /empresas/{empresa_id}/onboarding/evidenceGET /empresas/{empresa_id}/onboarding/evidence/exportGET /empresas/{empresa_id}/automation_settingsPATCH /empresas/{empresa_id}/automation_settingsGET /empresas/{empresa_id}/brandingPATCH /empresas/{empresa_id}/branding