Artifacts and public links
PDF, XML, the copia cedible, and how to hand a document to someone who has no key.
The authenticated artifacts
# PDF — la copia original (tributaria), tamaño carta
curl -L https://api.facturia.cl/v1/documents/doc_01K2R7.../pdf \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" -o factura.pdf
# Copia cedible (facturas: tipo 33 y 34)
curl -L "https://api.facturia.cl/v1/documents/doc_01K2R7.../pdf?copy=cedible" \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" -o factura-cedible.pdf
# XML firmado, exactamente como se envió al SII (ISO-8859-1)
curl -L https://api.facturia.cl/v1/documents/doc_01K2R7.../xml \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" -o dte.xmlcopy: original (the default) or cedible. Any other value is 400 invalid_field. Every PDF is
letter size — the printed-sample rules of the SII, Chile's tax authority, are built on letter, not A4.
PDFs are rendered when the SII accepts the document; the XML exists from the moment the document is
signed. A document in queued has no XML yet → 409 artifact_not_ready.
A document issued directly in the SII portal never has an XML here, and never will. Facturia only
captures the signed XML of the documents it moved through the emission_channel=sfg issuing chain
itself; one your account issued in the Portal MIPYME before Facturia, or alongside it — imported into
/v1/documents by the history backfill — was never in Facturia's custody. GET …/xml on one of those
answers 409 artifact_not_available, never artifact_not_ready: retrying does nothing, because nothing
is on its way. The error names the only way to get it: the manual "Respaldo de DTE y/o IECV" download in
the Portal MIPYME itself, which requires solving a reCAPTCHA (on the search and on the download) and
caps out at 20 documents per operation. GET …/pdf is unaffected: it proxies the SII's own render and
answers the same for either origin.
The two copies are two different files, generated together when the document is accepted. The
original carries no acknowledgement-of-receipt box and no CEDIBLE legend; the cedible carries both.
expand=pdf_base64 and the §9.7 email always attach the original: the cedible is an instrument for
assigning the credit, not the factura you send a customer so they pay.
Trap
Two parameters that were here and are gone
format (carta | 80mm) was withdrawn. No handler ever read it: ?format=80mm returned the same
letter-size PDF as everything else, so a POS integration wired to it printed a letter page on a thermal
roll with nothing in the response saying so. There is no thermal layout. The same goes for
pdf: { "format": … } at issue time.
copy=both was withdrawn. The SII's Manual de Muestras Impresas allows exactly one page per file
(p.12), so the two copies are two files and one response carries one of them.
Both now answer 400 invalid_field instead of being ignored in silence.
Trap
Documents rendered before 2026-08-15
They have a single stored file, and for a factura — Chile's electronic invoice — that file is the
cedible: the old renderer drew the cedible layout on the only copy it made. For those documents
copy=cedible is served correctly from it, and copy=original returns those same bytes, legend
included, instead of breaking a read that works today. Asking for either copy queues a re-render, so the
second call gets the true original.
The copia cedible belongs to facturas: tipo 33 and tipo 34. Asking for copy=cedible on any other
type returns 422 cedible_not_available.
Trap
The copia cedible does not exist on emission_channel=sfg empresas, for any type
On that channel the SII signs and stamps the document itself — this API hands back the SII's own PDF
(mipeDisplayPDF.cgi) instead of rendering one, and those bytes carry no acknowledgement-of-receipt box
and no CEDIBLE legend to draw over. Asking for copy=cedible there returns 422 cedible_not_available_for_channel, never an original dressed up as a cedible. copy=original (the
default) does not change: it still returns the SII's own printed representation.
When you do not send ?copy=, the default is not always original. It resolves in this order, and
the first one that says anything wins:
- An explicit
?copy=on this read. Always wins. - This document's own default —
pdf.cedibleat issue time:truemakes THIS document serve the copia cedible by default;falsepins it explicitly to the original even if the empresa (next item) says otherwise. - The empresa's
branding.cedible_by_default. It applies to every future document that does not pin its own.
No default can invent a copy the type does not have, or one the channel does not produce — a type
outside 33/34, or any type on an emission_channel=sfg empresa, always resolves to original, in
silence. Only an explicit ?copy=cedible on the wrong type is 422 cedible_not_available, and only
an explicit ?copy=cedible on the SFG channel is 422 cedible_not_available_for_channel.
That is a legal fact before it is a product decision. Ley 19.983 art. 1 requires the copy in every
operation "en que el vendedor o prestador del servicio esté sujeto a la obligación de emitir factura":
the trigger is the factura, with no mention of IVA or afecta. Res. Ex. SII N°14/2005 names it
explicitly for "facturas de ventas y servicios exentos o no afectos", with the legend
FACTURA EXENTA CUADRUPLICADO COBRO EJECUTIVO. And the SII's own Manual de Muestras Impresas §1.4
requires it for the Factura No Afecta o Exenta Electrónica.
Trap
Two honest limits on the copia cedible
We are narrower than the law. Tipos 46 and 52 are cedible too, and we do not render their cedible layout yet. We prefer a product limit said out loud to a claim about a file we do not produce.
Cedibility also depends on the payment terms. A factura paid al contado — cash, on the spot — is
not cedible. This parameter looks only at the document type, so it will hand you a copia cedible for a
cash factura whose copy has no legal use. Getting it right needs an FmaPago mapping we have not
verified, and guessing would be worse than saying this.
Public links
The URLs above need your key. To hand an artifact to someone who has none — the receptor, their accountant, a WhatsApp thread — ask for a public link at issue time. Both artifacts support it, on the same terms:
"pdf": { "public": true },
"xml": { "public": true }which add a public_links object to the response of that issue call, and only there:
"public_links": {
"object": "public_links",
"pdf_public_url": "https://ver.facturia.cl/d/8f2c1a9e4b?k=SkQ2b1F4c0pYb2s5",
"xml_public_url": "https://ver.facturia.cl/d/3d9f2b8a1c.xml?k=WmY3aE1uUjBkNVRi"
}The key does not appear when the issue call asked for no link: absent, not present as null. And it
lives in the response to the issue call, not in the document object — no read, listing, expansion or
webhook carries a public URL.
Note the shape: each artifact has its own slug and its own secret, so rotating the PDF link does not
touch the XML one. A URL without its ?k= is not a short version of the link: it is a different link
that does not exist, and answers 404.
Trap
These URLs are shown exactly once
The secret half is stored hashed, the same as an API key, so we cannot reconstruct a working URL afterwards — the same property that keeps a dump of the database from being a dump of documents.
That is why the document object has no public-link field at all. Reading the document again tells
you nothing about its links: we no longer hold the half that makes them work. The pdf_public_url and
xml_public_url fields lived on the document object until ticket #18 and always returned a secretless
URL that answered 404; we withdrew them instead of storing the secret recoverably, because a URL that
opens an artifact without a key should not end up in every log, cache and proxy a read touches.
Capture the URL when you receive it, or mint a new one with POST …/public_links.
The XML link is not a convenience twin of the PDF one: it is the artifact that matters to whoever has to do something with the document. The PDF is for a person to read; the XML is what the receptor's accounting system ingests and what their accountant will ask for when the PDF does not add up.
# Sin header Authorization — ese es el punto
curl -L https://ver.facturia.cl/d/8f2c1a9e4b.xml -o dte.xml
# Encender un enlace después de emitir, o rotarlo (invalida la URL anterior)
curl -X POST https://api.facturia.cl/v1/documents/doc_01K2R7.../public_links \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
-H "Content-Type: application/json" \
-d '{"artifacts":["pdf","xml"],"expires_in_days":30}'
# Revocar todo lo compartido de este documento
curl -X DELETE https://api.facturia.cl/v1/documents/doc_01K2R7.../public_links \
-H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"Trap
A third artifact: cedible
{"artifacts":["cedible"]} mints a link to the real cedible file —
https://ver.facturia.cl/d/{slug}.cedible?k=…, cedible_public_url in the response — not the pdf
link under another name. It is a real, separate artifact, so it inherits the same rules: it exists only
for facturas (tipo 33/34; 422 cedible_not_available on any other type), and it is not in the default
set — omitting artifacts still mints exactly pdf and xml. Ask for it explicitly, every time.
pdf: { "public": true } at issue time does not mint it either: that shortcut is still only for the
original copy. It does not exist on emission_channel=sfg empresas either, for any type, for the same
reason ?copy=cedible does not exist there (above): asking for artifacts: ["cedible"] returns
422 cedible_not_available_for_channel instead of a .cedible link that could only resolve to the
non-cedible file.
Expiry
Links are signed, unguessable, expiring (90 days by default), scoped to one artifact of one document,
and revocable at any time. POST …/public_links and the response to an issue call are the only two
places a working URL appears.
| Unit | Where | Range |
|---|---|---|
link_expires_in_days | in the pdf / xml object of the issue call | 1–365, or null for no expiry |
expires_in_days | in POST /v1/documents/{document_id}/public_links | 1–365 |
link_expires_in_seconds | in the pdf / xml object of the issue call | 30 s – 365 days |
expires_in_seconds | in POST /v1/documents/{document_id}/public_links | 30 s – 365 days |
Seconds win when you send both units. Days are the right unit for a link you hand to a receptor and the wrong one for a link you hand to a machine. Use seconds for anything that has to be dead within minutes — an upload to an SII form, a one-time download in a chat.
The response of POST …/public_links returns both units, so a ten-minute link is never reported as
"0 days":
{ "expires_in_seconds": 600, "expires_in_days": 0.00694 }The public_links of an issue call returns no expiry: there pdf and xml carry their own
link_expires_in_*, so a single pair of fields would be false every time they differ.
What it exposes and what it does not
A public link exposes exactly one artifact of one document. It never exposes the empresa's other documents, or their metadata, or anything about your account, and it cannot be escalated into an API credential.
xml_public_url serves the signed bytes exactly as they were sent to the SII, with
Content-Type: application/xml; charset=ISO-8859-1 — byte for byte identical to the authenticated
/xml endpoint, because a document whose bytes change with the route is a document whose signature
somebody will eventually fail to verify.