# Market Data Proxy > A Rust proxy server that provides a unified API for prediction markets and crypto data. Supports Polymarket (Gamma + CLOB), Kalshi, Gemini, Coinbase, and Forecastex. All endpoints authenticate via X-API-Key header. The proxy handles platform-specific auth (e.g. Kalshi RSA-PSS signing) automatically. ## Overview - Local server: `http://localhost:8080` - API prefix: `/api/v1/{platform}/{resource}` - WebSocket prefix: `ws://localhost:8080/ws/{platform}/{channel}` - Authentication: `X-API-Key: ` header on all requests except `/health` - Health check: `GET /health` (no auth required) Platform keys: `polymarket`, `polymarket-clob`, `kalshi`, `gemini`, `coinbase`, `forecastex` ## Authentication Set `USER_API_KEYS` env var (comma-separated) to configure valid keys. Default: `test_key`. For Kalshi authenticated endpoints (orders, portfolio), set `KALSHI_ACCESS_KEY` and `KALSHI_PRIVATE_KEY` (or `KALSHI_PRIVATE_KEY_PATH`) in `.env`. The proxy signs requests automatically. WebSocket auth uses query param: `?api_key=YOUR_KEY` ## Polymarket Gamma (`/api/v1/polymarket/`) Upstream: `https://gamma-api.polymarket.com`. All public, no platform credentials needed. - `GET /markets` — List markets. Query: `limit`, `offset`, `active`, `closed`, `tag_id` - `GET /markets/{id}` — Get market by ID - `GET /markets/slug/{slug}` — Get market by slug - `GET /markets/{id}/tags` — Market tags - `GET /events` — List events. Query: `limit`, `offset`, `active` - `GET /events/{id}` — Get event by ID - `GET /events/slug/{slug}` — Get event by slug - `GET /events/tags` — Event tags - `GET /tags` — List tags - `GET /tags/{id}` — Get tag - `GET /tags/slug/{slug}` — Get tag by slug - `GET /tags/{id}/related` — Related tags - `GET /series` — List series - `GET /series/{id}` — Get series - `GET /comments` — List comments - `GET /comments/{commentId}` — Get comment - `GET /comments/user/{address}` — Comments by wallet address - `GET /search` — Search markets, events, profiles. Query: `q` - `GET /sports` — Sports metadata - `GET /sports/types` — Valid sports market types - `GET /sports/teams` — List teams ## Polymarket CLOB (`/api/v1/polymarket-clob/`) Upstream: `https://clob.polymarket.com`. Public for price/book; trading requires L2 credentials forwarded in headers. - `GET /book?token_id=TOKEN_ID` — Order book for token - `POST /book` — Multiple order books (body: array of token IDs) - `GET /price?token_id=TOKEN_ID&side=BUY|SELL` — Best price - `POST /prices` — Batch prices (up to 500) - `GET /midpoint?token_id=TOKEN_ID` — Midpoint price - `GET /midpoints` — Batch midpoints - `GET /spread?token_id=TOKEN_ID` — Bid-ask spread - `GET /spreads` — Batch spreads - `GET /last-trade-price?token_id=TOKEN_ID` — Last trade price - `GET /last-trade-prices` — Batch last trade prices - `GET /fee-rate?token_id=TOKEN_ID` — Fee rate - `GET /tick-size?token_id=TOKEN_ID` — Tick size - `GET /time` — Server time - `GET /prices-history?token_id=TOKEN_ID&interval=1d` — Historical prices. Intervals: 1m, 5m, 1h, 6h, 1d, 1w - `GET /markets` — List markets - `GET /markets/{id}` — Get market - `GET /markets/slug/{slug}` — Get market by slug - `GET /markets/{id}/tags` — Market tags - `GET /markets/{id}/top-holders` — Top holders - `GET /markets/{id}/open-interest` — Open interest - `GET /markets/{id}/positions` — Positions - `GET /events` — List events - `GET /events/{id}` — Get event - `GET /events/{id}/live-volume` — Live volume - `GET /profile/{walletAddress}` — Public profile - `GET /profile/{walletAddress}/positions/current` — Current positions - `GET /profile/{walletAddress}/positions/closed` — Closed positions - `GET /profile/{walletAddress}/activity` — User activity - `GET /profile/{walletAddress}/total-value` — Total value - `GET /trades` — Trades by user or market - `GET /leaderboard/traders` — Trader leaderboard - `GET /builders/leaderboard` — Builder leaderboard - `GET /bridge/assets` — Bridge assets - `POST /bridge/deposit-addresses` — Create deposit addresses - `POST /bridge/quote` — Bridge quote - `GET /bridge/transaction/{id}` — Transaction status - `POST /bridge/withdrawal-addresses` — Withdrawal addresses ## Kalshi (`/api/v1/kalshi/`) Upstream: `https://api.elections.kalshi.com/trade-api/v2`. Markets/events public; orders/portfolio require `KALSHI_ACCESS_KEY` + `KALSHI_PRIVATE_KEY`. **Exchange:** - `GET /exchange/status` - `GET /exchange/announcements` - `GET /exchange/schedule` **Markets:** - `GET /markets` — List markets. Query: `limit`, `cursor`, `event_ticker`, `series_ticker`, `status` (open/closed/settled), `min_close_ts`, `max_close_ts` - `GET /markets/{market_id}` — Get market - `GET /markets/{market_id}/orderbook` — Orderbook - `GET /markets/candlesticks?tickers=TICKER&period_interval=60&start_ts=TS&end_ts=TS` - `GET /markets/candlesticks/batch` - `GET /markets/trades` **Events:** - `GET /events` — List events - `GET /events/{event_ticker}` — Get event - `GET /events/multivariate` - `GET /events/{event_ticker}/metadata` - `GET /events/{event_ticker}/forecast-percentile-history` - `GET /events/candlesticks` **Series:** - `GET /series` - `GET /series/{series_ticker}` **Historical (public):** - `GET /historical/markets` - `GET /historical/markets/{market_id}` - `GET /historical/markets/candlesticks` - `GET /historical/fills` - `GET /historical/orders` - `GET /historical/cutoff-timestamps` **Orders (requires auth):** - `GET /orders` — Query: `status` (resting/canceled/executed), `ticker`, `limit` - `POST /orders` — Create order. Body: `{ticker, side: yes|no, action: buy|sell, count, type: limit|market, yes_price, no_price}` - `GET /orders/{order_id}` - `DELETE /orders/{order_id}` — Cancel - `POST /orders/batch` — Batch create - `DELETE /orders/batch` — Batch cancel - `POST /orders/{order_id}/amend` - `POST /orders/{order_id}/decrease` - `GET /orders/queue-positions` - `GET /orders/{order_id}/queue-position` **Order groups:** - `GET /order-groups`, `POST /order-groups` - `GET /order-groups/{group_id}`, `DELETE /order-groups/{group_id}` - `PUT /order-groups/{group_id}/reset|trigger|limit` **Portfolio (requires auth):** - `GET /portfolio/balance` - `GET /portfolio/positions` - `GET /portfolio/fills` - `GET /portfolio/settlements` - `GET /portfolio/resting-order-value` - `POST /portfolio/subaccounts` - `POST /portfolio/transfer` - `GET /portfolio/subaccounts/balances` - `GET /portfolio/subaccounts/transfers` **RFQ:** - `GET /rfqs`, `POST /rfqs`, `GET /rfqs/{id}`, `DELETE /rfqs/{id}` - `GET /quotes`, `POST /quotes`, `GET /quotes/{id}`, `DELETE /quotes/{id}` - `PUT /quotes/{id}/accept`, `PUT /quotes/{id}/confirm` **Misc:** - `GET /search/tags` - `GET /search/filters` - `GET /account/api-limits` - `GET /live-data/{ticker}`, `GET /live-data` - `GET /incentive-programs` - `GET /structured-targets`, `GET /structured-targets/{target_id}` - `GET /milestones`, `GET /milestone/{milestone_id}` - `GET /multivariate/event-collections`, `GET /multivariate/event-collection/{collection_id}` ## Gemini (`/api/v1/gemini/`) Upstream: `https://api.gemini.com`. Use sandbox: `GEMINI_BASE_URL=https://api.sandbox.gemini.com`. - `GET /v1/prediction-markets/categories` — List categories - `GET /v1/prediction-markets/events` — List events - `GET /v1/prediction-markets/events/{ticker}` — Get event - `POST /v1/prediction-markets/order` — Place order (requires Gemini signing) - `POST /v1/prediction-markets/order/cancel` — Cancel order (requires signing) - `POST /v1/prediction-markets/positions` — Get positions (requires signing) - `POST /v1/prediction-markets/orders/active` — Get active orders (requires signing) - `POST /v1/prediction-markets/orders/history` — Get order history (requires signing) - `GET /v1/book/{symbol}` — Order book - `GET /v1/trades/{symbol}` — Recent trades. Query: `limit_trades`, `since` - `GET /v2/ticker/{symbol}` — Ticker ## Coinbase (`/api/v1/coinbase/`) Upstream: `https://api.exchange.coinbase.com`. All public, no credentials needed. - `GET /products` — List trading pairs - `GET /products/{id}` — Get product (e.g. `BTC-USD`) - `GET /products/{id}/ticker` — Current ticker - `GET /products/{id}/book?level=2` — Order book - `GET /products/{id}/trades` — Recent trades - `GET /products/{id}/candles?granularity=3600&start=ISO&end=ISO` — OHLCV candles. Granularity: 60, 300, 900, 3600, 21600, 86400 - `GET /currencies` — List all supported currencies - `GET /time` — Server time ## Forecastex (`/api/v1/forecastex/`) Path-based routing to forecastex.com or IBKR Client Portal Gateway. Read-only—no trading endpoints. **forecastex.com** (FORECASTEX_BASE_URL): - `GET /contracts?page=1&pageSize=100&sortBy=open_interest&sortOrder=desc` — List event contracts **IBKR Gateway** (FORECASTEX_IBKR_BASE_URL, e.g. localhost:5000/v1/api): - `GET /trsrv/event/category-tree` — Event contract category tree - `GET /tickle` — Keep session alive (proxy runs every 60s) - `GET /iserver/secdef/search?symbol=FF` — Search underlying index - `GET /iserver/secdef/strikes` — Strike values - `GET /iserver/secdef/info` — Contract info - `GET /forecast/category/tree` — Category tree - `GET /forecast/contract/details?conid=` — Contract details - `GET /forecast/contract/market?underlyingConid=` — Contracts for market - `GET /forecast/contract/rules?conid=` — Contract rules - `GET /forecast/contract/schedules?conid=` — Trading schedules ## WebSockets Connect: `ws://localhost:8080/ws/{platform}/{channel}?api_key=YOUR_KEY` | Platform channel | Upstream | Notes | |-----------------|----------|-------| | `/ws/polymarket/market` | `wss://ws-subscriptions-clob.polymarket.com/ws/market` | Send subscription with `assets_ids`. PING every 10s | | `/ws/polymarket/user` | `wss://ws-subscriptions-clob.polymarket.com/ws/user` | Order/trade lifecycle | | `/ws/polymarket/sports` | `wss://sports-api.polymarket.com/ws` | Auto-streams. Respond to server `ping` with `pong` | | `/ws/kalshi` | `wss://api.elections.kalshi.com/trade-api/ws/v2` | Proxy handles RSA-PSS auth. Subscribe: `{"id":1,"cmd":"subscribe","params":{"channels":["ticker"]}}` | | `/ws/coinbase` | `wss://ws-feed.exchange.coinbase.com` | Subscribe: `{"type":"subscribe","product_ids":["BTC-USD"],"channels":["ticker","level2"]}` | | `/ws/gemini` | `wss://wsapi.fast.gemini.com` | Subscribe per Gemini WS docs | ## Configuration (env vars) ``` USER_API_KEYS=key1,key2 # Proxy auth keys (default: test_key) RATE_LIMIT_PER_MIN=100 # Per-key rate limit (0 = disabled) CORS_ALLOWED_ORIGINS= # Comma-separated origins (empty = allow all) HOST=127.0.0.1 PORT=8080 RUST_LOG=info KALSHI_ACCESS_KEY= # Kalshi API key ID KALSHI_PRIVATE_KEY_PATH= # Path to PEM private key KALSHI_BASE_URL=https://api.elections.kalshi.com/trade-api/v2 KALSHI_WS_URL=wss://api.elections.kalshi.com/trade-api/ws/v2 POLYMARKET_BASE_URL=https://gamma-api.polymarket.com POLYMARKET_CLOB_BASE_URL=https://clob.polymarket.com POLYMARKET_WS_BASE_URL=wss://ws-subscriptions-clob.polymarket.com POLYMARKET_SPORTS_WS_URL=wss://sports-api.polymarket.com/ws GEMINI_BASE_URL=https://api.gemini.com GEMINI_WS_URL=wss://wsapi.fast.gemini.com COINBASE_BASE_URL=https://api.exchange.coinbase.com COINBASE_WS_URL=wss://ws-feed.exchange.coinbase.com FORECASTEX_BASE_URL=https://forecastex.com/api FORECASTEX_IBKR_BASE_URL=https://localhost:5000/v1/api FORECASTEX_IBKR_TICKLE_ENABLED=true ``` ## Project structure ``` src/ ├── main.rs # Entry point ├── lib.rs # App builder ├── config.rs # Configuration and platform URLs ├── auth.rs # API key validation ├── proxy.rs # Proxy handler and request forwarding └── error.rs # Error types tests/ └── integration.rs ``` ## Error responses - `401` — Missing/invalid X-API-Key - `429` — Rate limit exceeded - `502/503` — Upstream error ```json {"error": "unauthorized", "message": "Invalid API key"} ```