Documents
A document starts as plain data that you describe in readable English (issuer.businessName, items[0].unitPrice) and ends as the XML the SII expects. createDocument is the first step: it validates what you give it, normalizes RUTs and text, numbers the lines, computes the totals and returns a Document. issueDocument runs the whole pipeline (create, stamp, sign) in one call. buildDocumentXml and the codec (toSiiDocument, fromSiiDocument) turn the model into SII tags and back.
Everything on this page lives in the dte-sdk entry point and runs on Node, Bun and Deno.
Documents, stamps, signatures and envelopes are tested against synthetic data and against the official schemas, but they have not been sent to the real SII yet. See Status for what has met the real SII and what has not.
Create a document#
import { createDocument } from 'dte-sdk'
const document = createDocument({
type: 33,
folio: 1,
issuer: {
rut: '76543210-3',
businessName: 'Comercial Andina SpA',
businessActivity: 'Software',
address: 'Av. Los Aromos 1540',
commune: 'Santiago',
activityCode: 620200,
},
receiver: {
rut: '12.345.678-5',
businessName: 'Comercializadora Rapel Ltda',
businessActivity: 'Comercio',
address: 'Pasaje Los Olmos 88',
commune: 'Providencia',
},
items: [
{ name: 'Plan anual', quantity: 1, unitPrice: 100000 },
{ name: 'Horas de consultoría', quantity: 3, unitPrice: 15000, discountPercent: 10 },
],
})
document.id // 'DTE_T33F1'
document.header.receiver.rut // '12345678-5'
document.items[1] // { lineNumber: 2, name: 'Horas de consultoría', quantity: 3, unitPrice: 15000, discountPercent: 10, discountAmount: 4500, amount: 40500 }
document.header.totals // { netAmount: 140500, vatRate: 19, vatAmount: 26695, totalAmount: 167195 }createDocument is pure: the same input gives the same output, it never mutates its argument, and it touches no network, file or secret. The folio is not checked against a CAF here; that happens when you stamp the document (see Stamp).
Issue in one call#
issueDocument takes a DocumentInput (or a Document you already created), a parsed CAF and a certificate, and returns a signed document:
import { readFileSync } from 'node:fs'
import { issueDocument, loadCertificateFromPkcs12, parseCaf } from 'dte-sdk'
const certificate = loadCertificateFromPkcs12(readFileSync('firma.pfx'), process.env.PFX_PASSWORD!)
const caf = parseCaf(readFileSync('caf-33.xml', 'utf8'))
const signed = issueDocument({ document: input, caf, certificate })
signed.xml // signed <DTE> with its TED, no XML declaration
signed.ted // the electronic stamp
signed.signedAt // Chile time of the signature| Option | Meaning |
|---|---|
document | A DocumentInput (it goes through createDocument) or an already created Document. |
caf | The parsed CAF that covers the type, the issuer and the folio. |
certificate | The issuer certificate that signs the document. |
now | Clock for the issue date, TSTED and TmstFirma. Tests use it to get deterministic output. |
checkCafExpiry | Check the CAF expiry (default true). See Stamp. |
timestamp | TSTED and TmstFirma exactly as given, instead of the Chile time of now. |
The result is a SignedDocument: a Document plus ted, xml, signedAt and signature. The steps it runs are the ones documented on Stamp and Sign; call them separately when you need to stop in the middle (to inspect the TED, for example).
Document types#
The SDK supports nine TipoDTE codes. DOCUMENT_TYPES names them in English, DOCUMENT_TYPE_VALUES lists them in ascending order, and isDocumentType(value) is a type guard.
| Code | DOCUMENT_TYPES key | Official name (documentTypeName) | Receipt | Exempt | Note | Needs receiver | Transferable |
|---|---|---|---|---|---|---|---|
| 33 | invoice | Factura Electrónica | no | no | no | yes | yes |
| 34 | exemptInvoice | Factura Exenta Electrónica | no | yes | no | yes | yes |
| 39 | receipt | Boleta Electrónica | yes | no | no | no | no |
| 41 | exemptReceipt | Boleta Exenta Electrónica | yes | yes | no | no | no |
| 43 | settlementInvoice | Liquidación Factura Electrónica | no | no | no | yes | yes |
| 46 | purchaseInvoice | Factura de Compra Electrónica | no | no | no | yes | yes |
| 52 | dispatchGuide | Guía de Despacho Electrónica | no | no | no | yes | yes |
| 56 | debitNote | Nota de Débito Electrónica | no | no | yes | yes | no |
| 61 | creditNote | Nota de Crédito Electrónica | no | no | yes | yes | no |
The columns are the answers of the helpers of the same name:
| Helper | true for | What it means |
|---|---|---|
isReceipt(type) | 39, 41 | Receipts (boletas): no identified receiver required, no TasaIVA, own reference shape, sent by REST. |
isExemptType(type) | 34, 41 | Every line is exempt. |
isNote(type) | 56, 61 | Debit and credit notes, which the SII expects to reference another document. |
requiresReceiver(type) | everything but 39 and 41 | The type needs an identified receiver. |
isTransferable(type) | 33, 34, 43, 46, 52 | The printed document carries a transferable copy (cedible) and the Ley 19.983 acknowledgement box. |
documentTypeName(type, variant) returns the official Spanish name. The variant is 'long' (the default, as in the table), 'short' (uppercase, no accents: NOTA CREDITO) or 'printed' (uppercase, as the SII printed-samples manual writes it: NOTA DE CRÉDITO ELECTRÓNICA).
createDocument does not force a note to carry a reference. The SII expects
every debit or credit note to reference the document it affects, so add
references yourself (see Credit and debit notes).
The DocumentInput model#
DocumentInput is what you give to createDocument. Required fields are in bold in the first table; everything else is optional. Fields named after SII tags in the tables below are the tags the codec writes (TipoDTE, MntTotal…), so you can follow a field from your input to the XML.
| Field | Type | Meaning |
|---|---|---|
type | DocumentType | TipoDTE: one of 33, 34, 39, 41, 43, 46, 52, 56, 61. |
folio | number | Folio: a positive integer authorized by a CAF. |
issueDate | string | FchEmis, YYYY-MM-DD (DD-MM-YYYY and DD/MM/YYYY are accepted). Defaults to today in Chile. |
issuer | IssuerInput | Who issues the document. |
receiver | ReceiverInput | Who receives it. Required on every type except 39 and 41. |
items | LineItemInput[] | The detail lines. At least one. |
references | ReferenceInput[] | References to other documents (every type except receipts). |
receiptReferences | ReceiptReferenceInput[] | References of a receipt (39 and 41 only). |
globalAdjustments | GlobalAdjustmentInput[] | Global discounts and surcharges (DscRcgGlobal). |
subtotals | SubtotalInfo[] | Informative subtotals (SubTotInfo), written as given. |
commissions | CommissionLineInput[] | Commission lines of a settlement invoice (43). |
transport | TransportInput | Transport data, for dispatch guides (52). |
options | HeaderOptions | Optional IdDoc fields: payment terms, due date, dispatch and transfer types… |
pricesIncludeVat | boolean | The unit prices already include VAT (MntBruto=1). |
withholdVat | boolean | Withhold all the VAT (ImptoReten 15). true by default on purchase invoices (46). |
totals | Totals | Explicit totals (advanced). Computed from the lines when omitted. |
Issuer#
The issuer is the company that issues the document (Emisor). The RUT may come in any shape (76.543.210-3, 76543210-3, 765432103); rut.ts validates it and the document keeps it as 76543210-3.
| Field | Required | SII tag | Limit | Notes |
|---|---|---|---|---|
rut | yes | RUTEmisor | Validated with rut.ts. | |
businessName | yes | RznSoc (RznSocEmisor on receipts) | 100 | Legal name. |
businessActivity | yes | GiroEmis (GiroEmisor on receipts) | 80 | Line of business. |
address | yes | DirOrigen | 70 | |
commune | yes | CmnaOrigen | 20 | |
city | no | CiudadOrigen | 20 | Defaults to the commune. |
activityCode | no | Acteco | SII economic activity code (a number). | |
phone | no | Telefono | Not written on receipts. | |
email | no | CorreoEmisor | Not written on receipts. | |
branch | no | Sucursal | 20 | Branch name. |
siiBranchCode | no | CdgSIISucur | Branch code registered at the SII. | |
salespersonCode | no | CdgVendedor | 60 | Not written on receipts. |
Receiver#
| Field | Required | SII tag | Limit | Notes |
|---|---|---|---|---|
rut | yes (if a receiver is given) | RUTRecep | Validated with rut.ts. | |
businessName | yes (if a receiver is given) | RznSocRecep | 100 | |
address | yes, except on receipts | DirRecep | 70 | |
commune | yes, except on receipts | CmnaRecep | 20 | |
businessActivity | no | GiroRecep | 40 | Not written on receipts. |
internalCode | no | CdgIntRecep | 20 | |
contact | no | Contacto | 80 | |
email | no | CorreoRecep | Not written on receipts. | |
city | no | CiudadRecep | 20 | |
postalAddress | no | DirPostal | 70 | Not written on receipts. |
postalCommune | no | CmnaPostal | 20 | Not written on receipts. |
postalCity | no | CiudadPostal | 20 | Not written on receipts. |
On a receipt (39, 41) you can leave receiver out. The document then carries the generic final consumer: RUT 66666666-6, name Consumidor Final, address Sin Direccion, commune Santiago. A receiver you do give on a receipt is kept as it is, with only what you wrote (its name stays required).
Items#
Each entry of items is a LineItemInput. The SDK numbers the lines from 1 (NroLinDet) and computes amount (MontoItem) unless you give it.
| Field | SII tag | Limit | Meaning |
|---|---|---|---|
name (required) | NmbItem | 80 | Item name. |
description | DscItem | 1000 | Description. |
quantity | QtyItem | Quantity. Counts as 1 in the amount when omitted, but it is not written. | |
unit | UnmdItem | 4 | Unit of measure. |
unitPrice | PrcItem | Unit price, net or VAT-included according to pricesIncludeVat. Counts as 0 when omitted. | |
unitPriceDecimals | PrcItem | Decimals the price is written with, an integer from 0 to 6. Without it the number is written as it is. | |
discountPercent | DescuentoPct | Line discount in percent. The SDK also writes DescuentoMonto. | |
surchargePercent | RecargoPct | Line surcharge in percent. The SDK also writes RecargoMonto. | |
exempt | IndExe | Shorthand for exemptIndicator: 1. | |
exemptIndicator | IndExe | 1 exempt, 2 non-billable, 3 container deposit, 4 not a sale, 5 deduction, 6 negative non-billable (EXEMPT_INDICATORS). | |
codes | CdgItem | Up to 5 codes: { type: 'INT1', value: 'T-100' }. | |
additionalTaxCodes | CodImpAdic | Up to 2 additional tax codes (numbers). | |
settledDocumentType | TpoDocLiq | 3 | Type of the settled document. Required on every line of a settlement invoice (43). |
referenceQuantity | QtyRef | Reference quantity. | |
referenceUnit | UnmdRef | 4 | Reference unit. |
referencePrice | PrcRef | Reference price. | |
amount | MontoItem | Explicit line amount. It skips the quantity times price computation. |
How the line amount is computed (rounding per line, discounts, surcharges) is on Totals.
An item is exempt when you set exempt: true, set an exemptIndicator, or when the document type itself is exempt (34 and 41 give every line IndExe=1 unless you set another indicator). The SDK does not check codes or additionalTaxCodes for length: they are written as you give them.
Prices with decimals need unitPriceDecimals, because the amount is always whole pesos but the SII reads PrcItem as written:
{ name: 'Tornillo', quantity: 3, unit: 'UN', unitPrice: 12.5, unitPriceDecimals: 2 }
// PrcItem is written as 12.50, MontoItem as 38 (round(3 × 12.5) = round(37.5))Credit and debit notes with references#
references links the document to others. For credit and debit notes (61, 56) it is how you say which document you are cancelling or correcting.
import { createDocument, REFERENCE_CODES } from 'dte-sdk'
const creditNote = createDocument({
type: 61,
folio: 1,
issuer,
receiver,
items: [{ name: 'Plan anual', quantity: 1, unitPrice: 100000 }],
references: [
{
documentType: 33,
folio: 1,
date: '2026-11-01',
code: REFERENCE_CODES.cancels,
reason: 'Anula factura',
},
],
})
creditNote.references
// [{ lineNumber: 1, documentType: 33, folio: '1', date: '2026-11-01', code: 1, reason: 'Anula factura' }]
creditNote.header.totals // { netAmount: 100000, vatRate: 19, vatAmount: 19000, totalAmount: 119000 }| Field | SII tag | Limit | Meaning |
|---|---|---|---|
documentType | TpoDocRef | Type of the referenced document (a number), or the string 'SET' for certification test sets. | |
folio | FolioRef | Folio of the referenced document (a number or a string; it is stored as a string). | |
date | FchRef | Date of the referenced document, YYYY-MM-DD (or DD-MM-YYYY). | |
code | CodRef | REFERENCE_CODES: cancels (1), correctsText (2), correctsAmounts (3). | |
reason | RazonRef | 90 | Reason of the reference. |
global | IndGlobal | A global reference (IndGlobal=1). | |
otherRut | RUTOtr | RUT of another issuer, validated with rut.ts. |
A credit note that does not give the right to reduce the VAT debit sets options: { noDebitReduction: 1 } (IndNoRebaja).
References of a receipt#
Receipts have their own schema for Referencia: no date, no global flag and no other-issuer RUT, and CodRef is the company's own free text. That is why a receipt uses receiptReferences instead of references, and the two never mix: references on a receipt, or receiptReferences on any other type, throws INVALID_OPTION with details.field naming the field.
const receipt = createDocument({
type: 39,
folio: 5,
issuer,
items: [{ name: 'Café', unitPrice: 1500 }],
pricesIncludeVat: true,
receiptReferences: [{ code: 'SET', reason: 'CASO-1' }],
})
receipt.receiptReferences // [{ lineNumber: 1, code: 'SET', reason: 'CASO-1' }]| Field | SII tag | Limit | Meaning |
|---|---|---|---|
code | CodRef | 18 | The company's own reference code; SET in the certification test set. |
reason | RazonRef | 90 | Reason. |
documentType | TpoDocRef | 3 | Type of the referenced document (a number or a string, stored as a string). |
folio | FolioRef | 18 | Folio of the referenced document (a number or a string, stored as a string). |
All four are optional, and empty texts are dropped.
Global discounts and surcharges#
globalAdjustments are discounts or surcharges over the whole document (DscRcgGlobal):
globalAdjustments: [
{ kind: 'discount', valueType: 'percent', value: 10, description: 'Descuento cliente' },
]| Field | SII tag | Limit | Meaning |
|---|---|---|---|
kind | TpoMov | 'discount' (D) or 'surcharge' (R). | |
valueType | TpoValor | 'percent' (%) or 'amount' ($). | |
value | ValorDR | The value, in the unit of valueType. | |
description | GlosaDR | 45 | Description. |
exemptIndicator | IndExeDR | 1 applies the adjustment to the exempt amount, 2 to the non-billable one. Omit it for the affected amount. |
The totals take them into account; see Totals.
Subtotals#
subtotals is a list of SubtotalInfo (SubTotInfo) that the SDK writes as given, without computing anything: number (NroSTI), totalAmount (ValSubtotSTI) and, optionally, description (GlosaSTI), order (OrdenSTI), netAmount, vatAmount, additionalTaxAmount, exemptAmount and the lines (LineasDeta) they include.
Commissions (settlement invoice 43)#
A settlement invoice (43) is issued by an agent on behalf of a principal. Every line needs settledDocumentType, and commissions carries the commission and other-charge lines (Comisiones):
const settlement = createDocument({
type: 43,
folio: 1,
issuer,
receiver,
items: [{ name: 'Venta liquidada', unitPrice: 10000, settledDocumentType: '33' }],
commissions: [
{ kind: 'commission', description: 'Comisión 10%', rate: 10, netValue: 1000, vatValue: 190 },
],
})| Field | SII tag | Limit | Meaning |
|---|---|---|---|
kind | TipoMovim | 'commission' (C) or 'otherCharge' (O). | |
description | Glosa | 60 | Description (required). |
rate | TasaComision | Commission rate in percent. | |
netValue | ValComNeto | Net value. | |
exemptValue | ValComExe | Exempt value. | |
vatValue | ValComIVA | VAT of the commission. |
calculateTotals does not derive the commission totals, own VAT (IVAProp) or third-party VAT (IVATerc) of a settlement invoice. Compute them yourself and pass them in totals (see Explicit totals). The signed element of a settlement invoice is Liquidacion, not Documento.
Transport (dispatch guide 52)#
A dispatch guide (52) says what moves, where and why. Two header options give the reason and who dispatches (options.transferType, options.dispatchType), and transport carries the vehicle, the carrier, the driver and the destination:
import { createDocument, DISPATCH_TYPES, TRANSFER_TYPES } from 'dte-sdk'
const guide = createDocument({
type: 52,
folio: 1,
issuer,
receiver,
items: [{ name: 'Pallet', quantity: 4, unit: 'UN', unitPrice: 25000 }],
options: {
transferType: TRANSFER_TYPES.sale,
dispatchType: DISPATCH_TYPES.byIssuerToReceiver,
},
transport: {
licensePlate: 'ABCD12',
carrierRut: '77654321-7',
driver: { rut: '10000004-0', name: 'Juan Perez' },
destinationAddress: 'Pasaje Los Olmos 88',
destinationCommune: 'Providencia',
},
})
guide.header.options // { transferType: 1, dispatchType: 2 }
guide.header.transport.driver // { rut: '10000004-0', name: 'Juan Perez' }
guide.header.totals // { netAmount: 100000, vatRate: 19, vatAmount: 19000, totalAmount: 119000 }transport field | SII tag | Limit | Meaning |
|---|---|---|---|
licensePlate | Patente | 8 | Vehicle license plate. |
carrierRut | RUTTrans | Carrier RUT, validated with rut.ts. | |
driver.rut | RUTChofer | Driver RUT, validated with rut.ts. | |
driver.name | NombreChofer | 30 | Driver name (required when driver is given). |
destinationAddress | DirDest | 70 | Destination address. |
destinationCommune | CmnaDest | 20 | Destination commune. |
destinationCity | CiudadDest | 20 | Destination city. |
TRANSFER_TYPES (IndTraslado): sale 1, pendingSale 2, consignment 3, freeDelivery 4, internalTransfer 5, otherNonSale 6, return 7, exportTransfer 8, exportSale 9. DISPATCH_TYPES (TipoDespacho): byReceiver 1, byIssuerToReceiver 2, byIssuerToOther 3.
A guide for a transfer without a price can leave unitPrice out: its totals are { vatRate: 19, totalAmount: 0 }.
Header options#
options holds the optional IdDoc fields. Only the ones you set are written, in schema order. You never need most of them; the useful ones are paymentTerms, dueDate, payments, transferType and dispatchType.
import { createDocument, PAYMENT_TERMS } from 'dte-sdk'
createDocument({
type: 33,
folio: 3,
issuer,
receiver,
items: [{ name: 'Plan anual', unitPrice: 100000 }],
options: {
paymentTerms: PAYMENT_TERMS.credit,
dueDate: '2026-12-15',
payments: [{ date: '2026-12-15', amount: 119000, note: 'Pago único' }],
},
})| Option | SII tag | Meaning |
|---|---|---|
noDebitReduction | IndNoRebaja | Credit note that does not reduce the debit (1). |
dispatchType | TipoDespacho | DISPATCH_TYPES (1 to 3). |
transferType | IndTraslado | TRANSFER_TYPES (1 to 9). |
printFormat | TpoImpresion | 'N' normal or 'T' ticket. |
serviceIndicator | IndServicio | SERVICE_INDICATORS: 1 periodic services, 2 periodic home services, 3 sales and services, 4 shows. Defaults to 3 on receipts. |
grossAmounts | MntBruto | Line amounts include VAT (1). Set for you by pricesIncludeVat. |
purchaseTransactionType | TpoTranCompra | Purchase transaction type (1 to 7). |
saleTransactionType | TpoTranVenta | Sale transaction type (1 to 4). |
paymentTerms | FmaPago | PAYMENT_TERMS: cash 1, credit 2, free 3. |
exportPaymentTerms | FmaPagExp | Export payment terms. |
settlementDate | FchCancel | Settlement date. |
settledAmount | MntCancel | Settled amount. |
outstandingBalance | SaldoInsol | Outstanding balance. |
payments | MntPagos | Installments: { date, amount, note? } (FchPago, MntPago, GlosaPagos). |
billingPeriod | PeriodoDesde, PeriodoHasta | { from, to }. |
paymentMedium | MedioPago | Payment medium. |
paymentAccountType | TpoCtaPago | Payment account type. |
paymentAccountNumber | NumCtaPago | Payment account number. |
paymentBank | BcoPago | Payment bank. |
paymentTermsCode | TermPagoCdg | Payment terms code. |
paymentTermsNote | TermPagoGlosa | Payment terms note. |
paymentTermsDays | TermPagoDias | Payment terms days. |
dueDate | FchVenc | Due date. |
netAmountsIndicator | IndMntNeto | Net amounts indicator. |
The SDK writes the options as you give them, without validating or measuring them. Dates in options are not normalized: write them as YYYY-MM-DD.
VAT-included prices#
When your prices already include VAT, say so with pricesIncludeVat: true. The SDK then derives net and VAT from the gross amount and, on every type but receipts, writes MntBruto=1 (options.grossAmounts). Receipts do not carry MntBruto, but the totals are derived the same way:
const receipt = createDocument({
type: 39,
folio: 1,
issuer,
items: [{ name: 'Café', quantity: 2, unitPrice: 1500 }],
pricesIncludeVat: true,
})
receipt.header.receiver // { rut: '66666666-6', businessName: 'Consumidor Final', address: 'Sin Direccion', commune: 'Santiago' }
receipt.header.options // { serviceIndicator: 3 }
receipt.header.totals // { netAmount: 2521, vatAmount: 479, totalAmount: 3000 }The SDK sets grossAmounts automatically only when the document has an affected amount: an exempt-only invoice with pricesIncludeVat gets no MntBruto.
Withholding (purchase invoice 46)#
A purchase invoice (46) is issued by the buyer, who withholds the VAT instead of paying it to the seller. The issuer is the buyer; the receiver is the seller. VAT is withheld in full by default:
const purchase = createDocument({
type: 46,
folio: 1,
issuer,
receiver: {
rut: '77654321-7',
businessName: 'Distribuidora Maipo Ltda',
businessActivity: 'Reciclaje',
address: 'Camino Lo Boza 410',
commune: 'Pudahuel',
},
items: [{ name: 'Chatarra', quantity: 10, unitPrice: 1000 }],
})
purchase.header.totals
// {
// netAmount: 10000,
// vatRate: 19,
// vatAmount: 1900,
// withholdings: [{ taxCode: 15, rate: 19, amount: 1900 }],
// totalAmount: 10000,
// }totalAmount leaves out the withheld VAT. Pass withholdVat: false to issue a 46 without withholding, and withholdVat: true on a debit or credit note that affects a purchase invoice, so the note withholds the VAT the same way. On any other type withholdVat defaults to false.
Explicit totals#
totals replaces the computed totals with the object you give (a Totals). Use it when you need amounts the SDK does not derive (commission totals of a settlement invoice, IVAProp, IVATerc, MontoPeriodo, SaldoAnterior, VlrPagar) or a VAT rate other than 19. The SDK trusts it: it does not check the object against the lines. See Totals for the fields.
What validation does#
createDocument fails early instead of letting the SII reject the whole envelope later (the SII rejects an envelope when one of its documents fails the schema). Among other things, it:
- Checks the type (
INVALID_DOCUMENT_TYPE) and the folio, a positive integer (INVALID_FOLIO). - Takes the issue date as given or today in Chile, and requires it to be a real date (
INVALID_DATE). - Normalizes every RUT with rut.ts (
INVALID_RUT, withdetails.fieldsuch asissuer.rutorreferences[0].otherRut). The check digit is validated; the value is never copied into the error message. - Sanitizes free text: removes apostrophes and double quotes (they break the SII signature checks), turns typographic dashes and ellipsis into ASCII, drops everything outside ISO-8859-1 (emoji, curly quotes) and trims.
"Premium" — grandebecomesPremium - grande. - Enforces the schema lengths of the sanitized text (
FIELD_TOO_LONG). - Requires what the type needs: the receiver (
MISSING_FIELD,details.field: 'receiver'), its address and commune on every type but receipts, at least one item, an item name, the issuer business name, activity, address and commune, andsettledDocumentTypeon every line of a 43. - Rejects non-finite numbers in amounts (
INVALID_AMOUNT) and aunitPriceDecimalsoutside 0 to 6 (INVALID_OPTION). - Numbers items, references, adjustments and commissions from 1.
- Computes line amounts and totals (see Totals), applies the receipt defaults and sets
MntBruto.
| Code | When |
|---|---|
INVALID_DOCUMENT_TYPE | type is not one of the nine supported codes. |
INVALID_FOLIO | folio is not a positive integer. |
INVALID_DATE | A date (issueDate, a reference date) is not a real calendar date. |
INVALID_RUT | A RUT fails rut.ts. details.field says which. |
MISSING_FIELD | A required field is empty or absent. details.field is its path, such as items[0].name. |
FIELD_TOO_LONG | A sanitized text exceeds its limit. details has field (the SII tag), maxLength, length and value. |
INVALID_AMOUNT | A quantity, price, percent or amount is not a finite number. |
INVALID_OPTION | references on a receipt, receiptReferences on another type, or a bad unitPriceDecimals. |
import { createDocument, isDteError } from 'dte-sdk'
try {
createDocument({ ...input, items: [{ name: 'x'.repeat(81), unitPrice: 1 }] })
} catch (error) {
if (isDteError(error) && error.code === 'FIELD_TOO_LONG') {
error.message // 'Field exceeds the maximum length allowed by the SII schema (field=NmbItem, maxLength=80, length=81)'
error.details // { field: 'NmbItem', maxLength: 80, length: 81, value: 'xxxxxxxx…' }
}
}Messages never include RUTs, names or item text; the offending value lives in details. If you log details, keep in mind that it can hold personal data. See Errors.
Field limits#
FIELD_LIMITS is the table of xs:maxLength values that createDocument enforces, keyed by SII tag:
| Tag | Max | Tag | Max |
|---|---|---|---|
RznSoc | 100 | NmbItem | 80 |
GiroEmis | 80 | DscItem | 1000 |
DirOrigen | 70 | UnmdItem, UnmdRef | 4 |
CmnaOrigen, CiudadOrigen, Sucursal | 20 | TpoDocLiq | 3 |
CdgVendedor | 60 | RazonRef | 90 |
RznSocRecep | 100 | CodRef, FolioRef | 18 |
GiroRecep | 40 | TpoDocRef | 3 |
Contacto | 80 | GlosaDR | 45 |
DirRecep, DirPostal | 70 | Glosa | 60 |
CmnaRecep, CiudadRecep | 20 | Patente | 8 |
CmnaPostal, CiudadPostal | 20 | NombreChofer | 30 |
CdgIntRecep | 20 | DirDest | 70 |
CmnaDest, CiudadDest | 20 |
FIELD_LIMITS also lists CorreoRecep (80) and GlosaSTI (40), but createDocument does not check the receiver email or the subtotal description today. Fields that are not free text (phone, email, activityCode, codes, options, subtotals) are written as you give them.
Issue date and the clock#
issueDate accepts YYYY-MM-DD, DD-MM-YYYY and DD/MM/YYYY (the way the SII portal and resolutions show dates) and is stored as YYYY-MM-DD. When you omit it, the SDK uses today's date in Chile (America/Santiago, with daylight saving), never the time zone of the process. That matters on a server in UTC: at 23:30 in Chile on 15 November the UTC date is already the 16th.
Every function that depends on the time takes the clock as an option, so tests are deterministic:
const document = createDocument(input, { now: new Date('2026-11-15T18:30:00Z') })
document.header.issueDate // '2026-11-15'issueDocument takes the same now and passes it to the stamp and the signature, so one now fixes FchEmis, TSTED and TmstFirma together. timestamp overrides the stamp and signature times with a string you choose (YYYY-MM-DDTHH:MM:SS).
Render the XML#
buildDocumentXml(document, options?) renders the unsigned <DTE> XML:
import { buildDocumentXml, documentElementName } from 'dte-sdk'
const xml = buildDocumentXml(document)
documentElementName(33) // 'Documento'
documentElementName(43) // 'Liquidacion'| Option | Meaning |
|---|---|
ted | The Ted to embed (verbatim, from stampDocument). |
signedAt | Value of TmstFirma. |
What it does:
- Declares the namespaces on the
DTEroot (http://www.sii.cl/SiiDteandxsi), so the signature digest inherits them. - Puts the
IDattribute (DTE_T33F1) on the signed element:Documento, orLiquidacionfor a settlement invoice (43).documentElementName(type)returns that name. - Writes the sections in schema order (
Encabezado,Detalle,SubTotInfo,DscRcgGlobal,Referencia,Comisiones,TED) and leaves out the empty ones. - Puts a line break after each of those sections. The SII rejects any line longer than about 4,090 characters (
CHR-00002), so a receipt with many lines would fail otherwise. The breaks sit inside the signed element and the digest covers them. - Escapes text values as XML entities.
You rarely call it yourself: signDocument calls it for you. It is useful to look at what would be signed, or to archive a draft.
The codec: SII tags and the English model#
The English model and the SII tags are two shapes of the same data. toSiiDocument maps a Document to the structure with the official tags in schema order (SiiDocumento: Encabezado.IdDoc.TipoDTE, Totales.MntTotal…), and fromSiiDocument maps it back:
import { fromSiiDocument, toSiiDocument } from 'dte-sdk'
const sii = toSiiDocument(document)
sii.Encabezado.Totales.MntTotal // 167195
sii.Detalle[0].NmbItem // 'Plan anual'
fromSiiDocument(sii) // a Document againtoSiiDocument is the only place that knows the tag names; buildDocumentXml always renders from its output. fromSiiDocument is for DTE you receive and for interoperability: it validates every RUT with rut.ts on the way in (INVALID_RUT). A receipt maps its references to and from receiptReferences. One thing does not survive the round trip: unitPriceDecimals, because a written 12.50 reads back as the number 12.5.
Next steps#
- Totals: how line amounts, VAT, exempt lines and withholding are computed.
- Stamp: put the TED on the document with a CAF.
- Sign: load the certificate and sign.
- Examples: invoice, credit note and receipt.