Offendersearch
FCRA API Reference

Evidence & verification

Official-source captures, the stored PDF, the signed receipt and how to verify a report.

Base URL https://api.offendersearch.app

Official-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_responseThe 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_resultOur 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_resultFor 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.

GET /v1/fcra/orders/{id}/evidence
{
  "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.

GET /v1/fcra/receipts/{orderId}
{
  "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.pdf and compare with pdfSha256.
  • Rebuild the signed payload: the signedFields and their values as compact JSON with the keys sorted, no spaces.
  • Verify with OpenSSL 3: save publicKeyPem as key.pem, base64url-decode signature into sig.bin, then openssl pkeyutl -verify -pubin -inkey key.pem -rawin -in payload.json -sigfile sig.bin.