Covered verification
Every verification a paid Developer or Scale account runs is put in front of a gate. The ones that qualify get a certificate carrying a contractual warranty, and every result tells you in the response whether it qualified. There is nothing to enable and no request field to set.
The caps and the condition are on /warranty. This page is the wire format.
The coverage block #
Present on every verification result: the completed /verify/status/{task_id}
body, GET /verifications/{id}, and the
verification.completed webhook. /verify/batch and
/select fan out to tasks whose results carry it through those same two.
It describes the pair (your account, this analysis), not the analysis alone. Two accounts can hold two certificates over one cached verdict, so the block you see is never the block another customer sees. It is absent for a reader who does not own the verification.
{
"verification_id": "a1b2c3d4",
"verdict": "True",
"coverage": {
"status": "covered",
"reasons": [],
"certificate_id": "9f2c...",
"certificate_url": "https://lenz.io/certificate/9f2c...",
"as_of": "2026-09-03T10:00:00+00:00",
"currency": "EUR",
"cap": 10000,
"aggregate": 500000,
"terms_version": "v1"
}
}
| Field | Meaning |
|---|---|
status | covered · pending_timestamp · uncovered |
reasons | Why not, when uncovered. Empty otherwise. Closed vocabulary, below. |
certificate_id | 128-bit hex, or null. Rotatable — treat it as a handle, not an identifier you store forever. |
certificate_url | The public certificate page. Anyone with the link can read it, which is the point: it is what you link from a published piece. |
as_of | The date the verdict is warranted as of — the analysis time, not the issue time. |
currency | ISO 4217. The currency the cited terms_version is written in — read it, do not assume EUR. |
cap / aggregate | The caps that applied when this certificate was issued, in major units (10000 means ten thousand, not a hundred). Not cents: these are contract figures, not amounts a payment processor charges. |
terms_version | Resolves at /terms/<version>, permanently. |
pending_timestamp, and when to publish #
A certificate is signed and given a qualified timestamp by an authority on the EU Trusted
List. That takes a few minutes, and until it lands the status is
pending_timestamp.
This matters if you publish automatically. Cover requires the timestamp to
precede what you publish or send, and
verification.completed fires while the qualified timestamp is still pending. Key on the
certificate.timestamped webhook instead, or poll until
status is covered. Publishing on the earlier event builds a
back catalogue that falls outside your own warranty, and nothing will tell you.
{
"event": "certificate.timestamped",
"verification_id": "a1b2c3d4",
"task_id": "…",
"status": "completed",
"result": null,
"coverage": { "status": "covered", "certificate_url": "…", … },
"attempt": 1,
"delivered_at": "2026-09-03T10:04:11+00:00"
}
coverage, not result. This event says a timestamp
landed, not that a verdict was produced, so result is null.
Why a verdict is uncovered #
Six values, deliberately fewer than the rules behind them. Two are actionable, two are rules about the analysis itself, and two are statements about Lenz rather than about your claim.
| Reason | What it means | What to do |
|---|---|---|
plan | Not on a paid Developer or Scale plan. | Upgrade. Nothing else about the verdict was the problem. |
depth | You sent depth: "low". | Send standard. |
verdict | The verdict is not True or False. | Nothing. Mostly True, Mixed and Mostly False are never warranted — a hedged verdict is one Lenz is telling you not to lean on. |
quality | The analysis did not meet the bar: low panel confidence, too few source domains, or a degraded run. | Nothing. Re-running the same claim will usually reach the same place. |
withdrawn | Lenz has retracted this verdict. | Stop relying on it going forward. The warranty still applies to anything you published or sent before the notice. |
issue_failed | The verdict qualified and Lenz failed to issue the record. | Nothing, and contact us — this is ours, not yours. |
More than one can appear. The order is stable, and plan comes first when it
fired — every other rule is still evaluated on a Free or Plus account, so a
["plan"] on its own means the verdict would otherwise have qualified.
Fetching the certificate #
Two ways to the same bytes. The API endpoint is key-authenticated and resolves by (verification, account); the public link is what you put in an article.
GET /api/v1/verifications/{verification_id}/certificate
Authorization: Bearer lenz_…
GET https://lenz.io/certificate/{certificate_id}.json
The document carries the signed payload, the record hash, the signature and both anchors. Nothing in it names your account — it is designed to be published.
Checking it without us #
A warranty you can only check by asking the warrantor is not much of a warranty. Download the record and run the checker against it:
pip install cryptography rfc8785 rfc3161-client
curl -O https://lenz.io/verify_certificate.py
curl -O https://lenz.io/.well-known/lenz-certificate-keys.json
python verify_certificate.py certificate.json \
--keys lenz-certificate-keys.json \
--tsa-roots qtsp-roots.pem
qtsp-roots.pem is the timestamping authority’s root certificate, from the
EU Trusted List.
It recomputes the record hash under RFC 8785, checks the ECDSA P-256 signature against the
published keys, and checks the qualified timestamp against roots you chose to
trust. Lenz is not in the loop. Exit codes: 0 the record checks out, 1 the
document does not match what Lenz signed, 3 a required check could not run —
which is not a pass.
withdrawn_at sits
outside the signed payload by design, so that retracting a verdict cannot invalidate a
record someone may already be entitled to claim on — which also means it can be stripped
from a downloaded file. Check the live certificate page for current status.