Notarized URL snapshots and offline verification
How jurat attestations work: signed, timestamped records of exactly what a URL returned, with archived bytes, ed25519 signatures and free offline verification.
What an attestation is
An attestation is a signed record of exactly what a public URL returned at a moment in time. When you call GET /v1/snapshot?url=... ($0.01 via x402), jurat fetches the URL server-side, archives the exact bytes, and returns a record like this:
{
"version": 1,
"signed": {
"id": "99f71eb3-b1ea-48a4-9809-38bd6530fb2e",
"url": "https://example.com",
"requested_url": "https://example.com",
"fetched_at": "2026-07-20T08:06:12.134Z",
"http_status": 200,
"content_type": "text/html",
"content_length": 559,
"sha256": "ff67a9d764d6a2367a187734e697f6a53217db9a21c101d410a113ca871a299d"
},
"signature": "pEh6W3XMy8qv/z0DPQGqtSsg2VihMGas8Np0UQtLdabB2IeK5B...",
"pubkey_id": "72ea8ce97b35f632",
"redirect_chain": [],
"retrieval_url": "https://jurat.dev/v1/attestation/<id>",
"body_url": "https://jurat.dev/v1/attestation/<id>/body"
}
The record and the archived bytes are retrievable forever, free, by anyone who has the id. Redirects are followed (maximum 5), each hop recorded in redirect_chain, and the final URL is what gets attested. Non-2xx responses are attested too: proof that a URL returned a 404 at a point in time is still proof.
What it does and does not claim
An attestation proves what jurat observed at fetched_at: this URL, these bytes, this hash. It does not claim the content is true, and every record says so. jurat is a witness, not a judge.
Verifying offline
Verification never requires trusting jurat after the fact, and it is never paywalled. The signature is ed25519 over the canonical form of the signed object: JSON.stringify with lexicographically sorted keys, no whitespace, UTF-8. Public keys live at /.well-known/jurat.json.
// Node.js
import crypto from 'node:crypto';
const att = await (await fetch('https://jurat.dev/v1/attestation/<id>')).json();
const wk = await (await fetch('https://jurat.dev/.well-known/jurat.json')).json();
const raw = Buffer.from(wk.pubkeys.find(k => k.pubkey_id === att.pubkey_id).public_key_b64, 'base64');
const key = crypto.createPublicKey({ key: Buffer.concat([Buffer.from('302a300506032b6570032100','hex'), raw]), format: 'der', type: 'spki' });
const canonical = JSON.stringify(Object.fromEntries(Object.entries(att.signed).sort(([a],[b]) => a < b ? -1 : 1)));
crypto.verify(null, Buffer.from(canonical), key, Buffer.from(att.signature, 'base64')); // true
# Python
import json, hashlib
canonical = json.dumps({k: signed[k] for k in sorted(signed)}, separators=(',', ':'), ensure_ascii=False)
# verify ed25519 signature over canonical.encode() with pynacl or cryptography
Change one byte of the record and verification fails. To check the archived bytes match, hash them: sha256(body) == signed.sha256. Every attestation also carries a merkle proof to a daily anchored root once anchored; see the anchors page.
Guardrails
The fetcher only touches public HTTP(S): private, loopback, link-local and carrier-grade NAT addresses are refused at no charge, URLs with embedded credentials are refused, bodies over 10 MB are refused, and the fetch times out at 20 seconds. jurat identifies itself honestly as juratbot.