Skip to content

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

receiver RUT validtrue
totals.net$120.000
totals.exempt$0
totals.vat (19%)$22.800
totals.total$142.800

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#

1

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.

TypeScript
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 }
2

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.

TypeScript
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 version

dte-sdk doesn't render PDFs: pass stamped.pdf417 to the barcode library you already use for the printed version.

3

Sign it with your certificate

loadCertificate() reads the .pfx file and its password once; signDte() adds the XMLDSig signature.

TypeScript
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 timestamp
4

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

TypeScript
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'
5

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.

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

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

Where to next#