MarketMaster

Dữ liệu thị trường dự đoán Kalshi và Polymarket chỉ đọc: lợi thế EV, chênh lệch giá giữa các sàn, ảnh chụp nhanh thị trường và giao dịch cá voi.

Tài liệu

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.

The API is read-only. It never places trades, moves funds, or exposes any individual user’s data — only public market data.

REST · JSON WebSocket streaming API-key auth CORS-enabled MCP server v1

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

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.

JavaScript / TypeScript

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 });

Python

pip install marketmaster
from marketmaster import MarketMaster

mm = MarketMaster(api_key="mmk_live_your_key")
edges = mm.edges(platform="kalshi", min_edge=5)["edges"]

MCP — Claude Desktop / Cursor

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" }
    }
  }
}

MCP — hosted endpoint (nothing to install)

The same six tools are also served from a hosted Streamable HTTP MCP endpoint, for clients that connect to a URL rather than spawning a local process. Authenticate with the same key as a bearer token.

https://api.marketmaster.live/mcp
{
  "mcpServers": {
    "marketmaster": {
      "url": "https://api.marketmaster.live/mcp",
      "headers": { "Authorization": "Bearer mmk_live_your_key" }
    }
  }
}

Use npx above if you want the server running locally; use the hosted endpoint if your client only takes a URL, or you would rather not run a process. Both expose identical tools and count against the same quota.

MarketMaster is listed in the official MCP Registry as live.marketmaster/mcp and on Smithery. A machine-readable description of the server is published at https://api.marketmaster.live/.well-known/mcp/server-card.json.

Every MCP tool is annotated readOnlyHint: true. An agent connected to MarketMaster can read market data and nothing else — it cannot place a trade, move funds, or reach another user’s account.

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.

Treat your key like a password. Requests without a valid key return 401.

Always send a descriptive 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
FormatAll requests and responses are JSON.
PricesProbabilities in 0.0–1.0 (e.g. 0.43 = 43¢ / 43% implied chance).
TimestampsISO 8601, UTC (e.g. 2026-06-20T21:25:12Z).
Venueskalshi, polymarket.
CORSEnabled for all origins — call directly from a browser app.
PaginationList endpoints accept limit and offset; response includes has_more.
CachingData endpoints send Cache-Control: public, max-age=30.

Pricing

All endpoints — including arbitrage, edges, whale feed, and WebSocket streaming — are available on every tier.

Free

$0/mo

60 req/min · 1,000 req/mo

  • All 6 REST endpoints
  • Edges, arb & whale feed
  • WebSocket: 1 connection
  • 1 API key

Most popular

API Starter

$9.99/mo

120 req/min · 50,000 req/mo

  • Everything in Free
  • 2× rate limit
  • WebSocket: 3 connections
  • No consumer app required

API Pro

$29.99/mo

300 req/min · 1,000,000 req/mo

  • 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?

FeatureMarketMasterCompetitors
Arbitrage feed✅ All tiersEnterprise only
EV / edge rankings✅ All tiersEnterprise only
Whale trade feed✅ All tiersEnterprise only
WebSocket streaming✅ All tiersEnterprise only
Entry priceFree forever$49+/mo to start
Kalshi + Polymarket + more✅ UnifiedSingle-venue

Rate limits

Limits are enforced per account across two windows: a per-minute rate and a monthly quota. Limits survive key rotation.

TierRateMonthly quotaWS connections
free60 req / min1,0001
api_starter120 req / min50,0003
pro (consumer)240 req / min200,0005
api_pro300 req / min1,000,00010

Rate-limit response headers

X-RateLimit-Limit-MinuteYour per-minute ceiling.
X-RateLimit-Remaining-MinuteRequests left this minute.
X-RateLimit-Limit-MonthYour monthly quota.
X-RateLimit-Remaining-MonthRequests 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

GET/v1/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

ParameterTypeDescription
platformstringFilter by venue: kalshi, polymarket.
categorystringFilter by category, e.g. politics, sports, crypto.
limitintegerMax rows. Default 50, max 200.
offsetintegerRows to skip. Default 0.
min_edgenumberMinimum edge in percentage points.

Response: edges[]

FieldTypeDescription
platformstringVenue.
market_idstringVenue’s native market identifier.
market_titlestringMarket question.
market_categorystringCategory (e.g. politics).
outcomestringYES or NO.
pricenumberLive market price for the side named by outcome (0–1). On a NO row this is the NO price, not the YES price.
fair_valuenumberModel’s estimated fair probability for the side named by outcome (0–1). Every price field on a row — price, fair_value, ask — is quoted on that same side, so fair_value − price is always the edge in your favour.
edge_pctnumberEdge in percentage points. Larger = more mispriced.
confidencenumberModel confidence (0–1).
match_group_idintegerCross-venue match group ID. Markets sharing this ID are the same real-world event on different venues.
expires_atstringWhen the market closes (ISO 8601).
computed_atstringWhen this edge was computed (ISO 8601).

Example

curl

curl "https://api.marketmaster.live/v1/edges?platform=kalshi&limit=2" \
  -H "x-api-key: mmk_live_your_key_here"

