Skip to content

API Reference

The complete public surface of the planned 1.0 API: nine functions, one error class, two constants and the types they share. Everything is a named export from dte-sdk.

TypeScript
import {
  createDte, calculateTotals, stampDte, signDte, loadCertificate,
  sendToSii, getStatus, toXml, parseCaf,
  DteError, DTE_TYPES, VAT_RATE,
  type Dte, type DteType, type LineItem, type Totals, type Caf,
  type StampedDte, type SignedDte, type Certificate,
} from 'dte-sdk'

Functions#

FunctionSignatureThrows (DteError code)
createDte()createDte(input): DteMISSING_FIELD, INVALID_RUT, INVALID_AMOUNT
calculateTotals()calculateTotals(dteOrItems, options?): TotalsINVALID_AMOUNT
parseCaf()parseCaf(xml): CafMISSING_FIELD
stampDte()stampDte(dte, caf, options?): StampedDteFOLIO_OUT_OF_RANGE, CAF_MISMATCH, CAF_EXPIRED
loadCertificate()loadCertificate(pfx, password): Promise<Certificate>CERTIFICATE_INVALID
signDte()signDte(stamped, certificate): Promise<SignedDte>—
sendToSii()sendToSii(docs, options): Promise<{ trackId, receivedAt }>SII_UNAVAILABLE, SII_REJECTED
getStatus()getStatus(trackId, options): Promise<Status>SII_UNAVAILABLE
toXml()toXml(doc, options?): string—

The functions follow the document lifecycle: createDte() → stampDte() → signDte() → sendToSii() → getStatus(). calculateTotals() and toXml() work at any stage.

createDte()#

TypeScript
function createDte(input: CreateDteInput): Dte

Builds an immutable, validated document and computes its totals with the same rules as calculateTotals().

TypeScript
interface CreateDteInput {
  type: DteType
  folio: number
  issuedAt?: string              // 'YYYY-MM-DD', defaults to today in America/Santiago
  issuer: {
    rut: string
    businessName: string
    activity: string
    acteco: number | number[]    // one or more economic activity codes
    address: string
    commune: string
    city?: string
  }
  receiver: {
    rut: string
    businessName: string
    activity?: string
    address?: string
    commune?: string
    city?: string
    email?: string
  }
  items: LineItem[]              // 1 to 60 lines
  references?: Reference[]       // required for types 56 and 61
  paymentMethod?: 'cash' | 'credit' | 'free'
  dueDate?: string               // 'YYYY-MM-DD'
}
 
interface Reference {
  type: DteType | 'SET'          // 'SET' for the SII's certification test set
  folio: number | string
  date: string                   // 'YYYY-MM-DD'
  code?: 1 | 2 | 3               // 1 = voids document, 2 = fixes text, 3 = fixes amounts
  reason?: string
}

Returns a Dte: the input with defaults resolved plus totals, deeply readonly.

Throws

  • MISSING_FIELD: a required field is absent or empty, including references on types 56 and 61.
  • INVALID_RUT: the issuer or receiver RUT is malformed or its Modulo 11 check digit is wrong.
  • INVALID_AMOUNT: a quantity, price or discount isn't a valid amount (negative, not finite, or a discount outside 0–100).

calculateTotals()#

TypeScript
function calculateTotals(dte: Dte): Totals
function calculateTotals(
  items: LineItem[],
  options?: { type?: DteType; vatRate?: number; grossPrices?: boolean },
): Totals

Computes net, exempt, IVA and total for a document or a bare list of lines. All amounts are integer CLP, rounded half-up.

  • Each line amounts to round(quantity × unitPrice × (1 − discountPercent / 100)).
  • Lines with exempt: true add to exempt; in types 34 and 41 every line is exempt and vat is 0.
  • IVA is computed once, on the net total: vat = round(net × vatRate / 100).
  • Receipts (type 39) default to grossPrices: true: prices include IVA, so net = round(total / 1.19) and vat = total − net.
  • vatRate defaults to VAT_RATE (19).
TypeScript
calculateTotals([{ name: 'Licencia anual', quantity: 1, unitPrice: 120000 }])
// { net: 120000, exempt: 0, vatRate: 19, vat: 22800, total: 142800 }
 
calculateTotals([{ name: 'Soporte mensual', quantity: 1, unitPrice: 11900 }], { type: 39 })
// { net: 10000, exempt: 0, vatRate: 19, vat: 1900, total: 11900 }

Throws INVALID_AMOUNT when a line has an invalid quantity, price or discount.

