# Evidence & verification

> Official-source captures from every live search, the stored report PDF and its hashes, the Ed25519-signed receipt, and how anyone can verify a PDF at /verify.

- **HTML:** https://offendersearch.app/docs/fcra/evidence
- **Base URL:** https://api.offendersearch.app
- **Authentication:** `X-API-Key` request header, on an account with FCRA Partner Access
- **FCRA API reference as markdown:** https://offendersearch.app/docs/fcra.md

## 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_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.

GET /v1/fcra/orders/{id}/evidence

```json
{
  "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}

```json
{
  "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`.

---

## Related

- Previous: [Results & records](https://offendersearch.app/docs/fcra/results.md)
- Next: [Controls & reason codes](https://offendersearch.app/docs/fcra/controls.md)
- Index: [FCRA API reference](https://offendersearch.app/docs/fcra.md)
