j:jurat

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.