parseCaf()#

TypeScript
function parseCaf(xml: string): Caf

Parses the CAF (Código de Autorización de Folios) XML the SII issues. The SII delivers these files in ISO-8859-1, so read them with that encoding.

TypeScript
const caf = parseCaf(await readFile('./caf/33.xml', 'latin1'))
 
caf.type         // 33
caf.issuerRut    // '76.123.456-0'
caf.range        // { from: 1001, to: 2000 }
caf.authorizedAt // 'YYYY-MM-DD'
caf.expiresAt    // 'YYYY-MM-DD'

The CAF's private key stays inside the object for stampDte() to use; it isn't exposed as a public field.

Throws MISSING_FIELD when the XML lacks an element a CAF requires.

stampDte()#

TypeScript
function stampDte(
  dte: Dte,
  caf: Caf | string,             // a parsed Caf or the raw CAF XML
  options?: { stampedAt?: Date },
): StampedDte

Generates the TED (Timbre Electrónico DTE) for the document and signs it with the CAF's private key.

Returns a StampedDte: the document plus ted (the <TED> XML) and pdf417 (the payload for the barcode on the printed version). dte-sdk doesn't render PDFs.

Throws

  • FOLIO_OUT_OF_RANGE: the folio isn't inside caf.range.
  • CAF_MISMATCH: the CAF is for a different document type or issuer RUT.
  • CAF_EXPIRED: the CAF had expired at stampedAt (default: now).

loadCertificate()#

TypeScript
function loadCertificate(pfx: Uint8Array, password: string): Promise<Certificate>

Loads a .pfx or .p12 digital certificate. A Node Buffer is a Uint8Array, so the result of readFile() works as is. The password is never logged, stored on the result or echoed in errors.

Throws CERTIFICATE_INVALID when the file isn't a readable .pfx/.p12 or the password doesn't open it.

signDte()#

TypeScript
function signDte(stamped: StampedDte, certificate: Certificate): Promise<SignedDte>

Signs the <Documento> element with XMLDSig. Signing runs on the Web Crypto API, so it works in Node, Deno, Bun and the browser.

Returns a SignedDte: the stamped document plus xml (the signed document) and signedAt (an ISO 8601 timestamp).

sendToSii()#

TypeScript
function sendToSii(
  docs: SignedDte | SignedDte[],
  options: {
    certificate: Certificate
    environment: 'certification' | 'production'
    senderRut: string                        // RUT of the certificate owner
    resolution: { number: number; date: string }
    signal?: AbortSignal
  },
): Promise<{ trackId: string; receivedAt: string }>

Wraps the documents in a signed EnvioDTE, authenticates with the SII (seed, then token, cached per certificate), uploads the envelope and returns the track id. environment has no default. In certification, resolution.number is 0.

TypeScript
const { trackId, receivedAt } = await sendToSii(signed, {
  certificate,
  environment: 'certification',
  senderRut: '15.234.567-4',
  resolution: { number: 0, date: '2026-09-01' },
  signal: AbortSignal.timeout(30_000),
})
// trackId: '0234567890'

Throws

  • SII_UNAVAILABLE: the SII couldn't be reached or didn't answer in time. Safe to retry.
  • SII_REJECTED: the SII refused the upload itself, for example on authentication or envelope errors. This differs from a document whose status ends up rejected.

getStatus()#

TypeScript
function getStatus(
  trackId: string,
  options: {
    certificate: Certificate
    environment: 'certification' | 'production'
    issuerRut: string
  },
): Promise<Status>
 
type Status = {
  state: 'received' | 'accepted' | 'accepted_with_objections' | 'rejected'
  documents: {
    type: DteType
    folio: number
    state: 'received' | 'accepted' | 'accepted_with_objections' | 'rejected'
    reason?: string
  }[]
}

Asks the SII for the result of an upload. state summarizes the upload; documents has one entry per document, with a reason when a document has objections or was rejected. A fresh track id usually reports received while the SII is still processing.

Throws SII_UNAVAILABLE when the SII can't be reached.

toXml()#

TypeScript
function toXml(
  doc: Dte | StampedDte | SignedDte,
  options?: { pretty?: boolean; encoding?: 'ISO-8859-1' | 'UTF-8' },
): string

Serializes a document at any stage to XML that follows the SII schema. encoding defaults to 'ISO-8859-1', which is what the SII expects; 'UTF-8' is handy for previews and storage.

