Skip to content

SII books

The SDK builds three XML documents that the SII calls books: the purchase and sales book (LibroCompraVenta, the IECV), the dispatch guide book (LibroGuia) and the folio consumption report (ConsumoFolios, the RCOF). You give it the lines, it computes the summaries, renders the XML in the format the SII validates, signs it with the sender's certificate and, with the web services client, uploads it.

Their use today is narrow. The RCV (registro de compras y ventas) replaced the purchase and sales book in production from period 2017-08, and the guide book is sent only when the SII asks for it; both are still part of the certification test set. The folio consumption report, now called Resumen de Ventas Diarias, is used in the receipts certification, and the SII probably no longer requires it in production. See Certification.

The books have not been sent to the real SII yet. In the tests they validate against the official XML schemas, and their XML matches golden files byte for byte. dte-sdk 0.8.0 is on npm, the latest release. It is not recommended for production yet: most areas have not met the real SII, and until 1.0.0 the API can change in any release. See Status.

Usage#

A sales book for one month, built from the documents you issued, signed and uploaded:

TypeScript
import {
  bookEntryFromDocument,
  createPurchaseSalesBook,
  signPurchaseSalesBook,
  uploadBook,
} from 'dte-sdk'
 
const book = createPurchaseSalesBook({
  cover: {
    issuerRut: '76543210-3', // the company that owns the book
    senderRut: certificate.rut!, // who signs and sends
    period: '2026-10',
    resolutionDate: '2026-01-15',
    resolutionNumber: 0, // 0 in certification
    operation: 'sale',
  },
  entries: invoices.map((document) =>
    bookEntryFromDocument(document, { bookRut: '76543210-3', operation: 'sale' }),
  ),
  // Receipts are not listed line by line: they go in as totals.
  summaryOnlyTotals: [{ type: 39, documentCount: 120, netAmount: 840000, vatAmount: 159600, totalAmount: 999600 }],
})
 
book.periodTotals // one row per document type, computed from the lines and the summary-only totals
book.totalMismatches // explicit line totals that differ from the SII formula (empty here)
 
const signed = signPurchaseSalesBook(book, certificate)
const receipt = await uploadBook(sii, { xml: signed.xml })
receipt.trackId // follow it with waitForUploadStatus

createPurchaseSalesBook validates and normalizes, signPurchaseSalesBook returns the book plus xml (with the ISO-8859-1 prolog, ready to upload) and the signature values, and uploadBook sends it. Every function is pure except the upload.

The three books at a glance#

BookBuildSignUploadSubmissions
Purchase and sales (LibroCompraVenta)createPurchaseSalesBooksignPurchaseSalesBookuploadBooktotal, partial, final, adjustment
Dispatch guides (LibroGuia)createGuideBooksignGuideBookuploadBooktotal, partial, final, adjustment
Folio consumption (ConsumoFolios)createFolioConsumptionsignFolioConsumptionuploadFolioConsumptionnone: resend the whole report

Each has a build…Xml function that renders the unsigned XML (buildPurchaseSalesBookXml, buildGuideBookXml, buildFolioConsumptionXml) and a verify…Signature function that checks the signature of an XML (verifyPurchaseSalesBookSignature, verifyGuideBookSignature, verifyFolioConsumptionSignature). The signature is an enveloped XMLDSig over the EnvioLibro (or DocumentoConsumoFolios) element, referenced by its ID.

toSiiPurchaseSalesBook, toSiiGuideBook and toSiiFolioConsumption convert a book to an object with the official SII tags (Caratula, Detalle, ResumenPeriodo…), if you want to see or post-process what is rendered.

The rendered XML starts with an ISO-8859-1 declaration, which uploadBook requires. It also puts a line break between every pair of adjacent tags, because the SII rejects lines longer than about 4 090 characters (CHR-00002).

The purchase and sales book#

The cover#

