vike

Parquet 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.

Coverage and sizes grow by a day every day, so read them from the manifest rather than from a page. The date= layout starts on 2026-07-25:

# the first and the newest day of the date= layout: dates[] is newest first
jq -r '.dates[-1].date, .dates[0].date' manifest.json

# how many days each layout covers: compare the two counts; the family layout is additive
jq '(.dates | length), ([.families[].date] | unique | length)' manifest.json

# bytes per stream across every day of the date= layout, to size a full pull before starting it
jq '[.dates[].streams | to_entries[]] | group_by(.key) | map({(.[0].key): (map(.value.bytes) | add)}) | add' manifest.json

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

Every Polymarket file under /archive/ except manifest.json and /archive/samples/ needs a credential. One more path on the same host is open: /archive/hl-samples/, the Hyperliquid archive's free samples. Hyperliquid's own files are not served under /archive/ at all, key or no key; they come only as temporary signed /dl/ links from /v1/hyperliquid/archive/url. A Polymarket download sends the key:

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