vike
GuidesPolymarket

Bulk archive

The full-L2 Polymarket Parquet archive — three streams, two Hive layouts, one UTC day per partition, with an open manifest and free samples that need no key.

The bulk archive is research-scale Polymarket history as Parquet, served as static files from https://data.vike.io/archive/. It is the whole Polymarket universe — crypto up/down, sports, politics — not a curated subset, and it is where full L2 book history is published in bulk: every book event as recorded, rather than a window or an aggregate over one.

Read the download guidance before you point a tool at it

Archive downloads do not spend your hourly budget, but a Parquet reader pointed at an archive URL issues thousands of Range requests for one file, each a round trip over a bandwidth-capped connection. Download whole files and read them locally — Downloading is the page that shows how.

The three streams

Every partition holds up to three files, one per stream:

StreamWhat it is
book_events.parquetFull L2. Book snapshots, price_change deltas, trade prints, tick-size changes and status markers, in sequence.
trades.parquetThe taker tape alone — the trade prints, without the book.
l1_quotes.parquetDerived top-of-book: bid, ask and both sizes, one row per change.

trades and l1_quotes are derivable from book_events and are published separately because they are two to three orders of magnitude smaller. If top-of-book is all you need, take l1_quotes — a day of it is hundreds of megabytes against several gigabytes of full L2.

Two layouts

The archive is Hive-partitioned, and there are two partitionings of the same data. The manifest carries both, under partitioning and family_partitioning.

By date — the primary layout, one UTC day per partition, every market in it:

/archive/venue=polymarket/date=YYYY-MM-DD/book_events.parquet
/archive/venue=polymarket/date=YYYY-MM-DD/trades.parquet
/archive/venue=polymarket/date=YYYY-MM-DD/l1_quotes.parquet

By family — an additive alternative that groups the crypto up/down families (asset × tenor) into their own much smaller files, so a single-family customer does not download a whole token-sorted day:

/archive/venue=polymarket/asset=btc/tenor=5m/date=YYYY-MM-DD/book_events.parquet

As of this writing the manifest lists asset values btc, eth, sol, xrp, doge, bnb, hype and other, with tenor values 5m, 15m, 4h and other. asset=other/tenor=other is the catch-all for every market that is not a recognised up/down family — sports, politics, one-off events — so nothing is silently dropped by choosing this layout.

Two caveats. The manifest states the first outright: a family with zero rows in a stream on a given day has no file for that stream, so a missing object is a legitimate answer, not an error. The second is visible in the manifest's own entries: the family layout is additive, and it does not currently cover every date the date= layout does. Enumerate families[] rather than assuming a date exists in both.

Choosing a narrower file is the supported way to fetch less data. Reading a byte range out of a wide one is not — see Downloading.

The manifest is the authority

curl -s https://data.vike.io/archive/manifest.json -o manifest.json

manifest.json needs no key. It is regenerated daily and carries generated_at, both partitioning patterns, the inline column schemas, the auth scheme, and one entry per partition with byte size and row count. Freshness says what to trust.

As of generated_at 2026-09-02T00:38:00Z:

Coverage (date= layout)39 consecutive UTC days, 2026-07-25 through 2026-09-01, no gaps
Coverage (asset=/tenor= layout)29 of those days, 2026-07-26 through 2026-09-01
Total size~248 GB across all streams and dates
book_events4.2–7.7 GB per day (median 5.8 GB); median ~364M rows/day
l1_quotes328–669 MB per day (median 464 MB); median ~42.8M rows/day
trades12–19 MB per day (median 14 MB); median ~1.14M rows/day
Largest family fileasset=other/tenor=other book events, ~2.9 GB/day median
Smallest family filesthe 4h tenors, 1.6–11.5 MB/day median

The three oldest partitions carry byte sizes but a null row count — treat rows as optional when parsing, not as a guaranteed field.

Column schemas