FieldRequiredMeaning
issuerRutyesRUT of the company the book belongs to (RutEmisorLibro).
senderRutyesRUT of the authorized sender, normally the holder of the certificate (RutEnvia).
periodyesTax period, YYYY-MM. On a special book, the earliest period it covers.
resolutionDateyesDate of the SII resolution, YYYY-MM-DD or DD-MM-YYYY.
resolutionNumberyesResolution number, an integer from 0 to 999 999. 0 in certification.
operationyes'sale' (VENTA) or 'purchase' (COMPRA).
kindno'monthly' (MENSUAL, the default), 'special' (ESPECIAL) or 'rectification' (RECTIFICA).
submissionno'total' (the default), 'partial', 'final' or 'adjustment'.
segmentNumberon partialNumber of the segment. Emitted only on a partial submission.
notificationFolioon specialFolio of the SII notification that asks for the book.
rectificationCodeon rectificationThe authorization code of the replacement, up to 10 characters (CodAutRec).

A missing required field is MISSING_FIELD { field: 'cover.…' }, a bad period or date is INVALID_DATE, a bad RUT is INVALID_RUT.

Kinds and submissions#

The kind says what the book is: the month's book, a special one the SII requested, or the replacement of one already sent. The submission says how it reaches the SII. A month can arrive in one go or in pieces, and a closed month can be corrected afterwards:

SubmissionWhat it carriesSummaries it gets
totalAll the lines of the period, in one book.Period summary.
partialOne segment of the lines, with its segmentNumber. Needs lines; takes no previous totals and no summary-only totals.Segment summary only.
finalThe closing of the segments: no lines; it needs previousTotals (the sum of the segments) or summaryOnlyTotals.Period summary.
adjustmentLines that add to or remove from a book already sent (adjustment: 'add' or 'remove' on each line; add is the default).Segment summary (the signed adjustment) and period summary.

The zones are validated before anything is rendered: a partial without lines is MISSING_FIELD { field: 'entries' }; a final with lines is INVALID_OPTION { field: 'entries' }; previousTotals on anything but a final or an adjustment is INVALID_OPTION { field: 'previousTotals' }; summaryOnlyTotals on a partial is INVALID_OPTION { field: 'summaryOnlyTotals' }; a final with neither previous nor summary-only totals is MISSING_FIELD { field: 'previousTotals' }. A special book without notificationFolio or a rectification without rectificationCode is MISSING_FIELD.

The lines#

Each entry of entries is a document, with the SDK's English names (the Spanish SII tag each one produces is in the JSDoc). The ones you will use most:

FieldMeaning
type, folioDocument type (see BOOK_DOCUMENT_TYPES) and folio.
dateDate of the document, YYYY-MM-DD or DD-MM-YYYY, from 2000 to 2050.
counterpartyRut, counterpartyNameThe customer on a sales book, the supplier on a purchase book. The name is at most 50 characters.
exemptAmount, netAmount, vatAmountAmounts. On a sales book they default to 0; on a purchase book vatAmount is the recoverable part.
taxRateVAT rate in percent. Filled with the VAT rate when a line has net and VAT and no rate.
otherTaxesAdditional taxes and retentions: { code, rate?, amount }, up to 20.
nonRecoverableVatNon-recoverable VAT on purchases: { code, amount }, up to 5 (see NON_RECOVERABLE_VAT_CODES).
commonUseVat, fixedAssetNetAmount, fixedAssetVatAmount, nonCreditableTax…The purchase-side specifics.
totalVatWithheld, partialVatWithheld, constructionCredit, containerDeposit, settlement…Retentions, credits and commissions of settlement invoices.
totalAmountThe line total. Computed with the SII formula when absent; kept and compared when present.
voidedtrue for a folio voided before it reached the SII: only type and folio travel and it adds no amounts.
adjustment'add' or 'remove' on an adjustment submission.

