Examples · Folio X
code, copy-pasteable
§ Concrete code, not abstract API docs

The protocols, in copy-pasteable code.

For developers integrating receipts.you into a pipeline. Every snippet is the actual JSON shape, command, or call you'd use — not pseudo-code, not an OpenAPI ref. If something here doesn't reproduce, file an issue.

§ POST /api/seal

Seal a hash into a receipt.

Compute the SHA-256 and perceptual hashes locally (image bytes never leave your environment), then POST the JSON envelope below to /api/seal.

POST https://receipts.you/api/seal
Content-Type: application/json

{
  "hash": "f1a2b3c4d5...",         // 64-char hex SHA-256 of file bytes
  "phash": "0123456789abcdef",     // 16-char hex pHash (DCT-based)
  "dhash": "fedcba9876543210",     // 16-char hex dHash (gradient-based)
  "stamped_hash": "9876...",       // OPTIONAL — hash of QR-stamped composite if you're providing it
  "note": "Tweet from @example",   // OPTIONAL — short user-supplied note
  "source": "https://twitter.com/example/status/123"  // OPTIONAL
}

→ 200 OK
{
  "id": "abc123xyz456",            // 12-char receipt ID
  "receipt_url": "https://receipts.you/r/abc123xyz456",
  "timestamp": "2026-05-25T14:32:00.000Z",
  "signature": "MEUCIQ...",         // base64-encoded ECDSA P-256 signature
  "anchor_status": "pending"        // → "confirmed" after OTS cron (~30 min)
}
§ SHA-256 in the browser

The privacy-preserving hash path.

Six lines of WebCrypto. The image bytes stay in your browser; only the 64-char hex goes outbound.

async function sha256OfFile(file) {
  const buffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
  const arr = Array.from(new Uint8Array(hashBuffer));
  return arr.map(b => b.toString(16).padStart(2, "0")).join("");
}

const hex = await sha256OfFile(file);  // 64-char hex string

See the deep-dive for why this is the whole privacy story, and how to verify it in DevTools.

§ POST /api/verify

Get a verdict for a file against a receipt.

POST https://receipts.you/api/verify
Content-Type: application/json

{
  "id": "abc123xyz456",            // receipt ID (from the QR or URL)
  "hash": "f1a2b3c4d5...",         // SHA-256 of file being verified
  "phash": "0123456789abcdef",     // pHash of same file
  "dhash": "fedcba9876543210"      // dHash of same file
}

→ 200 OK
{
  "verdict": "identical",           // or "recompressed" / "similar" / "qr_pasted" / "mismatch"
  "phash_distance": 0,
  "dhash_distance": 0,
  "receipt": {
    "id": "abc123xyz456",
    "timestamp": "2026-05-25T14:32:00.000Z",
    "anchor_status": "confirmed",
    "ots_block_height": 851234
  }
}
§ Verify offline with openssl

Five commands, no service dependency.

# 1. Hash the file
openssl dgst -sha256 -hex screenshot.png
# → SHA256(screenshot.png) = <64-char hex>   (compare to receipt's "hash")

# 2. Reconstruct the signed payload
echo -n "<hash>:<iso_timestamp>" > payload.txt

# 3. Verify ECDSA signature against our public key
echo -n "<base64_signature>" | base64 -d > sig.bin
openssl dgst -sha256 -verify pubkey.pem -signature sig.bin payload.txt
# → Verified OK

# 4. Verify the OpenTimestamps anchor
pip install opentimestamps-client
echo -n "<base64_ots_proof>" | base64 -d > screenshot.png.ots
ots verify screenshot.png.ots
# → Success! Bitcoin attests data existed as of <date>

See the full walkthrough for context on each command.

§ GET /api/ots-status/<id>

Inspect a receipt's anchor state.

GET https://receipts.you/api/ots-status/abc123xyz456

→ 200 OK
{
  "id": "abc123xyz456",
  "anchor_status": "confirmed",       // or "pending"
  "ots_upgraded": true,
  "ots_block_height": 851234,
  "ots_block_time": "2026-05-25T15:02:11Z",
  "ots_proof_base64": "AHJv..."        // full OpenTimestamps inclusion proof
}
§ GET /healthz

Worker liveness.

GET https://receipts.you/healthz
→ 200 OK
{
  "status": "ok",
  "version": "<commit-sha>",
  "ots_cron_last_run": "2026-05-25T14:30:00.000Z",
  "rate_limit": { "ip_per_min": 1000, "ip_per_day": 10000 }
}
§ Rate limits

Calibrated to let real usage through.

  • 1000 requests per IP per minute
  • 10,000 requests per IP per day
  • No country-level gating

If you have a use case that legitimately exceeds these, email [email protected] and we'll figure something out. The limits exist to make trivial abuse uneconomic, not to push real users to a paid tier.

§ Receipt JSON shape

What you get back from a receipt page.

Append .json to any receipt URL to get the full machine-readable record:

GET https://receipts.you/r/abc123xyz456.json

{
  "id": "abc123xyz456",
  "hash": "f1a2b3c4d5...",
  "phash": "0123456789abcdef",
  "dhash": "fedcba9876543210",
  "stamped_hash": "9876...",
  "timestamp": "2026-05-25T14:32:00.000Z",
  "signature": "MEUCIQ...",            // base64 ECDSA P-256
  "signer_kid": "v1",                  // key ID, maps to /.well-known/receipts-pubkey.pem
  "anchor": {
    "status": "confirmed",
    "ots_proof_base64": "AHJv...",
    "ots_block_height": 851234
  },
  "note": "Tweet from @example",
  "source": "https://twitter.com/example/status/123"
}
Drop a screenshot →
free · no signup · stays in your browser