/api/v1/cards tenant key Lists your cards — a thin shape suited for inventory reconciliation. Newest first by default; pass ?since for a drift-free keyset sweep. Every row now carries a `type` of "RAW" or "GRADED", and `?type=` picks which inventory you sweep. Leaving ?type off returns exactly the rows this endpoint has always returned, so an existing sweep needs no edit — what changes is that the graded ones among them are finally labelled as such. `?type=graded` lists your slabs, which had no endpoint at all before, and `?type=all` sweeps both as one continuous pass. Inventory is mixed: a row is either a TCG card or a sports card, and category tells you which vocabulary it speaks before you read anything else. Every row also carries a sub_account label — null while the card sits in your own inventory — and ?sub_account= narrows the list to one of them; omit it and you get everything, exactly as you did before sub-accounts existed. Cards are returned only while your account is active — a suspended account receives an empty list rather than an error, so treat an unexpectedly empty sweep as a signal to check with your account manager rather than as zero inventory.
When a raw-card response includes identity and pricing, use their status fields to interpret it. An unconfirmed identity has null market and insured prices; declared_value remains separate. A returned card is not automatically verified or eligible for a quote or order. Intake-held, REMOVED and other-account cards remain excluded; existing graded publication controls still apply.
Start a full sweep with GET /cards?type=raw&since=0 (or type=all for both streams), then pass each returned cursor unchanged. Upsert by unique_id and repeat full sweeps to refresh existing identity and pricing. since is a visible-sequence cursor, not an updated_at change feed. Historical rows newly included by a visibility-policy change can be behind a saved cursor; a real identity publication can return the same unique_id again. Reading inventory generates no receipt or confirmation events and changes no publication timestamp. GET /inventory/events alone does not guarantee historical-card or catalog-price updates.
?sub_account= is additive, not a behavior change. Omitted, empty, or `all` returns exactly the same ROWS this endpoint returned before sub-accounts existed — every card, across your own inventory and every sub-account. The sub_account label is additive; later fields follow it. Filters and visibility policy still apply. A visibility-policy change requires a fresh full sweep as described above.
energy_types is additive too — no existing key changes value or moves relative to another — but unlike sub_account it is not appended last: it sits between game and name on every row. A reader that picks keys by name is unaffected; anything that golden-diffs the body sees one change the first time it sweeps after the field lands.
language, language_code, tcgplayer_id, series, supertype, subtypes and rarity are additive and appended last, in that order, after inbound_shipment — and after identity / pricing on accounts that receive them. No existing key changes value or moves. A golden-diff sees one change the first time it sweeps after they land, then nothing.
inbound_shipment is recorded going forward only. Every card stored before it existed reads null, and so does a card the bench stored as not coming from a tracked package — null means "no box recorded", not "not received". To reconcile one delivery, sweep GET /cards?type=all&inbound_shipment_id=<id> once the box is RECEIVED and its cards have had their check-in hour, and compare against the card_count you declared.
A card that came out of a box you announced with declared_value_min_cents / declared_value_max_cents (POST /inbound-shipments) has every value WE set clamped into those bounds: a value below the minimum is raised to the minimum, a value above the maximum is lowered to the maximum. That covers a declared_value with source "RIP", the catalog fallback behind insured_price and, where returned, pricing.market_price. A declared value you set yourself (source "tenant") is never changed, and a card we cannot price stays null rather than taking the minimum. The bounds in force when a card is priced are the ones applied.
Sweep inventory with ?since, not ?offset: keyset pagination is drift-free under concurrent inserts and is not bounded by the 10,000-row offset cap. Start with since=0, then pass the returned cursor until has_more is false. Upsert repeated unique_ids: a later real publication can resequence an existing card.
New cards show up about an hour after a delivery is checked in, not the moment it is scanned — that hour is when we confirm every card is filed to the right account and sub-account. When they appear they are placed after everything already in your sweep, so keep your cursor and sweep again: they arrive from where you left off, never behind it.
?type=all is one sweep, not two. It pages through your raw cards first and then continues into your slabs, and a single page can hold the tail of one and the head of the other — so keep following pagination.cursor until has_more is false rather than stopping when the first GRADED row appears, and do not assume a page is all one type. For published-only accounts reconcile the sweep with pending_identity. When pending raw rows are returned, they can overlap that count; do not add the count to the returned cards.
A card that comes back to us on a returned order — the carrier could not deliver it, and the order is RETURNED (see Get order) — is placed after everything already in your sweep when the warehouse scans it back in, so a saved ?since= cursor re-delivers it with a new position. It is OFFCHAIN again, still under the same sub_account, and can be ordered again with a corrected address. Upsert by unique_id, as with any re-delivery; the activity log carries a card.returned event for it as well.
Adopt `type` before you adopt `?type=`. Reading the field costs nothing and immediately tells you which of the rows you already receive are slabs; a row you have been pricing as a raw card and that comes back "GRADED" was always a slab, and its declared_value is the figure to trust.
Read sports.record_status before you trust a sports row. "provisional" means the row was machine-extracted from the warehouse scan at intake and has never been checked against a catalog — the player name, parallel, print run and card number are a model reading a photograph, and any of them can change. "canonical" is catalog-verified; "merged" means the row has been folded into a canonical one. A provisional that later merges is repointed at the canonical row, so sports.id can change for a card you have already seen: key your records on unique_id, never on sports.id.
GET /api/v1/cards?limit=10 These inputs are shared across all docs pages, so an id entered here carries over.
Not run yet. Press Run to make a live call against https://service.rip.fun (through this demo's server-side proxy; the API key never reaches the browser).
curl -X GET 'https://service.rip.fun/api/v1/cards?limit=10' \ -H 'X-API-Key: rip_…'
| Field | Type | Required | Description |
|---|---|---|---|
unique_id | query string | — | Exact-match filter (applies to both pagination modes) |
type | query string | — | `raw` | `graded` | `all`. OMITTING it is not the same as `raw`: omitted, you get every row this endpoint has always returned — which includes a small number of older slabs that live alongside the raw cards — so nothing disappears from a sweep you already run. `raw` is the clean raw-only stream, `graded` is your slabs only, `all` is both as one sweep (raw first, then graded). Branch on the `type` field on each row, not on which query you sent |
sub_account | query string | — | Narrow to one sub-account by slug, matched case-insensitively. Omitted, empty, or `all` returns every card across your own inventory and all sub-accounts; `main` (or `none`) returns only cards in your own inventory. An unrecognized slug returns 400 listing every slug your account accepts — retired ones included, since their cards stay filterable — never an empty 200. Supply it once: repeating it (?sub_account=a&sub_account=b) is a 400. Read the slugs from GET /sub-accounts |
inbound_shipment_id | query int | — | Only the cards that came out of one package: the id of an inbound shipment you announced (POST /inbound-shipments returns it). Applies to raw and graded rows alike, combines with every other filter, and works in both pagination modes. An id that is not one of your shipments simply matches nothing. Cards stored before boxes were recorded have no box and never match |
since | query cursor | — | Keyset cursor: pass the previous page’s pagination.cursor. Returns the cards after that cursor, in the order they became visible to you — drift-free under concurrent inserts and not bounded by the offset cap. A card that appears later (still being checked in, or still being identified) is placed after everything you have already swept, so the cursor you saved picks it up and nothing is skipped. So is a card that comes back to us on a returned order (see Get order): it is OFFCHAIN again and re-delivered with a new position, so upsert by unique_id. It does not track arbitrary updates. After a visibility-policy change, start a fresh sweep at since=0 to include historical rows newly returned by the API. Omit to use offset mode |
limit | query int | — | 1..100, default 50 |
offset | query int | — | 0..10_000, default 0 (ignored when since is supplied) |
data)| Field | Description |
|---|---|
cards[].identity | Optional metadata on RAW-card responses: { status, published_at, blockers }. status is "unconfirmed" while identity_published_at is null, "confirmed" when published, or "unknown" if the deployment cannot track publication. published_at is the actual ISO 8601 timestamp or null; it is never fabricated. The pending blocker is "identity_not_published". A sports.record_status of "canonical" alone does not establish publication |
cards[].pricing | Optional metadata on RAW-card responses: { status, market_price, currency, blockers }. status is "unverified" for pending identity, "unpriced" for published identity without a catalog price, or "priced" when a catalog price is available. market_price is a USD decimal string or null, never declared_value. Pending identity always returns null with blocker "identity_not_published"; missing catalog price returns null with "missing_market_price". priced reports an available catalog price, not a new live appraisal. On a card from a box announced with declared-value bounds, market_price is clamped into those bounds — see the declared-value bounds note |
cards[].unique_id / card_id | The canonical CARD-RIP… identifier + catalog card id. card_id is null on sports cards — they are not in the TCG catalog |
cards[].type | "RAW" | "GRADED", on every row — including the raw ones you already receive, so you never have to infer it from the absence of another key. It is a SECOND discriminator, orthogonal to category: a row is (tcg | sports) × (RAW | GRADED). A GRADED row is a card in a grading company’s sealed holder; its grade and cert live in the `graded` block |
cards[].graded | Grading identity. Present only when type is "GRADED" — the key is absent, not null, on a raw row: company (PSA | CGC | BGS | TAG | …), grade (as written, e.g. "10" or "9.5", null if we could not resolve it), grade_numeric (tenths, so 100 is a 10 — compare on this, never parse the string), cert_number, cert_url (the grader’s own verification page), verification_status ("verified" means the grading company’s record confirmed this cert; anything else means we could not reach or match it and the grade is our reading of the label), graded_at |
cards[].category / game | The discriminator, on every row. category is "tcg" | "sports"; game is pokemon | onepiece | basketball | baseball | football | soccer | hockey | other. Both are null when the row resolves to neither catalog — treat that as unknown, not as TCG. Branch on category; new game values are added over time |
cards[].energy_types | The card’s Pokémon energy types, as the catalog spells and orders them — ["Fire"], or ["Fire", "Water"] on a dual-type card. On every row, raw and GRADED alike. [] on a Pokémon card whose catalog row carries no types: usually a Trainer or an Energy card, but also a Pokémon whose types we do not hold, so do not read [] as a card category. null on everything else — sports, One Piece, and any row whose catalog card or game we cannot resolve — so null means "not a Pokémon card we can speak for", never "Colorless". These are the elemental types only: not rarity, not supertype (Pokémon / Trainer / Energy) and not the energy cost of an attack, and none of it is inferred from a name, a cert or a scan |
cards[].sub_account | Which of your sub-accounts the card is filed under: { slug, name }, or null when it is in your own inventory. A label, not a boundary — a filed card is handled, billed, insured and shelved exactly like the rest of your inventory and sits in the same bin. GET /sub-accounts enumerates the slugs; POST /cards/sub-account changes them |
cards[].inbound_shipment | The package the card arrived in: { id, tracking_number, carrier, reference } — the inbound shipment you announced with POST /inbound-shipments (id is the one GET /inbound-shipments/:id takes; tracking_number, carrier and reference are what you sent). The receiving bench picks the box as it stores each card. null when no box was recorded: cards stored before this field existed, cards that came in some other way than a package you shipped, and cards the bench stored as not from a tracked box. Appended after the existing card fields; optional raw identity/pricing metadata follows it |
cards[].name / card_number / set_name | Catalog identity of the card, filled from whichever catalog owns the row. On a sports card, name is the player name and card_number / set_name come from the sports catalog row |
cards[].language / language_code / tcgplayer_id / series / supertype / subtypes / rarity | The catalog identity behind card_id, on every row, raw and GRADED alike. language and language_code are the set’s language in canonical form — "English" / "en", "Japanese" / "ja", "Chinese (Traditional)" / "zh-Hant" — the same spellings the catalog API uses. series is the set’s series name ("Scarlet & Violet"); tcgplayer_id is the catalog card’s TCGplayer product id, as a string. supertype, subtypes and rarity are the catalog’s own strings, spelled and cased as the catalog has them: on a Pokémon card supertype is Pokémon / Trainer / Energy, subtypes is a list such as ["Basic"] or ["Stage 1"] and rarity reads "Illustration Rare"; on a One Piece card supertype carries the card type (Leader / Character / Event / Stage). subtypes is [] on a catalog card that has none. All seven are null on a sports row and on any row whose catalog card we cannot resolve — null means "no catalog card to speak for", never a value — and language, language_code and series are also null on a catalog card with no set. None of it is read from a name, a cert or a scan. Appended as the last keys of the row, after inbound_shipment (and after the optional identity / pricing metadata where your account receives it) |
cards[].release_date / year | Set release date (ISO 8601, always null for sports cards — a sports row carries a season, not a release date) + release year (sports cards use the season start year) |
cards[].image_url / scan_image_url | Catalog image + your scanned image. image_url is null on sports cards: we hold no licensed product art for them, so scan_image_url — our own warehouse photograph — is the only image you get. On a GRADED row scan_image_url is a photograph of the SLAB, not of the bare card: the grading company’s own cert scan, replaced by our warehouse photograph if we take one |
cards[].insured_price | Legacy effective insured/carried value as a USD decimal string: declared_value.amount_cents takes precedence when present, otherwise the catalog price is used; null when neither exists. On a card from a box announced with declared-value bounds, the catalog fallback is clamped into those bounds (a value you set is used as-is). An unconfirmed raw identity returns null, even if a declared value or provisional catalog price exists. When supplied, pricing.market_price is the raw catalog market price. On a GRADED row read declared_value instead: that figure is snapshotted from the price for the actual (card, company, grade), whereas insured_price can fall back to the RAW card’s price, which understates a high-grade slab by a wide margin |
cards[].declared_value | The value this copy is carried and insured at: { amount_cents, currency, source, effective_at }. source is "tenant" once you set one via PUT /declared-values, or "RIP" while it is still our snapshot of the catalog price at scan time. amount_cents is null when the card has neither. A "RIP" value on a card from a box announced with declared_value_min_cents / declared_value_max_cents is clamped into those bounds — see the notes |
cards[].sports | Sports-catalog identity. Present only when category is "sports" — the key is absent, not null, on every other row: id, sport, player_name, team, manufacturer, set_name, year (the raw season string, e.g. "2023-24"), card_number, parallel_name, print_run, is_autograph, is_relic, is_rookie, record_status, position, jersey_number, league. The last three are what the card itself prints about the player, read off our warehouse scan at intake (the catalog does not supply them), and are null until a scan has filled them. Every field inside is nullable |
pending_identity | Cards we have received and are still identifying. Counts the streams you asked for, so with ?type=graded it is slabs only and with ?type=all it is both. Under a published-only visibility policy these cards are excluded from cards[]. When pending raw cards are returned, an unpublished row can be both listed and counted here. Do not add pending_identity to cards[] to derive a total; deduplicate by unique_id and use identity.status when supplied. This count is not a total of extra unseen cards. Cards still in their check-in hour (see below) are in neither yet. Present only while the count is available |
(still being checked in) | Newly received cards appear about an hour after the warehouse finishes checking a delivery in. Until then they are not in cards[], not counted in pending_identity, and not reachable by unique_id anywhere in this API — that hour is when we confirm every card is filed to the right account (and sub-account) before you see it. Reconcile a delivery an hour or more after it is received |
(removed duplicates) | A card we added by mistake — the same physical card scanned in twice — is taken back out of your inventory and drops out of cards[] for good. For published cards, a card.removed event in GET /inventory/events names its unique_id (and the copy we kept). Unpublished pending cards can disappear without this event; reconcile full sweeps and unique-id lookups; a card you have already fetched is only ever removed within three hours of its intake |
cards[].created_at / updated_at | Intake + last-touch timestamps |
pagination | Offset mode: { limit, offset, has_more }. Keyset mode (?since): { cursor, has_more, limit } — pass cursor back until has_more is false. With ?type= the cursor is an opaque STRING ("r:1234", "g:57") because raw and graded cards are numbered independently; without ?type= it stays the plain integer it has always been. Either way, treat it as a token to hand back rather than a number to store or compare |
| Status | Code | When |
|---|---|---|
| 400 | invalid cursor | since is not a cursor this endpoint issued (fails loudly instead of re-delivering your whole inventory). Also when a cursor from one stream is replayed against another — a "g:" cursor with ?type=raw — or when a bare integer is sent with ?type=all, where it cannot say which stream it belongs to |
| 400 | invalid type | type is not raw, graded or all (the message lists the valid values) |
| 400 | invalid sub_account | sub_account is a slug your account does not have (the message lists every accepted slug, retired included), or was supplied more than once |
| 400 | Invalid inbound_shipment_id | inbound_shipment_id is not a single positive integer |
| 403 | Tenant API key required | key is not tenant-scoped |
See Errors for the response envelope and the full code list.