ochk.io · auth host API
The OrangeCheck family auth host. Issues Ed25519-signed oc_session cookies
(Domain=.ochk.io) that every consumer subsite verifies locally via
@orangecheck/auth-core +
@orangecheck/auth-client — no DB on consumers, no
per-request fetch to the auth host.
Live spec & interactive explorer. The canonical contracts are at
https://ochk.io/api/openapi(OpenAPI 3.1 JSON, CORS-permissive, cached 5 min) andhttps://ochk.io/api-explorer(Swagger UI with "Try it out" enabled). Generate clients withopenapi-generator-cli generate -i https://ochk.io/api/openapi -g <lang>.
Surface map
| Tag | Endpoints | Purpose |
|---|---|---|
auth | /api/auth/jwks, /me, /signin, /logout, /switch, /account | Session lifecycle. JWT verification key, current session, sign-in/out, multi-account switch, account edit. |
challenge | /api/challenge (GET + POST) | BIP-322 signed-challenge primitive — issue + verify, the building block. |
email-otp | /api/auth/email-otp/start, /verify | Email-OTP sign-in. Same session-cookie outcome as BIP-322. |
oauth | /api/auth/<provider>/start, /callback, /api/auth/providers | Google / GitHub sign-in run on the auth host. Same session-cookie outcome; /providers lists the configured providers. |
connect | /api/connect/provision, /api/connect/apps | OrangeCheck Connect — provision a did:oc for an integrator's own users. See Integration. |
public | /api/family-stats | Cross-subdomain family-wide stats. No auth. |
forms | /api/contact | Contact-form relay via Resend. Same-origin only. |
Auth scheme
cookieAuth — the oc_session cookie issued by /api/auth/signin (or
/api/auth/email-otp/verify). Cookie attributes:
Domain=.ochk.io(cross-subdomain)HttpOnly(no JS access)Secure(TLS only)SameSite=Lax
Consumer sites verify locally — no fetch to the auth host on every request. The
Ed25519 public JWK lives at /api/auth/jwks
and /.well-known/jwks.json; consumers cache it as the OC_AUTH_PUBLIC_JWK env
var at deploy time.
Sign-in primitives
Three paths, one outcome — each lands the same oc_session cookie, so
consumer code never branches on how the user signed in.
BIP-322 (canonical):
GET /api/challenge?addr=...&audience=...&purpose=login→{ message, nonce, expiresAt }- User signs
messagewith their Bitcoin wallet via@orangecheck/wallet-adapteror directly. POST /api/auth/signinwith{ address, message, signature, nonce }→ 200 +oc_sessioncookie set.
Email-OTP (lower-friction):
POST /api/auth/email-otp/startwith{ email }→ server emails 6-digit codePOST /api/auth/email-otp/verifywith{ email, code }→ 200 +oc_sessioncookie set.
OAuth providers (Google / GitHub):
GET /api/auth/<provider>/start→ redirect to the provider's consent screen.- The provider redirects back to
/api/auth/<provider>/callback; the auth host verifies the result and sets theoc_sessioncookie. GET /api/auth/providerslists which providers are configured — this is what<OcSignIn>reads to render its provider buttons.
A provider sign-in mints the same federation-custodied account that email-OTP does. For layering OrangeCheck onto an integrator's own OAuth instead, see OrangeCheck Connect.
Multi-account sign-in
Hold multiple OrangeCheck accounts in one browser and switch between them
without re-authenticating — same pattern as Google or GitHub. Three endpoints
touch this: POST /api/auth/signin?add=1 (and the email-OTP equivalent) brings
a new account into the browser's roster; POST /api/auth/switch flips the
active session; POST /api/auth/logout?scope=current signs out of just the
current account while keeping the rest of the roster alive. Full narrative at
Multi-account.
Live spec — embedded
The OAuth-provider (
/api/auth/<provider>/*) and Connect (/api/connect/*) endpoints are newer than the embedded OpenAPI document — their contracts are documented under Integration and OrangeCheck Connect until the spec catches up.