---
title: "Read a stored validation record"
method: GET
path: "/v1/records/{id}"
tags: ["Records"]
---

# Read a stored validation record

`GET /v1/records/{id}`

## Request

- Base URL: `https://api.attestwire.com`
- URL: `https://api.attestwire.com/v1/records/{id}`
- Auth: none required

Returns the record created by a `POST /v1/validate?record=true` call, exactly as it was stored.

NO API KEY. The 128-bit id in the path is the credential: it is unguessable, so anyone holding the link reads the record and anyone who does not cannot enumerate their way to one. That is deliberate — the audience for a record is a buyer, an auditor or an onboarding desk, none of whom are our customers.

It is safe to hand out because of what is in it: a SHA-256 fingerprint of the document, the findings, and what ran. **The document itself was never stored**, and neither was the email or the API key of whoever ran the check.

The URL is the verification. This page is served by Attestwire from its own stored copy of the record, so whoever holds the link sees exactly what Attestwire recorded when the check ran — nobody can edit it, and the record cannot be re-issued with different findings under the same address. The link is also the only credential: the record id is 128 random bits and is not guessable, so anyone you send it to can read the record, and anyone you do not, cannot. Nothing here identifies the customer who ran the check, and the document itself was never stored — only its SHA-256 fingerprint, which you can reproduce from your own copy of the file.

IMMUTABLE, so the response carries a one-year `Cache-Control: public, max-age=31536000, immutable`. There is no update path — a record cannot be re-issued with different findings at the same address. The only thing that changes is that it stops existing 365 days after creation.

CORS is `*`, so a supplier-onboarding page can render one in the browser.

The same record with a human face is at `/r/{id}`, which is not described here because it is an HTML page rather than an API.

## Path parameters

- `id` string, required

## Response `200`

The record. Read `statement` before quoting anything else from it.

