# Polymarket API — Order Book, Fills & History (`dirtycode/polymarket-api`) Actor

Live and historical Polymarket data in one normalized schema: market odds, full L2 order-book depth (every bid and ask level with size), executed trades with on-chain transaction hashes, and an archived order book over time for backtesting. No login, no cookies, no account.

- **URL**: https://apify.com/dirtycode/polymarket-api.md
- **Developed by:** [Dirtycode](https://apify.com/dirtycode) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Polymarket API — Markets · Order Book · Fills · History

Live and **historical** Polymarket data in one normalized schema. Market odds, full L2
order-book depth, executed trades with on-chain transaction hashes, and an order-book archive
that Polymarket itself does not publish. All public data — **no login, no cookies, no account.**

### Four datasets, priced separately

| Mode | What you get | Price |
|---|---|---|
| `markets` | Live odds across Polymarket | **$0.005** per market |
| `orderbook` | Every bid and ask level with size | **$0.02** per snapshot |
| `fills` | Executed trades, with on-chain hash | **$0.002** per trade |
| `history` | Our archived order book over time | **$0.002** per snapshot |

You only pay for rows you actually receive.

### History — our own archive, and who else has one

**Polymarket publishes no historical order book.** Once a moment passes, the depth at that moment
is gone from the venue, so any archive exists only because someone captured it live.

**We are not the only ones who did, and you should know that before you buy.** Specialist vendors
sell Kalshi and Polymarket order-book history with more depth and finer resolution than this
actor: tick-level with millisecond timestamps, delivered as Parquet or queryable via DuckDB and
Snowflake, some reaching back years. If you need the deepest or finest historical book available,
buy from them — we would rather say so than have you find out after paying.

**What this actor is for:** the same data through Apify, per row, with no subscription, no sales
call and no minimum. That is a convenience and pricing proposition, not a claim to exclusive data.

#### Exactly what the archive covers

| | |
|---|---|
| **Markets** | The **top ~50 Polymarket markets by traded volume**, re-selected hourly. A market that slips out of the top stays in the archive until it falls well below it, so series do not break on noise. |
| **Not covered** | Anything outside that set. A market ranked ~52 by volume — still tens of millions traded — has **no history here at all**, and the run will say so. |
| **Starts** | **2026-07-27 19:53 UTC** for the current market set. Earlier rows exist but describe a different, badly chosen set of markets; treat 27 July as the start. |
| **Interval** | 5 minutes. An unchanged book is stored once with a widening span, so quiet markets have fewer rows without losing coverage. |

**Ask for a window before 2026-07-27 and you will correctly get nothing** — the `coverage` block
says `firstObservedAt` so you can check before you build on it. We would rather show you the edge
of the archive than let you discover it inside a backtest.

Every history response carries **`coverage`**, and this is the point:

| Field | What it tells you |
|---|---|
| `firstObservedAt`, `lastObservedAt` | The full span we hold for this market — not just your window |
| `gaps` | Every real interruption, as from/to/minutes. Empty means continuous. |
| `expectedIntervalMinutes` | The cadence we aim to capture at |
| `medianTimeBetweenChangesMinutes` | How often this market actually moved |
| `longestStableSpanMinutes` | Longest stretch the book sat unchanged |

**Rows are spans, not instants.** A book that does not change is stored once: `observedAt` is when
we first saw it, `observedUntil` when we last saw it still unchanged. On a quiet market those can
be half an hour apart with nothing missed.

That distinction is on every row for a reason. **If you write your own coverage check, compare the
start of each row to the END of the previous one.** Comparing consecutive `observedAt` values
reports every quiet stretch as an outage — a mistake this API's own gap calculation made until
2026-07-28, and one an independent reviewer made the same day. You should be able to contradict
our coverage claim with the data we hand you; that is the only kind of honesty that means anything
for history nobody can re-derive.

A backtest run across a gap you did not know about produces a confident wrong number and the data
gets the blame, so you get the holes up front — and the means to check them.

### Markets

| Field | Notes |
|---|---|
| `question`, `ticker`, `url` | Market identity and link (`ticker` is the slug) |
| `conditionId`, `tokenIds` | **The ids the other three modes take.** `conditionId` for fills; `tokenIds[0]` — the YES token — for order book and history |
| `yesPrice`, `noPrice` | Implied probability, 0..1 |
| `volume`, `liquidity` | In **USD**, unlike Kalshi where volume is contracts |
| `volumeUnit`, `liquidityUnit` | Always `usd` here. Carried on every row so a cross-venue query cannot silently compare dollars against Kalshi's contracts. |
| `closeTime` | Polymarket's stated end date. **A past date does not mean trading stopped** — measured 2026-07-30, 6 of the top 60 had a past end date while still accepting orders, one with 2 bids and 53 asks against $62.6M volume. Trust `status`. |
| `status` | Only open markets are returned |

**Not returned here, and stated rather than left for you to discover:** `spread` is always null in
this mode — the market listing gives a price, not a book, so the real bid/ask gap comes from
`orderbook`. `openInterest` is not published by Polymarket at all. `resolvedOutcome` is always
null because we only return open markets.

### Order book

Full L2 depth: `bestBid`, `bestAsk`, `midPrice`, `spread`, `lastTrade`, plus `bids[]` and
`asks[]` with price and size. Addressed by **CLOB token id**.

**A book with no bids and no asks is not returned and not charged.** An empty book is
indistinguishable from a broken parser, so it is reported as a failure rather than billed as a row.

### Fills

| Field | Description |
|---|---|
| `side`, `price`, `size`, `value` | Direction, price, shares, notional |
| `outcome`, `asset` | Which outcome token changed hands |
| `wallet` | Trader's proxy wallet — the basis for whale tracking |
| `transactionHash` | Verify any row on-chain yourself |
| `ts` | Venue timestamp |

Each fill carries a `summary` **broken down per outcome, never blended.** A single market's trade
feed contains both outcome tokens: on one live market the YES fills averaged 0.003 and the NO
fills 0.997. A combined average of 0.5 is not a compromise between them — it describes no
instrument that exists, and a backtest built on it would be confidently wrong. So there is
deliberately no top-level VWAP.

⚠️ **Trades come newest-first, so your limit decides how far back you reach.** The busiest markets
hold well over 10,000 trades; until 2026-07-31 this actor silently returned at most 500 of them.
It now pages, and **Max results** is what bounds it — raise it to go further back. Rows the venue
re-serves while paging (a live feed shifts under you) are deduplicated on the on-chain
transaction, so summing `value` over the result does not double-count.

Fills are addressed by **market `conditionId`** (the `0x…` id), **not** the CLOB token. Passing
the token returns an empty success at the venue rather than an error, so the actor names which
identifier it wants when you get it wrong.

### Limits, stated up front

- **Two different identifiers.** Order book and history take the CLOB token id; fills take the
  market conditionId. They are not interchangeable.
- **History depth grows forward.** The archive starts when our capture started; it does not reach
  back before that, and no vendor can sell you what was never recorded.
- **Snapshots, not a stream.** Each run captures a moment.

### Use cases

Backtesting with real depth rather than assumed fills, quant research, whale and wallet tracking,
market-making analytics, cross-venue arbitrage against Kalshi, AI agents needing live odds.

***

#### For the developer (not shown on the Store)

Thin wrapper over the private `scraper-gateway`; contains no scraping logic. Set Actor
**environment variables** (secret): `SCRGW_URL`, `SCRGW_SECRET`. Pay-per-event events:
`market-returned` ($0.005), `orderbook-snapshot` ($0.02), `fill-returned` ($0.002),
`history-snapshot` ($0.002). History requires the gateway to have `DATABASE_URL_RO` set (read-only
role; the collector holds the only write credential).
Deploy: `apify push --force`.

# Actor input Schema

## `mode` (type: `string`):

markets = live odds across Polymarket · orderbook = full L2 depth for given tokens · fills = executed trades for given markets · history = our archived order book over time, which Polymarket itself does not publish

## `ids` (type: `array`):

Order book and history: CLOB token id(s) — the long numeric token. Fills: market conditionId(s) — the 0x… id. These are different identifiers; using the wrong one returns nothing. Repeated ids are ignored: each distinct id is fetched and charged once.

## `limit` (type: `integer`):

Hard cap on rows for the WHOLE run — across every market you submit, not per market — and therefore on what the run costs. Defaults to 100 so a first run is cheap. For the `fills` mode this also decides how far back the trade history reaches: trades come newest-first, so a low limit gives you recent trades, not the whole market. That matters because the summary's VWAP is computed over exactly what was fetched — on one measured market the newest 100 trades gave 0.0725 while all 26,910 gave 0.1207. The maximum is 50,000; at $0.002 per trade that run costs $100.

## `proxy` (type: `string`):

Optional public HTTP/HTTPS proxy. Leave empty to use the default egress.

## `from` (type: `string`):

ISO-8601 start, inclusive. Leave empty for the whole archive.

## `to` (type: `string`):

ISO-8601 end, exclusive.

## Actor input object example

```json
{
  "mode": "markets",
  "limit": 100
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("dirtycode/polymarket-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("dirtycode/polymarket-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call dirtycode/polymarket-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=dirtycode/polymarket-api",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/bempnLmeSQFp6JZWX/builds/enS9qbjJ79wkzYoge/openapi.json
