{"openapi":"3.1.0","info":{"title":"attest.ochk.io · OC Attest verification API","version":"0.1.0","description":"OC Attest is the **identity** verb of the OrangeCheck family — a Bitcoin-bonded sybil-resistance primitive. An attestation says \"this Bitcoin address controls ≥ X sats that have been unspent for ≥ Y days.\" Anyone can verify offline; nobody custodies anything.\n\nThis API is the **server-side** verification + discovery surface. The full client SDK (`@orangecheck/sdk`) verifies envelopes locally — these endpoints exist for thin clients, language runtimes without an SDK port, and observability (`/api/stats`, `/api/discover`).\n\nEvery endpoint is **public**. No auth, no API keys. Rate-limited per-IP.","contact":{"name":"OrangeCheck","url":"https://attest.ochk.io"},"license":{"name":"Documentation: CC0"}},"servers":[{"url":"https://attest.ochk.io","description":"Production"}],"tags":[{"name":"check","description":"The load-bearing sybil-gate primitive. One call, one pass/fail."},{"name":"verify","description":"Full envelope verification — checks signature, recomputes the canonical id, asserts the bond meets policy."},{"name":"discover","description":"Read-only attestation discovery — list attestations matching a subject across relays."},{"name":"publish","description":"Server-side broadcaster for client-built envelopes. The browser POSTs a fully-signed envelope; the server verifies + fans out to the Nostr relay set."},{"name":"stats","description":"Public network-wide aggregate stats. Heavy-cached."}],"components":{"schemas":{"BtcAddress":{"type":"string","description":"Bitcoin address (P2PKH / P2SH / P2WPKH / P2TR).","minLength":26,"maxLength":96,"example":"bc1qmr7qn2t0ahj56ztpdswt54kn2r863ukd8wadj8"},"AttestationId":{"type":"string","description":"Lowercase 64-hex attestation envelope id (sha256 of canonical message).","pattern":"^[0-9a-f]{64}$"},"CheckOutcome":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"score":{"type":"integer","description":"Sybil-resistance score (sats × days normalized)."},"sats":{"type":"integer"},"days":{"type":"integer"},"attestation_id":{"$ref":"#/components/schemas/AttestationId"},"address":{"$ref":"#/components/schemas/BtcAddress"},"identity":{"type":"string","description":"Off-chain identity bound to this attestation (e.g. github:alice)."},"reason":{"type":"string","description":"When ok=false — the failure reason."}}}}},"paths":{"/api/check":{"get":{"tags":["check"],"summary":"One-call sybil-gate check","description":"The load-bearing primitive. Pass either an address, an attestation id, or an off-chain identity binding (github:user, twitter:user, etc.); pass min_sats and/or min_days as the policy floor. Server returns ok=true/false with the most-recent attestation that satisfies the policy. The same logic is in @orangecheck/sdk's `check()` helper for client-side use; this endpoint exists for runtimes without a TS SDK port.","parameters":[{"name":"addr","in":"query","schema":{"$ref":"#/components/schemas/BtcAddress"},"description":"Subject Bitcoin address."},{"name":"id","in":"query","schema":{"$ref":"#/components/schemas/AttestationId"},"description":"Specific attestation envelope id."},{"name":"identity","in":"query","schema":{"type":"string"},"description":"Off-chain identity binding (e.g. github:alice)."},{"name":"min_sats","in":"query","schema":{"type":"integer","minimum":0}},{"name":"min_days","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckOutcome"}}}},"400":{"description":"invalid_query","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"invalid_query"},"detail":{"type":"string"}}}}}},"429":{"description":"Per-IP token-bucket exceeded.","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"rate_limited"},"detail":{"type":"string"}}}}}}}}},"/api/verify":{"post":{"tags":["verify"],"summary":"Verify a complete attestation envelope","description":"Full verification path: recomputes the canonical message, recomputes the envelope id (rejects on mismatch), verifies the BIP-322 (or legacy) signature against the asserted address, asserts the bond meets the supplied policy. Same logic as @orangecheck/sdk's `verify()`; exposed here for thin clients.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["envelope"],"properties":{"envelope":{"type":"object","description":"Full canonical AttestationEnvelope JSON, exactly as produced by createAttestation()."},"policy":{"type":"object","description":"Optional bond floor (min_sats, min_days).","properties":{"min_sats":{"type":"integer"},"min_days":{"type":"integer"}}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"reason":{"type":"string"},"attestation_id":{"$ref":"#/components/schemas/AttestationId"}}}}}},"400":{"description":"invalid_body","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"invalid_body"},"detail":{"type":"string"}}}}}},"429":{"description":"Per-IP token-bucket exceeded.","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"rate_limited"},"detail":{"type":"string"}}}}}}}}},"/api/discover":{"get":{"tags":["discover"],"summary":"List attestations matching a subject","description":"Returns every attestation the family Nostr relay set knows about for the subject (address or identity binding). Lightly parsed so callers can browse history. Unlike /api/check (returns one most-recent passing attestation), this returns the full set.","parameters":[{"name":"addr","in":"query","schema":{"$ref":"#/components/schemas/BtcAddress"}},{"name":"identity","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"OK — possibly empty","content":{"application/json":{"schema":{"type":"object","properties":{"attestations":{"type":"array","items":{"type":"object"}},"relay_status":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"invalid_query","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"invalid_query"},"detail":{"type":"string"}}}}}},"429":{"description":"Per-IP token-bucket exceeded.","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"rate_limited"},"detail":{"type":"string"}}}}}}}}},"/api/publish-attestation":{"post":{"tags":["publish"],"summary":"Broadcast a client-signed attestation envelope","description":"The client builds + signs the envelope locally (via @orangecheck/sdk createAttestation()); the server re-verifies the signature against the canonical message, builds the kind-30078 Nostr event, signs the OUTER Nostr wrapper with attest.ochk.io's stable service key, and fans out to a wide relay set. Returns per-relay accept/reject. The inner BIP-322 signature (over the canonical message) is the trust anchor; the outer Nostr signature exists only because relays require event signatures.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["envelope"],"properties":{"envelope":{"type":"object"}}}}}},"responses":{"200":{"description":"Published to at least one relay","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"attestation_id":{"$ref":"#/components/schemas/AttestationId"},"nostr_event_id":{"type":"string"},"relay_status":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"invalid_body","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"invalid_body"},"detail":{"type":"string"}}}}}},"422":{"description":"signature_invalid","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"signature_invalid"},"detail":{"type":"string"}}}}}},"429":{"description":"Per-IP token-bucket exceeded.","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"rate_limited"},"detail":{"type":"string"}}}}}},"502":{"description":"all_relays_failed","content":{"application/json":{"schema":{"type":"object","required":["ok","reason"],"properties":{"ok":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"all_relays_failed"},"detail":{"type":"string"}}}}}}}}},"/api/stats":{"get":{"tags":["stats"],"summary":"Network-wide aggregate stats","description":"Heavy-cached (10 min). Total attestations, total addresses with at least one attestation, sats-backed total, recent activity counters.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}