Explore & loan data auth
Explore (CLOB analytics) and loan-data routes use a dedicated Explore gate on the API. This is separate from the public quote create flow and from maker org API keys.
Formula:
(SIWE ∧ Turnstile + allowed Origin) ∨ taker-delegate JWT (API-key mint only)
Plus an :explore rate limit on IP, wallet, and taker API key.
For SIWE login itself, see Authentication (SIWE). For taker delegate mint used by wallet backends, see Add to your crypto wallet (API taker).
Who uses which path
| Audience | Credential | Turnstile | Origin |
|---|---|---|---|
Trading app (/explore, /loan-explorer) | SIWE bearer from wallet sign-in | Yes when enforced (explore-clob / explore-loan) | Browser sends allowed Origin (Vite dev proxy injects it for same-origin GETs) |
| Integrator backend | Short-lived taker-delegate JWT minted with a taker API key | Skipped | Not required |
| Maker API keys | Rejected on explore / loan-data routes | ||
| Long-lived taker API keys | Not sent directly. Mint a delegate first | ||
| SIWE-minted delegates (taker admin UI) | Work for quote create only; rejected on explore → 401 taker API key delegate required |
Website path (trading app)
After the user signs in with Ethereum, the app sends:
Authorization: Bearer <siwe-session-token>
Origin: <allowed CORS origin>
CF-Turnstile-Response: <token> # when Turnstile is enforced
Turnstile public config comes from GET /api/landing-page-settings (turnstile.sitekey, turnstile.enabled). The shared client (@opshuns/shared) attaches Turnstile automatically on explore paths when enforcement is on.
Widget action must match the route family:
| Routes | Turnstile action |
|---|---|
/api/explore/clobs/*, /api/clob/* | explore-clob |
/api/explore/loans/*, /api/margin/loans* | explore-loan |
When TURNSTILE_SECRET is unset on the API, Turnstile fails open; Origin is still required on the website path.
Trading app pages
| Path | Gate | Data loaded after sign-in |
|---|---|---|
/explore | SIWE + Explore gate | CLOB book, activity, fills, summary |
/loan-explorer | SIWE + Explore gate | Loan summary, at-risk, recent openings, activity chart, live stream |
/trade (advanced terminal) | SIWE (UI only) | RFQ quote box; quote create itself remains public on the API |
CLOB triplet discovery still uses public GET /api/indexer/cores plus optional on-chain mothership logs before authenticated book calls.
Integrator path (taker delegate)
Do not send a long-lived taker API key on explore requests. Mint a short-lived delegate per end-user wallet:
POST /api/taker/delegate
Authorization: Bearer <taker-api-key-secret>
Content-Type: application/json
{ "address": "0xUserWallet", "expires_in": 300 }
Use the returned JWT as Authorization: Bearer <delegate-jwt> on explore HTTP routes. Delegates minted this way stamp key_id in the JWT (required for explore). SIWE-minted delegates from the taker admin console omit key_id and are rejected here.
Example:
GET /api/clob/0x…/book?chain_id=31337
Authorization: Bearer <taker-delegate-jwt>
No Turnstile or Origin header is required on this path.
Protected routes
| Method | Path | Turnstile action |
|---|---|---|
GET | /api/explore/clobs/:address | explore-clob |
POST | /api/explore/clobs/:address/stream | explore-clob |
GET | /api/clob/book | explore-clob |
GET | /api/clob/:address/book | explore-clob |
GET | /api/clob/:address/activity | explore-clob |
GET | /api/clob/:address/fills | explore-clob |
GET | /api/clob/:address/summary | explore-clob |
GET | /api/explore/loans/summary | explore-loan |
GET | /api/explore/loans/at-risk | explore-loan |
GET | /api/explore/loans/recent | explore-loan |
GET | /api/explore/loans/activity | explore-loan |
GET | /api/explore/loans/events | explore-loan |
POST | /api/explore/loans/stream | explore-loan |
GET | /api/margin/loans | explore-loan |
GET | /api/margin/loans/:id | explore-loan |
GET | /api/margin/loans/:id/collateral | explore-loan |
WebSockets
Stream tokens are minted over HTTP under the Explore gate, then joined anonymously on /socket:
| Topic | Mint | Push events |
|---|---|---|
explore_clob_stream:<token> | POST /api/explore/clobs/:address/stream | explore_clob_snapshot, explore_clob_event, explore_clob_transfer |
explore_loan_stream:<token> | POST /api/explore/loans/stream | explore_loan_snapshot, explore_loan_event |
Socket join only validates the stream token: not SIWE or Turnstile.
API pipeline
:api → :authenticated → :explore_access
RequireExploreAuth # :siwe | :taker_delegate with key_id
Geoblock (:quote)
RateLimit (:explore. IP + wallet + taker key)
ExploreAccess # taker_delegate OR (Origin + Turnstile)
Errors
| HTTP | error | Typical cause |
|---|---|---|
401 | siwe or taker delegate required | Missing / invalid bearer, or maker/taker long-lived key used as Bearer |
401 | taker API key delegate required | SIWE-minted delegate on an explore route |
403 | origin header required | Website path without Origin (non-browser client) |
403 | origin not allowed | Origin not on CORS allowlist |
403 | turnstile verification required | Turnstile enforced but token missing / invalid |
429 | too many requests | Explore rate limit (IP, wallet, or taker key) |
Local development
- API: include trading origins in
CORS_ORIGINS, e.g.http://localhost:3000,http://localhost:3005. - Web: leave
VITE_API_URLempty so Vite proxies/apiand/socket(the proxy addsOriginfor same-origin GETs). - Turnstile: optional locally (
VITE_TURNSTILE=false); the API fails open withoutTURNSTILE_SECRET.
Related
- Authentication (SIWE)
- WebSockets & pubsub: explore stream topics
- Add to your crypto wallet (API taker): delegate mint for quote create (same mint endpoint; explore requires API-key mint)
- Trading app. Explore