Quick Start
This guide issues a type 33 electronic invoice end to end: build the document, stamp it with a CAF, sign it with your certificate, send it to the SII's certification environment and check the result.
dte-sdk@0.0.2 is a placeholder release that only exports hello().
This guide shows how the planned 1.0 API will work; details may change
before it ships.
You'll need a CAF for type 33 and a digital certificate. New to those terms? Read Core Concepts first. Not installed yet? See Installation.
Try it first#
No CAF or certificate is needed to see how a document comes together. Edit the lines below and watch the totals and the XML update:
Try it — build a document
toXml() preview — unsigned, no TED
<DTE version="1.0">
<Documento ID="F1024T33">
<Encabezado>
<IdDoc>
<TipoDTE>33</TipoDTE>
<Folio>1024</Folio>
<FchEmis>2026-10-30</FchEmis>
</IdDoc>
<Emisor>
<RUTEmisor>76123456-0</RUTEmisor>
<RznSoc>Arrow Software SpA</RznSoc>
</Emisor>
<Receptor>
<RUTRecep>77654321-7</RUTRecep>
</Receptor>
<Totales>
<MntNeto>120000</MntNeto>
<TasaIVA>19</TasaIVA>
<IVA>22800</IVA>
<MntTotal>142800</MntTotal>
</Totales>
</Encabezado>
<Detalle>
<NroLinDet>1</NroLinDet>
<NmbItem>Licencia anual</NmbItem>
<QtyItem>1</QtyItem>
<PrcItem>120000</PrcItem>
<MontoItem>120000</MontoItem>
</Detalle>
</Documento>
</DTE>The five steps#
Build the document
createDte() validates the input (RUT check digits, amounts, required
fields) and computes the totals. It throws a DteError instead of
returning a document the SII would reject.
import { createDte } from 'dte-sdk'
const invoice = createDte({
type: 33,
folio: 1024,
issuer: {
rut: '76.123.456-0',
businessName: 'Arrow Software SpA',
activity: 'Desarrollo de software',
acteco: 620200,
address: 'Av. Providencia 1234',
commune: 'Providencia',
},
receiver: {
rut: '77.654.321-7',
businessName: 'Comercial Los Andes Ltda.',
activity: 'Comercio al por menor',
address: 'Huérfanos 1055',
commune: 'Santiago',
},
items: [{ name: 'Licencia anual', quantity: 1, unitPrice: 120000 }],
})
invoice.totals // { net: 120000, exempt: 0, vatRate: 19, vat: 22800, total: 142800 }Stamp it with the CAF
stampDte() generates the TED, the electronic stamp, with the private
key in your CAF. Folio 1024 sits inside the CAF's 1001–2000 range, so
stamping succeeds; a folio outside it throws FOLIO_OUT_OF_RANGE.
import { readFile } from 'node:fs/promises'
import { parseCaf, stampDte } from 'dte-sdk'
// The SII delivers CAF files encoded as ISO-8859-1.
const caf = parseCaf(await readFile('./caf/33.xml', 'latin1'))
caf.range // { from: 1001, to: 2000 }
const stamped = stampDte(invoice, caf)
stamped.ted // '<TED version="1.0">…</TED>'
stamped.pdf417 // payload for the barcode on the printed versiondte-sdk doesn't render PDFs: pass stamped.pdf417 to the barcode
library you already use for the printed version.
Sign it with your certificate
loadCertificate() reads the .pfx file and its password once;
signDte() adds the XMLDSig signature.
import { readFile } from 'node:fs/promises'
import { loadCertificate, signDte } from 'dte-sdk'
const certificate = await loadCertificate(
await readFile('./certs/signer.pfx'),
process.env.DTE_CERT_PASSWORD!,
)
const signed = await signDte(stamped, certificate)
signed.xml // the signed document, ready to send
signed.signedAt // ISO 8601 timestampSend it to the SII
sendToSii() wraps the document in a signed EnvioDTE, authenticates
with the certificate and uploads it. senderRut is the RUT of the
certificate's owner, not the company's. In the certification
environment the resolution number is 0.
import { sendToSii } from 'dte-sdk'
const { trackId } = await sendToSii(signed, {
certificate,
environment: 'certification',
senderRut: '15.234.567-4',
resolution: { number: 0, date: '2026-09-01' },
})
trackId // '0234567890'Check the result
The SII validates uploads asynchronously, so a fresh track id usually
reports received first. Ask again after a short wait until it settles
on accepted, accepted_with_objections or rejected.
import { getStatus } from 'dte-sdk'
const status = await getStatus(trackId, {
certificate,
environment: 'certification',
issuerRut: '76.123.456-0',
})
status.state // 'accepted'
status.documents // [{ type: 33, folio: 1024, state: 'accepted' }]Putting it together#
The same flow as one script, with polling and error handling:
import { readFile } from 'node:fs/promises'
import {
createDte, parseCaf, stampDte, loadCertificate,
signDte, sendToSii, getStatus, DteError,
} from 'dte-sdk'
const environment = 'certification'
const issuerRut = '76.123.456-0'
async function issueInvoice() {
const caf = parseCaf(await readFile('./caf/33.xml', 'latin1'))
const certificate = await loadCertificate(
await readFile('./certs/signer.pfx'),
process.env.DTE_CERT_PASSWORD!,
)
const invoice = createDte({
type: 33,
folio: 1024,
issuer: {
rut: issuerRut,
businessName: 'Arrow Software SpA',
activity: 'Desarrollo de software',
acteco: 620200,
address: 'Av. Providencia 1234',
commune: 'Providencia',
},
receiver: {
rut: '77.654.321-7',
businessName: 'Comercial Los Andes Ltda.',
activity: 'Comercio al por menor',
address: 'Huérfanos 1055',
commune: 'Santiago',
},
items: [{ name: 'Licencia anual', quantity: 1, unitPrice: 120000 }],
})
const signed = await signDte(stampDte(invoice, caf), certificate)
const { trackId } = await sendToSii(signed, {
certificate,
environment,
senderRut: '15.234.567-4',
resolution: { number: 0, date: '2026-09-01' },
})
// The SII answers asynchronously: poll with a growing delay.
for (let attempt = 1; attempt <= 10; attempt++) {
const status = await getStatus(trackId, { certificate, environment, issuerRut })
if (status.state !== 'received') return status
await new Promise((resolve) => setTimeout(resolve, attempt * 5000))
}
throw new Error(`Still processing, check track id ${trackId} later`)
}
try {
const status = await issueInvoice()
console.log(status.state) // 'accepted'
} catch (error) {
if (error instanceof DteError) {
console.error(error.code) // e.g. 'CAF_EXPIRED', never a RUT or an amount
}
throw error
}Rule of thumb: keep the CAF and the certificate on the server, run
every new document type through certification before you switch
environment to 'production', and store the track id next to the folio
so you can check the status again later.