API Documentation
REST and WebSocket API for the PMDI prediction market data platform. Current version: v1
Authentication
All API requests (except /public/*) require a valid API key. Pass your key in the X-API-Key request header.
API keys are issued per buyer account and are tied to your subscription tier. Keys can be rotated at any time from the dashboard. Never embed your API key in client-side code.
# Include in every authenticated request
X-API-Key: pmdi_live_xxxxxxxxxxxxxxxxxxxx
Keys beginning with pmdi_live_ are production keys. pmdi_test_ keys are sandbox-only and do not count against rate limits.
Base URL
https://api.pmdi.io/v1
All endpoint paths below are relative to this base URL. The API uses HTTPS exclusively. HTTP requests are redirected.
Rate Limits
Limits are enforced per API key on a rolling 60-second window. Exceeding the limit returns HTTP 429 with a Retry-After header indicating seconds until the window resets.
| Tier | Requests per minute | Burst (5s) | WebSocket connections |
|---|---|---|---|
| Trial | 5 | 10 | Not available |
| Pro | 300 | 600 | 1 shared |
| Institutional | Unlimited | Unlimited | 1 private endpoint |
Response headers include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix timestamp).
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | invalid_params | Query parameters failed validation |
| 401 | unauthorized | Missing or invalid API key |
| 403 | forbidden | Endpoint not available on your tier |
| 404 | not_found | Market ID or resource not found |
| 429 | rate_limited | Rate limit exceeded. See Retry-After header |
| 500 | server_error | Internal error. Logged to data_quality_log automatically |
| 503 | unavailable | Ingestor or DB temporarily unavailable |
Endpoint Reference
Markets
Query active and historical markets tracked by PMDI across Polymarket and Kalshi.
/markets
List markets
Returns a paginated list of markets. By default returns active markets only.
| Parameter | Type | Description |
|---|---|---|
| platform | string | Filter by polymarket or kalshi |
| category | string | Filter by category (e.g. crypto, politics) |
| is_active | boolean | Default true |
| limit | integer | Max 500, default 100 |
| offset | integer | Pagination offset, default 0 |
/markets/{market_id}
Get market detail
Price Ticks
Raw tick-level price data. Hypertable partitioned by time. Subject to historical depth limits by tier.
/markets/{market_id}/ticks
Price tick series
Returns time-ordered price tick series for a given market.
| Parameter | Type | Description |
|---|---|---|
| from | ISO 8601 | Start of time range. Required. |
| to | ISO 8601 | End of time range. Defaults to now. |
| resolution | string | raw, 1m, 5m, 1h. Default raw. |
| limit | integer | Max 10,000 per request |
Signals
Computed probability velocity and anomaly signals. Requires Pro or Institutional tier.
/signals
List recent signals
Returns signals sorted by computed timestamp descending.
| Parameter | Type | Description |
|---|---|---|
| market_id | string | Filter by specific market |
| anomaly_only | boolean | Return only anomaly-flagged signals |
| window | integer | Window seconds: 60, 300, 900, 3600 |
| since | ISO 8601 | Return signals since timestamp |
| limit | integer | Max 1,000 |
/markets/{market_id}/signals
Signals for a market
Correlations
Cross-asset correlation data between prediction market probabilities and asset prices. Pro and Institutional only.
/correlations
Cross-asset correlations
Returns correlation records between market probabilities and tracked assets (BTC, ETH, 10Y Treasury, SPX).
| Parameter | Type | Description |
|---|---|---|
| market_id | string | Required. Market to query. |
| asset | string | Filter by asset: BTC, ETH, SPX, US10Y |
| window_days | integer | 7, 30, or 90 |
Insights (Annotations)
Claude-annotated signal narratives enriched with news context. Pro and Institutional only.
/insights
List annotated insights
Returns recent Claude-annotated insights sorted by confidence score descending.
| Parameter | Type | Description |
|---|---|---|
| min_confidence | float | Minimum confidence score 0.0–1.0. Default 0.7 |
| trigger_type | string | velocity_anomaly, correlation_spike, volume_surge |
| limit | integer | Max 100 |
/insights/rss
RSS feed (public)
WebSocket Feed
Real-time stream of price ticks, signals, and insights. Pro and Institutional only. Authenticate via query parameter on the initial connection.
wss://api.pmdi.io/v1/feed?key=YOUR_API_KEY
After connecting, send a JSON subscription message to select channels:
// Subscribe to specific channels
{
"type": "subscribe",
"channels": ["ticks", "signals", "insights"],
"market_ids": ["KXBTCD-25MAY30-T102000"] // optional filter
}
The server sends a heartbeat every 30 seconds. If no message is received for 60 seconds, the connection should be considered stale and reconnected.
Public Stats
No authentication required.
/public/stats
System stats
classified_markets, ticks_today, signals_today, and uptime_percent. Updated every 60 seconds.
Python Examples
Fetch active markets
import httpx
API_KEY = "pmdi_live_xxxxxxxxxxxxxxxxxxxx"
BASE_URL = "https://api.pmdi.io/v1"
HEADERS = {"X-API-Key": API_KEY}
with httpx.Client() as client:
resp = client.get(
f"{BASE_URL}/markets",
headers=HEADERS,
params={"platform": "kalshi", "limit": 50},
)
resp.raise_for_status()
markets = resp.json()
print(f"Fetched {len(markets['items'])} markets")
Fetch recent anomaly signals
import httpx
from datetime import datetime, timedelta, timezone
# Signals from the last hour, anomalies only
since = (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat()
with httpx.Client() as client:
resp = client.get(
f"{BASE_URL}/signals",
headers=HEADERS,
params={
"anomaly_only": True,
"since": since,
"window": 300,
},
)
resp.raise_for_status()
for signal in resp.json()["signals"]:
print(signal["market_id"], signal["velocity_score"])
Subscribe to WebSocket feed (async)
import asyncio
import json
import websockets
API_KEY = "pmdi_live_xxxxxxxxxxxxxxxxxxxx"
WS_URL = f"wss://api.pmdi.io/v1/feed?key={API_KEY}"
async def stream():
async with websockets.connect(WS_URL) as ws:
# Subscribe to signals and insights
await ws.send(json.dumps({
"type": "subscribe",
"channels": ["signals", "insights"],
}))
async for message in ws:
event = json.loads(message)
if event["type"] == "signal":
print("Signal:", event["market_id"], event["velocity_score"])
elif event["type"] == "insight":
print("Insight:", event["narrative"][:80])
asyncio.run(stream())
cURL Examples
List markets
curl -s https://api.pmdi.io/v1/markets \
-H "X-API-Key: pmdi_live_xxxxxxxxxxxxxxxxxxxx" \
-G \
--data-urlencode "platform=polymarket" \
--data-urlencode "limit=20" \
| python3 -m json.tool
Fetch price ticks for a market
curl -s "https://api.pmdi.io/v1/markets/KXBTCD-25MAY30-T102000/ticks" \
-H "X-API-Key: pmdi_live_xxxxxxxxxxxxxxxxxxxx" \
-G \
--data-urlencode "from=2026-05-01T00:00:00Z" \
--data-urlencode "resolution=5m" \
--data-urlencode "limit=288"
Get latest high-confidence insights
curl -s "https://api.pmdi.io/v1/insights?min_confidence=0.85&limit=10" \
-H "X-API-Key: pmdi_live_xxxxxxxxxxxxxxxxxxxx"
Check public system stats (no auth)
curl -s https://api.pmdi.io/v1/public/stats | python3 -m json.tool