OC Me · HTTP API
The hosted me.ochk.io substrate exposes the contracts the SDK calls into.
The SDK is the canonical integration path; this page is the raw HTTP surface for
clients that aren't TypeScript or that want to bypass the SDK.
Every endpoint is at https://me.ochk.io/api/... unless noted. Auth is by
cross-subdomain oc_session cookie (Domain=.ochk.io, HttpOnly, SameSite=None,
Secure) issued by the auth host at https://ochk.io, OR by an
Authorization: Bearer <jwt> header (cross-domain integrators that captured the
session token from the popup-signin postMessage). Mutating endpoints are
rate-limited per IP per bucket; limits are returned in X-RateLimit-* headers.
Sign-in endpoints (auth host · https://ochk.io)
The session cookie is issued by the family auth host, not by me.ochk.io
directly. me.ochk.io's /signin page calls these endpoints cross-origin with
credentials — they are CORS-allowed for any *.ochk.io browser origin.
| Method | Path | Purpose |
|---|
POST | /api/auth/email-otp/start | issue a stateless OTP token + email a 6-digit code (rate-limited 5/hr) |
POST | /api/auth/email-otp/verify | verify {email, code, token}, upsert account, set oc_session cookie |
GET | /api/challenge | issue a fresh BIP-322 challenge to sign |
POST | /api/auth/signin | verify a signed BIP-322 challenge, upsert account, set oc_session |
POST | /api/auth/logout | clear the oc_session cookie |
GET | /api/auth/me | crypto-only identity lookup; 401 otherwise |
See
oc-me-web/src/pages/signin.tsx
for the canonical client implementation against these endpoints.
Public endpoints
| Method | Path | Purpose |
|---|
GET | /api/health | liveness probe |
GET | /api/health/deep | dependency probe |
GET | /api/envelope/[id] | canonical envelope JSON for a billable event |
GET | /api/payment/[id] | canonical envelope for a payment |
GET | /api/checkout/lightning/[hash] | invoice status by payment_hash |
GET | /api/platform-fee-policy | published PLATFORM_FEE_POLICY |
GET | /api/abuse-limits | published per-class abuse rate limits |
GET | /api/integrator-config?project_key=… | sample IntegratorPriceConfig lookup |
GET | /api/stats/earned | aggregate sat-earnings (last 7d, by class) |
POST | /api/integrator-config | validate an IntegratorPriceConfig (echoes on success) |
POST | /api/checkout/lightning | generate a BOLT-11 invoice (rate-limited 60/min) |
POST | /api/checkout/stripe | generate a Stripe checkout URL |
POST | /api/contact | contact form submission (Resend-backed) |
POST | /api/integrator/event | fire a billable event under a project_key (live mode requires domain verification) |
Authenticated endpoints (oc_session required)
| Method | Path | Purpose |
|---|
GET | /api/auth/me | crypto-only identity lookup; 401 otherwise |
GET | /api/me/profile | profile + onboarding state |
GET | /api/me/balance | sat balance + last-7d aggregate + by-class breakdown |
GET | /api/me/sites | list of sites the identity has signed in to |
GET | /api/me/sites/[domain] | per-site activity + IntegratorPriceConfig |
GET | /api/me/notifications | notifications feed |
POST | /api/me/notifications | mark read/unread ({ id, read } or { all: true, read: true }) |
GET | /api/me/export | GDPR-style account dump · downloadable JSON |
Authenticated · session lifecycle (Class C)
| Method | Path | Purpose |
|---|
POST | /api/session/create | open a Class C session — billable atom |
POST | /api/session/refresh | refresh inside the policy window — free |
POST | /api/session/invalidate | tear down — free, telemetry-only |
Authenticated · payment authorization (Class B)
| Method | Path | Purpose |
|---|
POST | /api/payment/authorize | authorize a payment, rate-limited 30/min · Class B billable |
Authenticated · integrator project lifecycle
| Method | Path | Purpose |
|---|
GET | /api/me/projects | list every project owned by your identity |
POST | /api/me/projects | create a project, rate-limited 5/min |
GET | /api/me/projects/[id] | per-project state · owner-gated |
POST | /api/me/projects/[id] | update IntegratorPriceConfig · owner-gated, validates |
DELETE | /api/me/projects/[id] | delete project · owner-gated |
POST | /api/me/projects/[id]/verify | run a domain-ownership check (DNS TXT, falls back to meta tag) · owner-gated, rate-limited 5/min |
DELETE | /api/me/projects/[id]/verify | reissue the verification token (rotate / retry from scratch) · owner-gated |
POST | /api/me/projects/[id]/test-fire | fire a real envelope under this project_key (same pipeline as production) · owner-gated · for the smoke-test sandbox |
GET | /api/me/projects/[id]/events | live event stream for this project · owner-gated |
GET | /api/me/projects/[id]/webhooks | list registered webhooks · owner-gated |
POST | /api/me/projects/[id]/webhooks | register a webhook URL · owner-gated |
POST | /api/developer/webhook-test | test-fire a synthetic envelope at a registered webhook |
Response shapes
All authenticated 401s are { ok: false } with
Cache-Control: no-store, private and Vary: Cookie. All 422 validation
failures return
{ error: string, details?: [{ subtype?: string, message: string }] }. All 429
rate-limit responses include a Retry-After header in seconds.
Rate limits per bucket
| Bucket | Limit | Window |
|---|
integrator.event | 120 | 60s |
payment.authorize | 30 | 60s |
wallet.send | 30 | 60s |
checkout.lightning | 60 | 60s |
me.attest-tier | 5 | 60s |
webhooks.register | 10 | 60s |
projects.test-fire | 20 | 60s |
developer.projects.create | 5 | 60s |
verify-domain:<addr> | 5 | 60s |
Limits are per IP, per bucket (except verify-domain which is per-account so
DNS retries don't get throttled by a noisy neighbor). Headers
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Window are emitted
on every rate-limited response. v1 buckets reset on cold start (per-instance
in-memory); production fronts every endpoint with an edge-level limiter.