API Documentation

Base URL: https://api.predmktdata.com

Quick start

Get data flowing in 3 steps:

  1. Sign in at predmktdata.com with Google and pick a plan. You get your API key instantly.
  2. Test your key:
curl -H "x-api-key: YOUR_KEY" https://api.predmktdata.com/status
  1. Download data — list available Parquet dumps and start downloading:
# List available files
curl -H "x-api-key: YOUR_KEY" https://api.predmktdata.com/dumps

# Download a Parquet file
curl -L -H "x-api-key: YOUR_KEY" -o fills.parquet \
  https://api.predmktdata.com/dumps/order_filled_events/20260405.parquet

# Or query remotely with DuckDB (no download needed)
# Get the presigned URL, then: SELECT * FROM read_parquet('URL')

That's it. You have Polymarket data. For full sync patterns, see Sync patterns below.

How Polymarket works

Polymarket is a prediction market where people bet on real-world outcomes. Every market is a yes/no question (e.g. "Will Bitcoin hit $100k by July 2026?"). Each side has a token that trades between $0 and $1 — if the event happens, "Yes" tokens pay out $1; otherwise, they're worth $0.

All trading happens on the Polygon blockchain — a public, permanent ledger where every transaction is recorded and verifiable. A wallet is a user's identity on the blockchain, represented by an address like 0xed86.... Every trade, deposit, and withdrawal is tied to a wallet address.

When a user buys an outcome, they receive outcome tokens — ERC-1155 tokens on Polygon, each identified by a large numeric token_id. On its own, a token_id is just a number — the included markets lookup table maps each one to its human-readable question and outcome (e.g. token 123... → "Will Bitcoin hit $100k?" / "Yes"). A user's position is their current holding of a specific outcome token: how many they hold, what they paid on average, and their realized profit/loss from any tokens they've already sold or redeemed.

What this API provides

predmktdata reads every relevant transaction from Polygon and delivers it as Parquet dumps and CSV API responses. You get two types of data:

You don't need to understand Solidity, ABIs, or how to talk to a blockchain node. The API handles all of that and gives you clean, structured data you can load into any database or spreadsheet.

Authentication

All endpoints require an API key via the x-api-key header. Get your key by signing in with Google at predmktdata.com.

curl -H "x-api-key: YOUR_API_KEY" https://api.predmktdata.com/status

Endpoints

GET /status LITE PRO

Current indexer state and table row counts. Returns JSON.

{
  "last_block": 84570000,
  "head_block": 84570000,
  "tables": {
    "order_filled_events": 854000000,
    "positions": 150000000,
    "payout_redemptions": 93000000, ...
  }
}
GET /events PRO

Fetch events as CSV. One table per request. Supports gzip and zstd compression.

ParamDefaultDescription
after_blockrequiredReturn events after this block number
limit5000Max blocks to include (1 - 5,000)
tablesrequiredSingle table name

Block metadata is returned in response headers:

  • x-after-block — your requested after_block
  • x-last-block — last block in this chunk (use as next after_block)
  • x-head-block — current chain head
  • x-reorg — present if you're ahead of the indexer (rollback to this block)
# Fetch fills for 500 blocks
curl --compressed -H "x-api-key: YOUR_KEY" \
  "https://api.predmktdata.com/events?after_block=84420000&limit=500&tables=order_filled_events"

# Response: CSV with header row
transaction_hash,log_index,block_number,exchange,maker,taker,...
0x7fe2...,210,84420001,ctf,0xed86...,0x9ce4...,...
The /events API covers the last ~2 days of blocks. For older data, use the Parquet dumps from /dumps.
GET /user/{address}/positions PRO

All current positions for a wallet address. Returns CSV. Includes unrealized_pnl and total_pnl columns computed from live market prices (updated every minute).

GET /user/{address}/fills PRO

All fills where address is maker or taker. Returns CSV.

ParamDefaultDescription
after_block0Only fills after this block
limit50000Max rows (up to 500,000)
GET /user/{address}/pnl PRO

Current PnL summary for a wallet. Realized from closed trades, unrealized mark-to-market on open positions using live prices (updated every minute). All values in USD. Returns JSON.

{
  "realized_pnl": -52.34,
  "unrealized_pnl": 184.21,
  "total_pnl": 131.87,
  "portfolio_value": 1250.00,
  "total_volume": 8340.50,
  "active_positions": 12,
  "markets_traded": 47
}
WS /ws/feed PRO

