Introduction
The MarketMaster API gives you programmatic access to our cross-platform prediction market engine: edges (mispriced markets ranked by edge size), markets (normalized snapshots across venues), a single-market lookup, cross-platform arbitrage spreads, the whale trade feed, and a real-time WebSocket stream. Every REST endpoint is a simple GET that returns JSON.
Quickstart
1. Generate a key on your dashboard (Developer API card). Copy it — it’s shown only once.
2. Send it in the x-api-key header.
3. Call an endpoint.
curl "https://api.marketmaster.live/v1/edges?limit=5" \ -H "x-api-key: mmk_live_your_key_here" \ -H "User-Agent: my-app/1.0"
SDKs & MCP
Official clients with auth, retries, rate-limit handling and a proper User-Agent built in. Or skip code entirely and plug the data into Claude or Cursor via MCP.
npm install @marketmaster/sdk
import { MarketMaster } from "@marketmaster/sdk"; const mm = new MarketMaster({ apiKey: process.env.MM_API_KEY }); const { edges } = await mm.edges({ platform: "kalshi", min_edge: 5 });
pip install marketmaster
from marketmaster import MarketMaster mm = MarketMaster(api_key="mmk_live_your_key") edges = mm.edges(platform="kalshi", min_edge=5)["edges"]
Six read-only tools (mm_edges, mm_markets, mm_market, mm_arbitrage, mm_whales, mm_status) via npx — no install. Add to claude_desktop_config.json (or ~/.cursor/mcp.json):
{
"mcpServers": {
"marketmaster": {
"command": "npx",
"args": ["-y", "@marketmaster/mcp"],
"env": { "MARKETMASTER_API_KEY": "mmk_live_your_key" }
}
}
}
Authentication
Authenticate every request with your API key in the x-api-key header. Keys look like mmk_live_… and are managed from your dashboard. A key is shown once at creation; store it in an environment variable, never commit it to source control, and rotate it from the dashboard if it’s ever exposed.
401.User-Agent header. Requests from default library agents (e.g. python-urllib) can be rejected by our edge bot protection. Our official SDKs set one automatically.Conventions
Base URL
https://api.marketmaster.live
| Format | All requests and responses are JSON. |
| Prices | Probabilities in 0.0–1.0 (e.g. 0.43 = 43¢ / 43% implied chance). |
| Timestamps | ISO 8601, UTC (e.g. 2026-06-20T21:25:12Z). |
| Venues | kalshi, polymarket. |
| CORS | Enabled for all origins — call directly from a browser app. |
| Pagination | List endpoints accept limit and offset; response includes has_more. |
| Caching | Data endpoints send Cache-Control: public, max-age=30. |
Pricing
All endpoints — including arbitrage, edges, whale feed, and WebSocket streaming — are available on every tier.
- All 6 REST endpoints
- Edges, arb & whale feed
- WebSocket: 1 connection
- 1 API key
- Everything in Free
- 2× rate limit
- WebSocket: 3 connections
- No consumer app required
- Everything in Starter
- 5× rate limit
- WebSocket: 10 connections
- For high-frequency pipelines
MarketMaster Pro ($12.99/mo consumer sub) also includes API access at 240 req/min · 200k req/mo, WebSocket: 5 connections, plus scanner, alerts & overlay.
Why MarketMaster vs alternatives?
| Feature | MarketMaster | Competitors |
|---|---|---|
| Arbitrage feed | ✅ All tiers | Enterprise only |
| EV / edge rankings | ✅ All tiers | Enterprise only |
| Whale trade feed | ✅ All tiers | Enterprise only |
| WebSocket streaming | ✅ All tiers | Enterprise only |
| Entry price | Free forever | $49+/mo to start |
| Kalshi + Polymarket + more | ✅ Unified | Single-venue |
Rate limits
Limits are enforced per account across two windows: a per-minute rate and a monthly quota. Limits survive key rotation.
| Tier | Rate | Monthly quota | WS connections |
|---|---|---|---|
free | 60 req / min | 1,000 | 1 |
api_starter | 120 req / min | 50,000 | 3 |
pro (consumer) | 240 req / min | 200,000 | 5 |
api_pro | 300 req / min | 1,000,000 | 10 |
Rate-limit response headers
X-RateLimit-Limit-Minute | Your per-minute ceiling. |
X-RateLimit-Remaining-Minute | Requests left this minute. |
X-RateLimit-Limit-Month | Your monthly quota. |
X-RateLimit-Remaining-Month | Requests left this month. |
Exceeding the per-minute rate returns 429 rate_limited; exhausting the monthly quota returns 429 quota_exceeded. Both include a Retry-After header in seconds.
Edges
The most mispriced markets right now — our model’s fair value vs. the live price, ranked by edge size. Returns one row per market, deduplicated to the latest reading.
Query parameters
| Parameter | Type | Description |
|---|---|---|
platform | string | Filter by venue: kalshi, polymarket. |
category | string | Filter by category, e.g. politics, sports, crypto. |
limit | integer | Max rows. Default 50, max 200. |
offset | integer | Rows to skip. Default 0. |
min_edge | number | Minimum edge in percentage points. |
Response: edges[]
| Field | Type | Description |
|---|---|---|
platform | string | Venue. |
market_id | string | Venue’s native market identifier. |
market_title | string | Market question. |
market_category | string | Category (e.g. politics). |
outcome | string | YES or NO. |
price | number | Live market price (0–1). |
fair_value | number | Model’s estimated fair probability (0–1). |
edge_pct | number | Edge in percentage points. Larger = more mispriced. |
confidence | number | Model confidence (0–1). |
match_group_id | integer | Cross-venue match group ID. Markets sharing this ID are the same real-world event on different venues. |
expires_at | string | When the market closes (ISO 8601). |
computed_at | string | When this edge was computed (ISO 8601). |
Example
curl "https://api.marketmaster.live/v1/edges?platform=kalshi&limit=2" \ -H "x-api-key: mmk_live_your_key_here"
const res = await fetch( "https://api.marketmaster.live/v1/edges?platform=kalshi&limit=2", { headers: { "x-api-key": process.env.MM_API_KEY } } ); const { edges } = await res.json();
import requests r = requests.get( "https://api.marketmaster.live/v1/edges", params={"platform": "kalshi", "limit": 2}, headers={"x-api-key": MM_API_KEY}, ) edges = r.json()["edges"]
{
"count": 2,
"edges": [{
"platform": "kalshi", "market_id": "PRES-2028-DEM",
"market_title": "Will a Democrat win the 2028 election?",
"outcome": "YES", "price": 0.43, "fair_value": 0.51,
"edge_pct": 8.0, "confidence": 0.62,
"expires_at": "2028-11-07T05:00:00Z", "computed_at": "2026-06-20T21:25:12Z"
}],
"generated_at": "2026-06-20T21:25:34Z"
}
Markets
The latest snapshot of all tracked markets across both venues — title, category, YES/NO price, 24h volume, and close time. Sorted by 24h volume (most active first).
Query parameters
| Parameter | Type | Description |
|---|---|---|
source | string | Filter by venue (alias: platform). |
category | string | Filter by category. |
limit | integer | Max rows. Default 100, max 500. |
offset | integer | Rows to skip. Default 0. |
Response: markets[]
| Field | Type | Description |
|---|---|---|
source | string | Venue. |
source_market_id | string | Venue’s native market id. |
title | string | Market question. |
category | string | Category. |
yes_price | number | Current YES price (0–1). |
no_price | number | Current NO price (0–1). |
volume_24h_usd | number | null | 24h traded volume in USD. |
close_time | string | null | Market close time (ISO 8601). |
last_trade_at | string | null | Last trade timestamp (ISO 8601). |
fetched_at | string | When we last snapshotted this market (ISO 8601). |
curl "https://api.marketmaster.live/v1/markets?category=politics&limit=50" \ -H "x-api-key: mmk_live_your_key_here"
Market
A single market’s latest snapshot plus any live edges computed for it. Identify by source + id.
Query parameters
| Parameter | Type | Description |
|---|---|---|
source | string · required | Venue: kalshi, polymarket. |
id | string · required | Venue’s native market id. |
Returns market (same shape as markets[]) and edges[] (same shape as edges[], most recent first). Unknown id returns 404 not_found.
curl "https://api.marketmaster.live/v1/market?source=kalshi&id=PRES-2028-DEM" \ -H "x-api-key: mmk_live_your_key_here"
Arbitrage
Cross-platform price spreads for the same outcome on matched markets, ranked widest first.
Query parameters
| Parameter | Type | Description |
|---|---|---|
min_spread | number | Minimum spread in percentage points. |
limit | integer | Max rows. Default 50, max 200. |
offset | integer | Rows to skip. Default 0. |
Response: opportunities[]
| Field | Type | Description |
|---|---|---|
match_group_id | integer | Cross-venue match group. |
title | string | Market question. |
outcome | string | Outcome being compared (YES/NO). |
spread_pct | number | Dearest minus cheapest price, in percentage points. |
buy_yes | object | Cheapest venue: { platform, market_id, price }. |
sell_yes | object | Dearest venue: { platform, market_id, price }. |
computed_at | string | When these prices were computed (ISO 8601). |
curl "https://api.marketmaster.live/v1/arbitrage?min_spread=2&limit=10" \ -H "x-api-key: mmk_live_your_key_here"
Whales
Recent large real-money trades across venues, newest first.
Query parameters
| Parameter | Type | Description |
|---|---|---|
source | string | Filter by venue. |
min | integer | Minimum trade size in USD. |
limit | integer | Max rows. Default 50, max 200. |
offset | integer | Rows to skip. Default 0. |
Response: trades[]
| Field | Type | Description |
|---|---|---|
source | string | Venue. |
source_market_id | string | Venue’s native market id. |
trader_name | string | null | Public trader handle, when available. |
side | string | Trade side (buy/sell). |
outcome | string | Outcome traded. |
size_usd | number | Notional size in USD. |
price | number | Trade price (0–1). |
title | string | Market question. |
trade_time | string | When the trade printed (ISO 8601). |
tx_hash | string | null | On-chain tx hash for on-chain venues. |
curl "https://api.marketmaster.live/v1/whales?min=10000&limit=20" \ -H "x-api-key: mmk_live_your_key_here"
Status
Returns your key’s tier, limits, and current usage. Use to monitor remaining quota.
{
"ok": true,
"tier": "free",
"limits": { "minute": 60, "month": 1000 },
"usage": { "minute": 3, "month": 412 }
}
WebSocket Streaming
Connect once and receive pushed events the moment they’re ready — no polling. The stream pushes edge batches and whale trade notifications from our ingest pipeline as each batch is produced.
Connection URL
wss://api.marketmaster.live/api/v1/stream
Authentication
Your API key must be sent at connection time. Two options depending on environment:
| Environment | How to authenticate |
|---|---|
| Node.js / Python / server | Pass x-api-key: mmk_live_... in the WebSocket upgrade request headers. |
| Browser | Append ?api_key=mmk_live_... as a query parameter — browsers cannot set custom headers on WebSocket connections. |
Channels
/v1/edges on receipt for the updated list./v1/whales on receipt for the latest fills.Client → server messages (send as JSON)
| Action | Payload | Effect |
|---|---|---|
subscribe | {"action":"subscribe","channel":"edges"} | Start receiving events for this channel. |
unsubscribe | {"action":"unsubscribe","channel":"edges"} | Stop receiving events for this channel. |
ping | {"action":"ping"} | Server replies with pong. Use to keep the connection alive. |
Server → client messages (receive as JSON)
| Type | Example payload | When |
|---|---|---|
welcome | {"type":"welcome","tier":"free","conn_id":"abc"} | Immediately on connect. |
subscribed | {"type":"subscribed","channel":"edges"} | After a successful subscribe. |
event | {"type":"event","channel":"edges","data":{"count":34,"ts":1751000000000}} | New data available for a subscribed channel. |
pong | {"type":"pong"} | Reply to your ping. |
error | {"type":"error","code":"too_many_connections","message":"..."} | Auth failure, connection limit exceeded, or invalid message. |
Connection limits by tier
| Tier | Max concurrent connections | Max channel subscriptions / connection |
|---|---|---|
free | 1 | 5 |
api_starter | 3 | 20 |
pro (consumer) | 5 | 50 |
api_pro | 10 | 100 |
Example — browser
// Pass key as query param; browsers can't set WebSocket headers const ws = new WebSocket( `wss://api.marketmaster.live/api/v1/stream?api_key=${MM_API_KEY}` ); ws.onopen = () => { ws.send(JSON.stringify({ action: "subscribe", channel: "edges" })); ws.send(JSON.stringify({ action: "subscribe", channel: "whales" })); }; ws.onmessage = async (e) => { const msg = JSON.parse(e.data); if (msg.type === "event" && msg.channel === "edges") { // New edge batch — pull fresh data const { edges } = await fetch("/v1/edges?limit=20", { headers: { "x-api-key": MM_API_KEY } }).then(r => r.json()); renderEdges(edges); } }; // Keep-alive ping every 30s setInterval(() => ws.send(JSON.stringify({ action: "ping" })), 30_000);
Example — Node.js
import WebSocket from "ws"; const ws = new WebSocket("wss://api.marketmaster.live/api/v1/stream", { headers: { "x-api-key": process.env.MM_API_KEY }, }); ws.on("open", () => { ws.send(JSON.stringify({ action: "subscribe", channel: "edges" })); ws.send(JSON.stringify({ action: "subscribe", channel: "whales" })); }); ws.on("message", (raw) => { const msg = JSON.parse(raw); console.log(msg.type, msg.channel ?? "", msg.data ?? ""); });
Example — Python
import asyncio, json, websockets async def stream(): uri = "wss://api.marketmaster.live/api/v1/stream" async with websockets.connect(uri, extra_headers={"x-api-key": MM_API_KEY}) as ws: await ws.send(json.dumps({"action": "subscribe", "channel": "edges"})) await ws.send(json.dumps({"action": "subscribe", "channel": "whales"})) async for raw in ws: msg = json.loads(raw) print(msg["type"], msg.get("channel"), msg.get("data")) asyncio.run(stream())
Errors
Errors return the appropriate HTTP status with a JSON envelope:
{ "error": { "code": "rate_limited", "message": "Per-minute rate limit exceeded." } }
| Status | Code | Meaning |
|---|---|---|
401 | missing_api_key | No x-api-key header sent. |
401 | invalid_api_key | Key is malformed, unknown, or revoked. |
400 | invalid_parameter | Query parameter invalid or required param missing. |
404 | not_found | No resource matched (e.g. unknown market id). |
426 | websocket_required | /api/v1/stream must be connected via WebSocket. |
429 | rate_limited | Per-minute rate exceeded — check Retry-After header. |
429 | quota_exceeded | Monthly quota exhausted — resets at month start. |
500 | auth_error | Temporary problem validating the key — retry. |
502 | upstream_error | Transient data load problem — retry. |
503 | streaming_unavailable | WebSocket streaming temporarily unavailable. |
WebSocket error frames use the same code / message shape, delivered as a JSON message before the server closes the connection.
Versioning & changes
The API is versioned in the path (/v1/). We may add new fields to responses and new channels to the stream at any time — write clients that ignore unknown fields and message types. Breaking changes would ship under a new version path. This is an early release; endpoints, limits, and stream channels may still evolve.
Disclaimer
Edge and fair-value figures are model estimates for informational purposes only. Not trading advice, not a guarantee of profit. Data provided as-is without warranty. You are responsible for complying with the terms and applicable laws of any venue you trade on.