# Polymarket Scraper — Resolved Markets & Price Before Close (`datahamster/polymarket-history-resolutions`) Actor

Polymarket scraper for resolved markets: the outcome plus the Yes price 1h/6h/24h/72h/7d before close, final price, min/max, volume and tags. Filter by tag, date range, event or market slug; optional full price series. Public APIs, no proxy, no login. For backtests and calibration studies.

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

## Pricing

from $2.00 / 1,000 market rows

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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 Scraper — Resolved Markets & Price Before Close

A Polymarket scraper for markets that have already closed: what the outcome was, and what the market priced 1 hour, 6 hours, 1 day, 3 days and 7 days before it closed. Filter by tag (weather, sports, politics, crypto …), date window, event or market slug — the Polymarket UI shows none of this once a market resolves.

### What you get (example output)

Real rows from the example dataset (weather tag, markets closed after 20 August):

| Field | Example value | What it is |
|---|---|---|
| `marketId` / `slug` | `1360605` / `will-any-month-of-2026-be-the-hottest-on-record` | Polymarket market id and slug (`url` is the full link) |
| `question` | `Will any month of 2026 be the hottest on record?` | Market question |
| `winner` / `yesResolved` | `Yes` / `true` | The outcome that resolved true |
| `pricesBefore` | `{"1h": 0.9995, "6h": 0.948, "24h": 0.954, "72h": 0.965, "168h": 0.93}` | Yes price at each hour offset before close (`null` where there were no trades) |
| `priceFinal` | `0.9995` | Last Yes price before the market closed |
| `priceMin` / `priceMax` | `0.9195` / `0.9995` | Range of the Yes price over the retrieved history |
| `volume` | `154744.66` | Traded volume in USD |
| `closedTime` / `endDate` | `2026-08-11T17:03:27+00:00` / `2027-01-10T00:00:00Z` | When the market actually closed, and its scheduled end |
| `historyPoints` | `180` | How many price points backed the snapshots |

Every row also carries `eventSlug`, `eventTitle`, `tags[]`, `outcomes[]`, `winnerIndex`, `resolved`, `startDate`, `liquidity`, `negRisk`, `yesTokenId`, `historyFrom` / `historyTo`, `series` (with `includeSeries`), `resolutionSource`, `umaResolutionStatus` and `scrapedAt`.

### Use cases

- **Backtesting a strategy** — join the outcome with the price 24 or 72 hours before close and measure what a rule would have earned.
- **Calibration studies** — the classic "is 80% really 80%?" question, answered over thousands of resolved markets at a fixed offset before close.
- **Research and dashboards** — a permanent archive of resolutions, prices and volumes for markets that have vanished from the Polymarket front end.

### How it works

Market metadata and resolutions come from Polymarket's public Gamma API, filtered by tag, closing-date window, event slug or market slug. For each market the actor pulls the CLOB price history for the Yes token at the chosen fidelity and reads off the price at every offset in `hoursBefore`, plus the final, minimum and maximum price. Everything is a public HTTP API — no proxy, no login, no anti-bot. In `monitor` mode a saved Task returns only markets that resolved since the previous run.

### Input

| Field | Meaning | Default |
|---|---|---|
| `tagId` | Gamma tag id, e.g. `84` = weather, `1` = sports, `2` = politics, `21` = crypto | `84` |
| `tagSlug` | Tag by name instead of id, e.g. `weather`, `nba`, `fed-rates`; used only when `tagId` is empty | `weather` |
| `closedAfter` | Keep markets ending on or after this ISO date, e.g. `2026-08-20` | `2026-08-20` |
| `closedBefore` | Keep markets ending on or before this ISO date, e.g. `2026-09-01` | empty |
| `marketSlugs` | Specific markets by slug (`polymarket.com/market/<slug>`) | empty |
| `eventSlugs` | Every market of these events (`polymarket.com/event/<slug>`) | empty |
| `onlyResolved` | Skip markets that are closed but not yet resolved, and open markets | `true` |
| `hoursBefore` | Hour offsets before close at which to snapshot the Yes price | `[1, 6, 24, 72, 168]` |
| `fidelityMinutes` | Resolution of the CLOB price history, in minutes | `60` |
| `includeSeries` | Attach the complete `[[timestamp, price], …]` series to each row (large) | `false` |
| `minVolume` | Skip markets below this USD volume | empty |
| `maxItems` | Stop after this many pushed rows | `20` |
| `mode` | `scrape` = every match; `monitor` = only markets resolved since the previous run of this Task | `scrape` |
| `monitorKey` | State key for monitor mode when not running as a saved Task | empty |
| `webhookUrl` | URL that receives a POST with the change summary in monitor mode | empty |
| `telegramBotToken` / `telegramChatId` | Optional Telegram destination for monitor-mode summaries | empty |