JavaScript

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();

Python

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"]

Response

{
  "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

GET/v1/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

ParameterTypeDescription
sourcestringFilter by venue (alias: platform).
categorystringFilter by category.
limitintegerMax rows. Default 100, max 500.
offsetintegerRows to skip. Default 0.

Response: markets[]

FieldTypeDescription
sourcestringVenue.
source_market_idstringVenue’s native market id.
titlestringMarket question.
categorystringCategory.
yes_pricenumberCurrent YES price (0–1).
no_pricenumberCurrent NO price (0–1).
volume_24h_usdnumber | null24h traded volume in USD.
close_timestring | nullMarket close time (ISO 8601).
last_trade_atstring | nullLast trade timestamp (ISO 8601).
fetched_atstringWhen we last snapshotted this market (ISO 8601).

curl

curl "https://api.marketmaster.live/v1/markets?category=politics&limit=50" \
  -H "x-api-key: mmk_live_your_key_here"

Market

GET/v1/market

A single market’s latest snapshot plus any live edges computed for it. Identify by source + id.

Query parameters

ParameterTypeDescription
sourcestring · requiredVenue: kalshi, polymarket.
idstring · requiredVenue’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

curl "https://api.marketmaster.live/v1/market?source=kalshi&id=PRES-2028-DEM" \
  -H "x-api-key: mmk_live_your_key_here"

Arbitrage

GET/v1/arbitrage

Cross-platform price spreads for the same outcome on matched markets, ranked widest first.

Spreads are indicative and gross of fees, slippage, and bid/ask depth — not guaranteed arbitrage. Always confirm executable prices on the venue.

Query parameters

ParameterTypeDescription
min_spreadnumberMinimum spread in percentage points.
limitintegerMax rows. Default 50, max 200.
offsetintegerRows to skip. Default 0.

Response: opportunities[]

FieldTypeDescription
match_group_idintegerCross-venue match group.
titlestringMarket question.
outcomestringOutcome being compared (YES / NO).
spread_pctnumberDearest minus cheapest price, in percentage points.
buy_yesobjectCheapest venue: { platform, market_id, price }.
sell_yesobjectDearest venue: { platform, market_id, price }.
computed_atstringWhen these prices were computed (ISO 8601).

curl

curl "https://api.marketmaster.live/v1/arbitrage?min_spread=2&limit=10" \
  -H "x-api-key: mmk_live_your_key_here"

Whales

GET/v1/whales

Recent large real-money trades across venues, newest first.

Query parameters

ParameterTypeDescription
sourcestringFilter by venue.
minintegerMinimum trade size in USD.
limitintegerMax rows. Default 50, max 200.
offsetintegerRows to skip. Default 0.

Response: trades[]

FieldTypeDescription
sourcestringVenue.
source_market_idstringVenue’s native market id.
trader_namestring | nullPublic trader handle, when available.
sidestringTrade side (buy / sell).
outcomestringOutcome traded.
size_usdnumberNotional size in USD.
pricenumberTrade price (0–1).
titlestringMarket question.
trade_timestringWhen the trade printed (ISO 8601).
tx_hashstring | nullOn-chain tx hash for on-chain venues.

curl

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.

WSS /api/v1/stream Real-time

Connection URL

wss://api.marketmaster.live/api/v1/stream

Authentication

Your API key must be sent at connection time. Two options depending on environment:

EnvironmentHow to authenticate
Node.js / Python / serverPass x-api-key: mmk_live_... in the WebSocket upgrade request headers.
BrowserAppend ?api_key=mmk_live_... as a query parameter — browsers cannot set custom headers on WebSocket connections.

Never expose your API key in public client-side browser code. Use a short-lived token or a server-side relay for browser deployments.

Channels

edges

Pushed every ~10 min

New edge batch computed. Fetch /v1/edges on receipt for the updated list.

whales

Pushed every ~5 min

New whale trades ingested. Fetch /v1/whales on receipt for the latest fills.

prices/*

Reserved

Per-market price streaming — coming soon.

The stream sends a lightweight notification (count + timestamp) rather than the full payload. Pull the relevant REST endpoint on receipt to get the data — this keeps stream payloads small and lets you filter before fetching.

Client → server messages (send as JSON)

ActionPayloadEffect
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)

TypeExample payloadWhen
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

TierMax concurrent connectionsMax channel subscriptions / connection
free15
api_starter320
pro (consumer)550
api_pro10100

Example — browser

JavaScript (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

JavaScript (Node.js / ws library)

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

Python (websockets library)

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." } }
StatusCodeMeaning
401missing_api_keyNo x-api-key header sent.
401invalid_api_keyKey is malformed, unknown, or revoked.
400invalid_parameterQuery parameter invalid or required param missing.
404not_foundNo resource matched (e.g. unknown market id).
426websocket_required/api/v1/stream must be connected via WebSocket.
429rate_limitedPer-minute rate exceeded — check Retry-After header.
429quota_exceededMonthly quota exhausted — resets at month start.
500auth_errorTemporary problem validating the key — retry.
502upstream_errorTransient data load problem — retry.
503streaming_unavailableWebSocket 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.