Evidence reports
Every completed check carries an evidence report: a document that records the verdict, the findings behind it with their sources, and the pin it was derived at. You can hand it to a compliance officer, an auditor or a counterparty, and they can confirm it is genuine without any account. The report is included in the check price and is never billed separately.
Where the link comes from
A completed check has a report object (see Address checks):
| Field | Description |
|---|---|
id | The report ID, r1_ followed by 32 characters. |
pdf_url | The PDF report. |
json_url | The machine-readable twin of the PDF. |
verification_url | The integrity lookup. |
report is null while a check is pending or failed. The link is permanent: it never expires.
Reports are opened without an API key. The report ID in the URL is the only protection, so anyone who holds the link can open the report. Treat it like the report itself: do not paste it into public tickets, chats or logs, and share it only with the people who should read the report.
Open the report
All three endpoints are public and need no Authorization header. They are rate limited per source IP address; see Rate limits and quotas.
PDF
GET /v1/reports/{token}/pdf, where {token} is the report ID.
The first time a report is opened in a given language and design, it is rendered on the spot and the request waits for it, up to about 20 seconds. Every later open streams the stored file, checked for integrity.
| Query parameter | Description |
|---|---|
lang | The report language. English when omitted. One of en, es, pt, ru, uk, tr, vi, id, zh, ja, ko, hi, ar, fr, de. |
templateId | The design by number, 1 to 6. |
templateCode | The design by name. Ignored when templateId is also sent. |
templateId | templateCode |
|---|---|
| 1 | datasheet |
| 2 | classic |
| 3 | editorial |
| 4 | signal |
| 5 | soft |
| 6 | depth |
When you send neither, the report opens in the design it was pinned to when it was created. An unknown language, id or code, or a malformed report ID, answers 400 VALIDATION_ERROR and parameter names the offending query parameter; codes are case sensitive.
If the document is still being generated and is not ready within the wait, the request answers 503 REPORT_RENDERING with Retry-After: wait that many seconds and open the same link again. If generation failed, it answers 503 REPORT_RENDER_FAILED; the message carries the short report ID to quote to support, and opening the link again after a short pause retries the render.
- curl
- Node.js
- Python
curl -s -o report.pdf "https://api.leeri.ai/v1/reports/$REPORT_ID/pdf?lang=es&templateCode=classic"
import { writeFile } from 'node:fs/promises';
const url = `https://api.leeri.ai/v1/reports/${reportId}/pdf?lang=es&templateCode=classic`;
const response = await fetch(url);
if (response.status === 503) {
console.log('not ready, retry in', response.headers.get('retry-after'), 'seconds');
} else if (response.ok) {
await writeFile('report.pdf', Buffer.from(await response.arrayBuffer()));
}
import requests
response = requests.get(
f"https://api.leeri.ai/v1/reports/{report_id}/pdf",
params={"lang": "es", "templateCode": "classic"},
timeout=60,
)
if response.status_code == 503:
print("not ready, retry in", response.headers.get("Retry-After"), "seconds")
elif response.ok:
with open("report.pdf", "wb") as f:
f.write(response.content)
Every design prints the report ID and the address of the verification page. All designs except depth also print the report hash.
JSON
GET /v1/reports/{token}/json returns the same report as data, in the usual success envelope. It is rebuilt from the verdict that was pinned when the report was registered, so it always describes that verdict, whatever has changed since.
Field in data | Description |
|---|---|
report_id | The report ID. |
schema_version | The payload version the report was registered at. Fixed for the life of the report: an older report is always served at its own version. |
payload | The report content that is hashed: the verdict, its pin, the result, the findings with their sources, exposure, paths and methodology. |
hash | algorithm (SHA-256), canonicalization (RFC 8785), value, and covers. |
hash.value is the SHA-256, in hex, of the canonical form of payload (RFC 8785 JSON canonicalization). It equals the hash registered when the link was first handed out.
hash.covers is payload for every current report: you can recompute the hash from the payload you received and compare it with hash.value. For some older reports it is unredacted_original: the hash covers an earlier form of the content that is not served, so it cannot be recomputed from the response and is confirmed through the verification endpoint instead.
Verification
GET /v1/reports/{token}/verification confirms that a report ID is genuine. It answers:
Field in data | Description |
|---|---|
valid | Always true when the ID exists; an unknown ID answers 404. |
issued_at | When the report was issued. |
payload_hash | The registered SHA-256, in hex. |
hash_algorithm | SHA-256. |
It reveals nothing about the address, the customer or the verdict, so you can use it to prove a report is authentic without exposing its content.
- curl
- Node.js
- Python
curl -s https://api.leeri.ai/v1/reports/$REPORT_ID/verification
const response = await fetch(`https://api.leeri.ai/v1/reports/${reportId}/verification`);
const { data } = await response.json();
console.log(data.valid, data.issued_at, data.payload_hash);
import requests
response = requests.get(f"https://api.leeri.ai/v1/reports/{report_id}/verification", timeout=30)
data = response.json()["data"]
print(data["valid"], data["issued_at"], data["payload_hash"])
Verify a report by hand
Anyone holding a report can check it at https://leeri.ai/verify. Paste the report ID and the page shows when the report was issued and the registered hash. Compare that hash with the one printed on the report. A different value means the document was changed after it was issued.
To check the JSON automatically, canonicalize payload with an RFC 8785 library, hash it with SHA-256 and compare the hex digest with both hash.value and the payload_hash returned by the verification endpoint.
Expected errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | A malformed report ID, or an unknown lang, templateId or templateCode. |
404 | NOT_FOUND | No report has this ID. |
429 | RATE_LIMIT_EXCEEDED | Too many requests from this IP address. Retry after Retry-After. |
503 | REPORT_RENDERING or REPORT_RENDER_FAILED | The PDF is still being generated, or generation failed. |
To replay the verdict behind a report, see Rederive.