### Pricing

| Event | Price |
|---|---|
| result | $0.002 per market ($2 per 1,000) |
| monitor-check | $0.005 per monitor run |
| change | $0.002 per newly resolved market |

Charged only for markets actually pushed. No proxy needed.

### Why this actor

- **Data the Polymarket UI deletes** — once a market resolves, its price history disappears from the front end; this returns it as a flat row.
- **Price before close at your own offsets** — `hoursBefore` is configurable, so a backtest at 12 h or 48 h needs no re-scraping of the raw series.
- **Outcome and price in one row** — no joining resolutions to a separate price export.
- **No proxy, no login, no anti-bot** — public APIs only, so runs are cheap and reliable.
- **$2 per 1,000 markets**, and no charge for empty runs — you pay per pushed row.
- **Every run leaves a `RUN_SUMMARY`** record in the key-value store with the reason for an empty result, so a zero-row run is diagnosable.

### Limits

- Price points come from Polymarket's CLOB history at the chosen fidelity; a market with no trades at an offset returns `null` for that offset (thin markets often return `null` at 72 h and 168 h).
- Gamma lists at most 5,000 markets per filter (50 pages × 100), so narrow the date window for large tags.
- Multi-outcome (neg-risk) events appear as one row per market, because each market is itself a Yes/No pair.
- `liquidity` and `resolutionSource` are often `null` on closed markets — Polymarket stops reporting them.
- No trader, wallet or person-level data is collected.

### FAQ

**How do I get historical Polymarket prices after a market closed?** Filter by `tagId`/`tagSlug` and a `closedAfter` / `closedBefore` window, or pass exact `marketSlugs`. Each row carries `pricesBefore`, `priceFinal`, `priceMin` and `priceMax`; set `includeSeries: true` for the full series.

**Can I check whether Polymarket odds are calibrated?** That is what the actor is built for: take `pricesBefore["24h"]` as the forecast and `yesResolved` as the outcome over a few thousand markets, then bucket by price.

**Which tag ids are available?** Any Gamma tag. Common ones are `84` (weather), `1` (sports), `2` (politics) and `21` (crypto); if you only know the name, put it in `tagSlug` and leave `tagId` empty.

### Changelog

- 0.1: initial release.

***

If this actor is useful, a review on its Apify Store page genuinely helps other buyers find it. Found a bug or need a field that is missing? Open a ticket on the **Issues** tab of the actor and it will be looked at.

# Actor input Schema

## `tagId` (type: `integer`):

Polymarket Gamma tag id, e.g. 84 = weather, 1 = sports, 2 = politics, 21 = crypto. Takes precedence over tagSlug.

## `tagSlug` (type: `string`):

Tag by name instead of id, e.g. "weather", "nba" or "fed-rates". Resolved to a tag id via Gamma and used only when tagId is empty.

## `closedAfter` (type: `string`):

Keep only markets whose end date is on or after this ISO date, e.g. "2026-08-20".

## `closedBefore` (type: `string`):

Keep only markets whose end date is on or before this ISO date, e.g. "2026-09-01". Leave empty for no upper bound.

## `marketSlugs` (type: `array`):