Real-time WebSocket feed of fills and position changes. Subscribe by wallet address or amount/price thresholds. Data pushes to you the moment the indexer commits — no polling needed.

Connect:

wscat -c "wss://api.predmktdata.com/ws/feed?x_api_key=YOUR_KEY"

Subscribe messages (send as JSON after connecting):

# Watch a specific wallet's fills and position changes
{"action": "subscribe", "type": "user", "address": "0xabc..."}

# Watch large fills (raw units, divide by 1e6 for USDC)
{"action": "subscribe", "type": "threshold", "min_fill_amount": 10000000000}

# Watch positions with low avg_price (cheap bets)
{"action": "subscribe", "type": "threshold", "max_avg_price": 50000}

# Unsubscribe
{"action": "unsubscribe", "type": "user", "address": "0xabc..."}

# Keepalive
{"action": "ping"}

Messages you receive:

{
  "fills": [{"transaction_hash": "0x...", "maker": "0x...", ...}],
  "positions": [{"user_address": "0x...", "amount": 5000000, ...}]
}

Limits: 100 total connections, 5 per API key, 20 user subscriptions per connection. Values are raw i64 (same as REST API — divide by 1e6).

WS /ws/stream PRO

Firehose WebSocket for keeping your own database in sync. Catches up from a recent block, then streams all new events and positions as the indexer commits them. No subscriptions needed — you get everything.

ParamDefaultDescription
x_api_keyrequiredAPI key (query string)
start_blockrequiredBlock to start from (max ~900 blocks / 30 min behind head)
tablesallComma-separated tables to stream (optional filter)

Connect:

wscat -c "wss://api.predmktdata.com/ws/stream?x_api_key=YOUR_KEY&start_block=84650000"

# Or stream only specific tables:
wscat -c "wss://api.predmktdata.com/ws/stream?x_api_key=YOUR_KEY&start_block=84650000&tables=order_filled_events,positions"

Messages you receive:

# Catchup batches (sent rapidly until caught up)
{"type": "batch", "from_block": 84650000, "to_block": 84650100,
 "order_filled_events": [{...}], "positions": [{...}], ...}

# Catchup complete signal
{"type": "caught_up", "block": 84651400}

# Live batches (every few seconds as indexer commits)
{"type": "batch", "from_block": 84651400, "to_block": 84651410, ...}

Limits: 10 concurrent stream connections (shared pool: 100 total, 5 per key). Slow consumers are disconnected (code 4008) — reconnect with a newer start_block.

GET /dumps LITE PRO

List available Parquet dump files. Returns JSON with file paths and sizes.

GET /dumps/{path} LITE PRO

Download a dump file (.parquet). Returns 302 redirect to a time-limited URL (20 min). Parquet files support remote querying with DuckDB via HTTP range requests.

Available tables

TableRowsParquetType
order_filled_events~865M~18 GBAppend-only
payout_redemptions~93M~1.8 GBAppend-only
position_splits~7.7M~240 MBAppend-only
position_merges~7.8M~240 MBAppend-only
position_conversions~1.8M~30 MBAppend-only
positions~150M~4 GBMutable (UPSERT)
markets~15K~1 MBLookup (overwritten daily)

Sizes are approximate and grow over time. With Parquet, you can query remotely with DuckDB without downloading — only the columns and rows you need are transferred.

Schema

order_filled_events

ColumnTypeNotes
timestamptimestampBlock timestamp
transaction_hashtext
block_numberbigint
makertextMaker wallet address
takertextTaker wallet address
maker_asset_idtextUsually "0" (USDC side)
taker_asset_idtextOutcome token ID
maker_amount_filledbigintRaw units (divide by 1e6 for USDC)
taker_amount_filledbigintRaw units (divide by 1e6)
feebigintRaw units
sidetextbuy or sell

The /events API returns additional columns: log_index, exchange. The timestamp column is named block_timestamp in API responses.

positions

ColumnTypeNotes
user_addresstextWallet address
token_idtextOutcome token ID
amountbigintCurrent position size (raw)
avg_pricebigintAverage entry price (divide by 1e6)
realized_pnlbigintRealized PnL (raw units)
total_boughtbigintTotal tokens bought (raw)
last_blockbigintLast block this position was updated