TypeScript
toXml(invoice, { pretty: true }) // readable, unsigned XML for previews
toXml(signed)                    // the exact signed XML, ISO-8859-1

Leave pretty off for anything you send: in a signed document, whitespace is part of the signed content.

Types#

DteType#

TypeScript
type DteType = 33 | 34 | 39 | 41 | 46 | 52 | 56 | 61

See Core Concepts for what each code means.

LineItem#

TypeScript
interface LineItem {
  name: string
  description?: string
  quantity: number
  unitPrice: number
  unit?: string                  // unit of measure, e.g. 'UN' or 'KG'
  discountPercent?: number       // 0 to 100
  exempt?: boolean               // keep this line out of the IVA base
}

Totals#

TypeScript
interface Totals {
  net: number      // taxable amount
  exempt: number   // exempt amount
  vatRate: number  // 19
  vat: number      // IVA
  total: number    // net + exempt + vat
}

Dte, StampedDte and SignedDte#

Each stage extends the previous one, so a SignedDte still has every field of the Dte it came from. All three are deeply readonly.

TypeScript
// Simplified: every property is readonly, all the way down.
interface Dte extends CreateDteInput {
  issuedAt: string          // always resolved
  totals: Totals
}
 
interface StampedDte extends Dte {
  ted: string               // the <TED> XML
  pdf417: string            // barcode payload for the printed version
}
 
interface SignedDte extends StampedDte {
  xml: string               // the signed document
  signedAt: string          // ISO 8601 timestamp
}

Caf#

TypeScript
interface Caf {
  readonly type: DteType
  readonly issuerRut: string
  readonly range: { readonly from: number; readonly to: number }
  readonly authorizedAt: string  // 'YYYY-MM-DD'
  readonly expiresAt: string     // 'YYYY-MM-DD'
}

Certificate#

An opaque handle created by loadCertificate() and accepted by signDte(), sendToSii() and getStatus(). It has no public fields: the private key and the password never leave it.

Shapes that aren't exported#

CreateDteInput, Reference, Status and the error code union are named on this page for readability but aren't exported. Derive them from the API when you need them:

TypeScript
import type { createDte, getStatus, DteError } from 'dte-sdk'
 
type CreateDteInput = Parameters<typeof createDte>[0]
type Reference = NonNullable<CreateDteInput['references']>[number]
type Status = Awaited<ReturnType<typeof getStatus>>
type DteErrorCode = DteError['code']

Constants#

TypeScript
import { DTE_TYPES, VAT_RATE } from 'dte-sdk'
 
VAT_RATE      // 19
DTE_TYPES[33] // 'Factura electrónica'
DTE_TYPES[61] // 'Nota de crédito electrónica'
ConstantTypeValue
VAT_RATEnumber19, the IVA rate in percent
DTE_TYPESRecord<DteType, string>The official Spanish name of each supported type

Errors#

Every function fails with a single error class:

TypeScript
class DteError extends Error {
  readonly code:
    | 'MISSING_FIELD' | 'INVALID_RUT' | 'INVALID_AMOUNT'
    | 'FOLIO_OUT_OF_RANGE' | 'CAF_MISMATCH' | 'CAF_EXPIRED'
    | 'CERTIFICATE_INVALID' | 'SII_UNAVAILABLE' | 'SII_REJECTED'
}

Messages are deliberately generic: they never include RUTs, amounts, CAF private keys or certificate passwords. Branch on code, not on the message text.

CodeThrown byMeaning
MISSING_FIELDcreateDte(), parseCaf()A required field or element is absent or empty
INVALID_RUTcreateDte()A RUT is malformed or has the wrong check digit
INVALID_AMOUNTcreateDte(), calculateTotals()A quantity, price or discount isn't a valid amount
FOLIO_OUT_OF_RANGEstampDte()The folio is outside the CAF's range
CAF_MISMATCHstampDte()The CAF is for another document type or issuer RUT
CAF_EXPIREDstampDte()The CAF had expired at the stamp date
CERTIFICATE_INVALIDloadCertificate()The file isn't a readable .pfx/.p12, or the password is wrong
SII_UNAVAILABLEsendToSii(), getStatus()The SII couldn't be reached or timed out; safe to retry
SII_REJECTEDsendToSii()The SII refused the upload
TypeScript
import { stampDte, DteError } from 'dte-sdk'
 
try {
  stampDte(invoice, caf)
} catch (error) {
  if (error instanceof DteError && error.code === 'CAF_EXPIRED') {
    // Request a new CAF for this document type, then stamp again.
  }
  throw error
}