What is a DTE? Chile's electronic tax documents, explained
A developer's guide to Chile's DTE system: the eight document types, folios and the CAF, the TED stamp, signatures, the SII lifecycle, and the pitfalls that break integrations.
If you sell anything in Chile, you issue DTEs. Every invoice, receipt and credit note travels to the tax authority as a signed XML file. For developers it's one of those domains that looks like "generate some XML and POST it" and turns out to be a chain of accounting and cryptographic rules, where a one-peso rounding difference or an expired file stops everything. This post explains the system from a developer's point of view: what a DTE is, which types you'll meet, the lifecycle every document goes through, and the pitfalls that catch most integrations.
What is a DTE?#
A DTE (Documento Tributario Electrónico) is a tax document issued electronically: an XML file, signed by the issuer, that the SII (Servicio de Impuestos Internos, Chile's tax authority) receives and validates. Electronic invoicing is mandatory for businesses in Chile, so for most companies DTEs aren't an option. They're simply how sales get documented.
The most important idea is that the XML is the document. The PDF you email to a customer, or the receipt printed at a till, is only a representation of it. That representation carries a barcode with a signed summary of the original, so anyone can check that the paper matches what was reported.
Every DTE is identified by three values: the issuer's RUT, the document type and the folio. The SII won't accept two documents that share all three.
The eight document types you'll meet#
Each type has a numeric code, and most integrations only ever touch a handful:
| Code | Document | When you use it |
|---|---|---|
| 33 | Electronic invoice | Taxable sales to other businesses |
| 34 | Exempt electronic invoice | Sales exempt from IVA |
| 39 | Electronic receipt | Sales to final consumers |
| 41 | Exempt electronic receipt | Exempt sales to final consumers |
| 46 | Electronic purchase invoice | The buyer issues it when the seller can't |
| 52 | Electronic dispatch guide | Moving goods, with or without a sale |
| 56 | Electronic debit note | Increasing or correcting a previous document |
| 61 | Electronic credit note | Voiding, reducing or correcting a previous document |
Invoices list net prices and add 19% IVA on top; receipts show prices with IVA already included. Credit and debit notes never stand alone: they must reference the document they modify.
The lifecycle of a DTE#
Every document, whatever its type, goes through the same five stages.
1. Folios and the CAF. You can't number documents freely. The SII authorizes folios in ranges, per document type, by issuing a CAF (Código de Autorización de Folios): an XML file with the range, your RUT, the SII's signature and a private key. That key is what proves each folio you use was authorized.
2. The TED. Each document gets a TED (Timbre Electrónico DTE), a small block that summarizes the key fields (RUTs, type, folio, date, total, first item), embeds the CAF and is signed with the CAF's private key. It's the TED that gets printed as a PDF417 barcode.
3. The signature. The whole document is then signed with XMLDSig using a digital certificate issued to a person authorized to sign for the company. The TED proves the folio; the signature proves the issuer.
4. The upload. Signed documents travel in an envelope called EnvioDTE. Before uploading, the client authenticates with the same certificate by signing a seed the SII provides and exchanging it for a token. The SII answers with a track id.
5. Acceptance. Receiving an upload isn't accepting it. The SII validates the schema, the signatures, the TED and its business rules asynchronously, and each document ends up accepted, accepted with objections (reparos) or rejected. Only then is the document really issued.
Common pitfalls#
Rounding IVA line by line#
Amounts in a DTE are whole pesos, and IVA is calculated on the net total, not per line. Take two lines of CLP 2,450 net each. Rounding IVA per line gives 465.5 → 466 twice, so 932. Calculating it on the 4,900 net total gives 931. A one-peso difference is enough for the SII's validation to flag the document, and it only shows up with certain prices, which is why it survives testing.
import { calculateTotals } from 'dte-sdk'
calculateTotals([
{ name: 'Soporte mensual', quantity: 1, unitPrice: 2450 },
{ name: 'Hosting mensual', quantity: 1, unitPrice: 2450 },
])
// { net: 4900, exempt: 0, vatRate: 19, vat: 931, total: 5831 }Round each line amount to whole pesos, add them up, and compute IVA once on the net total. Never let floating-point amounts reach the XML.
Letting a CAF expire#
CAFs have an expiry date. Teams request a generous range, use part of it, and months later find that the remaining folios can't be used. Size your requests to your actual volume, monitor the expiry date, and request the next CAF before the current one runs out of folios or time.
Sending to the wrong environment#
The SII runs a certification environment for testing and a production environment for real documents, and the two share nothing: a CAF from certification is useless in production. The expensive mistake goes the other way: test documents sent to production are real tax documents. Make the environment an explicit setting in every call, never a default.
Mixing up encodings#
The SII works in ISO-8859-1. An address like "Huérfanos" encoded as UTF-8 in one step and as ISO-8859-1 in another produces garbled text at best and an invalid signature at worst, because the signature covers the exact bytes. Pick one encoding at the boundary and keep it.
Treating "received" as "accepted"#
A track id means the upload arrived, nothing more. Store it next to the folio, check the status until it settles, and only then deliver the document to the customer.
How dte-sdk models it#
dte-sdk maps the lifecycle to one function per stage, and its types follow the same order: you can't sign a document that hasn't been stamped, or send one that hasn't been signed.
import { createDte, stampDte, signDte, sendToSii, getStatus } from 'dte-sdk'
const dte = createDte({ type: 33, folio: 1024, issuer, receiver, items })
const stamped = stampDte(dte, caf) // TED, signed with the CAF key
const signed = await signDte(stamped, certificate) // XMLDSig with your certificate
const { trackId } = await sendToSii(signed, {
certificate,
environment: 'certification',
senderRut: '15.234.567-4',
resolution: { number: 0, date: '2026-09-01' },
})
const status = await getStatus(trackId, {
certificate,
environment: 'certification',
issuerRut: '76.123.456-0',
})createDte() validates RUTs and amounts and computes totals with the rounding rules above, so most mistakes surface as a typed DteError in your code instead of a rejection from the SII. stampDte() checks that the folio is inside the CAF's range, that the CAF matches the document type and issuer, and that it hasn't expired. environment has no default. Error messages never include RUTs, amounts, keys or passwords, so they're safe to log.
dte-sdk is pre-release: the published 0.0.2 is a placeholder, and the
code above shows the planned 1.0 API, which may still change.
Further reading#
- Core Concepts: document types, CAF, TED, certificates and SII states in more depth
- Quick Start: issue a type 33 invoice end to end
calculateTotals(): exactly how net, exempt, IVA and total are computedstampDte(): folios, CAF checks and the TED- Credit note example: references and voiding an invoice
- Security: handling CAF keys, certificates and personal data