- ValidationRecord: A stored validation record. IMMUTABLE — there is no update path, so this object is byte-identical on every read until it expires. READ THE `statement` FIELD. It is the exact contract this artefact makes and it is deliberately narrow: a record of an automated software check, not a legal or tax conclusion and not an acceptance guarantee. SIGNED TWICE when the deployment has both keys. `signature_ed25519` is over the canonical JSON of every field here EXCEPT the two signatures, and anybody can check it against `GET /.well-known/attestwire-signing-keys.json`. `signature` (HMAC-SHA256) is over every field EXCEPT `signature` itself — the Ed25519 signature included — and only Attestwire can check it. Canonical means object keys sorted at every level, no whitespace. Records from before the Ed25519 key carry only `signature`; a deployment with no key configured issues records with neither.
  - `type` 'attestwire.validation-record', required
  - `version` 1, required
  - `id` string, required
  - `created` string, date-time, required
  - `expires` string, date-time, required
  - `document` object, required: The document, as a fingerprint. THE DOCUMENT ITSELF WAS NEVER STORED.
    - `sha256` string, required
    - `hash_input` 'document-bytes' | 'xml-utf8-bytes' | 'canonical-json', required: `document-bytes` — the submitted file byte for byte, as it arrived: XML in whatever encoding it declares, or the PDF itself; `shasum -a 256 invoice.xml` (or `invoice.pdf`) reproduces it. `canonical-json` — the invoice model with keys sorted and whitespace removed, used when the invoice arrived as our JSON model and there was no file to hash. `xml-utf8-bytes` is on records stored before 2026-09-24, when a document was fingerprinted as its text re-encoded as UTF-8 — the file's own bytes only for a UTF-8 file without a byte-order mark.
    - `bytes` integer, required: Length of what was hashed.
  - `valid` boolean, required: No fatal findings. Copied from the verdict.
  - `counts` object, required
    - `errors` integer, required
    - `warnings` integer, required
    - `information` integer, required
  - `findings` RecordFinding[], required
    - `rule` string, nullable, required
    - `severity` 'fatal' | 'warning' | 'information' | 'null', nullable, required
    - `message` string, nullable, required
    - `xpath` string: Absent when the finding names no location.
  - `findings_truncated` FindingsTruncated: Present ONLY when a findings array was truncated, and absent — not null, not zeroed — otherwise. Each of `errors`, `warnings` and `information` is capped independently; past the cap the array is cut and this object states what was left out. `valid` still reflects the whole document: truncation changes what is printed, never the verdict. Reaching the cap takes a hundred-plus invoice lines on which every field is wrong, and at that scale the errors are systematic — every distinct problem in the document already appears in what you were given. Treat it as a hint to fix the pattern and re-validate, not as an error.
    - `total` integer, required: Findings the engine produced, across all three severities.
    - `returned` integer, required: Findings actually listed in this response.
    - `cap` integer, required: The per-severity cap this deployment applied.
    - `by_severity` object, required: Per-array totals, so you can see which one overflowed.
      - `errors` TruncationCount, required
        - `total` integer, required
        - `returned` integer, required
      - `warnings` TruncationCount, required
        - `total` integer, required
        - `returned` integer, required
      - `information` TruncationCount, required
        - `total` integer, required
        - `returned` integer, required
    - `note` string, required: Prose explanation, safe to show a user or an agent verbatim.
  - `provenance` Provenance, required: What ran. Present on every metered response — POST /v1/validate and POST /v1/parse, JSON body or XML document, and the JSON envelope of POST /v1/generate including the `422` it returns for an invoice it will not emit. WHY IT EXISTS. Pin `@attestwire/en16931` and your CI gets the same answer for the same invoice forever; the hosted API always runs the current rule set, because a compliance service that froze its rules would be answering last quarter's regulation. Both are correct and the difference used to be invisible — a `valid: false` looked identical whether your invoice had changed or our rules had. This says which. The one metered response WITHOUT it is `/v1/generate?format=xml`, which returns the document alone: an XML file has nowhere to put this, and this API does not write its own metadata into a customer's tax document.
    - `engine` string, required: The npm package every compliance decision on this API comes from. Install this package at `engine_version` and you have the build that judged your document.
    - `engine_version` string, required
    - `ruleset` string, required: The standard and our build of it, as one identifier to quote.
    - `profile` string, nullable, required: The CIUS that ACTUALLY ran, read from the result rather than from your request: a body naming no profile is judged as `en16931`, and this is where you see that. `null` when there was no verdict to read it from.
    - `kosit_conformance` KositConformance, required: When the official KoSIT validator was last run against this engine's committed fixtures, and with what. **NOTHING IS SENT TO KoSIT WHEN YOU CALL US.** This is a dated record of a check we ran on release, not a per-call verification, and it says nothing about YOUR document.
      - `recorded` string, date, required: The date of the last recorded full fixture run.
      - `validator` string, required: Version of the KoSIT standalone validator used.
      - `configuration` string, required: Version of the XRechnung validator configuration used.
      - `configuration_published` string, date, required: The date that configuration was published by KoSIT.
  - `statement` string, required: The liability wording, in full and verbatim on every record. Rendered unaltered on the HTML page too.
  - `signature_ed25519` RecordSignatureEd25519: The `signature_ed25519` field is an Ed25519 signature over this record, which anybody can check against the public key Attestwire publishes, without asking us: it proves the record was issued by the holder of that key and has not changed by one byte since. Signed bytes: the canonical JSON of the record without `signature` and `signature_ed25519` (object keys sorted at every level, no whitespace), as UTF-8. The public key is the entry in `GET /.well-known/attestwire-signing-keys.json` whose `kid` equals `key_id`.
    - `algorithm` 'Ed25519', required
    - `key_id` string, required: The `kid` of the published key that made the signature.
    - `value` string, required: The 64-byte signature, lowercase hex.
  - `signature` RecordSignature: The `signature` field is an HMAC-SHA256 over this record, which proves the record was issued by Attestwire and has not been modified since. HMAC is symmetric: the key that signs is the key that verifies and only Attestwire holds it, so checking that signature means asking us — email hello@attestwire.com with the record id. The Ed25519 signature, on a record that carries one, is the one you can check yourself.
    - `algorithm` 'HMAC-SHA256', required
    - `key_id` string, required: Which signing key was used — the first 16 hex of its SHA-256. Names the key across a rotation; reveals nothing about it.
    - `value` string, required: Lowercase hex MAC over the canonical record.

## Other responses

- `404`: No record with that id. Two causes, and they are indistinguishable from here because an expired record leaves nothing behind: the id is wrong or incomplete, or the record is past its 365-day expiry and has been deleted. Not cached — a client that races its own validation response must not get a "gone" pinned in a shared cache.
- `500`: Unhandled fault while reading a validation record. Never expected; the body carries no detail on purpose (a fault message can quote the offending value). Retry, and email hello@attestwire.com if it persists.

---

[API](https://skmtc.dev/attestwire/apis/attestwire-api.md) · [All operations](https://skmtc.dev/attestwire/apis/attestwire-api/llms.txt) · [OpenAPI document](https://skmtc.dev/attestwire/apis/attestwire-api/revisions/0036f8e043b2?raw)
