Verify the chain
What the chain proves — and what it does not
It proves that a given root existed by a given time.
It does not prove that any measurement is true.
Those are two different claims and we never blur them. Anchoring stops a record being altered after the fact; it says nothing about whether the observation was correct when it was made. Anyone telling you otherwise about any anchored dataset — including ours — is overselling it.
The chain and the records on this site cover different ranges
The chain below runs REL-SNAP-001 to REL-SNAP-016. The endpoint records elsewhere on this site are built from an export covering REL-SNAP-002 to REL-SNAP-016. Those are not the same set, and the difference is stated here rather than left for a reader to discover.
In the chain but not in the records: REL-SNAP-001. That snapshot is published, anchored and verifiable exactly like the others — its row is in the table below and you can recompute its root. It is excluded from the export because it measured a capped sample whose control group overlapped its primary group, so it holds more rows than it holds distinct endpoints and cannot be turned into a table keyed on the endpoint. Excluding it is a limit of the derived view, not a retraction: nothing about it has been withdrawn, and the published entry stands.
The published snapshots
This list is current as of 2026-08-20. It is a dated list, not a live view — if you are reading it much later, there may be more snapshots than appear here. Anchored fields are read from the published snapshot entry, cited by identifier. Locator fields identify the entry and the account that published it. Derived fields come from our own manifest and are not part of what the chain commits to. Each entry is verifiable independently at https://gist.github.com/<gist_id>.
Each snapshot is cited by identifier only. An account-qualified link stops resolving if the publishing account is ever renamed, and a citation inside a permanent record should not depend on a name that can change.
The identity publishing this chain changed at REL-SNAP-009. Earlier entries were published under a different identity and remain there permanently: gists cannot be transferred between accounts, and re-publishing them would mint new creation timestamps, destroying the very commitment they exist to make. So the record is deliberately not uniform, and that non-uniformity is the evidence it was never rewritten. The merkle chain verifies identically across the change, because the publishing identity is not an input to any leaf, any root, or any verification step.
Concretely: REL-SNAP-009’s prev_root is 8b693174c107fb55f0951995bec23dd8d495374f9a79687b99f38fcf4e3a8d52, which is exactly REL-SNAP-008’s merkle_root. The link across the change is the same arithmetic as every other link, and you can recompute it from the two published entries.
Every entry resolves by identifier alone — no account name, no permission, no cooperation from us — so the list below is complete and checkable exactly as it stands. What is not complete is any single publisher listing: no one account lists every entry, which is why each snapshot is cited by identifier and never by an account-qualified link.
Which columns the chain actually commits to. The published entry for each snapshot carries exactly these fields: snapshot_id, captured_at_utc, merkle_root, prev_root, method. So Captured and Root are checkable against the entry itself. Endpoints is not — it comes from our own manifest and is not part of the anchored commitment. It is shown because it is useful, and marked because presenting it identically to the checkable columns would imply a guarantee it does not carry. Nor is the publishing identity, and for a different reason: it is public and anyone can check it on the entry itself, but it is not part of any leaf, any root, or any verification step — which is precisely why the chain verifies identically across the change at REL-SNAP-009.
Where an observation was made from. Every snapshot records avantage — the machine the probes went out from. It matters because a server can answer one network and refuse another: a datacentre address is sometimes blocked where a residential one is not, and that difference is a fact about the path, not about the server. This table spans 2 vantages: residential-local, hetzner-nbg1. Rows either side of a change are not comparable on this point. 3 of them predate the field entirely and appear asnot recorded — a declared state, not a gap. Reading it as though it meant the same machine would be a guess.Vantage is committed to the public archive but is not one of the five fields inside the merkle commitment, so it is not chain-verifiable — the same standing as the endpoint count beside it.
The first snapshots were not yet on a schedule. The first 4 — REL-SNAP-001 to REL-SNAP-004 — were run by hand, before any scheduler existed, and their capture times span 08:01 to 17:06 UTC. Everything after them was captured by a schedule, in the 08:00 UTC hour. A reader comparing an early snapshot with a later one is comparing different times of day, which moves traffic, maintenance windows and rate-limit buckets — so those rows carry that difference in addition to whatever else changed. It is stated here for the same reason the basis column exists: snapshots are not all the same shape.
| Snapshot | Captured | Endpoints (not anchored) | Basis | Vantage (not anchored) | Root | Read it |
|---|---|---|---|---|---|---|
| REL-SNAP-001 | 3200 | capped sample — 3,000 primary (cap 3,000) + 200 control | not recorded | ff17c9b33971441a… | human ·API | |
| REL-SNAP-002 | 10494 | census — every deduped remote endpoint in the registry | not recorded | db87347d6922d627… | human ·API | |
| REL-SNAP-003 | 10536 | census — every deduped remote endpoint in the registry | not recorded | 5f2db20bcf3a3f3c… | human ·API | |
| REL-SNAP-004 | 10637 | census — every deduped remote endpoint in the registry | residential-local | 1d0e1fe621dde25e… | human ·API | |
| REL-SNAP-005 | 10707 | census — every deduped remote endpoint in the registry | residential-local | 1497fc262cd0df17… | human ·API | |
| REL-SNAP-006 | 10793 | census — every deduped remote endpoint in the registry | residential-local | 9eceb703b84faeec… | human ·API | |
| REL-SNAP-007 | 10904 | census — every deduped remote endpoint in the registry | residential-local | 82589400f131aeb5… | human ·API | |
| REL-SNAP-008 | 11037 | census — every deduped remote endpoint in the registry | residential-local | 8b693174c107fb55… | human ·API | |
| REL-SNAP-009 | 11155 | census — every deduped remote endpoint in the registry | residential-local | b0e3fd9a92db5afa… | human ·API | |
| REL-SNAP-010 | 11247 | census — every deduped remote endpoint in the registry | residential-local | e118ca0aab405985… | human ·API | |
| REL-SNAP-011 | 11371 | census — every deduped remote endpoint in the registry | residential-local | 0b645390db06d592… | human ·API | |
| REL-SNAP-012 | 11584 | census — every deduped remote endpoint in the registry | residential-local | 2b3c0cc88ef52a1f… | human ·API | |
| REL-SNAP-013 | 11658 | census — every deduped remote endpoint in the registry | residential-local | 37d41e69788c7c2f… | human ·API | |
| REL-SNAP-014 | 12043 | census — every deduped remote endpoint in the registry | residential-local | d6753b3998f541a9… | human ·API | |
| REL-SNAP-015 | 12324 | census — every deduped remote endpoint in the registry | hetzner-nbg1 | 1d649e84476aefad… | human ·API | |
| REL-SNAP-016 | 13131 | census — every deduped remote endpoint in the registry | hetzner-nbg1 | 17511d860b51f4ff… | human ·API |
Do not read the Endpoints column as a trend. The first snapshot was a capped sample, not a census, so the step from it to the second measures a change in what we looked at — not growth in the population. The selection rule it used is written out on the methodology page, where it is marked retired.
Check the chain links
Each snapshot names its predecessor's root. Fetch any two consecutive snapshots and confirm that the later one's prev_root equals the earlier one's merkle_root, byte for byte. A break anywhere means the sequence you are holding is not the sequence that was published.
For the most recent entry, REL-SNAP-016, the root is 17511d860b51f4ff351e1d73c0277000b189f9db5c0b0bd4d17fb06ceac9e13e and it follows 1d649e84476aefad29ab8762ae25dc2e6ef9f1e1341b9589b57629fc3b9fad08.
Recompute a root
The method is stated so it can be reimplemented without our code:
- A leaf is the SHA-256 of the canonical JSON encoding of one record.
- Leaves are sorted ascending as lowercase hexadecimal strings.
- A parent is the SHA-256 of the two child hex strings concatenated as text.
- A level with an odd number of nodes duplicates its last node.
- A single leaf is its own root. An empty set is rejected rather than given a root.
Canonical JSON means: object keys sorted ascending, arrays left in order, integers only — a non-integer is rejected rather than rounded, because two implementations formatting the same float is a classic way to produce different bytes for the same value — undefined keys dropped, explicit nulls kept, no whitespace.
The limitation, stated rather than buried
The published export is a derived view of the archive. It deliberately omits the raw error text a server returned, because republishing a third party's own words on a page about that third party is not something a record should do. One consequence follows directly: an export record cannot be re-hashed into its own leaf.
So every exported record carries its leaf_hash instead, and the chain you can verify runs:
leaf_hash → inclusion proof → merkle_root → published snapshot
That establishes the leaf is committed to the published root. It does not establish that the leaf corresponds to the record shown beside it — proving that step requires the raw record, which the archive holds. This is the same shape as a transparency log, and the same limit applies to those. We would rather state it than let a reader discover it later.