The Prediction Markets
Guide

Track live prices: from REST to WebSocket

Most apps start by polling and move to streaming. Here is how to do both with the same ids.

Poll or stream?

Poll /pricesStream on the WebSocket
Best forUpdates every minute or slower; up to 100 contracts per callSecond-by-second updates: bots, live boards
KeyAny key, including the demo keyYour own key (not the demo key)
CostOne request per pollNo per-message cost; connection and subscription limits apply

1. Get prices and stream ids from REST

Every v1 outcome carries a quote and stream ids. Get them from event detail for one event, or bulk prices for contracts you already know.

Request
curl -s 'https://api.thepredictionmarkets.com/api/public/v1/prices?ids=kalshi:KXPRESNOMD-28-GN,polymarket:0x0f49db97f71c68b1e42a6d16e3de93d85dbf7d4148e3f018eb79e88554be9f75' \
  -H 'X-API-Key: tpm_demo_key_authorized_2026'

2. Poll in bulk (simple)

Polling loop
import os
import time

import requests

BASE = "https://api.thepredictionmarkets.com/api/public/v1"
HEADERS = {"X-API-Key": os.environ["TPM_API_KEY"]}
IDS = ["kalshi:KXPRESNOMD-28-GN", "polymarket:0x0f49db97f71c68b1e42a6d16e3de93d85dbf7d4148e3f018eb79e88554be9f75"]

while True:
    body = requests.get(f"{BASE}/prices", params={"ids": ",".join(IDS)}, headers=HEADERS, timeout=20).json()
    for row in body["data"]:
        q = row["quote"]
        print(row["id"], q["probability"], q["freshness"])
    if body["unresolved"]:
        print("not tracked:", body["unresolved"])
    time.sleep(60)

3. Stream (live)

Pass each row's stream object to subscribe. Save the latest cursor so you can resume after a disconnect without missing a change.

REST then WebSocket
# pip install requests "websockets>=14"
import asyncio
import json
import os

import requests
import websockets

KEY = os.environ["TPM_API_KEY"]
BASE = "https://api.thepredictionmarkets.com/api/public/v1"
IDS = "kalshi:KXPRESNOMD-28-GN,polymarket:0x0f49db97f71c68b1e42a6d16e3de93d85dbf7d4148e3f018eb79e88554be9f75"

# 1. REST: current prices plus stream ids
rows = requests.get(f"{BASE}/prices", params={"ids": IDS}, headers={"X-API-Key": KEY}, timeout=20).json()["data"]
subscriptions = [row["stream"] for row in rows if row["stream"]]


# 2. WebSocket: live changes
async def stream():
    async with websockets.connect("wss://api.thepredictionmarkets.com/api/public/v1/stream", additional_headers={"X-API-Key": KEY}) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "welcome":
                await ws.send(json.dumps({"op": "subscribe", "id": "s1", "subscriptions": subscriptions, "snapshot": True}))
            elif msg["type"] == "quote":
                q = msg["quote"]
                print(q["provider"], q["market_id"], q["price"], q["feed_status"], "cursor", msg["cursor"])


asyncio.run(stream())

For full reconnect logic (sequence gaps, resume tokens, resync snapshots), see the complete WebSocket client.

4. Fall back gracefully

  • If the socket closes with 1011 or sends stream_unavailable, poll /prices until it reconnects.
  • Check /status: when a venue's live_feed is not ok, expect aging quotes from it.
  • Show freshness next to every price so users can tell live data from old data.

Build with TPM

Get your free API key

One key gives you Kalshi and Polymarket events, prices with freshness, price history, cross-venue gaps and the live quotes WebSocket.