The /events API and /user endpoints add block_timestamp. The /user/*/positions endpoint also adds unrealized_pnl and total_pnl (computed from live market prices).

markets

Lookup table that maps token IDs to human-readable market info. Join with event or position tables on yes_token_id / no_token_id to see which question and outcome a trade or position belongs to. Overwritten daily.

ColumnTypeNotes
condition_idtextUnique identifier for the market condition
questiontextThe market question (e.g. "Will Bitcoin hit $100k?")
outcome_yestextLabel for the Yes side (e.g. "Yes", "Bitcoin")
outcome_notextLabel for the No side (e.g. "No", "Ethereum")
yes_token_idtextToken ID for the "Yes" outcome
no_token_idtextToken ID for the "No" outcome
market_slugtextURL slug on polymarket.com
end_date_isotextMarket end date (ISO 8601)
neg_riskbooleanUses NegRisk exchange (multi-outcome markets)
To look up which market a trade belongs to, join the event's token_id (or taker_asset_id for fills) against yes_token_id or no_token_id in the markets table.

resolution_proposals (UMA Oracle)

Who proposed the outcome of each market. Indexed from UMA OptimisticOracleV2, filtered to Polymarket adapters only. Full snapshot updated daily in uma/ folder.

ColumnTypeNotes
transaction_hashtextOn-chain transaction
block_numberbigintPolygon block number
requestertextAdapter contract address (V1/V2/V3)
proposertextAddress that proposed the outcome
proposed_pricetext1e18 = Yes, 0 = No, 0.5e18 = Unknown
ancillary_datatextQuestion text (human-readable)
request_timestampbigintUMA request timestamp (unix)
expiration_timestampbigintChallenge window end (unix)

resolution_disputes (UMA Oracle)

When someone challenges a proposed outcome. ~2% of proposals are disputed.

ColumnTypeNotes
transaction_hashtextOn-chain transaction
block_numberbigintPolygon block number
disputertextAddress that disputed
proposertextOriginal proposer being disputed
proposed_pricetextThe price being challenged
ancillary_datatextQuestion text

resolution_settlements (UMA Oracle)

Final resolved outcome and bond payouts.

ColumnTypeNotes
transaction_hashtextOn-chain transaction
block_numberbigintPolygon block number
proposertextOriginal proposer
disputertextDisputer (0x0 if undisputed)
settled_pricetextFinal outcome (1e18=Yes, 0=No)
payouttextBond payout to winner
ancillary_datatextQuestion text

Error responses

All errors return JSON with a detail field:

StatusMeaningExample
401Missing or invalid API key{"detail": "Unauthorized"}
403Plan doesn't include this endpoint{"detail": "Pro plan required"}
400Bad request{"detail": "Specify exactly one table per request."}
422Missing required parameter{"detail": [{"type": "missing", ...}]}
429Rate limit exceeded{"detail": "Rate limit exceeded"}

Sync patterns

Want your own complete, always-updated Polymarket database?

We wrote a step-by-step guide: create the schema, backfill the full history from dumps, and keep it synced in real time. Read the full guide ↗

Lite: dumps only

  1. GET /dumps to list available Parquet dump files
  2. Download daily event files in parallel
  3. Download the latest positions snapshot and markets lookup
  4. Query directly with DuckDB, or INSERT into your database (UPSERT for positions)
  5. Run daily: download new daily files + fresh positions/markets snapshots
Event tables are append-only — use INSERT. Positions are mutable — always use UPSERT (INSERT ... ON CONFLICT (user_address, token_id) DO UPDATE).

Pro: dumps + near real-time API

  1. Do the Lite setup above for fast backfill
  2. Find MAX(last_block) from the positions dump — this is your sync cursor
  3. Use GET /events to sync the remaining gap to chain head
  4. Continue polling every few seconds for near real-time updates
The positions dump is not atomic — rows may have different last_block values. Use MAX(last_block) as your sync cursor. The API will send updates for any positions that changed in the gap, and your UPSERT will reconcile them.

Compression

The API supports two compression algorithms via Accept-Encoding header:

Units and scaling

Coverage

Plans

FeatureLite ($49/mo)Pro ($149/mo)
Full history of trades (since 2022)
Daily position snapshot (05:00 UTC)
Markets lookup table
UMA resolution data (proposals, disputes, settlements)
UpdatesEnd-of-dayReal-time (<1s)
Real-time API (/events)
Per-wallet queries (/user/*, /user/*/pnl)
WebSocket real-time feed (/ws/feed)
Firehose stream (/ws/stream)
Rate limit (API)Dumps only5 req/s
Dump downloads/day5001,000