{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/attestwire-com/json-schema/attestwire-com-validation-record-schema.json",
  "title": "ValidationRecord",
  "description": "A stored validation record. IMMUTABLE — there is no update path, so this object is byte-identical on every read until it expires.\n\nREAD 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.\n\nSIGNED 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.",
  "x-generated": "2026-11-02",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/attestwire-com-openapi.json#/components/schemas/ValidationRecord",
  "type": "object",
  "required": [
    "type",
    "version",
    "id",
    "created",
    "expires",
    "document",
    "valid",
    "counts",
    "findings",
    "provenance",
    "statement"
  ],
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "attestwire.validation-record"
      ]
    },
    "version": {
      "type": "integer",
      "enum": [
        1
      ]
    },
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{32}$"
    },
    "created": {
      "type": "string",
      "format": "date-time"
    },
    "expires": {
      "type": "string",
      "format": "date-time"
    },
    "document": {
      "type": "object",
      "description": "The document, as a fingerprint. THE DOCUMENT ITSELF WAS NEVER STORED.",
      "required": [
        "sha256",
        "hash_input",
        "bytes"
      ],
      "properties": {
        "sha256": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        },
        "hash_input": {
          "type": "string",
          "enum": [
            "document-bytes",
            "xml-utf8-bytes",
            "canonical-json"
          ],
          "description": "`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": {
          "type": "integer",
          "description": "Length of what was hashed."
        }
      }
    },
    "valid": {
      "type": "boolean",
      "description": "No fatal findings. Copied from the verdict."
    },
    "counts": {
      "type": "object",
      "required": [
        "errors",
        "warnings",
        "information"
      ],
      "properties": {
        "errors": {
          "type": "integer"
        },
        "warnings": {
          "type": "integer"
        },
        "information": {
          "type": "integer"
        }
      }
    },
    "findings": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/RecordFinding"
      }
    },
    "findings_truncated": {
      "$ref": "#/$defs/FindingsTruncated"
    },
    "provenance": {
      "$ref": "#/$defs/Provenance"
    },
    "statement": {
      "type": "string",
      "description": "The liability wording, in full and verbatim on every record. Rendered unaltered on the HTML page too."
    },
    "signature_ed25519": {
      "$ref": "#/$defs/RecordSignatureEd25519"
    },
    "signature": {
      "$ref": "#/$defs/RecordSignature"
    }
  },
  "$defs": {
    "FindingsTruncated": {
      "type": "object",
      "description": "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.",
      "required": [
        "total",
        "returned",
        "cap",
        "by_severity",
        "note"
      ],
      "properties": {
        "total": {
          "type": "integer",
          "description": "Findings the engine produced, across all three severities."
        },
        "returned": {
          "type": "integer",
          "description": "Findings actually listed in this response."
        },
        "cap": {
          "type": "integer",
          "description": "The per-severity cap this deployment applied."
        },
        "by_severity": {
          "type": "object",
          "description": "Per-array totals, so you can see which one overflowed.",
          "required": [
            "errors",
            "warnings",
            "information"
          ],
          "properties": {
            "errors": {
              "$ref": "#/$defs/TruncationCount"
            },
            "warnings": {
              "$ref": "#/$defs/TruncationCount"
            },
            "information": {
              "$ref": "#/$defs/TruncationCount"
            }
          }
        },
        "note": {
          "type": "string",
          "description": "Prose explanation, safe to show a user or an agent verbatim."
        }
      }
    },
    "KositConformance": {
      "type": "object",
      "description": "When the official KoSIT validator was last run against this engine's committed fixtures, and with what.\n\n**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.",
      "required": [
        "recorded",
        "validator",
        "configuration",
        "configuration_published"
      ],
      "properties": {
        "recorded": {
          "type": "string",
          "format": "date",
          "description": "The date of the last recorded full fixture run."
        },
        "validator": {
          "type": "string",
          "description": "Version of the KoSIT standalone validator used."
        },
        "configuration": {
          "type": "string",
          "description": "Version of the XRechnung validator configuration used."
        },
        "configuration_published": {
          "type": "string",
          "format": "date",
          "description": "The date that configuration was published by KoSIT."
        }
      }
    },
    "Provenance": {
      "type": "object",
      "description": "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.\n\nWHY 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.\n\nThe 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.",
      "required": [
        "engine",
        "engine_version",
        "ruleset",
        "profile",
        "kosit_conformance"
      ],
      "properties": {
        "engine": {
          "type": "string",
          "description": "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": {
          "type": "string"
        },
        "ruleset": {
          "type": "string",
          "description": "The standard and our build of it, as one identifier to quote."
        },
        "profile": {
          "type": [
            "string",
            "null"
          ],
          "description": "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": {
          "$ref": "#/$defs/KositConformance"
        }
      }
    },
    "RecordFinding": {
      "type": "object",
      "description": "One finding as a record states it. FOUR FIELDS, not the eight a `TeachingError` carries: a record is read by a third party asking \"what did the check say\", not by the person editing the invoice, so `fix`, `example`, `docsUrl` and `field` are left in the validation response where they are useful. `message` is the engine's own text, verbatim.",
      "required": [
        "rule",
        "severity",
        "message"
      ],
      "properties": {
        "rule": {
          "type": [
            "string",
            "null"
          ]
        },
        "severity": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "fatal",
            "warning",
            "information",
            null
          ]
        },
        "message": {
          "type": [
            "string",
            "null"
          ]
        },
        "xpath": {
          "type": "string",
          "description": "Absent when the finding names no location."
        }
      }
    },
    "RecordSignature": {
      "type": "object",
      "description": "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.",
      "required": [
        "algorithm",
        "key_id",
        "value"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "enum": [
            "HMAC-SHA256"
          ]
        },
        "key_id": {
          "type": "string",
          "description": "Which signing key was used — the first 16 hex of its SHA-256. Names the key across a rotation; reveals nothing about it."
        },
        "value": {
          "type": "string",
          "description": "Lowercase hex MAC over the canonical record."
        }
      }
    },
    "RecordSignatureEd25519": {
      "type": "object",
      "description": "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`.",
      "required": [
        "algorithm",
        "key_id",
        "value"
      ],
      "properties": {
        "algorithm": {
          "type": "string",
          "enum": [
            "Ed25519"
          ]
        },
        "key_id": {
          "type": "string",
          "description": "The `kid` of the published key that made the signature."
        },
        "value": {
          "type": "string",
          "pattern": "^[0-9a-f]{128}$",
          "description": "The 64-byte signature, lowercase hex."
        }
      }
    },
    "TruncationCount": {
      "type": "object",
      "required": [
        "total",
        "returned"
      ],
      "properties": {
        "total": {
          "type": "integer"
        },
        "returned": {
          "type": "integer"
        }
      }
    }
  }
}