Specific market slugs as in polymarket.com/market/<slug>, e.g. \["will-any-month-of-2026-be-the-hottest-on-record"]. Bypasses the tag and date filters.

## `eventSlugs` (type: `array`):

Event slugs as in polymarket.com/event/<slug>, e.g. \["what-caused-the-blue-origin-new-glenn-explosion"]. Every market of the event is returned, one row each.

## `onlyResolved` (type: `boolean`):

If true, skip markets that are closed but not yet resolved as well as open markets, so every row has a winner. Example: true.

## `hoursBefore` (type: `array`):

Hour offsets before the close time at which to snapshot the Yes price, e.g. \[1, 6, 24, 72, 168]. They become the keys of pricesBefore ("1h", "6h", ...).

## `fidelityMinutes` (type: `integer`):

Resolution of the CLOB price history in minutes, e.g. 60 for hourly points. Smaller values give more precise snapshots and heavier responses.

## `includeSeries` (type: `boolean`):

If true, attach the complete \[\[timestamp, price], ...] Yes-price series to each row. Rows get large; leave false unless you need the raw curve. Example: false.

## `minVolume` (type: `integer`):

Skip markets whose traded volume in USD is below this number, e.g. 1000. Leave empty to keep thin markets.

## `maxItems` (type: `integer`):

Stop after this many pushed rows, e.g. 20. You are charged only for rows actually pushed.

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

"scrape" returns every market matching the filters; "monitor" returns only markets that resolved since the previous run of the same saved Task. Example: "scrape".

## `monitorKey` (type: `string`):

State key for monitor mode when the actor is not run as a saved Task, e.g. "weather-resolutions". Runs sharing a key share the comparison state.

## `webhookUrl` (type: `string`):

URL that receives a POST with the monitor-mode change summary (JSON), e.g. "https://hooks.zapier.com/hooks/catch/123/abc". Leave empty to skip.

## `telegramBotToken` (type: `string`):

Optional Telegram bot token used to send monitor-mode change summaries, e.g. "123456789:AAF...". Leave empty to skip Telegram.

## `telegramChatId` (type: `string`):

Optional Telegram chat id that receives monitor-mode summaries, e.g. "-1001234567890". Required only when telegramBotToken is set.

## Actor input object example

```json
{
  "tagId": 84,
  "tagSlug": "weather",
  "closedAfter": "2026-08-20",
  "onlyResolved": true,
  "hoursBefore": [
    1,
    6,
    24,
    72,
    168
  ],
  "fidelityMinutes": 60,
  "includeSeries": false,
  "maxItems": 20,
  "mode": "scrape"
}
```

# Actor output Schema

## `results` (type: `string`):

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record (pushed, skipped, emptyReason)

# 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 = {
    "tagId": 84,
    "tagSlug": "weather",
    "closedAfter": "2026-08-20",
    "hoursBefore": [
        1,
        6,
        24,
        72,
        168
    ],
    "fidelityMinutes": 60,
    "maxItems": 20,
    "mode": "scrape"
};

// Run the Actor and wait for it to finish
const run = await client.actor("datahamster/polymarket-history-resolutions").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 = {
    "tagId": 84,
    "tagSlug": "weather",
    "closedAfter": "2026-08-20",
    "hoursBefore": [
        1,
        6,
        24,
        72,
        168,
    ],
    "fidelityMinutes": 60,
    "maxItems": 20,
    "mode": "scrape",
}

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

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

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

```

## CLI example

```bash
echo '{
  "tagId": 84,
  "tagSlug": "weather",
  "closedAfter": "2026-08-20",
  "hoursBefore": [
    1,
    6,
    24,
    72,
    168
  ],
  "fidelityMinutes": 60,
  "maxItems": 20,
  "mode": "scrape"
}' |
apify call datahamster/polymarket-history-resolutions --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datahamster/polymarket-history-resolutions"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nZL14jebCTjzKgaKl/builds/iziKHSLOZP83cvFYa/openapi.json
