Pith. sign in

CONTRACT 9 paper endpoints 5 json schemas

Every Pith record is public JSON: signed, versioned, and fetchable without a key.

live example · response captured 2026-07-09 · hashes shortened

curl -s https://pith.science/pith/34BOUJ2M/integrity.json
{
  "pith_number": "34BOUJ2M",
  "arxiv_id": "34BOUJ2M",
  "integrity": {
    "available": true,
    "summary": {"critical": 0, "advisory": 0, "informational": 0},
    "clean": true,
    "findings": [],
    "snapshot_sha256": "c28c3603d3b5d939..."
  },
  "events": [],
  "protocol_url": "https://pith.science/pith-integrity-protocol"
}

Journals, repositories, crawlers, and agents can consume these records directly. Every endpoint on this page is an unauthenticated HTTP GET returning JSON or SVG; findings and events come back with the hashes and Ed25519 signatures needed to re-verify them offline.

What {id} accepts

resolution rules tested live 2026-07-09

identifier in handexamplehow to use it
short Pith Number suffix34BOUJ2M (from pith:34BOUJ2M)works as {id} on every endpoint below
full Pith Number suffix34BOUJ2MZM6ASZFWKVDHL2BAZ3works as {id} on every endpoint below
raw arXiv ID2605.13860accepted directly by integrity.json, claims.json, and the badge; for the rest, resolve to a Pith Number first
arXiv ID or DOI, unresolved10.xxxx/...GET /api/pith-number/resolve?arxiv=2605.13860&json=true (or doi=) returns the full record; omit json=true to get a 302 to the HTML resolver

Paper record endpoints

GET/pith/{id}/integrity.json

Detector summary, findings, and the signed pith.integrity.v1 event replay for one paper. Each event carries its payload, payload_sha256, signature_b64, and signing_key_id, so the whole record re-verifies offline. Clean papers return "clean": true with empty findings, as in the live example above.

GET/pith/{id}/bundle.json

The full Open Graph Bundle: canonical record, source aliases, every signed event, timestamp proofs, mirror hints, and the public keys needed to check it all. The same bundle is served at /.well-known/pith/{id}/bundle.json.

{
  "bundle_type": "pith_open_graph_bundle",
  "bundle_version": "1.0",
  "pith_number": "pith:2026:34BOUJ2MZM6ASZFWKVDHL2BAZ3",
  "short_pith_number": "pith:34BOUJ2M",
  "canonical_sha256": "df02ea274ccb3c09...",
  "source": {"kind": "arxiv", "id": "2605.13860", "version": "1"},
  "events": [
    {
      "event_type": "record_created",
      "content_sha256": "8c1f94ae05e1b73e...",
      "signature": {"...": "..."},
      "created_at": "2026-05-17T23:39:19Z"
    }
  ],
  "mirror_hints": ["..."],
  "public_keys": [{"key_id": "pith-v1-2026-05", "algorithm": "ed25519", "...": "..."}]
}

response captured 2026-07-09, truncated

GET/pith/{id}/state.json

Current state computed from the bundle's events by the deterministic merge pith-open-graph-merge-v1: event counts, validity tallies, and the merged current record. Recompute it yourself from the bundle; the answers must match.

GET/pith/{id}.json

The full Pith Number record: canonical record, signed receipt, graph snapshot, events, aliases, and link targets. GET /pith/{id} with Accept: application/json returns the same payload as application/ld+json.

GET/pith/{id}/claims.json

The machine-readable claim ledger for one paper, with provenance anchors (cited works, locations, formal-bridge modules) per claim.

GET/badge/{id}.svg

Embeddable SVG badge for author, lab, or journal pages. See embed the badge below for the snippet and a live render.

GET/api/pith-number/{id}/graph.json

The graph snapshot alone: paper metadata, references, and integrity block without the record envelope.

GET/api/pith-number/{id}/events.json

The signed event list alone, for replay without downloading the full bundle.

GET/api/pith-number/resolve

Alias resolution: ?arxiv=2605.13860&json=true or ?doi=...&json=true returns the full record for that paper; without json=true it 302s to the HTML resolver page.

Verify signatures

Pith signs with one Ed25519 key, published at /pith-signing-key.json:

{
  "key_id": "pith-v1-2026-05",
  "algorithm": "ed25519",
  "format": "raw",
  "public_key_b64": "stVStoiQhXFxp4s2pdzPNoqVNBMojDU/fJ2db5S3CbM=",
  "fingerprint_sha256_hex": "8d4b5ee74e4693bcd1df2446408b0d54",
  "url": "https://pith.science/pith-signing-key.json"
}

The signed message is always the 32 raw bytes of a SHA-256 digest. For the record receipt that digest is canonical_sha256. This runs as-is and raises on a bad signature:

import base64, json, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

key = json.load(urllib.request.urlopen("https://pith.science/pith-signing-key.json"))
rec = json.load(urllib.request.urlopen("https://pith.science/pith/34BOUJ2M.json"))

pub = Ed25519PublicKey.from_public_bytes(base64.b64decode(key["public_key_b64"]))
pub.verify(base64.b64decode(rec["receipt"]["signature_b64"]),
           bytes.fromhex(rec["canonical_sha256"]))

recipe executed against the live site 2026-07-09: signature verified

Integrity events follow the same rule: payload_sha256 is the SHA-256 of the event payload canonicalized as JSON with sorted keys and no whitespace (Python: json.dumps(payload, sort_keys=True, separators=(",", ":"))), and signature_b64 signs those digest bytes.

Confirmed Bitcoin timestamp anchors download as real OpenTimestamps files at /api/pith-number/{id}/anchors/{event_id}.ots for local ots verify against canonical_sha256. The mirror manifest at /pith-mirrors.json lists conforming hosts; state is computed by deterministic merge, never by trusting a manifest. Detector contracts and framing rules live at /pith-integrity-protocol.

Embed the badge

pith:34BOUJ2M verified live render of /badge/34BOUJ2M.svg
<a href="https://pith.science/paper/34BOUJ2M">
  <img src="https://pith.science/badge/34BOUJ2M.svg"
       alt="pith:34BOUJ2M verified" height="24">
</a>

A shareable standalone badge page is served at /badge/{id}.html.

Schemas

Auth, caching, versioning, errors