The line total follows the formula of the SII format for each side of the book: net plus exempt plus VAT plus additional taxes, less withheld VAT and so on, and on purchases plus common-use VAT, non-recoverable VAT and tax without credit. Retention taxes do not add to a purchase total, and code 60 (initial invoice retention) subtracts.

An invalid line is INVALID_DOCUMENT_TYPE, INVALID_FOLIO, INVALID_AMOUNT, INVALID_DATE, INVALID_RUT, INVALID_OPTION or FIELD_TOO_LONG, with details.index telling which line.

Explicit totals: totalMismatches and strictTotals#

If you pass a totalAmount that is not what the formula gives, the SDK keeps your value (the SII accepts some of those) and reports the difference, so a decision stays yours:

TypeScript
const book = createPurchaseSalesBook({
  cover,
  entries: [{ type: 33, folio: 101, netAmount: 100000, vatAmount: 19000, totalAmount: 120000 }],
})
book.totalMismatches // [{ index: 0, type: 33, folio: 101, expected: 119000, actual: 120000 }]

With strictTotals: true the same input throws BOOK_TOTAL_MISMATCH instead, with details: { index, expected, actual } for the first mismatch.

Summaries and carrying totals between submissions#

The SDK computes the summaries from the lines: one row per document type, in the order of first appearance.

  • segmentTotals is the summary of this submission's own lines. It exists on partial and adjustment submissions that have lines; otherwise it is null.
  • periodTotals is the summary of the whole period: the lines, plus summaryOnlyTotals, plus previousTotals. It is null on a partial and when there is nothing to add up.
  • summaryOnlyTotals carries what the format excludes from the detail: receipts and a few other types (SUMMARY_ONLY_DOCUMENT_TYPES: 35, 38, 39, 41, 48, 105, 500, 501, 919, 920, 922 and 924). Give the totals of that type as { type, documentCount, netAmount, vatAmount, totalAmount… }; whatever you leave out counts as 0.

periodTotals is a plain JSON value. Store it after each submission and pass it back as previousTotals in the next one; accumulateBookTotals(base, delta) adds two lists of rows by document type (and, inside a type, non-recoverable VAT and other taxes by code):

TypeScript
import { accumulateBookTotals, createPurchaseSalesBook } from 'dte-sdk'
 
const cover = { issuerRut: '76543210-3', senderRut: '12345678-5', period: '2026-10', resolutionDate: '2026-01-15', resolutionNumber: 0, operation: 'sale' as const }
 
// Two segments of the month…
const first = createPurchaseSalesBook({ cover: { ...cover, submission: 'partial', segmentNumber: 1 }, entries: firstHalf })
const second = createPurchaseSalesBook({ cover: { ...cover, submission: 'partial', segmentNumber: 2 }, entries: secondHalf })
 
// …and the closing, with no lines, that carries what the segments added up to.
const final = createPurchaseSalesBook({
  cover: { ...cover, submission: 'final' },
  previousTotals: accumulateBookTotals(first.segmentTotals!, second.segmentTotals!),
  summaryOnlyTotals: [{ type: 39, documentCount: 120, netAmount: 840000, vatAmount: 159600, totalAmount: 999600 }],
})
final.periodTotals // the whole month

An adjustment signs its lines (remove counts negative) and carries them on top of the previous totals, so its periodTotals is the corrected month.

Common-use VAT and factors#

When a period summary has common-use VAT, the book needs the proportionality factor: pass commonUseVatFactor (0.001 to 99.999, at most 3 decimals); without it, the build fails with MISSING_FIELD { field: 'commonUseVatFactor' }. additionalTaxFactors maps an additional tax code to its factor (0.001 to 1, at most 4 decimals). A factor out of range is INVALID_OPTION.

Ids and the clock#

The EnvioLibro ID is generated (LCV_<dd>_<mm>_<yyyy>_<hh>_<mm>_<ss>_<ms> in Chile time, from now) unless you pass id. now also sets the signing timestamp (signedAt). Pass both to make a build reproducible in tests.

