Errors and status codes
Every failure the SDK raises on purpose is a DteError with a stable code. You branch on the code and read the context from details; you never parse the message. This page lists every code, what raises it and what its details usually hold, then covers the SII status codes (upload, reception, document, claims and transferability) that are returned as data rather than thrown.
Using DteError#
import { createDocument, DteError, errorCode, 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…' }
}
}| Export | What it does |
|---|---|
DteError | The error class. code is a DteErrorCode, details is a read-only record of context. |
isDteError(value) | Type guard: true when value is a DteError. |
errorCode(value) | The code when value is a DteError, else undefined. Handy when you report failures as data. |
DTE_ERROR_CODES | The object with every code: DTE_ERROR_CODES.INVALID_RUT === 'INVALID_RUT'. |
DteErrorCode, DteErrorDetails | The types of the code and of the details. |
isRetryableError(value) | True for the errors worth another attempt. See Retrying. |
details versus message#
The message is generic and safe to log: it is a fixed sentence per code, followed by a short list of safe detail keys, that is, field names, codes and numbers. It never includes RUTs, names, item text, URLs or response bodies. The safe keys are field, maxLength, length, type, folio, from, to, kind, index, status, httpStatus, code, attempts, channel, operation, trackId, expected, actual, rejection, step, module, day, listed and covered.
Everything else lives in details only: the offending value, a RUT, a response excerpt (with tokens and seeds redacted), the URL that was called. Treat details as sensitive: do not send it whole to a log or to an error tracker. Pick the keys you need (error.details.field, error.details.status).
logger.error('upload failed', { code: error.code, status: error.details.status }) // fine
logger.error('upload failed', error.details) // may carry RUTs, a response excerpt, the offending valueError codes#
Codes are grouped by what they are about. "Typical details" are the keys the code usually carries; an operation may add more (every transport error also carries operation).
Input validation#
| Code | Raised when | Typical details |
|---|---|---|
INVALID_RUT | A RUT is malformed or its check digit is wrong. Raised by every function that takes a RUT. | field |
INVALID_DOCUMENT_TYPE | The document type is not supported. Also: a type outside 33, 34 and 43 in the claim registry, outside BOOK_DOCUMENT_TYPES in a book, outside 39 and 41 in a folio consumption report, a non-guide in guideEntryFromDocument. | field, type, index |
INVALID_FOLIO | A folio is not a positive integer (books: at most 10 digits). In a folio consumption report, a folio repeated across lines. | field, folio, type, index |
INVALID_DATE | A date is not YYYY-MM-DD, a period is not YYYY-MM, or a book date is outside 2000 to 2050. | field |
INVALID_AMOUNT | An amount is not a finite number (books: not a safe integer, or negative where the schema forbids it). | field, index |
MISSING_FIELD | A required field or option is absent: for example companyRut in a call with no client default, cover.notificationFolio in a special book. | field |
FIELD_TOO_LONG | A text exceeds the maximum length of the SII schema (for example 80 characters for an item name). | field, maxLength, length, value |
INVALID_OPTION | An option has an invalid value: an unknown environment or channel, a non-positive timeout, a bad trackId, a seed that is not digits, a fileName that is not safe, a book zone that does not fit its submission… | field, sometimes index |
ENCODING_UNSUPPORTED_CHAR | A text contains a character outside ISO-8859-1, so it cannot be sent or stamped. | index, codePoint |
CAF and folios#
| Code | Raised when | Typical details |
|---|---|---|
CAF_INVALID | The CAF XML cannot be read or is not what a CAF must be. parseCaf and verifyCafSignature also refuse a DOCTYPE, and parseCaf a text with more than one <CAF or </CAF and a CAF whose private key is not the pair of its public key (reason: 'RSASK is not the pair of RSAPK'). An empty FRMA is no longer CAF_INVALID: verifyCafSignature answers invalid. | reason |
CAF_EXPIRED | The CAF is more than 6 months past its authorization date; raised when you stamp. | type, authorizationDate |
CAF_TYPE_MISMATCH | The CAF is for another document type than the document you are stamping. | type, cafType |
CAF_ISSUER_MISMATCH | The CAF belongs to another issuer RUT than the document's. | type |
FOLIO_OUT_OF_RANGE | The folio is outside the CAF range, or a folio ledger or the certification folio allocator runs out of folios. | folio, from, to, type |
Certificate#
| Code | Raised when | Typical details |
|---|---|---|
CERT_INVALID | The PKCS#12 or PEM cannot be read, has no private key or no certificate, or the key does not match the certificate. | reason |
CERT_PASSWORD_WRONG | The PKCS#12 password is wrong. | none |
CERT_EXPIRED | The certificate has expired. | expiresAt |
CERT_NOT_YET_VALID | The certificate is not valid yet: its notBefore is still ahead. | validFrom |
XML and signatures#
| Code | Raised when | Typical details |
|---|---|---|
XML_PARSE_FAILED | An XML cannot be parsed, an element to sign has no ID, or the expected root or closing tag is missing. verifyReceivedDocuments also throws it for a DOCTYPE, an envelope without exactly one SetDTE and a prefixed DTE or Documento. Every DOM the SDK parses refuses elements nested deeper than 100 levels with it, before anything walks them. | field, reason, root, line, column |
SIGNATURE_NOT_FOUND | A signature is verified on an XML that has no Signature (or no SignedInfo). | field |
ENVELOPE_EMPTY | An envelope has no documents. | kind or field |
ENVELOPE_KIND_MISMATCH | A document does not belong in this kind of envelope (a receipt in an EnvioDTE, or an invoice in an EnvioBOLETA). | kind, type |
BOOK_TOTAL_MISMATCH | With strictTotals, an explicit book line total differs from the SII formula. | index, expected, actual |
Transport#
| Code | Raised when | Typical details |
|---|---|---|
TRANSPORT_UNAVAILABLE | There is no fetch in the runtime, or node:https or node:fs could not be loaded by a Node adapter. | reason |
TRANSPORT_FAILED | The request failed before a response arrived (DNS, connection refused or reset, a request that cannot be built). | reason, operation, responseStarted |
TRANSPORT_TLS | The TLS handshake or the certificate verification failed. | reason (the OpenSSL or OS code), operation |
REQUEST_TIMEOUT | The timeout of the call elapsed. | operation, timeoutMs, reason |
REQUEST_ABORTED | The caller's AbortSignal fired. | operation |
When a transport failure happens after the response headers arrived (while the body was being read), details.responseStarted is true: the SII did answer, at least in part.
SII web services#
| Code | Raised when | Typical details |
|---|---|---|
SII_HTTP_ERROR | The SII answered with an HTTP status that is not 2xx. | httpStatus, operation, channel, url, body (excerpt, redacted), retryAfterMs |
SII_RESPONSE_INVALID | The answer cannot be interpreted: not the XML or JSON that the channel returns, or without the expected element or code. | operation, body |
SII_SOAP_FAULT | The SII answered with a SOAP fault. | code, reason (redacted), operation |
SII_SEED_FAILED | The SII refused to issue a seed. | code, channel |
SII_TOKEN_FAILED | The SII refused to issue a token for a signed seed. | code, channel |
SII_TOKEN_INVALID | The SII rejected the token. The client renews it and repeats the call once; this surfaces only if the fresh token is refused too. | operation, channel, httpStatus or status or code, attempts |
SII_UPLOAD_REJECTED | The SII refused an upload: a DTEUpload status other than received or duplicate, or more than 500 receipts on the REST channel. | status (a number, or 'TOO_MANY'), errors, fileName, operation, message, trackIdElement |
SII_QUERY_ERROR | A status query answered with a negative code -1 to -14: the query service failed, not your envelope. | operation, code, reason |
WAIT_TIMEOUT | waitForUploadStatus ran out of time budget while the status was still pending. | trackId, attempts, lastCode |
Portal#
| Code | Raised when | Typical details |
|---|---|---|
SII_PORTAL_LOGIN_FAILED | The login gave no session. | operation, portalMessage |
SII_PORTAL_CERT_NOT_ENABLED | The certificate is not enabled at the SII as a login method. | operation, portalMessage |
SII_PORTAL_SESSION_LIMIT | The SII reports too many authenticated sessions for the RUT. | operation |
SII_PORTAL_SESSION_EXPIRED | The portal keeps asking to authenticate. | operation, step |
SII_PORTAL_BLOCKED | The SII firewall or a captcha blocked the request. | operation, step, httpStatus, portalMessage |
SII_PORTAL_REJECTED | The portal understood the request and refused it. details.rejection says why. | rejection, operation, plus extras such as portalMessage, file, httpStatus |
SII_PORTAL_UNEXPECTED_PAGE | The portal answered a page that cannot be interpreted. | operation, step, portalMessage, body |
Optional dependencies#
| Code | Raised when | Typical details |
|---|---|---|
DEPENDENCY_UNAVAILABLE | A function of dte-sdk/pdf needs pdf-lib or bwip-js and it is not installed. | module, reason |
PDF_RENDER_FAILED | The PDF library failed while drawing a printed sample. | reason, type, folio |
Retrying#
isRetryableError(error) is true for:
| Error | Condition |
|---|---|
TRANSPORT_FAILED, REQUEST_TIMEOUT | always |
SII_RESPONSE_INVALID | always (a page that cannot be read is usually a passing failure of the service) |
SII_QUERY_ERROR | always |
SII_HTTP_ERROR | httpStatus 429, 405 or 5xx |
SII_UPLOAD_REJECTED | status 9 ("system blocked") |
It is false for everything else, including TRANSPORT_TLS (a misconfigured trust store does not fix itself) and SII_TOKEN_INVALID (the client already renews the token once). The SII client uses it as the default of retry.retryOn, with exponential backoff and six attempts. Two things to remember:
- An upload or a claim is not retried just because the error is retryable. Those calls change state at the SII and are sent again only while no answer has arrived.
SII_UPLOAD_REJECTEDwith status 9 is "retryable" by this function, but an upload that got that answer is never re-sent automatically: you decide. See the retry policy. - The portal has its own rule,
isRetryablePortalErrorfromdte-sdk/portal: transport failures, timeouts, HTTP 502, 503 and 504, and the intermittent TLS failures that the SII produces with a client certificate (EPROTO). A session limit, a block or a rejection is never repeated, because repeating makes them worse. Its policy,DEFAULT_PORTAL_RETRY_POLICY, is 4 attempts at 2, 4 and 8 seconds, without jitter.
Portal rejections#
A SII_PORTAL_REJECTED is the portal refusing a request on business grounds: the company is not enrolled, the quota is exhausted, the backup is blocked. details.rejection is a PortalRejection code. portalRejectionMessage(rejection) and the PORTAL_REJECTIONS map, both from dte-sdk/portal, give the full explanation in Spanish, written for the operator, with what to do about it.
import { isDteError } from 'dte-sdk'
import { portalRejectionMessage, requestCaf, type PortalRejection } from 'dte-sdk/portal'
try {
await requestCaf(portal, { type: 33, quantity: 50 })
} catch (error) {
if (isDteError(error) && error.code === 'SII_PORTAL_REJECTED') {
console.error(portalRejectionMessage(error.details.rejection as PortalRejection))
console.error(error.details.portalMessage) // what the portal said, when it said something
} else {
throw error
}
}The codes, by area:
| Area | rejection | Meaning (short) |
|---|---|---|
| Access | inPersonRequired | The legal representative must complete the enrolment in person at an SII office. |
companyNotAuthorized | The company is not authorized as an electronic issuer in this environment. queryAuthorizedIssuer raises it for a RUT that is not in the public registry. | |
optionNotAuthorized | The SII says the option is not authorized, without a cause; usually the company is not authorized yet. | |
userWithoutPermission | The certificate's RUT has no permissions in the company in this environment, or the SII says the user is not authorized to request stamping ("No está autorizado para solicitar timbraje"). | |
personWithPendingIssues | The authenticated person has pending matters with the SII. | |
activityVerificationPending | The RUT has no activity verification for this document type. | |
notEnrolled | The company is not enrolled in the electronic invoice application, so the test set cannot be downloaded. | |
| Folios and CAF | stampingBlocked | The SII does not authorize stamping: there are enough unused folios. |
typeNotEnabled | The SII does not offer this document type for stamping for this RUT. | |
quotaInsufficient, quotaExceeded, quotaCapReached, foliosShort | The SII authorizes fewer folios than asked, or the request exceeds the maximum. | |
noAnnullableFolios | Stamping is limited and no folios remain that can be annulled. | |
stampingUncertain | The SII showed the request form again without giving folios or saying why, so it is not known whether it authorized the range. The request is not sent again: check the reobtainable ranges before asking again. requestCafInBatches reports it as such. | |
rangeAlreadyAuthorized, rangeAnnulled | The range was already authorized (it can be obtained again) or has been annulled. | |
reobtainFormMissing, reobtainDownloadMissing, reobtainCafMissing | The SII did not return the form, the download or the CAF when obtaining a range again. A login page in their place is SII_PORTAL_SESSION_EXPIRED. | |
| Certification | advanceRefused, declarationDeclined | The portal did not advance the application or did not offer the declaration form. |
exchangeUploadRefused | The exchange portal refused a response file (details.file). | |
samplesReviewRefused, sampleUploadRefused | The printed samples portal did not create the review, or refused a PDF with a 4xx status. | |
receiptsNotEnrolled, receiptsTrackIdRefused, receiptsComplianceRefused | The receipts application refused: not enrolled, the set's track id, or the compliance declaration. | |
| MIPYME backup | backupCaptcha | The SII turned on a captcha. Try later, never in a loop. |
backupIndeterminate | The page gave several messages and no single verdict. Try later. | |
backupNoData | The SII says there is no information for this RUT. A definitive answer. | |
backupRefused, backupNoCompany | The portal refused, or answered the company selection page without a message. | |
backupDayOverLimit, backupDayUnknownTypes | A single day exceeds the 20 documents per download and cannot be split. Nothing was downloaded. | |
| Register | purchaseChangeNotConfirmed | The SII did not confirm a change in the purchase register, but it may have been applied: read the document again before retrying. |
| Activity verification | activityVerificationRefused | The SII answered the activity verification with an error code; its words go in details.portalMessage when it said something. |
See Portal and Register for the functions behind them.
Status codes#
The SII reports outcomes with short codes. The SDK returns them as data (they are not exceptions), classifies them and gives their Spanish gloss. The glosses are in Spanish because they are the SII's own vocabulary.
import {
classifyDocumentStatus,
classifyUploadStatus,
documentStatusMessage,
isRetryableStatus,
isTokenErrorStatus,
uploadStatusMessage,
} from 'dte-sdk'
classifyUploadStatus('EPR') // 'accepted'
classifyUploadStatus('RCT') // 'rejected'
classifyUploadStatus('-11') // 'queryError': retry
classifyUploadStatus('RLV') // 'rejected' on SOAP…
classifyUploadStatus('RLV', 'rest') // …and 'accepted' on the receipts REST API
classifyUploadStatus('XYZ') // 'unknown'
classifyDocumentStatus('FAU') // 'notReceived'
uploadStatusMessage('EPR') // 'Envío procesado'
documentStatusMessage('FAU') // 'DTE no recibido por el SII'
isRetryableStatus('-11') // true
isTokenErrorStatus('-3') // true: renew the token and try once moreUpload statuses#
The ESTADO of QueryEstUp (or the REST estado) of an envelope or a book. classifyUploadStatus(code, channel = 'soap') returns one of these classes; uploadStatusMessage(code, channel) the gloss, or Estado desconocido: <code>.
| Class | Codes |
|---|---|
accepted | EPR (envío procesado), LOK (envío de libro aceptado) |
acceptedWithObjections | RPR (aceptado con reparos) |
pending | REC (recibido, esperando validación), SOK (schema validado, validando firma), FOK (firma validada, procesando), PRD (en proceso, validando carátula), CRT (carátula validada, finalizando), DNK (en proceso de revisión), PDR (en proceso, validando), LSO (schema de libro correcto, procesando contenido), 0 (enviado, pendiente de validación) |
rejected | RPT (rechazado por schema), RFR (error en firma), VOF (error interno del SII), RCT (error en carátula), RCH (rechazado), RCO (envío rechazado), RSC (error en schema), RLV (error en documento; success on REST), LNC (tipo de envío de libro no corresponde), LRH (libro rechazado con historial) |
queryError | -1 to -14: failures of the query service, not of your file. -3 and -13 mean the token was refused. |
unknown | Anything else. |
isRetryableStatus(code) is true for the queryError class. isTokenErrorStatus(code) is true for -3, -13, 001, 002 and 003: the client renews the token once when it sees one.
Reception statuses#
The STATUS of the RECEPCIONDTE answer that DTEUpload gives when it takes (or refuses) a file. RECEPTION_STATUS is the catalog, classifyReceptionStatus(code) the class and receptionStatusMessage(code) the gloss. SII_UPLOAD_REJECTED.details.status is this code as a number.
STATUS | Class | Meaning |
|---|---|---|
0 | received | Envío recibido, TrackID asignado. |
1 | rejected | El remitente no tiene permiso para enviar por esta empresa. |
2 | rejected | Error en el tamaño del archivo (muy grande o muy chico). |
3 | rejected | Archivo cortado o incompleto. |
5 | notAuthenticated | No autenticado: token ausente o vencido. The client renews the token and sends again once. |
6 | rejected | Empresa no autorizada a enviar archivos. |
7 | schemaInvalid | Esquema inválido o archivo ya enviado (ver DETAIL). The client tells them apart: schema or charset errors (CHR-…, SCH-…, cvc-…) are a rejection; otherwise, with a TrackID in the answer, it is a duplicate. |
8 | rejected | Error en la firma del documento. |
9 | rejected | Sistema bloqueado, reintentar más tarde. The only one that clears by itself. |
99 | alreadyUploaded | Archivo ya fue enviado anteriormente. A duplicate when the answer names the TrackID. |
Document statuses#
The ESTADO of QueryEstDte (or the REST codigo) of one document. classifyDocumentStatus(code) and documentStatusMessage(code); any negative code is a queryError, anything else unlisted is unknown.
| Class | Code | Gloss |
|---|---|---|
received | DOK | Documento recibido por el SII, datos coinciden |
receivedWithMismatch | DNK | Documento recibido por el SII, datos no coinciden |
notReceived | FAU | DTE no recibido por el SII |
notAuthorized | FNA | DTE no autorizado |
EMP | Empresa no autorizada a emitir DTE | |
modified | TMD, TMC | Existe nota de débito o de crédito que modifica textos |
MMD, MMC | Existe nota de débito o de crédito que modifica montos | |
cancelled | AND, ANC | Existe nota de débito o de crédito que anula el documento |
FAN | Folio anulado |
Claim and transferability codes#
The registry answers with a codResp number. claimResponseMessage(code) gives the gloss (Código de respuesta desconocido: <code> for a code not listed), and CLAIM_RESPONSE_CODES is the catalog.
codResp | Gloss |
|---|---|
0 | Acción completada OK |
1 | RUT del emisor erróneo |
2 | Folio erróneo |
3 | El tipo de documento no corresponde |
4 | Acción inválida |
5 | DTE ya reclamado |
6 | No se puede otorgar recibo de mercaderías a un DTE reclamado |
7 | Evento registrado previamente |
8 | Han pasado más de 8 días desde la recepción del documento |
9 | No existen registros para los parámetros indicados |
10 | DTE no recibido en el SII desde el 14-01-2017 |
11 | No se puede reclamar un DTE aceptado previamente |
12 | No se puede aceptar un DTE rechazado previamente |
13 | No se puede reclamar un DTE con recibo de mercaderías |
14 | Solo la empresa receptora o emisora puede consultar |
15 | Lista de eventos del documento |
16 | El documento no tiene eventos registrados |
17 | Solo la empresa receptora puede registrar eventos |
18 | Documento no recibido en el SII |
19 | Reclamo por mercaderías ya realizado |
27 | No se puede registrar un evento de un DTE pagado al contado o gratuito |
-1 | Error interno del SII, reintentar |
queryTransferable answers with codes 20 to 25. transferabilityMessage(code) gives the gloss, and TRANSFERABILITY_CODES the catalog, with what each implies:
codResp | transferable | Gloss |
|---|---|---|
20 | false | Tipo de documento no cedible |
21 | false | No cedible: anulado por nota de crédito dentro de los 8 días |
22 | null | Sin registro de reclamo ni de recibo de mercaderías |
23 | true | Cedible, sin reclamos registrados |
24 | false | No cedible: reclamado por el receptor |
25 | true | Cedible: transcurridos 8 días sin reclamo, se presume recibido |
The events that listClaimEvents reports (CLAIM_EVENT_CODES): ACD (acepta contenido), RCD (reclamo al contenido), ERM (otorga recibo de mercaderías o servicios), RFP (reclamo por falta parcial), RFT (reclamo por falta total), NCA (nota de crédito de anulación), ENC (otra nota de crédito) and PAG (DTE pagado al contado, which the SII registers itself).
Code 27 is the registry refusing an event on a document paid in cash or free: production answered it on 11 November 2026 to an acceptance of an invoice whose events listed PAG alone. receiverAnswer reads such a document as paidInCash, so there is nothing to send (see receiverAnswer). Before 0.8.0 the catalog did not know it, and claimResponseMessage(27) gave Código de respuesta desconocido: 27.
Next steps#
- SII web services shows where these errors and statuses come from, and how retries and token renewal work.
- Portal explains the session, the limits and the refusals of the portal.
- Security says what to keep out of your logs.