Private known-card beta · USD only
JK Index Pricing API v0 consumer documentation
This is the integration source of truth for all four supported V0 routes. It covers resolution, ambiguity selection, price snapshots, batch, history, caching, authentication, quotas, and errors.
Building a Muse integration? Start with the connector overview and example prompts below. Separate access approval is required.
If you see 404 on /api/v0/*, you're likely calling jkindex.io instead of api.jkindex.io.
Muse connector: start with a simple question
Explore Pokémon, One Piece and Riftbound cards, rank cards in a set, check reviewed release dates and follow up with prices or history. These suggested questions describe the pricing pilot; a real Muse connection and directory availability remain unverified.
Explore a Pokémon set
“Most expensive cards in Ascended Heroes?”
See the top single cards, starting with ungraded, near-mint prices in USD.
Compare a One Piece card
“How do raw and PSA 10 prices compare for Monkey D. Luffy?”
Choose a printing, then compare recorded prices with sale counts and source dates.
Find a Riftbound card
“Find Ahri cards in Riftbound.”
Browse matching cards and choose the printing you want to explore.
Then keep the conversation going
- “What about PSA 10?”
- “How recent are those sales?”
- “Show its price history.”
Choose a card before asking about its history. You can keep the same set or card selected as you explore.
Rankings default to ungraded near-mint single cards in USD, using the latest recorded sale. Release information covers English North America and includes official sources and a review date. Choose a set or printing when needed; missing and stale data remain explicit. The pilot accepts no payments.
This connector uses Raw API with a separately approved bearer API key at https://api.jkindex.io/api/partners/v1. Existing v0 access does not automatically grant connector access. Key-specific usage limits and account, plan and regional conditions are finalized during provisioning.
Simple guide, developer setup and submission detailsConnector OpenAPI specification
Reviewer setup and tool permissions
Muse review: six public tools
Use the six-tool submission specification. The broader partner API specification also documents four personal tools that are disabled and outside this submission.
These tools return structured market information: the selected printing, separate raw and graded observations, sale counts, source dates and explicit gaps. A collector can complete a price comparison in the conversation; opening the website is optional.
| Operation | Result | Classification | Sensitive write | Usage units |
|---|---|---|---|---|
searchCards | Find candidate card printings | Read | No | 1 |
getCardInsights | Compare selected raw and PSA price segments | Read | No | 1 per segment |
getCardPriceHistory | Read price history for one card and condition | Read | No | 1 |
searchSets | Find candidate sets | Read | No | 1 |
getSetCardRanking | Rank indexed single cards by latest recorded sale | Read | No | 1 |
getSetReleases | Read reviewed release facts and verification status | Read | No | 1 |
Classifications above are proposed for Meta review. Each tool requires a separately approved public-data bearer key. Operational side effects are usage accounting, credential last-use updates and request logging. No tool changes collections, permissions, orders or payments.
Review environment: production, with real public catalog and pricing data. No individual account sign-in is required for these six tools. The dedicated review credential is supplied through the portal’s secure credential field and remains available during review. Its allocation is 30 usage units per minute and 200 per rolling 30 days, including operator checks. This is a review allowance, not a general-user plan. Supply the raw key in the credential field; requests use Authorization: Bearer <issued-key>. Never put it in a chat prompt.
Support and security contact: support@jkindex.io. JK Index’s founder is responsible for connector security.
- Find Ahri in Riftbound. Choose a returned printing if there are multiple matches.
- Compare that card’s raw near-mint and PSA 10 prices. Include sale counts, source dates and any missing or stale data.
- Ask for its PSA 10 price history, retaining the same card. An empty series is a valid result.
- Find Ascended Heroes in Pokémon and request its highest-priced raw near-mint cards; follow up with PSA 10.
- Check the reviewed One Piece release information. When verification has expired, report that a latest-release answer is unavailable.
The API has passed direct production checks across Pokémon, One Piece and Riftbound. Muse credential mapping, tool selection, permission prompts and follow-ups still need end-to-end review. Respect Muse’s approval controls. For 401 or 403, stop and resolve access; for 429 or 503, respect Retry-After. Do not exhaust the review allowance to demonstrate errors.
Personal connection · not available yet
Ask about the cards you own
A personal key will let an agent read your JK Index collection with the permissions you choose. Personal keys are currently disabled. Connecting an individual account through Muse’s directory has not yet been verified.
- “What is my collection worth?”
- “Show my most valuable Riftbound cards.”
- “Which of my raw cards are worth a closer look?”
- “How do grading costs change the comparison for this card?”
Grade picks compare recorded raw and PSA sales against modeled grading and shipping costs. They identify the lowest observed grade whose median clears that basis. They do not assess your card’s condition, predict its grade or recommend a submission. Missing, thin and older sales remain explicit.
Setup when personal access becomes available
- Sign in to your verified JK Index account and open Personal keys.
- Name a key for the agent. Allow collection access to see holdings and values; allow both collection and grading access for grade picks.
- Copy the key once into the agent’s supported secure credential store. Never put it in a chat prompt or URL. Follow the confirmed integration instructions when available.
- Start with one of the questions above. Revoke the key from Personal keys whenever you want to stop future requests.
Revoking access does not delete information an agent already received. Use a service you trust and review its privacy terms. These reads cannot add cards, analyze photos, trade, open prediction events or play Blitz.
Initial personal-key limits: 30 usage units per minute, 3,000 per rolling 30 days and up to three active keys. Grade picks cost one unit per five holdings whose evaluation starts, rounded up, with a minimum of one. Individual account and pilot availability will be stated before access opens.
Where this is going
The pricing connector is step one. Next is a connection your Muse agent manages for you. It knows the cards you own and keeps them priced. It tells you which raw cards are worth grading. It follows Before the Slab events for cards you own: when one opens, it briefs you and proposes a call, you approve it, and when the grade comes back it tells you how you did. Blitz comes with it: daily runs, challenges from friends and creators, wager mode, stream nights.

- Your account. Connect it once, with permissions you choose. Ask “what should I grade?”
- The arena. Before the Slab and Blitz: event briefs, challenges, results.
- Positions you approve. The agent proposes. You decide, every time.
- The Sunday brief. Alerts and a weekly rundown, without asking.
The pricing API above is available to approved integrations; Muse directory approval is pending. Future personal and BTS/Blitz tools require separate compatibility and policy review. Read the vision page.
The existing v0 API contract continues below.
1. Base URL and authentication
Send every request to https://api.jkindex.io. The website host does not serve the API. Every supported V0 route requires a hand-issued, per-consumer bearer key:
Authorization: Bearer YOUR_API_KEY- Never put a key in a query string, browser URL, repository, or support message.
- A revoked, inactive, missing, or invalid key returns the same stable 401 body.
- Keys and both quotas are per consumer. The exact per-minute and rolling-30-day limits are communicated at issuance.
- Every response carries
X-Request-Id. Include it with the UTC timestamp when reporting an issue.
2. First known-card request
V0 is structured resolution, not search. Start with a TCG identifier, set identifier, and collector number that you already hold.
curl --get \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "tcg=pokemon" \
--data-urlencode "set=jungle" \
--data-urlencode "number=64" \
--data-urlencode "segment=raw_nm" \
--data-urlencode "currency=usd" \
"https://api.jkindex.io/api/v0/price"{
"outcome": "matched",
"canonical_id": "jungle-064-pikachu-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "64",
"number_display": "64",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"image_url_absolute": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"display_name": "Pikachu",
"edition": "1st",
"match": {
"status": "exact",
"confidence": "exact",
"reason_codes": [
"set_slug_exact",
"collector_number_exact"
]
},
"confidence": "exact",
"candidate_ref": "c1_<opaque-reference-from-response>",
"other_printings": [],
"pricing": {
"segment": "raw_nm",
"as_of": "2026-07-30T18:00:00Z",
"snapshot": {
"latest": "30.00",
"median": "20.00",
"count": 3,
"currency": "USD"
},
"freshness": {
"state": "fresh",
"age_days": 1,
"fresh_max_age_days": 7,
"stale_after_days": 21
},
"segment_basis": "strict_segment",
"reason_code": null
}
}Money is serialized as fixed-decimal strings. canonical_id is a stable identity value, not a selection protocol.
3. Understand matched, ambiguous, and not-found outcomes
| Outcome | Meaning | Next action |
|---|---|---|
matched | Exactly one printing resolved. | Read identity and the segment-scoped pricing block. |
ambiguous | Several active printings match. | Choose a candidate and copy its candidate_ref verbatim. |
not_found | The reference or requested contract dimension failed closed. | Branch on the top-level reason_code; do not infer catalog-wide absence. |
These are domain answers over HTTP 200. Only a matched response has match, categorical confidence, and pricing. Ambiguous and not-found outcomes do not carry a top-level confidence value.
{
"outcome": "ambiguous",
"candidates": [
{
"canonical_id": "jungle-054-jigglypuff-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "54",
"number_display": "54",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": null,
"display_name": "Jigglypuff",
"edition": "1st",
"variant": "1st",
"candidate_ref": "c1_<first-opaque-reference>",
"variant_is_unique": false,
"image_url_absolute": null
},
{
"canonical_id": "jungle-054-jigglypuff-unlimited",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "54",
"number_display": "54",
"variant": "unlimited",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": null,
"display_name": "Jigglypuff",
"edition": "unlimited",
"variant": "unlimited",
"candidate_ref": "c1_<second-opaque-reference>",
"variant_is_unique": true,
"image_url_absolute": null
}
]
}{
"outcome": "not_found",
"reason_code": "unknown_candidate_ref",
"message": "unknown_candidate_ref"
}4. Select an ambiguous printing with candidate_ref
- Render candidate identity, display name, edition, variant, and image as provided.
- Keep the selected candidate's opaque
candidate_refunchanged. - Repeat price or history with that value. Do not parse or construct a reference.
CANDIDATE_REF='PASTE_CANDIDATE_REF_VALUE_VERBATIM'
curl --get \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "tcg=pokemon" \
--data-urlencode "set=jungle" \
--data-urlencode "number=54" \
--data-urlencode "candidate_ref=${CANDIDATE_REF}" \
--data-urlencode "segment=raw_nm" \
"https://api.jkindex.io/api/v0/price"{
"outcome": "matched",
"canonical_id": "jungle-054-jigglypuff-unlimited",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "54",
"number_display": "54",
"variant": "unlimited",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": null,
"image_url_absolute": null,
"display_name": "Jigglypuff",
"edition": "unlimited",
"match": {
"status": "exact",
"confidence": "exact",
"reason_codes": [
"set_slug_exact",
"candidate_ref_match"
]
},
"confidence": "exact",
"candidate_ref": "c1_<second-opaque-reference>",
"other_printings": [],
"pricing": {
"segment": "raw_nm",
"as_of": "2026-07-30T18:00:00Z",
"snapshot": {
"latest": "30.00",
"median": "20.00",
"count": 3,
"currency": "USD"
},
"freshness": {
"state": "fresh",
"age_days": 1,
"fresh_max_age_days": 7,
"stale_after_days": 21
},
"segment_basis": "strict_segment",
"reason_code": null
}
}A recognized reference selects exactly one active card in the supplied TCG and set. Empty whitespace is treated as no reference. Every other supplied value— malformed, stale after catalog changes, or unknown—returns unknown_candidate_ref and never falls back to number matching. Candidate refs have no published expiration time.
variant remains exact-after-trim and case-sensitive. It can help, but candidates with variant_is_unique: false prove that a variant is not always sufficient; candidate_ref is the reliable path.
5. GET /api/v0/price
| Parameter | Required | Contract |
|---|---|---|
tcg | yes | pokemon or one-piece; case-insensitive. |
set | yes | Set slug, set code, or registered alias; case-insensitive. |
number | yes | Structured collector number; normalization is listed below. |
variant | no | Exact-after-trim, case-sensitive printing discriminator. |
language | no | Defaults to en; accepted and echoed, not an enforced catalog filter. |
segment | no | Required for non-null pricing; one of the seven supported segments. |
currency | no | Defaults to usd; USD is the only supported currency. |
candidate_ref | no | Opaque selector copied verbatim from a prior candidate or match. |
Supported number forms
Numeric (54, 054), optional hash (#54), fraction (54/102), alpha suffix (54a), subset code (TG12), and alphanumeric prefixed forms (OP01-121) are supported. Prefix matching is set-scoped; the resolver never guesses across sets.
Verified One Piece forms include set alias OP01 withOP01-121 or bare 121, case-insensitiveop01-001, and set OP07 with eitherOP07-119 or bare 119. The OP01-121 production shape is legitimately ambiguous and returns candidate refs.
curl --get \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "tcg=one-piece" \
--data-urlencode "set=OP01" \
--data-urlencode "number=OP01-121" \
"https://api.jkindex.io/api/v0/price"
# The verified bare-number equivalent uses set=OP01 and number=121.Pricing-null cases
- No segment: matched identity with
pricing.reason_code: pricing_requires_segment. - Supported segment with no qualifying sale: matched identity with
pricing.reason_code: no_pricing_for_segment. - Unsupported segment: top-level not-found
unsupported_segmentplussupported_segments. - Non-USD: top-level not-found
unsupported_currencyplussupported_currencies: ["USD"].
{
"outcome": "matched",
"canonical_id": "jungle-064-pikachu-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "64",
"number_display": "64",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"image_url_absolute": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"display_name": "Pikachu",
"edition": "1st",
"match": {
"status": "exact",
"confidence": "exact",
"reason_codes": [
"set_slug_exact",
"collector_number_exact"
]
},
"confidence": "exact",
"candidate_ref": "c1_<opaque-reference-from-response>",
"other_printings": [],
"pricing": {
"segment": "psa_10",
"snapshot": null,
"reason_code": "no_pricing_for_segment"
}
}6. POST /api/v0/price/batch
Batch is live. Send references with 1–50 items. Each item accepts the same dimensions as price plus optional request_id(maximum 128 characters, echoed) and per-item candidate_ref.
- Input order and count are preserved; results carry zero-based
index. - Each item resolves independently. A domain-invalid item is a per-item outcome; it does not fail valid siblings.
- A structurally invalid body or item fails the whole request with 422.
- More than 50 references fails with 400
batch_too_largebefore lookup. - Quota cost is N references, reserved all-or-nothing before resolution. A refused batch consumes nothing.
- Identical references are resolved once and fanned out; each item still appears in its original position.
{
"references": [
{
"request_id": "direct-match",
"tcg": "pokemon",
"set": "jungle",
"number": "64",
"segment": "raw_nm",
"currency": "usd"
},
{
"request_id": "needs-selection",
"tcg": "pokemon",
"set": "jungle",
"number": "54"
},
{
"request_id": "bad-reference",
"tcg": "pokemon",
"set": "jungle",
"number": "54",
"candidate_ref": "c1_<unknown-opaque-reference>"
}
]
}{
"outcome": "batch",
"count": 3,
"generated_at": "2026-07-31T18:00:00Z",
"results": [
{
"index": 0,
"request_id": "direct-match",
"etag": "\"0123456789abcdef\"",
"outcome": "matched",
"canonical_id": "jungle-064-pikachu-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "64",
"number_display": "64",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"image_url_absolute": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"display_name": "Pikachu",
"edition": "1st",
"match": {
"status": "exact",
"confidence": "exact",
"reason_codes": [
"set_slug_exact",
"collector_number_exact"
]
},
"confidence": "exact",
"candidate_ref": "c1_<opaque-reference-from-response>",
"other_printings": [],
"pricing": {
"segment": "raw_nm",
"as_of": "2026-07-30T18:00:00Z",
"snapshot": {
"latest": "30.00",
"median": "20.00",
"count": 3,
"currency": "USD"
},
"freshness": {
"state": "fresh",
"age_days": 1,
"fresh_max_age_days": 7,
"stale_after_days": 21
},
"segment_basis": "strict_segment",
"reason_code": null
}
},
{
"index": 1,
"request_id": "needs-selection",
"etag": "\"123456789abcdef0\"",
"outcome": "ambiguous",
"candidates": [
{
"canonical_id": "jungle-054-jigglypuff-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "54",
"number_display": "54",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": null,
"display_name": "Jigglypuff",
"edition": "1st",
"variant": "1st",
"candidate_ref": "c1_<first-opaque-reference>",
"variant_is_unique": false,
"image_url_absolute": null
},
{
"canonical_id": "jungle-054-jigglypuff-unlimited",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "54",
"number_display": "54",
"variant": "unlimited",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": null,
"display_name": "Jigglypuff",
"edition": "unlimited",
"variant": "unlimited",
"candidate_ref": "c1_<second-opaque-reference>",
"variant_is_unique": true,
"image_url_absolute": null
}
]
},
{
"index": 2,
"request_id": "bad-reference",
"etag": "\"23456789abcdef01\"",
"outcome": "not_found",
"reason_code": "unknown_candidate_ref",
"message": "unknown_candidate_ref"
}
]
}Each result is a normal price outcome plus index,request_id, and its quoted etag. The batch-levelgenerated_at is assembly time; price time remainspricing.as_of per item.
7. GET /api/v0/price/history
History returns derived aggregates, never raw sale rows. Identity must resolve to one card and segment is required.
| Parameter | Required/default | Contract |
|---|---|---|
tcg, set, number | required | Same resolution rules as price. |
segment | required | One supported raw or PSA segment. |
currency | usd | USD only. |
variant, language, candidate_ref | optional | Same identity semantics as price. |
start | end minus 90 days | Inclusive ISO date. |
end | today | Inclusive ISO date; future input clamps to today. |
interval | week | day, week, month. |
limit | 400 | 1–400 points. |
cursor | optional | Exclusive: return buckets after this prior interval_start. |
The inclusive range may span at most 366 days. Points are ascending. Missing intervals are omitted, not zero-filled or forward-filled. An empty series returns points: [], point_count: 0,series_freshness: null, and next_cursor: null.
An ambiguous history reference returns the candidates plus message: history_requires_unambiguous_reference. Copy a candidate ref into the same history request to obtain the series.
curl --get \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "tcg=pokemon" \
--data-urlencode "set=jungle" \
--data-urlencode "number=64" \
--data-urlencode "segment=raw_nm" \
--data-urlencode "start=2026-06-01" \
--data-urlencode "end=2026-07-31" \
--data-urlencode "interval=week" \
--data-urlencode "limit=2" \
"https://api.jkindex.io/api/v0/price/history"{
"outcome": "history",
"canonical_id": "jungle-064-pikachu-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "64",
"number_display": "64",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"segment": "raw_nm",
"currency": "USD",
"interval": "week",
"start": "2026-06-01",
"end": "2026-07-31",
"points": [
{
"interval_start": "2026-07-06",
"median": "20.00",
"latest": "30.00",
"observation_count": 3
},
{
"interval_start": "2026-07-13",
"median": "22.00",
"latest": "24.00",
"observation_count": 2
}
],
"point_count": 2,
"missing_intervals_policy": "omitted",
"series_freshness": {
"state": "fresh",
"age_days": 1,
"fresh_max_age_days": 7,
"stale_after_days": 21,
"last_sale_at": "2026-07-30T18:00:00Z"
},
"next_cursor": "2026-07-13",
"retention": "full_source_history"
}curl --get \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "tcg=pokemon" \
--data-urlencode "set=jungle" \
--data-urlencode "number=64" \
--data-urlencode "segment=raw_nm" \
--data-urlencode "start=2026-06-01" \
--data-urlencode "end=2026-07-31" \
--data-urlencode "interval=week" \
--data-urlencode "limit=2" \
--data-urlencode "cursor=2026-07-13" \
"https://api.jkindex.io/api/v0/price/history"{
"outcome": "history",
"canonical_id": "jungle-064-pikachu-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "64",
"number_display": "64",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"segment": "raw_nm",
"currency": "USD",
"interval": "week",
"start": "2026-06-01",
"end": "2026-07-31",
"points": [
{
"interval_start": "2026-07-20",
"median": "25.00",
"latest": "27.00",
"observation_count": 2
}
],
"point_count": 1,
"missing_intervals_policy": "omitted",
"series_freshness": {
"state": "fresh",
"age_days": 1,
"fresh_max_age_days": 7,
"stale_after_days": 21,
"last_sale_at": "2026-07-30T18:00:00Z"
},
"next_cursor": null,
"retention": "full_source_history"
}8. Handle freshness, unavailable pricing, and images
A priced response carries as_of, a snapshot, andfreshness with state, age_days,fresh_max_age_days, and stale_after_days.fresh is at or below the first threshold,aging is between thresholds, and stale is older. Stale pricing remains served and is labeled; it is never presented as current.
{
"outcome": "matched",
"canonical_id": "jungle-064-pikachu-1st",
"identity": {
"tcg": "pokemon",
"set_slug": "jungle",
"set_code": "JNG",
"number_norm": "64",
"number_display": "64",
"variant": "1st",
"language": "en",
"set_alias": null,
"matched_set_alias": null
},
"image_url": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"image_url_absolute": "https://cdn.jkindex.io/cards/jungle/64-1st.webp",
"display_name": "Pikachu",
"edition": "1st",
"match": {
"status": "exact",
"confidence": "exact",
"reason_codes": [
"set_slug_exact",
"collector_number_exact"
]
},
"confidence": "exact",
"candidate_ref": "c1_<opaque-reference-from-response>",
"other_printings": [],
"pricing": {
"segment": "raw_nm",
"as_of": "2026-05-31T18:00:00Z",
"snapshot": {
"latest": "30.00",
"median": "20.00",
"count": 3,
"currency": "USD"
},
"freshness": {
"state": "stale",
"age_days": 61,
"fresh_max_age_days": 7,
"stale_after_days": 21
},
"segment_basis": "strict_segment",
"reason_code": null
}
}No qualifying observation is different from stale data: no_pricing_for_segment has a null snapshot and noas_of or freshness. History freshness describes the newest sale across the whole requested series, not only the current page.
image_url is the stored value and may be null.image_url_absolute passes an absolute URL through, joins a configured asset base only for supported relative paths, and otherwise remains null. Render these values as returned; never invent a card-image host or path.
9. Cache with ETag and conditional 304
| Route | Cache-Control | If-None-Match |
|---|---|---|
GET /api/v0/price | public, max-age=300 | 304 supported |
POST /api/v0/price/batch | public, max-age=300 | 304 supported |
GET /api/v0/price/history | public, max-age=900 | 304 supported |
GET /api/v0/health | private, max-age=30 | Not supported; always 200 when authenticated |
ETags are quoted opaque values. Cache keys must include every request dimension, including TCG, set form, number form, variant, language, segment, currency, candidate ref, and history page dimensions. Never construct or parse an ETag.
curl --get \
-H "Authorization: Bearer YOUR_API_KEY" \
-H 'If-None-Match: "0123456789abcdef"' \
--data-urlencode "tcg=pokemon" \
--data-urlencode "set=jungle" \
--data-urlencode "number=64" \
--data-urlencode "segment=raw_nm" \
"https://api.jkindex.io/api/v0/price"
# Unchanged representation:
HTTP/1.1 304 Not Modified
ETag: "0123456789abcdef"
Cache-Control: public, max-age=300
X-Request-Id: 12345678-1234-1234-1234-123456789abc10. Handle errors, retries, and rate limits
| HTTP | Meaning | Retry |
|---|---|---|
| 200 | Domain envelope: matched, ambiguous, not_found, batch, history, or health. | Branch on the body; do not retry a deterministic reason unchanged. |
| 304 | Cached price, batch, or history representation is current; empty body. | Use the cached body. |
| 400 | Batch exceeds 50 items. | Split the batch. |
| 401 | Missing, invalid, inactive, or revoked key. | Stop and correct credentials; do not loop. |
| 422 | FastAPI request validation failed. | Correct the request shape. |
| 429 | Per-minute or rolling-30-day per-key limit refused the request. | Honor Retry-After; refused work consumes nothing. |
| 503 | quota_unavailable: usage limits could not be confirmed. | Honor Retry-After; no pricing work runs. An uncertain reservation may have consumed capacity. |
| 5xx | Unexpected service failure. | Bounded exponential backoff with jitter; include request ID if persistent. |
The supported V0 application does not emit an application-level 403. A 403 from an upstream edge or unusual client is outside the JSON contract; record the body, headers, client, UTC timestamp, and request ID rather than treating it as a bad key.
Stable public headers are X-Request-Id on every response,ETag and Cache-Control on successful domain responses, and Retry-After on 429. There are no promisedX-RateLimit-Remaining headers.
{
"error": "unauthorized",
"message": "Missing or invalid API key"
}{
"detail": [
{
"type": "missing",
"loc": [
"query",
"tcg"
],
"msg": "Field required",
"input": null
}
]
}HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-Request-Id: 12345678-1234-1234-1234-123456789abc
{
"error": "rate_limited",
"message": "Too many requests"
}GET /api/v0/health
This route requires the same bearer key and does not consume the pricing rate limit. It always returns HTTP 200 after authentication with status, UTC time, deployed version, and checks.
{
"status": "ok",
"time": "2026-07-31T18:00:00Z",
"version": "0123456789ab",
"checks": {
"db": "ok",
"fx_table": "ok",
"fx_rate_usd_cad": "ok"
}
}checks.db is relevant to request serviceability. Thefx_table and fx_rate_usd_cad entries are operator diagnostics retained in the health envelope; they do not enable or imply consumer currency conversion. USD remains the only supported currency.
The public website status page calls unauthenticated readiness and checks route presence. It is not the keyed health response and does not spend your key quota.
Coverage and catalog discovery are excluded
GET /api/v0/coverage is not registered, is absent from public OpenAPI, and returns the standard 404 {"detail":"Not Found"}. The beta does not enumerate every supported set or card and does not promise an exact live coverage summary. Consumers provide known identifiers. A per-request not_found or no_pricing_for_segmentresult must not be generalized to the catalog.
Current changelog
- 2026-07-31 — Consumer documentation parity. All four live routes, exact response schemas, bearer security, batch/history workflows, caching, errors, and public examples aligned with runtime.
- 2026-07-31 — Controlled beta without discovery. USD known-card resolution and pricing remain supported; coverage and catalog enumeration are excluded.
- 2026-07-29 — USD beta closure. Non-USD fails closed; candidate refs complete ambiguity selection; unknown refs do not fall back; matched results disclose other printings.
- 2026-07-28 — Consumer-readiness additions. One Piece prefixed/bare-number corrections, variant detail, confidence correction, truthful image URL handling, freshness labels, ETag/304, batch, history, authentication, and quota hardening.
Support checklist
Report the exact request without its key, full response, UTC timestamp, X-Request-Id, and ETag. Load, soak, stress, or high-concurrency testing requires prior written authorization with an explicit request budget, concurrency cap, time window, and stop conditions.