FatCat common API
The only public surface for tokenized stocks. Built for a crowd: stocksUI, bots, and many keys at once. The indexer never serves HTTP. Each client is isolated by key. Markets are coalesced so a stampede is one Carbon read, not one per caller.
Clients
Never receive Redis or Postgres URLscommonAPI — public surface
AuthRegistry · rate limits · no JupiterCarbon Postgres
source of truthRedis
fan-out onlySolana RPC
not live yetIndexer — private
Phase 1 OPENx on mainPublic, authenticated Private writer Postgres is truth. Redis never blocks ingest.
Liveness
checking
Auth
API keys plus OAuth2 client_credentials. Handlers take ApiIdentity, not a mode switch.
Execution
dry-run POST /v1/tx/send is 403 live_disabled.
Read pool
sqlx connections set default_transaction_read_only and statement timeouts.
Scale
Stateless REST. N replicas. Each one subscribes to Redis itself. No sticky sessions.
Many clients
Per-key quotas. REST, gRPC, and MCP. Markets cache so Carbon is not stampeded.
MCP
Agents POST JSON-RPC to /mcp. Same keys as REST. Tools wrap markets, portfolio, quote.
Security controls
| Control | Posture |
|---|---|
| Auth | API keys and OAuth2 client_credentials (JWT). REST rejects query-string keys. OIDC/Privy is a fail-closed hook. |
| REST credentials | X-Api-Key or Authorization: Bearer only. ?api_key= is 401. |
| WebSocket | Header preferred; query fallback for handshake. Origin allow-list, idle timeout, max connections, max frame size. Binary frames closed. |
| Scopes | Free cannot read compliance REST or subscribe to compliance / lifecycle. Market detail omits flags for free. |
| Rate limits | Per-IP (TCP peer; X-Forwarded-For only if TRUST_PROXY), per-key, global cap. 429 + Retry-After. |
| CORS | Exact origins in production. No wildcard, no credentialed *, no mirrored methods/headers. |
| Inputs | Solana pubkeys (32-byte base58). Amounts are positive u128 decimals. Redis keys interpolated only after validation. |
| Errors | Internal messages logged, never returned. Health does not leak config. |
| Headers | nosniff, DENY framing, no-referrer, no-store, CSP with frame-ancestors none. |
Horizontal scale
| Layer | Setup |
|---|---|
| Replicas | Run N identical processes behind one TLS load balancer. REST is stateless; any replica can serve any call. |
| Probes | /health = process up (liveness). /ready = Carbon reachable (readiness). Drain a replica on 503. |
| WebSocket | Each replica SUBSCRIBEs event:token_lifecycle and fans out to its own clients. Sticky sessions are not required. |
| Postgres | Read replica + SELECT-only role. Connection budget is replicas × DATABASE_MAX_CONNECTIONS. Markets is two queries, not 1+N. |
| Redis | One subscriber per replica. Indexer publishes. Clients never receive the URL. No new channel names, no Lua. |
| Rate limits | Governor is in-process (per replica). Put the global cap at the edge. TRUST_PROXY only behind a proxy you own. |
| Do not | Do not expose the indexer. Do not share Redis with browsers. Do not raise the DB pool without checking indexer headroom. |
| Stampede | Authenticated GET /v1/markets is coalesced per replica (default 750ms). A burst of callers shares one catalog page load. Market detail is not cached — premium flags stay gated. |
Live modules
Same-origin GET /v1. Many clients share this catalog; new endpoints register here.
- modules
- checking…
Live response headers
Same-origin probe of GET /health.
- status
- checking…
REST (v1)
| Method | Path | Notes |
|---|---|---|
| GET | /openapi.json | OpenAPI 3.1. Public. No secrets |
| GET | /metrics | Prometheus. Public. No fingerprints |
| POST | /mcp | MCP Streamable HTTP. Auth. JSON-RPC tools |
| GET | /v1 | Public module catalog |
| POST | /v1/oauth/token | OAuth2 client_credentials → Bearer JWT |
| GET | /health | Public liveness, no config leak |
| GET | /ready | Postgres ping |
| GET | /v1/markets | Auth. Paged active catalog with captured mint metadata and optional pool prices |
| GET | /v1/markets/{mint} | Auth. compliance flags premium-only |
| GET | /v1/portfolio/{wallet} | Auth. validated pubkey |
| GET | /v1/multiplier/{mint} | Auth. Redis SETEX then Carbon |
| GET | /v1/compliance/events | Premium scope |
| POST | /v1/tx/quote | shared-tx, Carbon pool state |
| POST | /v1/tx/send | 403 live_disabled |
| gRPC | /fatcat.v1.FatCat | Native + gRPC-Web. Same auth metadata (x-api-key / Bearer) |
| WS | /v1/ws | swaps, pool_updates, compliance, lifecycle |
Local development keys are disabled in production. Send the header, never the query string:
curl -s -H 'X-Api-Key:' ORIGIN/v1/markets