The manifest carries these inline, under schema. They are spelled in the source store's own type vocabulary — Decimal(p,s), Enum8(...), LowCardinality(String) — not in Parquet's, so what your reader hands back does not always match the spelling character for character. The two places where that matters are noted below.

book_events

ColumnTypeNotes
token_idStringThe outcome token. A market has one per outcome.
condition_idStringThe market. 0x-prefixed.
tsInt64Event time, epoch milliseconds UTC, as published.
local_tsInt64Recorder receive time, epoch ms. Never earlier than ts.
seqUInt64Feed sequence number.
event_typeEnum8('book','price_change','trade','tick_size_change','status')
is_snapshotUInt81 on a full-book row, 0 on a delta.
sideEnum8('none','buy','sell')
price, sizeDecimal(9,4), Decimal(18,6)The changed level, on a delta.
best_bid, best_askDecimal(9,4)Present in the schema; use l1_quotes for top-of-book.
bids, asksStringJSON arrays of [price, size] pairs.
tick_sizeDecimal(9,4)
statusLowCardinality(String)Empty on ordinary rows.

Two things about that table are easy to get wrong, both checked against the current free sample:

  • bids/asks carry only the side that changed. On a book snapshot row both hold the whole ladder. On a price_change only the column matching side is populated and the other is an empty string — side = buy fills bids, side = sell fills asks. trade and status rows populate neither.
  • The two Enum8 columns arrive as bytes, not strings. They are written as the Parquet ENUM logical type, so pyarrow types them binary and you read b'price_change', not 'price_change'. Decode before comparing, or the filter silently matches nothing.

trades

token_id, condition_id (String), ts, local_ts (Int64), seq (UInt64), price, size (Float64), side (String — buy or sell).

Note the type differences from book_events: trades carries price/size as Float64 and side as a plain String — not a Decimal and an Enum8 — so the decoding note above does not apply here. seq is 0 on every row of the current sample; order on ts, not on seq. Do not assume one schema across streams.

l1_quotes

token_id, condition_id (String), ts, local_ts (Int64), bid, ask (Decimal(9,4)), bid_size, ask_size (Decimal(18,6)).

Two properties worth relying on

Files are token-sorted. Rows for one token_id are contiguous and ascend by ts within that run. That is what makes the family layout worth having, and it means a single-token extraction is a contiguous scan once the file is local.

Recorder gaps are marked, not hidden: Freshness.

Free samples

/archive/samples/ needs no key and is a real slice of the full archive, not a synthetic or truncated demo: one complete crypto up/down 5-minute market — the busiest of its kind that day by book_events row count — with all three streams for both outcome tokens across the market's entire lifecycle, open through resolution.

curl -s -O https://data.vike.io/archive/samples/README.txt
curl -s -O https://data.vike.io/archive/samples/book_events.parquet
curl -s -O https://data.vike.io/archive/samples/trades.parquet
curl -s -O https://data.vike.io/archive/samples/l1_quotes.parquet

The samples are regenerated daily from a recent real day, so the market slug, condition_id and sizes change; README.txt names the market and date of the current one. Same column schema as the full archive, at a few megabytes rather than a few gigabytes — the right place to write and debug a parser before you create a key.

Authentication

Everything under /archive/ except manifest.json and /archive/samples/ needs a credential:

curl -H "X-API-Key: $VIKE_API_KEY" -o trades-2026-09-01.parquet \
  "https://data.vike.io/archive/venue=polymarket/date=2026-09-01/trades.parquet"

The manifest names the header as X-API-Key; the REST API's spec spells it X-API-KEY. HTTP header names are case-insensitive, so these are the same header and the same key — the same one that gates data.vike.io/v1 and MCP. Unlike those, an archive download does not spend the hourly budget; it is recorded in your usage view and nothing more. An unauthenticated request to a gated object is a 401, not a 404: the path resolved, the credential did not.

Gated responses carry Cache-Control: private, no-store, so the CDN never caches an authenticated file — caching one would bypass both the gate and the usage record.

On this page