Evidence & verification
Official-source captures, the stored PDF, the signed receipt and how to verify a report.
Base URL https://api.offendersearch.appOfficial-source evidence
Every live check in an order — whether it finds a match or not — keeps hash-pinned evidence, so a clear can be shown, not only asserted. Each capture records its time, a SHA-256 hash and the bytes, and says which kind it is:
| `kind` | What it is |
|---|---|
source_response | The official source’s own response, as received: URL, HTTP status, content type and body. Recorded wherever the live check receives the source’s response directly. |
parsed_result | Our structured reading of what that source’s live search returned, as canonical JSON. Recorded for every live sex-offender check, so no check is ever without evidence. |
service_result | For criminal searches: the criminal records service’s live answer for that source. The court’s own response bytes are not yet carried across to the order. |
A body over 2 MB is stored up to the limit with truncated: true — its sha256 is still the hash of the full body — and at most 25 captures are kept per source. Captures are kept for as long as the order’s evidence record.
{
"orderId": "fo_7c1e9b2a4d3f",
"captures": [
{ "captureId": "cap_31f0a9", "searchId": "fs_a14c", "source": "WI",
"kind": "source_response",
"url": "https://…official registry search page…",
"retrievedAt": "2026-10-02T14:01:31Z", "httpStatus": 200,
"contentType": "text/html; charset=utf-8",
"sha256": "e3b0c442…b855", "size": 48213, "truncated": false },
{ "captureId": "cap_31f0b2", "searchId": "fs_a14c", "source": "WI",
"kind": "parsed_result", "url": null,
"retrievedAt": "2026-10-02T14:01:31Z", "httpStatus": null,
"contentType": "application/json",
"sha256": "9f86d081…0f00", "size": 2210, "truncated": false }
]
}GET /v1/fcra/orders/{id}/evidence/{captureId} returns one capture’s stored bytes, with its original content type and the headers X-Capture-SHA256, X-Capture-Kind, X-Capture-Source and X-Capture-Truncated. Only the account that placed the order can read them. Each search’s coverage rows list the captureIds taken from that source.
Every capture is either bytes a source returned or our labelled reading of them. Nothing in an FCRA report is a re-creation of a source’s page.
The stored PDF
GET /v1/fcra/orders/{id}/report?format=pdf returns the PDF rendered once, when the order completed, and stored — so its bytes, and its hash, never change. It lists every search, its coverage, every furnished record with its match basis, the withheld counts, every evidence capture with its hash, and a QR code to the order’s verification page. The PDF response carries its hash in the X-PDF-SHA256 header. format=json returns the same report as JSON, with the capture metadata in evidence[] and an integrity block holding reportSha256 (computed over the canonical JSON without that block), pdfSha256 and verifyUrl. Both are available once the order completes (409 report_not_ready before then).
The signed receipt
GET /v1/fcra/receipts/{orderId} — public, no key — returns an Ed25519-signed receipt. The signature covers the fields listed in signedFields: the order id, the completion time, the control version, reportSha256 and pdfSha256. The receipt contains no personal data, and returns 404 for an unknown or unfinished order.
{
"orderId": "fo_7c1e9b2a4d3f",
"completedAt": "2026-10-02T14:04:13Z",
"controlVersion": "fcra-controls-2026-10-02.1",
"reportSha256": "4b1f…9e0c",
"pdfSha256": "a90d…71b4",
"issuedAt": "2026-10-02T14:04:13Z",
"algorithm": "Ed25519",
"keyId": "fcra-receipts-1",
"signature": "Qk3x…",
"signedFields": ["completedAt", "controlVersion", "orderId", "pdfSha256", "reportSha256"],
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----"
}Verify a PDF
Anyone holding the PDF — the end user, the consumer, an auditor — can open https://offendersearch.app/verify/{orderId} (the QR code on the PDF goes there), choose the file, and check it. The browser computes the file’s SHA-256 locally and compares it with the signed pdfSha256, and checks the signature with the published key. The file is never uploaded, and the page shows only the receipt.
Offline
- Hash the file:
shasum -a 256 report.pdfand compare withpdfSha256. - Rebuild the signed payload: the
signedFieldsand their values as compact JSON with the keys sorted, no spaces. - Verify with OpenSSL 3: save
publicKeyPemaskey.pem, base64url-decodesignatureintosig.bin, thenopenssl pkeyutl -verify -pubin -inkey key.pem -rawin -in payload.json -sigfile sig.bin.