Deriving lines from documents#

You rarely write lines by hand. Three functions derive them from documents issued with this SDK:

TypeScript
import {
  bookEntryFromDocument,
  folioConsumptionEntryFromDocument,
  guideEntryFromDocument,
} from 'dte-sdk'
 
bookEntryFromDocument(document, { bookRut: '76543210-3', operation: 'sale' })
bookEntryFromDocument(received, { bookRut: '76543210-3', operation: 'purchase', creditNoteSign: 'negative' })
guideEntryFromDocument(guide) // a dispatch guide (52)
folioConsumptionEntryFromDocument(receipt) // a receipt (39 or 41)
  • bookEntryFromDocument takes the document and { bookRut, operation, creditNoteSign? }. The counterparty is the receiver when the owner of the book issued the document and the issuer otherwise, which covers sales, received purchases and purchase invoices (46) on either side. The name is cut to 50 characters, because the book field is shorter than the document's. creditNoteSign: 'negative' negates the amounts of a credit note (61); the default, 'positive', is how the format has it. Not inferred, and left for you to spread into the result when the case needs them: issuedByBookOwner, purchaseInvoice and the reference to the voided document.
  • guideEntryFromDocument takes a dispatch guide; any other type is INVALID_DOCUMENT_TYPE. The transfer is a sale by default, and the receiver and the amounts travel only on sales: a non-sale guide reports its total alone.
  • folioConsumptionEntryFromDocument takes a receipt; any other type is INVALID_DOCUMENT_TYPE.

The dispatch guide book#

TypeScript
import { createGuideBook, guideEntryFromDocument, signGuideBook, uploadBook } from 'dte-sdk'
 
const guideBook = createGuideBook({
  cover: {
    issuerRut: '76543210-3',
    senderRut: certificate.rut!,
    period: '2026-10',
    resolutionDate: '2026-01-15',
    resolutionNumber: 0,
    notificationFolio: 3, // always required: the folio of the SII notification
  },
  entries: guides.map((guide) => guideEntryFromDocument(guide)),
})
 
await uploadBook(sii, { xml: signGuideBook(guideBook, certificate).xml }) // sent as LibroGuia.xml

The cover is the same as the purchase and sales book's, without operation and kind (the schema admits only the special kind), and with notificationFolio mandatory on every guide book. The submissions work the same way, including segmentNumber on a partial, previousTotals on a final or an adjustment and adjustment: 'add' | 'remove' on the lines.

A line is { folio, voided?, adjustment?, transferType?, date?, receiverRut?, receiverName?, netAmount?, taxRate?, vatAmount?, totalAmount?, modifiedAmount?, referenceType?, referenceFolio?, referenceDate? }:

  • transferType is the operation, from 1 to 7 (GUIDE_TRANSFER_TYPES: 1 sale, 2 sale to be made, 3 consignment, 4 demonstration, 5 internal transfer, 6 other non-sale transfer, 7 return guide). The schema stops at 7, so the values 8 and 9 of the guide's IndTraslado cannot be reported on a line.
  • voided is 'folio' (voided before reaching the SII), 'guide' (voided afterwards) or 'partialReceipt' (goods partly received, with the corrected modifiedAmount). The first two drop the amounts and the receiver; partialReceipt keeps them (GUIDE_VOID_CODES: 1, 2 and 3).
  • vatAmount is computed from the rate and the net when absent or 0, and totalAmount is net plus VAT when absent. The rate and the VAT are always emitted, as 0 when absent.

The summary (GuideTotals) is { voidedFolios, voidedGuides, saleGuides, saleGuidesAmount, modifiedAmount, transfers }, where transfers has one { type, count, amount } per non-sale operation. Like the book's, it is plain JSON: store periodTotals, pass it back as previousTotals, and add two of them with accumulateGuideTotals(base, delta).

The folio consumption report (RCOF)#

The report says which receipt folios a company used on a range of days. It is built from the receipts you issued (types 39 and 41):

