Skip to main content
pokemon.card_prices.snapshot and pokemon.product_prices.snapshot announce a file, not a set of changed records. Card and sealed product prices rewrite most of their rows every day, so the useful unit is not one changed price, it is the current file. This page is about that file. For the event that announces it, see Events.

The file

Each snapshot is flat NDJSON, gzipped: one price row per line, and every line applies on its own. Read and apply one line at a time rather than holding the whole file in memory.

Card prices

One line per row, exactly as it appears in the file:
variant_id is the same id the card object’s variants[].id carries, and the same id pokemon.card_variants.deleted names: join on it directly, with no lookup table in between. Rows are ordered by (card_id, variant_id, condition), so a byte-diff between two days’ files lands on exactly the rows that changed.

Sealed product prices

A sealed product has no printing to key on, so its row carries product_id and variant where a card row carries card_id and variant_id. Rows are ordered by (product_id, condition, variant). Both rows share the rest of their shape. currency is always USD today: read it per row rather than assume it, the same as the event’s own currency field. market, low and high are integer cents, null where nothing is priced, and listing_count is null under the same condition. Where present, it counts how many active listings those three figures are computed from, capped at 25.

Naming

Each file is named by the snapshot date (the UTC date the file was published, not any date inside it) and its own sha256.
Read url from the event or from the manifest below rather than building it yourself. A url always answers with the same bytes, so cache it for as long as you keep it. Two publishes on one date answer at two urls. Each file supersedes every earlier one, including an earlier publish of the same date. If a delivery never arrives, there is nothing to replay: fetch the current file instead.

observed_on rides on the row

The file is named by the date it was published. A row’s own observed_on can be older, sometimes by as much as 30 days: not every price is observed the moment a snapshot is taken. Trust each row’s own date for when that figure was true, never the date in the file’s name.

Verifying the download

sha256 is the digest of the gzipped bytes exactly as downloaded, before decompression. Hash what you fetched and compare it to the field of the same name, on the event or on the manifest below, before you read a single row.

Finding the current file

GET /v1/prices/snapshots answers with the newest snapshot of each kind:
Either value is null where nothing has published for that kind yet. Call this to bootstrap a mirror without waiting for a delivery, or to pick up a snapshot whose event you missed: the same six fields, the same file, whichever way you learned about it. The call itself is one ordinary /v1 request, and the file it names costs nothing beyond that: it lives on assets.cromos.so, outside /v1 entirely.

Retention

Each file, and the manifest entry naming it, stays available for 30 days. GET /v1/prices/snapshots always answers with the newest of each kind, never a history. A url is not a permanent name for one set of bytes: a later publish of the same date replaces the file at the same url (see Naming). A (url, sha256) pair you stored from an earlier delivery can stop matching what is at that url now. Verify against the sha256 that came with the url you are about to fetch, not one saved from an earlier delivery.

Applying a snapshot

Fetch the file, verify it, then read it one line at a time:
manifest is one manifest object: snapshot_date, rows, subjects, url, sha256, bytes. GET /v1/prices/snapshots hands you that object directly. A delivery does not: unwrap it first. A snapshot event’s data is { currency, snapshot }, and snapshot is an array, under the same key every other event carries its records under. It holds one entry in the ordinary case, and can hold more than one when a single delivery announces several days’ files together. Entries are sorted by snapshot_date ascending, so snapshot[0] is the OLDEST file in the delivery, not the newest: always read the last entry, snapshot.at(-1). GET /v1/prices/snapshots answers differently: its data.cards and data.products are each already a single manifest object (or null), not an array, because that endpoint only ever answers with the newest of each kind. Write your own upsertPrice, keyed on (card_id, variant_id, condition) for cards, or (product_id, condition, variant) for products: a row for a key you already hold replaces it. The file is a complete snapshot, not a set of changes. A printing priced yesterday and missing from today’s file is not priced today: if your own store still shows a price for it, its absence from the file is the signal to clear it.