# Resolved Markets — Full Reference > Real-time orderbook data platform for Polymarket prediction markets across crypto, sports, economics, and weather categories. Captures high-frequency snapshots (~20Hz for crypto, 2Hz for sports) and trades, stored in ClickHouse. REST API + WebSocket + MCP server + CLI. Free tier available; Pro $17/mo; Enterprise $249/mo. Resolved Markets is an independent, third-party data service — **not affiliated with Polymarket**. It reads from Polymarket's public CLOB API and Binance price feeds. Do not confuse it with Polymarket itself. All prediction market prices are USDC probabilities between 0.01 and 0.99. Each market has two tokens: - **Up** (Yes outcome) — price represents probability the event resolves "Yes" - **Down** (No outcome) — price = 1 − Up price (always mirrors the Up token) Each token has its own live orderbook with independent bid/ask ladders. Base URL: `https://api.resolvedmarkets.com` OpenAPI 3.1 spec: `https://resolvedmarkets.com/openapi.json` — import into Postman, Insomnia, RapidAPI, GPT Actions, Cursor, or any code generator for typed clients. AI agent integration: see `https://resolvedmarkets.com/ai-agents` for MCP server config, tool-use templates, and a copy-paste system prompt for Claude / ChatGPT / Gemini / Cursor / Windsurf. Category-specific landing pages: - Crypto (BTC, ETH, SOL, XRP): `https://resolvedmarkets.com/data/crypto` - Sports (NBA, NFL, EPL): `https://resolvedmarkets.com/data/sports` - Weather (daily city temperature, hurricanes, climate): `https://resolvedmarkets.com/data/weather` - Economics (FOMC, NFP): `https://resolvedmarkets.com/data/economics` - Hyperliquid perps (BTC, ETH, SOL, XRP): `https://resolvedmarkets.com/data/hyperliquid` - Quant research focus (ClickHouse, full archive, sequence numbers): `https://resolvedmarkets.com/research/quant` - Historical Polymarket orderbook guide: `https://resolvedmarkets.com/guides/polymarket-historical-orderbook-data` - FAQ: `https://resolvedmarkets.com/faq` - About: `https://resolvedmarkets.com/about` Comparison pages: - vs Polymarket API: `https://resolvedmarkets.com/compare/polymarket-api` - vs PolyBackTest: `https://resolvedmarkets.com/compare/polybacktest` - Dome API alternative: `https://resolvedmarkets.com/compare/dome-alternative` --- ## Authentication All authenticated REST endpoints require: ``` X-API-Key: rm_your_key ``` API keys start with `rm_`. Get one at [resolvedmarkets.com/api-keys](https://resolvedmarkets.com/api-keys) after signing up. WebSocket connections authenticate via a message after connecting — not via query string. CLI auth resolution order: `--key` flag → `RM_API_KEY` environment variable → `~/.resolved-markets` config file. --- ## Public Endpoints ### GET /health No auth required. Returns HTTP 503 when ClickHouse or Redis is down. ```json // Fully healthy { "status": "healthy", "clickhouse": true, "redis": true, "pipeline_ready": true, "uptime": 6412, "ws_clob": true, "ws_trade": true } // Pipeline starting up (ws_clob/ws_trade not yet included) { "status": "starting", "clickhouse": true, "redis": true, "pipeline_ready": false, "uptime": 12 } // Degraded { "status": "degraded", "clickhouse": false, "redis": true, "pipeline_ready": false, "uptime": 3 } ``` `ws_clob` = Polymarket CLOB WebSocket connected. `ws_trade` = Polymarket trades WebSocket connected. Both `false` means live orderbook data is stale. `ws_clob`/`ws_trade` are only present when `pipeline_ready: true`. --- ### GET /v1/public-stats No auth required. `snapshot_count` is a string (large integer from ClickHouse aggregation). ```json { "snapshot_count": "1785412", "active_markets": 110, "prices": { "BTC": 65699.85, "ETH": 1979.57, "SOL": 82.48, "XRP": 1.3261 }, "exchange": { "snapshots_last_5min": 42, "latest_snapshot": "2026-03-28 01:23:45.678" } } ``` `exchange.snapshots_last_5min` = number of exchange (Hyperliquid) snapshots written in the last 5 minutes. `exchange.latest_snapshot` = timestamp of most recent exchange snapshot (null if none). Useful for diagnosing whether exchange data collectors are active. --- ### GET /v1/status No auth required. Returns per-service health checks, overall status, and 90-day uptime history. ```json { "overall": "operational", "services": [ { "service": "clickhouse", "status": "operational", "latency_ms": 12, "checked_at": "2026-03-28T01:00:00.000Z" }, { "service": "redis", "status": "operational", "latency_ms": 2, "checked_at": "2026-03-28T01:00:00.000Z" }, { "service": "clob_ws", "status": "operational", "latency_ms": 0, "checked_at": "2026-03-28T01:00:00.000Z" }, { "service": "trade_ws", "status": "operational", "latency_ms": 0, "checked_at": "2026-03-28T01:00:00.000Z" } ], "uptime_90d": { "clickhouse": [{ "date": "2026-03-28", "status": "operational", "uptime_pct": 100, "total_checks": 1440, "successful_checks": 1440 }] }, "uptime_pct": { "clickhouse": 99.98, "redis": 100, "clob_ws": 99.95, "trade_ws": 99.95 } } ``` `overall` is `"operational"` when all services are up, `"degraded"` when any is degraded, `"down"` when any is down. Checks run every 60 seconds internally. --- ## Market Endpoints (API Key Required) ### GET /v1/categories Returns an array of all enabled market categories with active market counts and capture intervals. ```json [ { "id": "crypto-updown", "category": "crypto", "displayName": "Crypto Up/Down", "activeMarkets": 16, "captureIntervalMs": 50, "refreshIntervalMs": 30000 }, { "id": "nba", "category": "sports", "displayName": "NBA", "activeMarkets": 28, "captureIntervalMs": 500, "refreshIntervalMs": 60000 }, { "id": "nfl", "category": "sports", "displayName": "NFL", "activeMarkets": 30, "captureIntervalMs": 500, "refreshIntervalMs": 60000 }, { "id": "epl", "category": "sports", "displayName": "English Premier League", "activeMarkets": 0, "captureIntervalMs": 500, "refreshIntervalMs": 60000 }, { "id": "fed-decisions", "category": "economics", "displayName": "Fed/FOMC Decisions", "activeMarkets": 3, "captureIntervalMs": 2000, "refreshIntervalMs": 300000 }, { "id": "nonfarm-payroll", "category": "economics", "displayName": "Nonfarm Payroll", "activeMarkets": 0, "captureIntervalMs": 2000, "refreshIntervalMs": 300000 }, { "id": "weather", "category": "weather", "displayName": "Weather & Climate", "activeMarkets": 15, "captureIntervalMs": 5000, "refreshIntervalMs": 600000 }, { "id": "daily-temperature", "category": "weather", "displayName": "Daily Temperature (NYC, etc.)", "activeMarkets": 18, "captureIntervalMs": 2000, "refreshIntervalMs": 300000 } ] ``` --- ### GET /v1/markets/live Returns an **array** of active markets (not wrapped in an object). Use `category` and `subcategory` to filter. **Query Parameters:** | Param | Type | Description | |-------|------|-------------| | `category` | string | `crypto`, `sports`, `economics`, `weather` | | `subcategory` | string | `BTC`, `ETH`, `SOL`, `XRP`, `NBA`, city name, etc. | **Example:** `GET /v1/markets/live?category=crypto&subcategory=BTC` ```json [ { "category": "crypto", "subcategory": "BTC", "label": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "timeframe": "5m", "conditionId": "0xeaa2eb4fa2415a872b124dc8a5a96d29b04224cd4540d48deec2d66df3ace618", "question": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "slug": "btc-updown-5m-1774633500", "tokenIds": [ "47282457674680091829729729779358890039575472266926662024051270695644596450451", "104634820967154531825080809339145208241815408671288421817256091404781070196574" ], "outcomes": ["Up", "Down"], "endDate": "2026-03-27T17:50:00.000Z", "active": true, "configId": "crypto-updown", "crypto": "BTC", "tokenIdUp": "47282457674680091829729729779358890039575472266926662024051270695644596450451", "tokenIdDown": "104634820967154531825080809339145208241815408671288421817256091404781070196574", "expired": false, "expiresIn": 114854 } ] ``` - `conditionId` is Polymarket's hex identifier for the market - `tokenIdUp` = `tokenIds[0]`, `tokenIdDown` = `tokenIds[1]` - `expiresIn` in milliseconds; set to 0 when expired - For crypto: `slug` = `{crypto}-updown-{timeframe}-{unix_seconds_of_end}` --- ### GET /v1/markets/by-slug/:slug Finds a live market by slug. Supports smart fuzzy matching — `btc-updown-5m` finds the current active BTC 5m market without knowing the timestamp suffix. Slug patterns: - Short crypto: `btc-updown-5m`, `btc-updown-5m-1774633500`, `eth-updown-15m` - 1h crypto: `btc-up-or-down-3pm-et` - 1d crypto: `btc-up-or-down-on-march-28` If not found in live markets, falls back to searching ClickHouse for historical records. ```json { "market_id": "0xeaa2eb4fa2415a872b124dc8a5a96d29b04224cd4540d48deec2d66df3ace618", "category": "crypto", "subcategory": "BTC", "label": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "crypto": "BTC", "timeframe": "5m", "slug": "btc-updown-5m-1774633500", "question": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "token_ids": ["472824...", "104634..."], "outcomes": ["Up", "Down"], "token_id_up": "47282457674680091829729729779358890039575472266926662024051270695644596450451", "token_id_down": "104634820967154531825080809339145208241815408671288421817256091404781070196574", "end_date": "2026-03-27T17:50:00.000Z", "active": true, "expired": false, "expires_in": 114854 } ``` Note: field names here are `snake_case`; the same value is `conditionId` in the `/v1/markets/live` response. Historical fallback response (market not currently live): ```json { "market_id": "0x...", "crypto": "BTC", "timeframe": "5m", "slug": "btc-updown-5m-1774633500", "active": false, "snapshots": 8442, "first_seen": "2026-03-27 17:45:22.611", "last_seen": "2026-03-27 17:50:23.011" } ``` --- ### GET /v1/markets/:id/orderbook Live in-memory orderbook for both tokens of a market. The conditionId can also be resolved from a slug — pass `?slug=btc-updown-5m` if you have a slug and not a conditionId. **This endpoint reads from live in-memory state** (not ClickHouse), so it always reflects the most recent Polymarket event. ```json { "marketId": "0xeaa2eb4fa2415a872b124dc8a5a96d29b04224cd4540d48deec2d66df3ace618", "crypto": "BTC", "timeframe": "5m", "cryptoPrice": 65613.65, "up": { "tokenId": "47282457674680091829729729779358890039575472266926662024051270695644596450451", "marketId": "0xeaa2eb...", "timestamp": 1774633719997, "eventTimestamp": 1774633719997, "captureTimestamp": 1774633720247, "sequenceNumber": 68034, "cryptoPrice": 65613.65, "cryptoPriceAgeMs": -1, "crypto": "BTC", "timeframe": "5m", "category": "crypto", "subcategory": "BTC", "outcomeIndex": 0, "bids": [ { "price": 0.41, "size": 1096.92 }, { "price": 0.40, "size": 1193.00 } ], "asks": [ { "price": 0.42, "size": 4.30 }, { "price": 0.43, "size": 48.00 } ], "bestBid": 0.41, "bestAsk": 0.42, "midPrice": 0.415, "spread": 0.01, "bidDepth": 6412.16, "askDepth": 38451.44 }, "down": { "tokenId": "104634820967154531825080809339145208241815408671288421817256091404781070196574", "bids": [{ "price": 0.58, "size": 4.30 }, ...], "asks": [{ "price": 0.59, "size": 1096.92 }, ...], "bestBid": 0.58, "bestAsk": 0.59, "midPrice": 0.585, "spread": 0.01, "bidDepth": 6710.65, "askDepth": 40633.82 } } ``` **Important field notes:** - `timestamp` / `eventTimestamp` / `captureTimestamp`: Unix milliseconds. `eventTimestamp` = when Polymarket emitted the event. `captureTimestamp` = when the server processed it. Difference = capture latency (~250ms typical). - `sequenceNumber`: Monotonically increasing per-market. Gaps = missed events. - `cryptoPriceAgeMs`: Milliseconds since last Binance price update. `-1` means the price is current or unavailable. - `bidDepth` / `askDepth`: Total USDC liquidity on each side across all price levels. - `up` / `down` keys are lowercase. - The Down token is the mirror of Up: Down bestBid = 1 − Up bestAsk, Down bestAsk = 1 − Up bestBid. - All field names are `camelCase` in the live orderbook response. --- ### GET /v1/markets/by-slug/:slug/orderbook Same response shape as `GET /v1/markets/:id/orderbook` but also includes a `slug` field and uses `crypto_price` (snake_case) at the root level. Useful when you have a slug but not a conditionId. --- ### GET /v1/markets/:id/snapshots Historical orderbook snapshots from ClickHouse. Default response **does not include bid/ask arrays** (large data). Use `GET /api/snapshot` with `?includebook=true` for full book data. **Query Parameters:** | Param | Type | Default | Description | |-------|------|---------|-------------| | `limit` | number | 500 | Max results; absolute max: 5000 | | `offset` | number | 0 | Pagination offset | | `from` | string | — | Start timestamp (ISO 8601) | | `to` | string | — | End timestamp (ISO 8601) | | `side` | string | — | Filter to `UP` or `DOWN` token only | **Response:** ```json { "market_id": "0xeaa2eb4fa2415a872b124dc8a5a96d29b04224cd4540d48deec2d66df3ace618", "total": "8442", "limit": 500, "offset": 0, "data": [ { "timestamp": "2026-03-27 17:50:23.011", "token_side": "UP", "crypto": "BTC", "timeframe": "5m", "crypto_price": 65663.78, "best_bid": 0.41, "best_ask": 0.42, "mid_price": 0.415, "spread": 0.01, "bid_depth_total": 6412.16, "ask_depth_total": 38451.44 } ] } ``` `total` is a string. Results are sorted by `timestamp DESC`. The response key is `data` (not `snapshots`). --- ### GET /v1/markets/:id/summary Aggregated statistics for a market over its entire history (or available data). ```json { "market_id": "0xeaa2eb4fa2415a872b124dc8a5a96d29b04224cd4540d48deec2d66df3ace618", "crypto": "BTC", "timeframe": "5m", "snapshot_count": "7187", "first_seen": "2026-03-27 17:45:22.611", "last_seen": "2026-03-27 17:49:32.724", "avg_mid_price": 0.5000333936273874, "min_mid_price": 0.115, "max_mid_price": 0.885, "avg_spread": 0.01177, "min_spread": -0.01, "max_spread": 0.13, "avg_crypto_price": 65670.84, "min_crypto_price": 65602.11, "max_crypto_price": 65738.60, "avg_bid_depth": 8182.64, "avg_ask_depth": 38736.44, "sides": [ { "token_side": "UP", "snapshots": "7252", "avg_bid": 0.4942, "avg_ask": 0.5060, "avg_mid": 0.5001, "max_bid": 0.91, "min_ask": 0.09 } ] } ``` `min_spread` can be negative when `bestAsk < bestBid` (crossed book), which happens when a market has just resolved. --- ### GET /v1/markets/history/recent All tracked markets (live + recently closed) with snapshot counts. Reads from a pre-aggregated materialized view for speed. **Query Parameters:** | Param | Type | Description | |-------|------|-------------| | `crypto` | string | Filter: `BTC`, `ETH`, `SOL`, `XRP` (crypto markets only) | | `timeframe` | string | Filter: `5m`, `15m`, `1h`, `1d` | | `limit` | number | Max results (default: 100, max: 500) | | `category` | string | Filter by category | | `subcategory` | string | Filter by subcategory | ```json { "total_markets": 437, "total_snapshots": 1866194, "live_count": 111, "closed_count": 326, "markets": [ { "crypto": "BTC", "timeframe": "5m", "market_id": "0xeaa2eb...", "first_seen": "2026-03-27 17:45:22.611", "last_seen": "2026-03-27 17:49:32.724", "snapshot_count": "7187", "is_live": true, "end_date": "2026-03-27T17:50:00.000Z", "question": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "category": "crypto", "subcategory": "BTC" } ] } ``` --- ### GET /v1/markets/history Full market history, no row limit. Same structure as `/history/recent` but without `live_count`/`closed_count` in the outer response. Accepts the same query params (except `limit`). --- ### GET /v1/stats Platform-wide statistics. Requires API key. ```json { "uptime_seconds": 7031, "active_markets": 111, "expired_markets": 0, "snapshot_buffer_depth": 48, "trade_buffer_depth": 2, "total_snapshots": "1894196", "total_distinct_markets": "332", "earliest_snapshot": "2026-03-27 03:29:54.110", "latest_snapshot": "2026-03-27 17:55:45.613", "prices": { "BTC": 65689.57, "ETH": 1977.50, "SOL": 82.21, "XRP": 1.3262 }, "per_crypto": [ { "crypto": "BTC", "snapshots": "695843", "markets": "179" }, { "crypto": "ETH", "snapshots": "496169", "markets": "59" }, { "crypto": "NBA", "snapshots": "42802", "markets": "27" }, { "crypto": "NFL", "snapshots": "11101", "markets": "30" }, { "crypto": "FOMC", "snapshots": "7197", "markets": "3" }, { "crypto": "Nyc", "snapshots": "752", "markets": "1" } ], "pipeline": { "capture_produced": 1340897, "ch_inserted": 1340803, "ch_errors": 0, "snapshot_flushes": 3682, "trade_flushes": 3666 } } ``` `per_crypto` uses the market `subcategory` field as the label — includes city names for weather markets, "NBA" for sports. `snapshot_buffer_depth` = pending snapshots in the in-memory batch queue; 0 is ideal. --- ### GET /api/debug Detailed pipeline diagnostics. Requires API key. ```json { "pipeline": { "capture_attempts": 27117904, "capture_throttled": 24921049, "capture_null_book": 9517, "capture_produced": 1822104, "ch_inserted": 1821799, "ch_error": 0, "snapshot_flush_count": 4880, "trade_flush_count": 4924, "snapshot_buffer_depth": 305, "trade_buffer_depth": 53 }, "db": { "total_snapshots": "2458631" }, "prices": { "BTC": 65880.94, "ETH": 1983.93, "SOL": 82.36, "XRP": 1.3221 }, "markets_loaded": 113, "markets_summary": ["BTC-5m", "ETH-5m", "SOL-5m", "XRP-5m", "BTC-1h", ...] } ``` `capture_throttled` = events skipped due to 50ms minimum capture interval (expected: ~90% of attempts). `capture_null_book` = events where the orderbook wasn't populated yet. --- ## Exchange Endpoints (API Key Required) Exchange data from centralized exchanges (currently Hyperliquid perpetual futures). Stored in `exchange_snapshots` table separately from Polymarket data. ### GET /v1/exchange/orderbook Live orderbook for an exchange/symbol pair. Returns the most recent snapshot from the last 10 seconds. **Query Parameters:** | Param | Type | Required | Description | |-------|------|----------|-------------| | `exchange` | string | Yes | Exchange identifier. Valid: `hyperliquid_perp` | | `symbol` | string | Yes | Trading pair symbol, e.g. `BTC`, `ETH`, `SOL`, `XRP` | **Example:** `GET /v1/exchange/orderbook?exchange=hyperliquid_perp&symbol=BTC` ```json { "exchange": "hyperliquid_perp", "symbol": "BTC", "timestamp": "2026-03-28 01:23:45.678", "best_bid": 65700.5, "best_ask": 65701.2, "mid_price": 65700.85, "spread": 0.7, "bid_depth_total": 125000.50, "ask_depth_total": 118000.30, "bids": [{ "price": 65700.5, "size": 1.25 }, ...], "asks": [{ "price": 65701.2, "size": 0.80 }, ...] } ``` Returns 404 if no data from the last 10 seconds exists for the pair. --- ### GET /v1/exchange/snapshots Historical exchange snapshots from ClickHouse. Does **not** include bid/ask arrays (too heavy for list view). **Query Parameters:** | Param | Type | Required | Description | |-------|------|----------|-------------| | `exchange` | string | Yes | Exchange identifier. Valid: `hyperliquid_perp` | | `symbol` | string | Yes | Trading pair symbol | | `from` | string | No | Start timestamp (ISO 8601). Defaults to last 24 hours | | `to` | string | No | End timestamp (ISO 8601) | | `limit` | number | No | Max results (default: 500, max: 5000) | **Example:** `GET /v1/exchange/snapshots?exchange=hyperliquid_perp&symbol=ETH&limit=100` ```json { "exchange": "hyperliquid_perp", "symbol": "ETH", "count": 100, "data": [ { "exchange": "hyperliquid_perp", "symbol": "ETH", "timestamp": "2026-03-28 01:23:45.678", "best_bid": 1980.25, "best_ask": 1980.50, "mid_price": 1980.375, "spread": 0.25, "bid_depth_total": 85000.00, "ask_depth_total": 72000.00 } ] } ``` History depth enforcement applies (free tier: 24h). Results sorted by `timestamp DESC`. --- ## Snapshot Endpoints (API Key Required) ### GET /api/snapshot Point-in-time snapshot lookup. Finds the latest snapshot at or before the given timestamp (within a 1-hour window). **Query Parameters:** | Param | Type | Required | Description | |-------|------|----------|-------------| | `timestamp` | string | Yes | ISO 8601 — finds most recent snapshot ≤ this time | | `marketId` | string | No | conditionId — returns Up/Down pair when provided | | `crypto` | string | No | Filter by crypto (if no `marketId`) | | `timeframe` | string | No | Filter by timeframe (if no `marketId`) | | `includebook` | string | No | `true` to include full bid/ask price level arrays | **Without `includebook=true`** (default): ```json { "up": { "crypto": "BTC", "timeframe": "5m", "market_id": "0xeaa2eb...", "token_id": "47282...", "token_side": "UP", "timestamp": "2026-03-27 17:46:59.982", "crypto_price": 65732.00, "best_bid": 0.13, "best_ask": 0.14, "mid_price": 0.135, "spread": 0.01, "bid_depth_total": 1031.71, "ask_depth_total": 42361.74 }, "down": null, "market_id": "0xeaa2eb...", "timestamp": "2026-03-27 17:46:59.982", "crypto_price": 65732.00, "crypto": "BTC", "timeframe": "5m" } ``` **With `&includebook=true`** — additional fields appear in the snapshot: ```json { "up": { "crypto": "BTC", "timeframe": "5m", "market_id": "0xeaa2eb...", "token_id": "47282...", "token_side": "UP", "timestamp": "2026-03-27 17:46:59.982", "crypto_price": 65732.00, "best_bid": 0.13, "best_ask": 0.14, "mid_price": 0.135, "spread": 0.01, "bid_depth_total": 1031.71, "ask_depth_total": 42361.74, "bids": [ { "price": 0.13, "size": 15.96 }, { "price": 0.12, "size": 378.49 }, { "price": 0.11, "size": 985.97 } ], "asks": [ { "price": 0.14, "size": 1088.15 }, { "price": 0.15, "size": 1053.90 } ], "event_timestamp": "2026-03-27 17:46:59.982", "capture_timestamp": "2026-03-27 17:47:00.615", "sequence_number": "1631", "crypto_price_age_ms": 7, "category": "crypto", "subcategory": "BTC", "label": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "outcome_index": 1 } } ``` Note: Historical snapshots use `snake_case` field names (`event_timestamp`, `sequence_number`, etc.) while the live orderbook endpoint uses `camelCase` (`eventTimestamp`, `sequenceNumber`). `down` may be `null` if no Down token snapshot exists near the requested timestamp. --- ### GET /api/snapshot/latest Last 5 snapshots from the past 10 minutes. Returns an **array** directly (not wrapped in an object). **Query Parameters:** | Param | Type | Description | |-------|------|-------------| | `crypto` | string | Filter: `BTC`, `ETH`, `SOL`, `XRP` | | `timeframe` | string | Filter: `5m`, `15m`, `1h`, `1d` | | `includebook` | string | `true` for full bid/ask arrays + extended fields | ```json [ { "crypto": "BTC", "timeframe": "1h", "market_id": "0x4bd699...", "token_id": "11189...", "token_side": "UP", "timestamp": "2026-03-27 18:03:26.506", "crypto_price": 65727.76, "best_bid": 0.62, "best_ask": 0.63, "mid_price": 0.625, "spread": 0.01, "bid_depth_total": 15213.86, "ask_depth_total": 50437.86, "bids": [], "asks": [] } ] ``` `bids` and `asks` are always present but empty unless `includebook=true`. With `includebook=true` the same extended fields as above (`event_timestamp`, `capture_timestamp`, etc.) are included. --- ## WebSocket API ### Endpoint: /ws/orderbook Real-time orderbook stream. Broadcasts to authenticated subscribers every **2 seconds**. Also pushes an immediate snapshot on subscription. **Full protocol:** ``` Client connects to: wss://api.resolvedmarkets.com/ws/orderbook 1. Client → Server (within 5 seconds): { "type": "auth", "apiKey": "rm_your_key" } 2. Server → Client (on auth success): { "type": "auth", "status": "ok" } { "type": "markets", "data": [ ] } 3. Client → Server: { "type": "subscribe", "crypto": "BTC" } 4. Server → Client (immediate + every 2s): { "type": "orderbook", "markets": [ { "marketId": "0xeaa2eb...", "crypto": "BTC", "timeframe": "5m", "question": "Bitcoin Up or Down - March 27, 1:45PM-1:50PM ET", "cryptoPrice": 65613.65, "up": { ...full orderbook object... }, "down": { ...full orderbook object... } } ] } 5. Server → Client (when market list changes, up to every 10s): { "type": "markets", "data": [ { "conditionId": "0xeaa2eb...", "crypto": "BTC", "timeframe": "5m", "question": "Bitcoin Up or Down...", "slug": "btc-updown-5m-1774633500", "tokenIdUp": "47282...", "tokenIdDown": "10463...", "endDate": "2026-03-27T17:50:00.000Z", "expired": false, "expiresIn": 114854 } ] } ``` **Error frames:** ```json { "type": "error", "code": 4001, "message": "Authentication timeout — send { type: \"auth\", apiKey: \"rm_...\" } within 5 seconds" } { "type": "error", "code": 4003, "message": "WebSocket access is not available on your tier. Upgrade to Pro or Enterprise." } { "type": "error", "code": 4003, "message": "Connection limit reached. Your pro tier allows 2 WebSocket connection(s)." } { "type": "error", "code": 4003, "message": "Invalid or revoked API key" } ``` **Close codes:** `4001` = auth timeout. `4003` = tier restriction or invalid key. **Subscribe options:** `BTC`, `ETH`, `SOL`, `XRP` **Tier limits:** Free = no WS access (dashboard falls back to REST polling every 10s). Pro = 1 concurrent connection. Enterprise = 10. **Heartbeat:** Server pings every 30s; dead connections are terminated. --- ## API Key Management (Clerk Session Required) These endpoints require a valid Clerk session (browser session cookie), not an API key. Used by the frontend dashboard. ### POST /v1/api-keys Generate a new API key. The raw key is shown **once** and never stored — only its SHA-256 hash is stored. ```bash # Request body (optional) { "label": "My integration" } # Response { "key": "rm_abc123...", "label": "My integration", "created_at": "2026-03-27 17:43:51.599", "message": "Store this key securely. It will only be shown once and cannot be retrieved later." } ``` ### GET /v1/api-keys ```json { "keys": [ { "key_preview": "rm_70e6d2...", "key_id": "a1b2c3d4e5f6...", "label": "My integration", "created_at": "2026-03-27 17:43:51.599", "last_used_at": "2026-03-27 18:00:00.000", "request_count": 4521 } ] } ``` ### GET /v1/api-keys/validate No Clerk session needed — just pass the key in `X-API-Key`. Returns `valid: false` if missing, malformed, or revoked. ```json { "valid": true, "label": "My integration", "created_at": "2026-03-27 17:43:51.599" } ``` ### DELETE /v1/api-keys/:key_id Pass `key_id` from the list response (the hash, not the raw key). ```json { "success": true, "message": "API key has been revoked." } ``` --- ## Payment & Tier Endpoints ### GET /v1/payments/packs No auth required. Lists subscriptions and credit packs. ```json { "packs": [ { "id": "pro_monthly", "tier": "pro", "priceUsd": 17, "credits": 50000 }, { "id": "enterprise_monthly", "tier": "enterprise", "priceUsd": 249, "credits": 500000 }, { "id": "credits_1k", "tier": null, "priceUsd": 2, "credits": 1000 }, { "id": "credits_10k", "tier": null, "priceUsd": 10, "credits": 10000 }, { "id": "credits_100k", "tier": null, "priceUsd": 49, "credits": 100000 } ], "tiers": { "free": { "rpm": 60, "wsMax": 0, "historyHours": 24, "mcp": "readonly", "maxKeys": 1 }, "pro": { "rpm": 500, "wsMax": 1, "historyHours": 0, "mcp": "full", "maxKeys": 5 }, "enterprise": { "rpm": 3000, "wsMax": 10, "historyHours": 0, "mcp": "full", "maxKeys": 20 } } } ``` `tier: null` on credit packs = adds credits without changing tier. `historyHours: 0` for pro/enterprise = unlimited history. ### GET /v1/user/tier Clerk session required. ```json { "tier": "pro", "creditsRemaining": 4500, "creditsTotal": 50000, "rateLimitRpm": 500, "wsConnectionsMax": 1, "historyDepthHours": 0, "mcpAccess": "full", "expiresAt": "2026-04-27T17:43:51.599Z", "limits": { "rpm": 500, "wsMax": 1, "historyHours": 0, "mcp": "full", "maxKeys": 5 } } ``` ### POST /v1/payments/create-invoice Clerk session required. Creates a NOWPayments invoice for crypto payment. ```json // Request { "packId": "pro_monthly" } // Response { "invoiceUrl": "https://nowpayments.io/payment/...", "invoiceId": "inv_123", "amount": 17, "currency": "USD" } ``` --- ## Error Handling All errors: ```json { "error": "Human-readable error message" } ``` | HTTP Code | When | |-----------|------| | 400 | Missing required params, invalid ISO timestamp, invalid query values | | 401 | Missing or invalid `X-API-Key` header | | 403 | Tier restriction (history depth, WS limit, MCP access) or API key limit reached | | 404 | Market not found, no snapshot at that timestamp | | 429 | Rate limit exceeded for your tier | | 503 | ClickHouse or Redis unavailable (health check) | | 500 | Internal server error | **Rate limit headers on every response:** ``` X-RateLimit-Limit: 500 X-RateLimit-Remaining: 487 X-RateLimit-Reset: 1711278060 ``` --- ## Data Model Reference ### Market (from /v1/markets/live) | Field | Type | Notes | |-------|------|-------| | `conditionId` | string | Hex market identifier (Polymarket's conditionId) | | `slug` | string | URL-friendly, e.g. `btc-updown-5m-1774633500` | | `question` | string | Human-readable question | | `category` | string | `crypto`, `sports`, `economics`, `weather` | | `subcategory` | string | `BTC`, `ETH`, `SOL`, `XRP`, `NBA`, city name, etc. | | `crypto` | string | Same as `subcategory` for crypto markets | | `timeframe` | string | `5m`, `15m`, `1h`, `1d` for crypto | | `tokenIdUp` | string | Large integer string, Up token | | `tokenIdDown` | string | Large integer string, Down token | | `endDate` | string | ISO 8601 market expiry | | `expiresIn` | number | Milliseconds until expiry (0 when expired) | | `configId` | string | Category config ID, e.g. `crypto-updown` | ### Live Orderbook Token (from /orderbook, camelCase) | Field | Type | Notes | |-------|------|-------| | `tokenId` | string | Large integer string | | `timestamp` / `eventTimestamp` | number | Unix ms (Polymarket event time) | | `captureTimestamp` | number | Unix ms (server processing time) | | `sequenceNumber` | number | Monotonic counter; gaps = missed events | | `cryptoPrice` | number | Underlying asset price at capture | | `cryptoPriceAgeMs` | number | Ms since last Binance update; -1 = fresh or unavailable | | `bids` / `asks` | Level[] | Full price ladder | | `bestBid` / `bestAsk` | number | Top-of-book prices (0.01–0.99) | | `midPrice` | number | (bestBid + bestAsk) / 2 | | `spread` | number | bestAsk − bestBid | | `bidDepth` / `askDepth` | number | Total USDC size across all levels | ### Historical Snapshot (from /api/snapshot with includebook=true, snake_case) | Field | Type | Notes | |-------|------|-------| | `token_side` | string | `"UP"` or `"DOWN"` | | `timestamp` | string | `"YYYY-MM-DD HH:MM:SS.mmm"` | | `event_timestamp` | string | Polymarket event time (only with `includebook=true`) | | `capture_timestamp` | string | Server processing time (only with `includebook=true`) | | `sequence_number` | string | Monotonic counter as string (only with `includebook=true`) | | `crypto_price` | number | Underlying asset price | | `crypto_price_age_ms` | number | Binance feed latency at capture (only with `includebook=true`) | | `best_bid` / `best_ask` | number | Top-of-book | | `mid_price` / `spread` | number | Derived from best bid/ask | | `bid_depth_total` / `ask_depth_total` | number | Total USDC liquidity | | `bids` / `asks` | Level[] | Full price ladder (only with `includebook=true`) | | `label` | string | Market label (only with `includebook=true`) | | `outcome_index` | number | 0 = first outcome, 1 = second (only with `includebook=true`) | ### Level ```json { "price": 0.41, "size": 1096.92 } ``` Prices are in 0.01 increments (Polymarket supports 1¢ tick size). Size is in USDC. --- ## Market Categories ### Crypto (`crypto-updown`) BTC, ETH, SOL, XRP prediction markets — will the price be Up or Down at the end of the window? Four timeframes per asset: 5 minutes, 15 minutes, 1 hour, 1 day. Captured at ~20Hz (~50ms minimum interval per token). At any time there are up to 4 × 4 = 16 live crypto markets. Slug patterns: `btc-updown-5m-{unix_end}`, `btc-updown-15m-{unix_end}`, `btc-up-or-down-3pm-et` (1h), `btc-up-or-down-on-march-28` (1d). ### Sports - **NBA** (`nba`): Basketball game outcome markets. Captured at 2Hz (500ms). - **NFL** (`nfl`): Football game outcome markets. Captured at 2Hz. - **EPL** (`epl`): English Premier League soccer. Captured at 2Hz. ### Economics - **FOMC** (`fed-decisions`): Federal Reserve interest rate decision markets. Captured every 2s. - **Nonfarm Payroll** (`nonfarm-payroll`): Monthly NFP report prediction markets. ### Weather - **Daily Temperature** (`daily-temperature`): Will temperature in a city be above/below threshold? ~30 cities: NYC, Atlanta, Chicago, Dallas, Miami, Seattle, Toronto, Paris, London, Munich, Tokyo, Seoul, Hong Kong, Shanghai, Sao Paulo, Buenos Aires, Wellington, and others. - **Weather & Climate** (`weather`): Hurricanes, Arctic sea ice, seasonal events. ### Exchange - **Hyperliquid Perp** (`hyperliquid_perp`): Perpetual futures orderbooks for BTC, ETH, SOL, XRP via Hyperliquid L2 book WebSocket. Event-driven deltas, sampled at 1/sec for storage. Data stored in `exchange_snapshots` table (separate from Polymarket data). Endpoints: `/v1/exchange/orderbook`, `/v1/exchange/snapshots`. ### Category Tier Gating The dashboard and API enforce category access by tier. **Free tier** users can access **Crypto** and **Weather** categories only. **Pro and Enterprise** users have access to all categories (Sports, Economics, Exchange, and any future additions). On the dashboard, locked categories display a lock icon and an upgrade prompt linking to the pricing page. Free-tier users without WebSocket access see data via REST polling fallback (every 10 seconds). --- ## CLI (`rm-api`) Install globally: `npm install -g @elcara-hq/resolved-markets` (binary: `rm-api`) ```bash # Setup rm-api config --set-key rm_your_key # save API key to ~/.resolved-markets rm-api config --set-url https://api.resolvedmarkets.com rm-api config --show # show current config rm-api doctor # check connectivity, config, and API health rm-api update # check for CLI updates # Data rm-api markets # list all live markets rm-api markets -c crypto # filter by category rm-api markets -c sports -s NBA # filter by category + subcategory rm-api markets --json # raw JSON output rm-api categories # list all market categories rm-api orderbook 0x1234... # show live orderbook for a market rm-api snapshots 0x1234... -l 20 # get 20 recent snapshots rm-api summary 0x1234... # aggregated market statistics rm-api stats # system stats (no auth needed) # Streaming & Monitoring rm-api stream -c crypto # stream all crypto via WebSocket (JSONL) rm-api stream -c crypto --format csv # CSV output rm-api stream -m 0x1234... # stream a specific market rm-api watch # live-refreshing TUI table (all markets) rm-api watch -c crypto -n 3 # crypto only, refresh every 3s # Alerts rm-api alert spread -m 0x1234... --above 0.05 --once # alert when spread > 5% rm-api alert price -m 0x1234... --below 0.30 # alert when price < 0.30 rm-api alert new-market -c crypto # alert on new market rm-api alert spread -m 0x... --above 0.05 --command "notify-send 'alert'" rm-api alert spread -m 0x... --above 0.05 --webhook https://hooks.slack.com/... # Analysis rm-api search "BTC 1 hour" # fuzzy market search rm-api search "BTC" --include-closed # include historical markets rm-api analyze spread -m 0x1234... --last 24h # spread distribution rm-api analyze depth -m 0x1234... --last 1h # liquidity analysis rm-api analyze volatility -c crypto --last 24h # volatility across category rm-api gaps -m 0x1234... # detect data gaps in snapshots rm-api gaps -c crypto --threshold 500 # check category with custom threshold (ms) # Export & Backtesting rm-api download -m 0x1234... --last 7d # bulk export to SQLite DB rm-api download -c crypto --from 2026-03-01 --to 2026-03-28 # date range rm-api download -m 0x1234... --last 24h --format csv -o data.csv rm-api replay -m 0x1234... --speed 10 # visual orderbook replay (10x) rm-api replay -m 0x1234... --step # manual step mode (Enter) rm-api backtest --builtin spread-revert -m 0x1234... --last 7d rm-api backtest --builtin momentum -m 0x... --last 24h --initial-balance 500 rm-api backtest --builtin depth-imbalance -m 0x... --last 7d rm-api backtest --strategy ./my-strategy.js -m 0x... --last 7d # Utilities rm-api usage # show API usage stats and tier info rm-api init my-trading-project # create a project scaffold rm-api completion bash >> ~/.bashrc # shell completions eval "$(rm-api completion zsh)" # zsh completions ``` --- ## MCP Server AI agents (Claude Desktop, Claude Code, etc.) connect via Model Context Protocol. ### Setup ```bash # stdio transport (default — for Claude Desktop/Code) HF_API_URL=https://api.resolvedmarkets.com \ HF_API_KEY=rm_your_key \ pnpm mcp:dev # HTTP transport MCP_TRANSPORT=http MCP_PORT=3002 pnpm mcp:dev # MCP endpoint: http://localhost:3002/mcp ``` ### Tools | Tool | Parameters | Description | |------|------------|-------------| | `list_markets` | `status?: "live"\|"history"`, `crypto?: string` | List active or historical markets | | `get_orderbook` | `market_id: string` | Live bid/ask for both tokens | | `get_snapshot` | `market_id: string`, `timestamp: string`, `side?: "UP"\|"DOWN"` | Point-in-time snapshot | | `query_snapshots` | `market_id: string`, `from?: string`, `to?: string`, `limit?: number (max 500)` | Time-range snapshot query | | `get_market_summary` | `market_id: string` | Aggregated stats | | `get_system_stats` | — | Platform health + current prices | ### Resources | URI | Description | |-----|-------------| | `markets://live` | All currently active markets (JSON) | | `prices://latest` | Current crypto prices + snapshot count (JSON) | ### Config | Env Var | Default | Description | |---------|---------|-------------| | `HF_API_URL` | `http://localhost:3001` | Backend API URL | | `HF_API_KEY` | — | API key (required for authenticated endpoints) | | `MCP_TRANSPORT` | `stdio` | `stdio` or `http` | | `MCP_PORT` | `3002` | HTTP port (only for `http` transport) | --- ## Code Examples ### Python — stream snapshots to a CSV ```python import requests, csv, sys API_KEY = "rm_your_key" BASE = "https://api.resolvedmarkets.com" headers = {"X-API-Key": API_KEY} # Find current BTC 5m market markets = requests.get(f"{BASE}/v1/markets/live", headers=headers, params={"category": "crypto", "subcategory": "BTC"}).json() btc_5m = next((m for m in markets if m["timeframe"] == "5m"), None) market_id = btc_5m["conditionId"] # Fetch last 1000 snapshots resp = requests.get(f"{BASE}/v1/markets/{market_id}/snapshots", headers=headers, params={"limit": 1000, "side": "UP"}).json() writer = csv.DictWriter(sys.stdout, fieldnames=["timestamp", "best_bid", "best_ask", "mid_price", "spread", "bid_depth_total", "ask_depth_total", "crypto_price"]) writer.writeheader() for row in resp["data"]: writer.writerow(row) ``` ### JavaScript — live orderbook via WebSocket ```javascript const WebSocket = require("ws"); const ws = new WebSocket("wss://api.resolvedmarkets.com/ws/orderbook"); ws.on("open", () => { ws.send(JSON.stringify({ type: "auth", apiKey: "rm_your_key" })); }); ws.on("message", (raw) => { const msg = JSON.parse(raw.toString()); if (msg.type === "auth" && msg.status === "ok") { ws.send(JSON.stringify({ type: "subscribe", crypto: "BTC" })); } if (msg.type === "orderbook") { for (const market of msg.markets) { const { timeframe, up } = market; if (!up) continue; console.log(`BTC ${timeframe}: bid=${up.bestBid} ask=${up.bestAsk} spread=${up.spread.toFixed(4)}`); } } if (msg.type === "error") { console.error("WS error:", msg.code, msg.message); ws.close(); } }); ``` ### Bash — quick market lookup and orderbook ```bash API_KEY="rm_your_key" BASE="https://api.resolvedmarkets.com" # Get current BTC price from public stats (no auth) curl -s "$BASE/v1/public-stats" | jq '.prices.BTC' # Find the current BTC 1h market MARKET=$(curl -s "$BASE/v1/markets/by-slug/btc-updown-1h" \ -H "X-API-Key: $API_KEY") MARKET_ID=$(echo $MARKET | jq -r '.market_id') # Get live orderbook curl -s "$BASE/v1/markets/$MARKET_ID/orderbook" \ -H "X-API-Key: $API_KEY" | jq '{ timeframe: .timeframe, cryptoPrice: .cryptoPrice, up: { bestBid: .up.bestBid, bestAsk: .up.bestAsk, spread: .up.spread }, down: { bestBid: .down.bestBid, bestAsk: .down.bestAsk } }' ``` --- ## Tiers & Pricing | Feature | Free | Pro ($17/mo) | Enterprise ($249/mo) | |---------|------|-------------|---------------------| | Rate limit | 60 req/min | 500 req/min | 3000 req/min | | History access | 24 hours | Unlimited | Unlimited | | WebSocket connections | 0 | 1 | 10 | | MCP access | read-only | full | full | | Max API keys | 1 | 5 | 20 | | Dashboard categories | Crypto + Weather | All | All | History depth is enforced on: `/v1/markets/:id/snapshots` (from/to params), `/api/snapshot` (timestamp lookback), and MCP `query_snapshots`. Free-tier dashboard shows lock icons on restricted categories (sports, economics, social, equities, exchange) and falls back to REST polling (every 10s) since WS is unavailable. Credit packs (don't change tier, add credits): | Pack | Credits | Price | |------|---------|-------| | `credits_1k` | 1,000 | $2 | | `credits_10k` | 10,000 | $10 | | `credits_100k` | 100,000 | $49 | Payments are processed via NOWPayments (cryptocurrency). See `GET /v1/payments/packs` for live pricing.