Facturiadocs
Issuing

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.xml

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

  1. An explicit ?copy= on this read. Always wins.
  2. This document's own default — pdf.cedible at issue time: true makes THIS document serve the copia cedible by default; false pins it explicitly to the original even if the empresa (next item) says otherwise.
  3. 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.

UnitWhereRange
link_expires_in_daysin the pdf / xml object of the issue call1–365, or null for no expiry
expires_in_daysin POST /v1/documents/{document_id}/public_links1–365
link_expires_in_secondsin the pdf / xml object of the issue call30 s – 365 days
expires_in_secondsin POST /v1/documents/{document_id}/public_links30 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.

On this page