Referencia de la API
La superficie pública completa de la API planificada para la 1.0: nueve funciones, una clase de error, dos constantes y los tipos que comparten. Todo es un export nombrado de 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'Funciones#
| Función | Firma | Lanza (código de DteError) |
|---|---|---|
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 | — |
Las funciones siguen el ciclo de vida del documento: createDte() → stampDte() → signDte() → sendToSii() → getStatus(). calculateTotals() y toXml() funcionan en cualquier etapa.
createDte()#
function createDte(input: CreateDteInput): DteCrea un documento inmutable y validado, y calcula sus totales con las mismas reglas que 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
}Devuelve un Dte: la entrada con los valores por defecto resueltos más totals, todo de solo lectura en profundidad.
Lanza
MISSING_FIELD: falta un campo obligatorio o está vacío, incluidas lasreferencesen los tipos 56 y 61.INVALID_RUT: el RUT del emisor o del receptor está mal formado o su dígito verificador Módulo 11 es incorrecto.INVALID_AMOUNT: una cantidad, precio o descuento no es un monto válido (negativo, no finito, o un descuento fuera de 0–100).
calculateTotals()#
function calculateTotals(dte: Dte): Totals
function calculateTotals(
items: LineItem[],
options?: { type?: DteType; vatRate?: number; grossPrices?: boolean },
): TotalsCalcula neto, exento, IVA y total de un documento o de una lista de líneas. Todos los montos son CLP enteros, con redondeo half-up.
- Cada línea vale
round(quantity × unitPrice × (1 − discountPercent / 100)). - Las líneas con
exempt: truesuman aexempt; en los tipos 34 y 41 todas las líneas son exentas yvates0. - El IVA se calcula una sola vez, sobre el neto total:
vat = round(net × vatRate / 100). - Las boletas (tipo 39) usan
grossPrices: truepor defecto: los precios incluyen IVA, así quenet = round(total / 1.19)yvat = total − net. vatRatetoma por defectoVAT_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 }Lanza INVALID_AMOUNT cuando una línea tiene una cantidad, precio o descuento inválido.
parseCaf()#
function parseCaf(xml: string): CafLee el XML del CAF (Código de Autorización de Folios) que emite el SII. El SII entrega estos archivos en ISO-8859-1, así que léelos con esa codificación.
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'La llave privada del CAF queda dentro del objeto para que la use stampDte(); no se expone como un campo público.
Lanza MISSING_FIELD cuando al XML le falta un elemento que todo CAF debe tener.
stampDte()#
function stampDte(
dte: Dte,
caf: Caf | string, // a parsed Caf or the raw CAF XML
options?: { stampedAt?: Date },
): StampedDteGenera el TED (Timbre Electrónico DTE) del documento y lo firma con la llave privada del CAF.
Devuelve un StampedDte: el documento más ted (el XML del <TED>) y pdf417 (el contenido del código de barras para la versión impresa). dte-sdk no genera PDF.
Lanza
FOLIO_OUT_OF_RANGE: el folio no está dentro decaf.range.CAF_MISMATCH: el CAF es de otro tipo de documento o de otro RUT emisor.CAF_EXPIRED: el CAF ya estaba vencido enstampedAt(por defecto, ahora).
loadCertificate()#
function loadCertificate(pfx: Uint8Array, password: string): Promise<Certificate>Carga un certificado digital .pfx o .p12. Un Buffer de Node es un Uint8Array, así que el resultado de readFile() sirve tal cual. La contraseña nunca se registra en logs, no se guarda en el resultado ni aparece en los errores.
Lanza CERTIFICATE_INVALID cuando el archivo no es un .pfx/.p12 legible o la contraseña no lo abre.
signDte()#
function signDte(stamped: StampedDte, certificate: Certificate): Promise<SignedDte>Firma el elemento <Documento> con XMLDSig. La firma corre sobre la Web Crypto API, así que funciona en Node, Deno, Bun y el navegador.
Devuelve un SignedDte: el documento timbrado más xml (el documento firmado) y signedAt (una marca de tiempo ISO 8601).
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 }>Envuelve los documentos en un EnvioDTE firmado, se autentica ante el SII (semilla y luego token, guardado en caché por certificado), sube el sobre y devuelve el track id. environment no tiene valor por defecto. En certificación, resolution.number es 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'Lanza
SII_UNAVAILABLE: no se pudo contactar al SII o no respondió a tiempo. Es seguro reintentar.SII_REJECTED: el SII rechazó el envío en sí, por ejemplo por errores de autenticación o del sobre. Es distinto de un documento cuyo estado termina enrejected.
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
}[]
}Consulta al SII el resultado de un envío. state resume el envío; documents trae una entrada por documento, con un reason cuando un documento tiene reparos o fue rechazado. Un track id recién creado suele responder received mientras el SII sigue procesando.
Lanza SII_UNAVAILABLE cuando no se puede contactar al SII.
toXml()#
function toXml(
doc: Dte | StampedDte | SignedDte,
options?: { pretty?: boolean; encoding?: 'ISO-8859-1' | 'UTF-8' },
): stringSerializa un documento en cualquier etapa a XML que sigue el esquema del SII. encoding es 'ISO-8859-1' por defecto, que es lo que espera el SII; 'UTF-8' es útil para previsualizar y almacenar.
toXml(invoice, { pretty: true }) // readable, unsigned XML for previews
toXml(signed) // the exact signed XML, ISO-8859-1No uses pretty en nada que vayas a enviar: en un documento firmado, los espacios en blanco son parte del contenido firmado.
Tipos#
DteType#
type DteType = 33 | 34 | 39 | 41 | 46 | 52 | 56 | 61Revisa Conceptos clave para saber qué significa cada código.
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 y SignedDte#
Cada etapa extiende la anterior, así que un SignedDte conserva todos los campos del Dte del que salió. Los tres son de solo lectura en profundidad.
// 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#
Un identificador opaco que crea loadCertificate() y que aceptan signDte(), sendToSii() y getStatus(). No tiene campos públicos: la llave privada y la contraseña nunca salen de él.
Formas que no se exportan#
CreateDteInput, Reference, Status y la unión de códigos de error se nombran en esta página para facilitar la lectura, pero no se exportan. Derívalas de la API cuando las necesites:
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']Constantes#
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'| Constante | Tipo | Valor |
|---|---|---|
VAT_RATE | number | 19, la tasa de IVA en porcentaje |
DTE_TYPES | Record<DteType, string> | El nombre oficial de cada tipo soportado |
Errores#
Todas las funciones fallan con una sola clase de error:
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'
}Los mensajes son genéricos a propósito: nunca incluyen RUT, montos, llaves privadas del CAF ni contraseñas del certificado. Decide según code, no según el texto del mensaje.
| Código | Lo lanza | Significado |
|---|---|---|
MISSING_FIELD | createDte(), parseCaf() | Falta un campo o elemento obligatorio, o está vacío |
INVALID_RUT | createDte() | Un RUT está mal formado o tiene el dígito verificador incorrecto |
INVALID_AMOUNT | createDte(), calculateTotals() | Una cantidad, precio o descuento no es un monto válido |
FOLIO_OUT_OF_RANGE | stampDte() | El folio está fuera del rango del CAF |
CAF_MISMATCH | stampDte() | El CAF es de otro tipo de documento o de otro RUT emisor |
CAF_EXPIRED | stampDte() | El CAF estaba vencido en la fecha de timbraje |
CERTIFICATE_INVALID | loadCertificate() | El archivo no es un .pfx/.p12 legible, o la contraseña es incorrecta |
SII_UNAVAILABLE | sendToSii(), getStatus() | No se pudo contactar al SII o se agotó el tiempo; es seguro reintentar |
SII_REJECTED | sendToSii() | El SII rechazó el envío |
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
}