Market data API
Read-only. Every Polymarket market RELVAL tracks, with live quotes and settlement rules.
- Base URL
/api/agent- Auth
- None
- Format
- JSON, NDJSON
- Rate limit
- 120 / min per IP
Quick start
- 1
Load everything once
All markets with rules, plus all events. Keep the first call's headers.
curl --compressed -D markets.headers \ "https://<host>/api/agent/snapshot?status=all&include=rules" > markets.ndjson curl --compressed "https://<host>/api/agent/snapshot?type=events" > events.ndjson - 2
Poll what changed
Start from the snapshot's
x-next-sinceheader, then pass each response'snext_sinceback assince.curl --compressed "https://<host>/api/agent/changes?since=<next_since>" - 3
Reload when metadata moves
/changesreports price changes only. If itsmeta_versiondiffers from the loaded one (markets opened, closed or delisted), repeat step 1.
/changes is at-least-once: no row is skipped, but a market can appear on two consecutive pages. De-duplicate by id, keeping the larger changed_at_ms.
Coverage
| Segment | What is in it |
|---|---|
core | Every open event except Sports, Crypto and Esports. |
sports | Top 100 Sports events by 24h volume that end within 7 days. |
top_list | Markets on the web board outside the two sets above, such as Crypto. |
Multi-outcome and neg-risk events are always complete, closed options included. Options never deployed on-chain have no token and appear by name under undeployed_options. Live counts: /api/agent.
Endpoints
All endpoints are GET.
| Endpoint | Returns |
|---|---|
/api/agent/snapshot | Everything as NDJSON, one row per line |
/api/agent/changes | Markets whose price changed since a time |
/api/agent/markets | Search, filter, sort and page markets |
/api/agent/markets/:id | One market in full, with book levels and rules |
/api/agent/markets/:id/history | Recorded mid prices |
/api/agent/events | Search events |
/api/agent/events/:id | A whole event with every option |
/api/agent/categories | Open market and event counts per category |
/api/agent | Machine-readable version of this page with live coverage |
GET/api/agent/snapshot
Rebuilt at most every 15 s. Allowed 6 times per minute per IP.
| Param | Values | Default |
|---|---|---|
type | markets, events | markets |
status | open, closed, tradable, all | open |
include | rules | none |
levels | 1 adds 8 book levels per side | 0 |
Response headers: x-market-count (rows), x-generated-at-ms, x-next-since. fields is not supported here; project the rows yourself.
GET/api/agent/changes
Oldest change first. A page never splits markets fetched at the same instant.
| Param | Values | Default |
|---|---|---|
since | epoch ms | required |
limit | 500 to 5000 | 2000 |
include, levels, fields | same as /markets |
GET/api/agent/markets
Filters combine with AND.
| Param | Values | Default |
|---|---|---|
q | Words that must all appear in question, event, option or slug | |
ids | Comma list, up to 1000. Unknown ids return in missing | |
category | Comma list, matches any | |
segment | core, sports, top_list | |
event_id | One event | |
status | open, closed, tradable, all | open (all with ids) |
min_volume_24h | Number | |
min_liquidity | Number | |
end_before, end_after | Epoch ms or ISO 8601 | |
changed_since | Epoch ms | |
sort | volume_24h, liquidity, end_date, change_24h, abs_change_24h, spread, changed_at. Prefix - for descending | -volume_24h |
limit | 1 to 1000 | 100 |
cursor | next_cursor from the previous page | |
include | rules | none |
levels | 1 adds book levels | 0 |
fields | Comma list of row keys to keep | all |
GET/api/agent/markets/:id
Adds book levels, rules, description, tick size, min order size, the parent event with its rules, and refresh_tier.
GET/api/agent/markets/:id/history
Only markets on the 10-second tier have history.
| Param | Values | Default |
|---|---|---|
minutes | 1 to 1440 | 120 |
side | yes, no | yes |
GET/api/agent/events
Takes the same q, category, segment, min_volume_24h, end_before and end_after as /markets.
| Param | Values | Default |
|---|---|---|
sort | volume_24h, end_date, market_count. Prefix - for descending | -volume_24h |
limit, cursor | Up to 1000 | 100 |
GET/api/agent/events/:id
Every option, each with its rules. For neg-risk events the yes mids add up to about 1.
| Param | Values | Default |
|---|---|---|
status | all, open, closed, tradable | all |
levels, fields | same as /markets |
Market row
One row per market, the same shape in every endpoint.
Identity
| Field | Type | Meaning |
|---|---|---|
id | string | Polymarket market id |
question | string | Market question |
option | string | null | Option label inside a multi-outcome event |
event_id, event_title | string | Parent event |
segment | string | core, sports or top_list |
neg_risk | boolean | Options of the event are mutually exclusive |
categories | string[] | Published Polymarket categories |
outcomes, token_ids | string[] | Aligned by index: outcomes[0] trades as token_ids[0] |
url | string | null | Event page on Polymarket |
State and activity
| Field | Type | Meaning |
|---|---|---|
status | string | open, not_accepting or closed |
end_date_ms | number | null | Scheduled end |
volume_24h, liquidity | string | null | As reported by Polymarket |
change_1h, change_24h, change_1w | string | null | Absolute price change: 0.08 is 8 cents |
last_trade_price | string | null | Last traded price |
tradable_now | boolean | Open, both sides quoted, quotes younger than 30 s |
Quotes (yes and no)
| Field | Type | Meaning |
|---|---|---|
bid, ask, mid | string | null | null when that side of the book is empty |
fetched_at_ms | number | When the book was read |
changed_at_ms | number | null | When bid, ask or mid last changed |
levels | object | Only with levels=1: 8 levels per side, best first |
yes or no is null when that book has never been fetched.
Example row
{
"id": "123456",
"question": "Will it snow in New York City on Christmas Day?",
"option": null,
"event_id": "54321",
"event_title": "…",
"segment": "core",
"neg_risk": false,
"categories": ["Weather"],
"outcomes": ["Yes", "No"],
"token_ids": ["…", "…"],
"status": "open",
"end_date_ms": 1798779540000,
"volume_24h": "48213.5",
"liquidity": "233290.86",
"change_1h": null,
"change_24h": "-0.004",
"change_1w": "0.01",
"last_trade_price": "0.037",
"yes": {
"bid": "0.036", "ask": "0.038", "mid": "0.037",
"fetched_at_ms": 1789294072890,
"changed_at_ms": 1789293876978
},
"no": { … },
"tradable_now": true,
"url": "https://polymarket.com/event/…"
}Settlement rules
Returned as rules on detail endpoints, and on list endpoints and snapshots with include=rules.
Market rules
| Field | Type | Meaning |
|---|---|---|
description | string | null | Full resolution text |
resolution_source | string | null | Source named by the market, often empty |
uma_resolution_status | string | null | For example proposed or resolved |
uma_end_date_ms | number | null | End of the UMA resolution window |
resolved_by | string | null | Resolver address |
outcome_prices | string[] | null | Settlement prices once resolved |
neg_risk_other | boolean | The catch-all Other option of a neg-risk event |
Event rules
| Field | Type | Meaning |
|---|---|---|
description | string | null | Event-level resolution text |
resolution_source | string | null | Source named by the event |
Conventions
| Topic | Rule |
|---|---|
| Prices | Decimal strings exactly as the order book returned them, never floats |
| Times | *_ms fields are epoch ms. Filters accept epoch ms or ISO 8601 |
| Server time | In the x-generated-at-ms header, not the body |
| Freshness | Board markets and markets recently opened on the website refresh about every 10 s; the rest about once a minute |
| Compression | Send Accept-Encoding: gzip |
| Caching | Send If-None-Match with the last ETag. An unchanged response is an empty 304 |
| Trading | Orders are placed on the website. Check tradable_now first |
Limits and errors
| Limit | Value |
|---|---|
| Requests | 120 per minute per IP, bursts of 30 |
| Snapshots | 6 per minute per IP |
| q | 200 characters, 8 distinct words |
| category, segment | 20 items |
| fields | 40 items |
| ids | 1000 items |
| Repeated parameter | Rejected with 400 |
| Cursors | Expire when market metadata refreshes. Paging by spread or changed_at is not stable |
| HTTP | code | What to do |
|---|---|---|
| 400 | BAD_REQUEST | Fix the parameter named in message |
| 404 | NOT_FOUND | The market or event is no longer tracked |
| 409 | CURSOR_EXPIRED | Start again from the first page |
| 429 | RATE_LIMITED | Wait retry-after seconds |
Error bodies are { ok: false, code, message }.