TypeScript
import {
  createFolioConsumption,
  folioConsumptionEntryFromDocument,
  signFolioConsumption,
  uploadFolioConsumption,
} from 'dte-sdk'
 
const report = createFolioConsumption({
  cover: {
    issuerRut: '76543210-3',
    senderRut: certificate.rut!,
    resolutionDate: '2026-01-15',
    resolutionNumber: 0,
    // from, to: default to the earliest and latest issue dates of the lines
    // sequence: 1 by default
  },
  entries: receipts.map((receipt) => folioConsumptionEntryFromDocument(receipt)),
  voidedFolios: [{ type: 39, folio: 52 }], // folios voided in your system before reaching the SII
})
 
report.summaries // one per receipt type present, type 39 first
await uploadFolioConsumption(sii, { xml: signFolioConsumption(report, certificate).xml })
  • Dates. from and to (FchInicio, FchFinal) default to the earliest and latest issueDate of the lines. A day without sales has no lines to read them from, so you must pass them: MISSING_FIELD { field: 'cover.from' } otherwise. A day without sales produces one summary of type 39 in zeros.
  • Summaries. One per receipt type present, with the net, VAT, exempt and total amounts, the counts of issued, voided and used folios, and the ranges. Type 41 (exempt receipt) ignores net and VAT; the VAT rate appears only on type 39.
  • Ranges. compactFolioRanges(folios) sorts folios and groups the consecutive ones into { from, to } ranges (a lone folio has from equal to to): compactFolioRanges([5, 1, 2, 3]) gives [{ from: 1, to: 3 }, { from: 5, to: 5 }]. The report lists issued folios and voided folios in separate ranges. A folio repeated across lines and voided folios is INVALID_FOLIO.
  • No submission kinds. To correct a report, send it whole again with a higher sequence (cover.sequence, 1 by default, up to 999; correlative is the optional counter within the day).
  • Voided folios here are those voided in your system before they reached the SII, not those cancelled with a credit note.

Uploading#

uploadBook(sii, { xml }) sends a signed book and uploadFolioConsumption(sii, { xml }) a signed report. Both go through DTEUpload as ISO-8859-1 and return an UploadReceipt with the trackId. The file name defaults to LibroGuia.xml for a guide book, LibroCV.xml for any other book and ConsumoFolios.xml for a report; fileName overrides it. The statuses of a book upload use their own codes: LOK (book accepted), LSO, LNC (type of submission does not match), LRH (rejected with history)… See SII web services and Errors.

Catalogs#

ExportWhat it holds
BOOK_OPERATIONS{ sale: 'VENTA', purchase: 'COMPRA' }
BOOK_KINDS{ monthly: 'MENSUAL', special: 'ESPECIAL', rectification: 'RECTIFICA' }
BOOK_SUBMISSIONS{ total: 'TOTAL', partial: 'PARCIAL', final: 'FINAL', adjustment: 'AJUSTE' }
BOOK_DOCUMENT_TYPESThe document types the book schema admits (DoctoType of LibroCV_v10.xsd), electronic or not; isBookDocumentType(value) is the guard.
SUMMARY_ONLY_DOCUMENT_TYPESTypes the format excludes from the detail: 35, 38, 39, 41, 48, 105, 500, 501, 919, 920, 922, 924.
NON_RECOVERABLE_VAT_CODESWhy the VAT of a purchase cannot be recovered: nonTaxableOperations 1, lateRegistration 2, rejectedExpenses 3, freeDeliveries 4, other 9.
GUIDE_TRANSFER_TYPESThe guide operations 1 to 7.
GUIDE_VOID_CODES{ folio: 1, guide: 2, partialReceipt: 3 }

Next steps#

  • SII web services uploads the signed XML and follows its status.
  • Certification uses the three books in the test sets and the receipts certification.
  • Errors lists BOOK_TOTAL_MISMATCH and the other codes the books raise.