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.
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#
| Function | Signature | Throws (DteError code) |
|---|---|---|
createDte() | createDte(input): Dte | MISSING_FIELD, INVALID_RUT, INVALID_AMOUNT |
calculateTotals() | calculateTotals(dteOrItems, options?): Totals | INVALID_AMOUNT |
parseCaf() | parseCaf(xml): Caf | MISSING_FIELD |
stampDte() | stampDte(dte, caf, options?): StampedDte | FOLIO_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()#
function createDte(input: CreateDteInput): DteBuilds an immutable, validated document and computes its totals with the same rules as calculateTotals().
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, includingreferenceson 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()#
function calculateTotals(dte: Dte): Totals
function calculateTotals(
items: LineItem[],
options?: { type?: DteType; vatRate?: number; grossPrices?: boolean },
): TotalsComputes 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: trueadd toexempt; in types 34 and 41 every line is exempt andvatis0. - IVA is computed once, on the net total:
vat = round(net × vatRate / 100). - Receipts (type 39) default to
grossPrices: true: prices include IVA, sonet = round(total / 1.19)andvat = total − net. vatRatedefaults toVAT_RATE(19).
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()#
function parseCaf(xml: string): CafParses 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.
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()#
function stampDte(
dte: Dte,
caf: Caf | string, // a parsed Caf or the raw CAF XML
options?: { stampedAt?: Date },
): StampedDteGenerates 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 insidecaf.range.CAF_MISMATCH: the CAF is for a different document type or issuer RUT.CAF_EXPIRED: the CAF had expired atstampedAt(default: now).
loadCertificate()#
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()#
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()#
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.
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 uprejected.
getStatus()#
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()#
function toXml(
doc: Dte | StampedDte | SignedDte,
options?: { pretty?: boolean; encoding?: 'ISO-8859-1' | 'UTF-8' },
): stringSerializes 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.
toXml(invoice, { pretty: true }) // readable, unsigned XML for previews
toXml(signed) // the exact signed XML, ISO-8859-1Leave pretty off for anything you send: in a signed document, whitespace is part of the signed content.
Types#
DteType#
type DteType = 33 | 34 | 39 | 41 | 46 | 52 | 56 | 61See Core Concepts for what each code means.
LineItem#
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#
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.
// 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#
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:
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#
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'| Constant | Type | Value |
|---|---|---|
VAT_RATE | number | 19, the IVA rate in percent |
DTE_TYPES | Record<DteType, string> | The official Spanish name of each supported type |
Errors#
Every function fails with a single error class:
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.
| Code | Thrown by | Meaning |
|---|---|---|
MISSING_FIELD | createDte(), parseCaf() | A required field or element is absent or empty |
INVALID_RUT | createDte() | A RUT is malformed or has the wrong check digit |
INVALID_AMOUNT | createDte(), calculateTotals() | A quantity, price or discount isn't a valid amount |
FOLIO_OUT_OF_RANGE | stampDte() | The folio is outside the CAF's range |
CAF_MISMATCH | stampDte() | The CAF is for another document type or issuer RUT |
CAF_EXPIRED | stampDte() | The CAF had expired at the stamp date |
CERTIFICATE_INVALID | loadCertificate() | The file isn't a readable .pfx/.p12, or the password is wrong |
SII_UNAVAILABLE | sendToSii(), getStatus() | The SII couldn't be reached or timed out; safe to retry |
SII_REJECTED | sendToSii() | The SII refused the upload |
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
}