DEV Community

Samir Chatwiti
Samir Chatwiti

Posted on

Bolivia's CUF: the invoice control code you can't fake (and how to verify it in one call)

Bolivia's Sistema de Facturación Electrónica en Línea (SFV/SIAT) reached full coverage on 1 October 2025 — the last taxpayer groups are now required to emit digitally signed XML invoices to the SIN. Every one of those invoices carries a CUF (Código Único de Facturación): a long hex string that most integrations treat as an opaque token.

It isn't opaque. The CUF is deterministically derived from the invoice header — NIT of the issuer, emission timestamp, branch (sucursal), invoice number, point of sale, sector, modality — run through the SIN's Módulo 11 self-check digit algorithm and encoded in Base16, with the current CUFD control code appended.

Which means two things:

If any header field and the CUF disagree, the invoice is inconsistent — even if the XML is schema-valid.
You can verify a CUF offline, by rebuilding it. Most validators just regex it. Rebuilding it is the real check.
Rebuilding a real CUF
Run the SIN's own published example invoice through a validator that reimplements the algorithm:

BO-CUF-VERIFY: CUF VERIFIES: its Base16 prefix reproduces from the header via the
SIN Modulo 11 self-check digit (modalidad=1, tipoEmision=1, tipoFactura=1);
recovered CUFD control code = '67A75AC82F24C74'.
The official example reproduces bit for bit — and the CUFD control code pops out of the decomposition.

Now tamper with one digit of that CUF and change montoTotal. Real output:

{
"valid": false,
"documentType": "facturaElectronicaCompraVenta",
"signatureValid": false,
"errorCount": 3,
"warningCount": 3,
"findings": [
{ "level": "warning", "ruleId": "BO-CUF-VERIFY",
"text": "CUF does NOT reconcile: no standard modalidad/tipoEmision/tipoFactura combination reproduces its Base16 prefix from the header (nitEmisor=1003579028, fechaEmision=20211007090124178, sucursal=0, numeroFactura=1, pos=0, sector=1) using the SIN Modulo 11 algorithm. The CUF or a header field may be inconsistent.",
"location": "cabecera/cuf" },
{ "level": "error", "ruleId": "BO-MATH-TOTAL",
"text": "montoTotal (500) != sum(subTotal) - descuentoAdicional (99).",
"location": "cabecera/montoTotal" }
]
}
The validator tells you which header fields it tried to reconcile against — that's the difference between "your CUF is wrong somewhere" and an actionable finding.

The full pre-flight in one call
The output above comes from the Bolivia SIAT e-Invoice Validator (SFV) — keyless REST API, free tier available:

curl --request POST \
--url https://bolivia-siat-e-invoice-validator-sfv.p.rapidapi.com/validate \
--header 'Content-Type: application/xml' \
--header 'x-rapidapi-host: bolivia-siat-e-invoice-validator-sfv.p.rapidapi.com' \
--header 'x-rapidapi-key: YOUR_RAPIDAPI_KEY' \
--data-binary @factura.xml
Layers checked: official SIN XSDs per document type (including the SignatureSchema) → SIN parametric code ranges (documento de identidad, método de pago, moneda, unidad de medida) → CUF/CUFD reconstruction → totals math per the SIN's Validaciones por Documento Sector → enveloped XML-DSig (document digest + RSA-SHA256 against the embedded certificate).

Keyless and stateless: no SIN account, no private key, nothing stored — the invoice is validated in-memory and discarded. Safe to run in CI on every generated document.

Practical takeaways
Don't regex the CUF — rebuild it. A format check passes tampered and stale CUFs alike; the Módulo 11 reconstruction doesn't.
Validate before the SIN sees it. Assert on ruleIds (BO-STRUCT-, BO-CODE-, BO-CUF-, BO-MATH-, BO-SIG-*) in your tests.
Scope honestly: whether a NIT is registered or a CUFD is currently live requires the SIN's online services; the trust chain to Bolivia's national CA (ADSIB) isn't checkable offline. valid: true means "consistent and ready", not "accepted".
I maintain validators like this for five mandated e-invoicing regimes (Malaysia, Dominican Republic, Nigeria, Bolivia, Costa Rica) — overview with links: github.com/SamirChatwiti/e-invoice-validators.

Top comments (0)