Verify a validation hash
Reproduce the sha256 and rulesetSha256 on a /v1/validate response and cross-check the ruleset against the public catalog, so you can confirm the result yourself.
Every POST /v1/validate response carries two verifiability hashes and the list of rule artifacts that produced the second one. With nothing but the values in your own response (plus one public, keyless endpoint) you can confirm two things independently:
- Beliq judged the exact bytes you sent (
sha256). - The ruleset that judged them is the one Beliq publicly pins (
rulesetSha256+rulesetArtifacts, cross-checked againstGET /v1/rulesets).
New to this? How verification works explains what a hash is and why it is on every result, in plain language, before the commands below.
For the full inventory of pinned artifacts, versions, sources, and checksums, see Validation artifacts. For the endpoint itself, see POST /v1/validate.
What is in the response
| Field | What it is |
|---|---|
sha256 |
The exact request body Beliq validated (lowercase-hex SHA-256). Always present. |
rulesetSha256 |
A single fingerprint of the rule artifacts the engine ran to judge the document. Present whenever a ruleset ran. |
rulesetArtifacts |
The component rows the fingerprint is built from: { key, version, fileSha256 } for each artifact that ran. |
driftCheckedAt |
A timestamp, not a hash: the ISO 8601 UTC instant when the artifacts above were last confirmed to still match their upstream authority sources. Present alongside rulesetSha256 when that confirmation is current for every artifact. See When the ruleset was last drift-checked. |
An example response (Netherlands NLCIUS), trimmed to the verifiability fields:
{
"success": true,
"data": {
"valid": false,
"format": "ubl",
"profileDetected": "netherlands-nlcius",
"sha256": "8b508d3648dbee66ca6798e6bc812842204974328fd626e3d9cdf60c98782cd1",
"rulesetSha256": "7a590a263701e50305677ec253c61d83a13336c0e8b10b01674573347a52de00",
"rulesetArtifacts": [
{ "key": "en16931_ubl_schematron", "version": "1.3.16", "fileSha256": "39f9d282867f1a49e7708d9e29a53da89643e1ee56f10cec1ebcf1277595fcbd" },
{ "key": "netherlands_nlcius_schematron", "version": "2.0.3.13", "fileSha256": "73da3db0b8cf6a37a8ad17051ac1603a83a695e2fa066820803aa1283a06e140" }
],
"driftCheckedAt": "2026-07-03T04:12:07Z"
}
}
Step 1: confirm the input hash
sha256 is the SHA-256 of the raw bytes you posted, computed before any processing. Hash your file and compare:
sha256sum invoice.xml
# 8b508d3648dbee66ca6798e6bc812842204974328fd626e3d9cdf60c98782cd1 invoice.xml
If that matches the sha256 in the response, the result applies to your exact file (no truncation, re-encoding, or substitution in transit).
Step 2: reproduce rulesetSha256 from the components
rulesetSha256 is deterministic. It is the SHA-256 of a canonical string built from the rulesetArtifacts rows:
- Format each row as
"<key>@<version>=<fileSha256>". - Sort the lines (byte order).
- Join them with a single newline (
\n), with no trailing newline. - Take the SHA-256 of the UTF-8 bytes.
Same document and same pinned ruleset always give the same hash. With the response saved as resp.json:
jq -rj '.data.rulesetArtifacts | map("\(.key)@\(.version)=\(.fileSha256)") | sort | join("\n")' resp.json | sha256sum
# 7a590a263701e50305677ec253c61d83a13336c0e8b10b01674573347a52de00 -
The same recipe in Python:
import json, hashlib
data = json.load(open("resp.json"))["data"]
lines = sorted(f'{a["key"]}@{a["version"]}={a["fileSha256"]}' for a in data["rulesetArtifacts"])
combined = hashlib.sha256("\n".join(lines).encode()).hexdigest()
print(combined == data["rulesetSha256"]) # True
The trailing-newline rule matters: jq -rj (raw, join-output) and "\n".join(...) both emit the string with no trailing newline, which is what the engine hashes. A loop that appends \n after each line would hash different bytes and not match.
rulesetSha256 and rulesetArtifacts are absent only when no ruleset ran (for example a body rejected as empty or unparseable before any artifact executes).
Step 3: cross-check the ruleset against the public catalog
Step 2 confirms the fingerprint is internally consistent. To confirm the artifacts are the ones Beliq pins for everyone, cross-check each component against the public catalog at GET /v1/rulesets (keyless, no quota cost):
curl -s https://api.beliq.eu/v1/rulesets | jq '.data.artifacts'
The artifacts array lists every rule artifact that can run for a publicly-supported format, each as { key, version, fileSha256 }. Each component in your response should appear there byte for byte (same key, version, and fileSha256). The per-request component and the catalog row come from the same pinned files, so the match is exact, not approximate.
Scope: publicly-supported formats
The public catalog lists publicly-supported formats only. A provisional national format (for example Italy FatturaPA, Spain Facturae, Slovenia e-SLOG) is validated against real, pinned artifacts, but those artifacts are intentionally kept off the public catalog. So for a provisional-format response the Step 2 reproduction still holds (it uses only your own components), while the Step 3 catalog match finds nothing. That is by design, not a data gap.
When the ruleset was last drift-checked
driftCheckedAt is a freshness signal, not a hash you reproduce. It is an ISO 8601 UTC timestamp (seconds precision, Z suffix, for example "2026-07-03T04:12:07Z") reporting when the pinned artifacts that judged your document were last confirmed to still match their upstream authority sources.
Beliq re-verifies its pinned ruleset against the authorities’ published sources on a nightly schedule. driftCheckedAt reports the most recent such check that the running engine can vouch for. Where rulesetSha256 answers “is the artifact Beliq ran the one it pins?”, driftCheckedAt answers “and when did Beliq last confirm that pin still matches what the authority publishes?”.
Read it as a conservative lower bound: “verified at least as recently as this instant.” The value reflects the drift run baked into the deployed engine build, so it can be older than Beliq’s latest nightly check, but it is never falsely fresh. Beliq never reports a time more recent than a check the running build actually passed.
The field is present only alongside rulesetSha256, and only when every pinned artifact used in your validation cleared a clean, matching check. If any artifact cannot be confirmed clean, driftCheckedAt is simply omitted. It never blocks or degrades a validation, so its absence means “freshness not confirmed for this response,” never that a check failed.
What the ruleset hash proves
For XSD-only national formats (Italy, Spain, Slovenia), the pinned artifact is the authority’s root XSD, vendored byte for byte. You can reproduce its fileSha256 directly from the authority’s published download (sources are listed on the Validation artifacts page).
For the EN 16931 Schematron families (EN 16931 base, XRechnung, Peppol BIS, Factur-X, NLCIUS), the pinned artifact is the compiled XSLT, which Beliq compiles from the authority’s published .sch sources at vendor time. So rulesetSha256 proves that the artifact Beliq ran is the one Beliq pins and re-verifies on a schedule. It is not a value you can reproduce by hashing the authority’s raw .sch file, because the compilation is Beliq’s. This is the honesty ceiling of the ruleset hash: it makes the ruleset that judged you transparent and self-consistent, and ties it to a value Beliq publishes for everyone.