Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Hobby Card Index Data Prices API
version: '1'
summary: Real sold prices, photo identification, cert lookups, pop reports, sold sales and price history for sports cards and trading cards.
description: 'Real sold prices for sports cards and trading cards: a catalog of more than 16 million cards across 25-plus categories (baseball, basketball, football, hockey, soccer, Pokemon, Magic: The Gathering, Yu-Gi-Oh, One Piece and more) with more than 22 million raw and graded prices, refreshed daily, plus card identification from a photo, PSA, BGS, CGC and SGC cert lookups, population reports, verified sold-sale records, daily price history, set lists, market movers, a price watch list with a daily webhook, and bulk price files.
## Keys and plans
Every endpoint needs an API key, sent as the `X-API-Key` header (or `Authorization: Bearer`). Keys come from Settings, API Access, after subscribing. An **Enterprise** plan ($49.99 a month) includes a key for your own business''s internal use: 1 request a second, up to 100,000 a day. Apps, websites and products whose users see the data need a **Commercial plan** (a commercial data license, Terms of Service section 8):
- Lite (plan id `lite`): $99 a month; 25,000 requests, 25,000 photo scans, 5,000 sold-sale lookups, 150 cert lookups, 10 lookups a second, price history.
- Growth Lite (plan id `growth_lite`): $249 a month; 125,000 requests, 125,000 photo scans, 25,000 sold-sale lookups, 750 cert lookups, 15 lookups a second, price history.
- Growth (plan id `growth`): $499 a month; 500,000 requests, 500,000 photo scans, 100,000 sold-sale lookups, 3,000 cert lookups, 20 lookups a second, price history.
- Platform (plan id `platform`): from $1,500 a month; 2,000,000 requests, 1,500,000 photo scans, 500,000 sold-sale lookups, 50,000 cert lookups, 50 lookups a second, price history, bulk price files.
A **free setup month** is by application in Settings, API Access (one per company): enough to build and test against real data, with daily pools. Plans, prices and how to buy, including how an AI agent can buy a plan for its user with no account (GET /api/v1/agent/plans, POST /api/v1/agent/checkout): https://hobbycardindex.com/developers#commercial-plans. Questions or a bigger deal: sean@hobbycardindex.com.
## Credit the data (required)
Commercial plans ask for one link back to https://hobbycardindex.com somewhere on your site, such as ''Prices and API supplied by Hobby Card Index''; it does not need to be beside the prices. Every successful JSON object response carries an `attribution` object (text, url, html, required, terms) and an `X-Attribution` header with the same credit, so it can be rendered straight from the response. Put the credit beside the prices or anywhere on your website, such as the footer. Snippet: `Prices from Hobby Card Index`.
## Conventions
- Base URL `https://hobbycardindex.com`; every path starts with `/api/v1/`. Prices are US dollars with two decimals; a grade with no price is `null`. Grade keys: `loose` (raw), `psa10`, `psa9`, `psa8`, `psa7`, `bgs10` (BGS 9.5), `cgc10`, `sgc10`.
- Successful responses are `{ "success": true, "data": { ... }, "attribution": { ... } }`; errors are `{ "success": false, "error": { "code", "message", "details"? } }` with the HTTP status that fits (400, 401, 402, 403, 404, 409, 413, 429, 500, 503). Streamed downloads (CSV, bulk files, the watch-list prices) are not wrapped.
- Card ids: pass the integer `id` every response carries, or `ext-` plus the card''s `externalId`. Picture links (`imageUrl`) go through our image proxy and need no key; a link can answer 404 later if the picture is withdrawn, so show a placeholder on a 404.
- Rate limits: 1 request a second on an Enterprise key, the plan''s lookups a second on a commercial key, with `RateLimit-*` and `X-RateLimit-*` headers on the one-second window. Commercial plans also return `X-HCI-Plan` and `X-HCI-*-Used` / `X-HCI-*-Allowance` headers for the monthly counters.
- The same data through an MCP server for AI assistants: POST https://hobbycardindex.com/api/v1/mcp, manifest https://hobbycardindex.com/.well-known/mcp.json.'
termsOfService: https://hobbycardindex.com/terms/
contact:
name: Sean Charman, Hobby Card Index
email: sean@hobbycardindex.com
url: https://hobbycardindex.com/developers
license:
name: Hobby Card Index Terms of Service (section 8, Commercial Data License)
url: https://hobbycardindex.com/terms/#data-license
x-logo:
url: https://hobbycardindex.com/hci-logo2.png
altText: Hobby Card Index
href: https://hobbycardindex.com
x-attribution:
text: Prices from Hobby Card Index
url: https://hobbycardindex.com
html: <a href="https://hobbycardindex.com" rel="noopener">Prices from Hobby Card Index</a>
required: true
terms: https://hobbycardindex.com/terms/
servers:
- url: https://hobbycardindex.com
description: Production
security:
- ApiKeyAuth: []
tags:
- name: Prices
description: Current prices by grade, search, and daily price history.
paths:
/api/v1/prices/search:
get:
tags:
- Prices
summary: Search cards and their current prices by grade
operationId: searchPrices
description: 'A plain `q` search returns the 10 best matches with every grade price: base cards first, then parallels, the most traded first within each. Any filter turns it into a paged, filtered search (25 a page by default) with `hasMore`. To list every card in one set, send `category` and `set` (a set id from GET /api/v1/sets) with no `q`; to get only cards added since a date, send `added_since`. Prices are real sold prices in US dollars. Every plan; one request.'
parameters:
- name: q
in: query
required: false
description: Search text, 2 to 200 characters, for example `Mike Trout 2011`. Required unless `set` or `added_since` is given.
schema:
type: string
minLength: 2
maxLength: 200
- name: category
in: query
required: false
description: A category id from GET /api/v1/market/categories, for example `pokemon` or `baseball`.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9-]{0,39}$
- name: set
in: query
required: false
description: A set id from GET /api/v1/sets. Needs `category`. Results come in card-number order.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9-]{0,79}$
- name: language
in: query
required: false
description: The language printed on the card.
schema:
type: string
enum:
- english
- japanese
- chinese
- korean
- german
- french
- spanish
- italian
- portuguese
- dutch
- thai
- indonesian
- russian
- polish
- name: year
in: query
required: false
description: Card year, four digits.
schema:
type: integer
minimum: 1850
maximum: 2100
- name: added_since
in: query
required: false
description: Only cards added to our catalog since this date or time (UTC), for example `2026-10-01` or `2026-10-01T12:00:00Z`. Results come in the order they were added. It can reach back up to 90 days.
schema:
type: string
- name: min_price
in: query
required: false
description: Lowest raw (ungraded) price, US dollars.
schema:
type: number
minimum: 0
maximum: 10000000
- name: max_price
in: query
required: false
description: Highest raw (ungraded) price, US dollars.
schema:
type: number
minimum: 0
maximum: 10000000
- name: sort
in: query
required: false
description: Order of a filtered search.
schema:
type: string
enum:
- price_asc
- price_desc
- volume_desc
- name_asc
- added_asc
- added_desc
- name: limit
in: query
required: false
description: Page size, 1 to 25. Default 25.
schema:
type: integer
minimum: 1
maximum: 25
default: 25
- name: offset
in: query
required: false
description: How many to skip. A `q` search pages through its best matches (up to the search ceiling); a set, or `added_since` without `q`, pages through every card, up to 100,000.
schema:
type: integer
minimum: 0
default: 0
responses:
'200':
description: Matching cards
headers:
X-Attribution:
$ref: '#/components/headers/X-Attribution'
X-HCI-Plan:
$ref: '#/components/headers/X-HCI-Plan'
X-HCI-Requests-Used:
$ref: '#/components/headers/X-HCI-Requests-Used'
X-HCI-Requests-Allowance:
$ref: '#/components/headers/X-HCI-Requests-Allowance'
RateLimit-Limit:
$ref: '#/components/headers/RateLimit-Limit'
RateLimit-Remaining:
$ref: '#/components/headers/RateLimit-Remaining'
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
const: true
data:
$ref: '#/components/schemas/SearchResponse'
attribution:
$ref: '#/components/schemas/Attribution'
required:
- success
- data
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/ServerError'
security:
- ApiKeyAuth: []
/api/v1/prices/{cardId}:
get:
tags:
- Prices
summary: One card's prices at every grade
operationId: getCardPrices
description: The card and its current prices by grade (raw, PSA 10, 9, 8 and 7, BGS 9.5, CGC 10, SGC 10) in US dollars, with recent sales volume and when the prices were last refreshed. Numbered parallels carry `printRun`. `imageUrl` is the card's picture through our image link (no key needed). Every plan; one request.
parameters:
- $ref: '#/components/parameters/cardId'
responses:
'200':
description: The card and its prices
headers:
X-Attribution:
$ref: '#/components/headers/X-Attribution'
X-HCI-Plan:
$ref: '#/components/headers/X-HCI-Plan'
X-HCI-Requests-Used:
$ref: '#/components/headers/X-HCI-Requests-Used'
X-HCI-Requests-Allowance:
$ref: '#/components/headers/X-HCI-Requests-Allowance'
RateLimit-Limit:
$ref: '#/components/headers/RateLimit-Limit'
RateLimit-Remaining:
$ref: '#/components/headers/RateLimit-Remaining'
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
const: true
data:
$ref: '#/components/schemas/CardPricesResponse'
attribution:
$ref: '#/components/schemas/Attribution'
required:
- success
- data
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/ServerError'
security:
- ApiKeyAuth: []
/api/v1/prices/{cardId}/history:
get:
tags:
- Prices
summary: Daily price history at every grade
operationId: getCardPriceHistory
description: The card's price at every grade, day by day, for charts. A grade with no price on a day is null, never 0; days we hold no prices for are not in the list. Individual sales are in /sales. Commercial plans with price history (every size), signed deals and admins; other keys get 403 REQUIRES_GROWTH. One request.
parameters:
- $ref: '#/components/parameters/cardId'
- name: days
in: query
required: false
description: How far back, 1 to 3,650 days. Default 365.
schema:
type: integer
minimum: 1
maximum: 3650
default: 365
responses:
'200':
description: Daily prices
headers:
X-Attribution:
$ref: '#/components/headers/X-Attribution'
X-HCI-Plan:
$ref: '#/components/headers/X-HCI-Plan'
X-HCI-Requests-Used:
$ref: '#/components/headers/X-HCI-Requests-Used'
X-HCI-Requests-Allowance:
$ref: '#/components/headers/X-HCI-Requests-Allowance'
RateLimit-Limit:
$ref: '#/components/headers/RateLimit-Limit'
RateLimit-Remaining:
$ref: '#/components/headers/RateLimit-Remaining'
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
const: true
data:
$ref: '#/components/schemas/HistoryResponse'
attribution:
$ref: '#/components/schemas/Attribution'
required:
- success
- data
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
$ref: '#/components/responses/ServerError'
security:
- ApiKeyAuth: []
components:
schemas:
SearchResponse:
type: object
properties:
results:
type: array
items:
$ref: '#/components/schemas/SearchResult'
count:
type: integer
description: How many results are in this response.
limit:
type: integer
description: 'Filtered searches only: the page size.'
offset:
type: integer
description: 'Filtered searches only: how many were skipped.'
hasMore:
type: boolean
description: 'Filtered searches only: whether another page exists.'
required:
- results
- count
Attribution:
type: object
properties:
text:
type: string
const: Prices from Hobby Card Index
url:
type: string
format: uri
const: https://hobbycardindex.com
html:
type: string
const: <a href="https://hobbycardindex.com" rel="noopener">Prices from Hobby Card Index</a>
description: 'Ready to paste: the credit as a link.'
required:
type: boolean
const: true
terms:
type: string
format: uri
const: https://hobbycardindex.com/terms/
required:
- text
- url
- html
- required
- terms
description: 'The credit this data must carry wherever it is shown: a visible "Prices from Hobby Card Index" link to https://hobbycardindex.com. Added to every successful JSON object response (also sent as the X-Attribution header).'
SearchResult:
type: object
properties:
id:
type: integer
description: The catalog id. Pass it to GET /api/v1/prices/{cardId} and the other card endpoints.
externalId:
type:
- string
- 'null'
description: Secondary id; the card endpoints also accept `ext-<externalId>`.
name:
type: string
description: Card name; a parallel is named in brackets.
year:
type:
- integer
- string
- 'null'
setName:
type:
- string
- 'null'
description: Set name.
setId:
type:
- string
- 'null'
description: The set id GET /api/v1/sets lists and `set=` takes.
cardNumber:
type:
- string
- 'null'
description: The number printed on the card.
printRun:
type:
- integer
- 'null'
description: For numbered cards, the print run (50 for a card numbered to /50); null when not numbered or unknown.
category:
type:
- string
- 'null'
description: Category.
playerName:
type:
- string
- 'null'
description: Player or character.
imageUrl:
type:
- string
- 'null'
description: The card's picture through our image link, or null when we have no usable picture. The link needs no key; it can answer 404 later if the picture is withdrawn.
format: uri
addedAt:
type:
- string
- 'null'
format: date-time
description: When the card joined our catalog.
prices:
oneOf:
- $ref: '#/components/schemas/SearchPrices'
- type: 'null'
hciUrl:
type:
- string
- 'null'
description: The card's page on hobbycardindex.com, with recent sales and price history.
format: uri
required:
- id
- name
Card:
type: object
properties:
id:
type: integer
externalId:
type:
- string
- 'null'
description: Secondary id; also accepted as `ext-<externalId>`.
name:
type: string
description: Card name; a parallel is named in brackets.
year:
type:
- integer
- string
- 'null'
setName:
type:
- string
- 'null'
description: Set name.
cardNumber:
type:
- string
- 'null'
description: The number printed on the card.
printRun:
type:
- integer
- 'null'
description: Print run for numbered cards; null otherwise.
category:
type:
- string
- 'null'
description: Category.
playerName:
type:
- string
- 'null'
description: Player or character.
imageUrl:
type:
- string
- 'null'
description: Picture through our image link, or null.
format: uri
hciUrl:
type:
- string
- 'null'
description: The card's page on hobbycardindex.com.
format: uri
required:
- id
- name
CardRef:
type: object
properties:
id:
type: integer
description: The catalog id. Pass it to the other card endpoints.
externalId:
type:
- string
- 'null'
description: Our catalog's secondary id for the card. Accepted by the card endpoints as `ext-<externalId>`.
name:
type: string
description: 'The card name, with the parallel in brackets when it is not the base card, for example `Mike Trout [Gold] #US175`.'
year:
type:
- integer
- string
- 'null'
description: The year the card came out (for Pokemon, the year its set was released); null when unknown.
setName:
type:
- string
- 'null'
description: The set the card belongs to.
category:
type:
- string
- 'null'
description: The category, for example Baseball or Pokemon.
playerName:
type:
- string
- 'null'
description: The player or character on the card.
required:
- id
- name
description: The card block the sales, listings, history and population endpoints return.
CardPricesResponse:
type: object
properties:
card:
$ref: '#/components/schemas/Card'
prices:
oneOf:
- $ref: '#/components/schemas/Prices'
- type: 'null'
description: null when we hold no price for this card.
required:
- card
- prices
SearchPrices:
allOf:
- $ref: '#/components/schemas/PriceGrid'
- type: object
properties:
volume:
type:
- integer
- 'null'
description: Recent sales volume, a count.
fromSales:
type: array
items:
type: string
description: Grades whose price came from our own recent sold sales.
description: The price block of a search result. null on the whole block when the card has no price at any grade, and on every result for keys on the pop + certs plan.
HistoryResponse:
type: object
properties:
card:
$ref: '#/components/schemas/CardRef'
days:
type: integer
description: The window that was applied.
history:
type: array
items:
$ref: '#/components/schemas/HistoryPoint'
description: Days we hold prices for, so dates can skip.
soldByGrade:
type: object
additionalProperties: true
description: 'Only with soldByGrade=1. Sold prices at every grade we have sales for, half grades and every grading company included, from our own verified sales. Keyed by grade ("PSA 8.5", "BGS 9", "Raw"), newest first: [{ date, price (the middle sale that day), sales }]. A day with no sale has no point.'
count:
type: integer
required:
- card
- days
- history
- count
Prices:
allOf:
- $ref: '#/components/schemas/PriceGrid'
- type: object
properties:
volume:
type:
- integer
- 'null'
description: Recent sales volume, a count.
updated:
type:
- string
- 'null'
format: date-time
description: When this card's prices were last refreshed.
fromSales:
type: array
items:
type: string
description: Grades (keys of this object) whose price came from our own recent sold sales because no other price was on file for that grade.
description: 'The price block of GET /api/v1/prices/{cardId}: the grade grid plus volume and the last update.'
HistoryPoint:
allOf:
- $ref: '#/components/schemas/PriceGrid'
- type: object
properties:
date:
type: string
format: date
description: The day, UTC.
volume:
type:
- integer
- 'null'
description: Sales volume that day, a count (0 is a real 0).
required:
- date
description: One day's prices. A grade with no price that day is null, never 0.
PriceGrid:
type: object
properties:
loose:
type:
- number
- 'null'
description: raw (ungraded) price in US dollars; null when there is no price at that grade.
psa10:
type:
- number
- 'null'
description: PSA 10 price in US dollars; null when there is no price at that grade.
psa9:
type:
- number
- 'null'
description: PSA 9 price in US dollars; null when there is no price at that grade.
psa8:
type:
- number
- 'null'
description: PSA 8 price in US dollars; null when there is no price at that grade.
psa7:
type:
- number
- 'null'
description: PSA 7 price in US dollars; null when there is no price at that grade.
bgs10:
type:
- number
- 'null'
description: BGS 9.5 (BGS 10 when there is no 9.5 price) price in US dollars; null when there is no price at that grade.
cgc10:
type:
- number
- 'null'
description: CGC 10 price in US dollars; null when there is no price at that grade.
sgc10:
type:
- number
- 'null'
description: SGC 10 price in US dollars; null when there is no price at that grade.
description: Current prices by grade, in US dollars with two decimals. A grade with no price is null; `bgs10` holds the BGS 9.5 price.
Error:
type: object
properties:
success:
type: boolean
const: false
description: false on every error the API itself sends. The per-second rate limiter's 429 body has only `error`.
error:
type: object
properties:
code:
type: string
description: Machine-readable code, for example VALIDATION_ERROR, INVALID_PARAM, MISSING_API_KEY, INVALID_API_KEY, KEY_REVOKED, PLAN_REQUIRED, TRIAL_ENDED, NOT_ON_PLAN, REQUIRES_COMMERCIAL_LICENSE, REQUIRES_GROWTH, REQUIRES_PARTNER_PLAN, RATE_LIMITED, MONTHLY_LIMIT_REACHED, DAILY_LIMIT_REACHED, NOT_FOUND, SERVER_ERROR.
message:
type: string
description: What went wrong and, where there is one, what to do about it.
details:
type: object
additionalProperties: true
description: 'Extra fields on some errors: plans_url, upgrade_url, limit, allowance, plan, upgrade.'
required:
- code
- message
required:
- error
description: The error envelope from every endpoint.
headers:
X-Attribution:
description: 'Always "Prices from Hobby Card Index (https://hobbycardindex.com)" on a successful JSON object response: the credit the data must carry.'
schema:
type: string
X-HCI-Requests-Used:
description: 'Commercial plans only: requests used this month, after this one.'
schema:
type: integer
X-HCI-Requests-Allowance:
description: 'Commercial plans only: the plan''s monthly requests.'
schema:
type: integer
RateLimit-Remaining:
description: Requests left in the current one-second window.
schema:
type: integer
RateLimit-Limit:
description: Requests allowed in the current one-second window (X-RateLimit-* are sent too).
schema:
type: integer
X-HCI-Plan:
description: 'Commercial plans only: the plan id behind this key.'
schema:
type: string
parameters:
cardId:
name: cardId
in: path
required: true
description: The card's `id` from search, scan, movers or watch-list results (an integer), or `ext-` followed by its `externalId` (for example `ext-67890`). Letters, digits, dashes, underscores and dots, up to 150 characters.
schema:
type: string
maxLength: 150
pattern: ^[a-zA-Z0-9\-_.]+$
responses:
NotFound:
description: 'NOT_FOUND: no card (or file) matches.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PaymentRequired:
description: 'TRIAL_ENDED: the free setup month is over and the first month of the plan is not paid yet.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: 'The key''s plan does not include this: PLAN_REQUIRED, DEALER_KEY_SCOPE, NOT_ON_PLAN, REQUIRES_COMMERCIAL_LICENSE, REQUIRES_GROWTH, REQUIRES_PARTNER_PLAN, REQUIRES_PLATFORM_PLAN, REQUIRES_PRICE_FILES, REQUIRES_ENTERPRISE, LISTINGS_NOT_ENABLED. The message says which plan has it and `details.plans_url` where to get it.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
TooManyRequests:
description: RATE_LIMITED (the per-second window; slow down and retry), RATE_LIMIT_EXCEEDED (the daily ceiling), MONTHLY_LIMIT_REACHED, DAILY_LIMIT_REACHED, SALES_LIMIT_REACHED, CERT_LIMIT_REACHED, POP_LIMIT_REACHED, LISTINGS_LIMIT_REACHED, SCAN_ENGINE_BUSY (Retry-After says when).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
ServerError:
description: 'SERVER_ERROR (500 or 503): something failed on our side; retry in a minute.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: 'The request is not valid: VALIDATION_ERROR, INVALID_PARAM, BAD_REQUEST, SEARCH_TOO_BROAD, NO_IMAGE, INVALID_IMAGE.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: No key (MISSING_API_KEY), a key we do not know (INVALID_API_KEY) or one that was revoked (KEY_REVOKED).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: 'Your HobbyCardIndex API key (hci_live_...). `Authorization: Bearer <key>` is accepted too. Keys: https://hobbycardindex.com/developers.'
externalDocs:
description: Developer documentation with examples in curl, JavaScript and Python
url: https://hobbycardindex.com/developers