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 slabs and certs 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: slabs and certs
description: Cert lookups and population reports.
paths:
/api/v1/prices/{cardId}/population:
get:
tags:
- slabs and certs
summary: Population report for one card
operationId: getCardPopulation
description: How many copies PSA, BGS, SGC and CGC have graded, grade by grade, with gem counts and gem rate. We hold a stored report for many cards; a card with none, or one over 30 days old, is fetched live within a daily budget. Platform, or Lite, Growth Lite and Growth with the pop + cert add-on. Pop reports have their own monthly number and stop there (429 POP_LIMIT_REACHED); each call also counts as a request.
parameters:
- $ref: '#/components/parameters/cardId'
responses:
'200':
description: The population report, or null when we hold none
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'
X-HCI-Pop-Used:
description: Pop reports used this month.
schema:
type: integer
X-HCI-Pop-Allowance:
description: The plan's monthly pop reports.
schema:
type: integer
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
const: true
data:
$ref: '#/components/schemas/PopulationResponse'
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/certs/{grader}/{certNumber}:
get:
tags:
- slabs and certs
summary: Look up a graded card by cert number
operationId: lookupCert
description: 'The grading company''s record for the cert (grade, autograph grade, player, year, set, card number and population when the grader reports it), the matching card in our catalog with its prices at every grade, and the `slab` block: the price at that exact grade, whether the cert verified, the grader''s own cert page (`verifyUrl`) and their slab photos when they have them. Every commercial size has a monthly number of cert lookups (hard stop, 429 CERT_LIMIT_REACHED); one lookup a second per key. A bad request or a busy day is not counted.'
parameters:
- name: grader
in: path
required: true
description: psa, bgs, cgc or sgc.
schema:
type: string
enum:
- psa
- bgs
- cgc
- sgc
- name: certNumber
in: path
required: true
description: The cert number printed on the slab label.
schema:
type: string
minLength: 1
maxLength: 25
responses:
'200':
description: The cert, the card and the slab block
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'
X-HCI-Certs-Used:
description: Cert lookups used this month.
schema:
type: integer
X-HCI-Certs-Allowance:
description: The plan's monthly cert lookups.
schema:
type: integer
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
const: true
data:
$ref: '#/components/schemas/CertResponse'
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:
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
schemas:
Slab:
type: object
properties:
grader:
type:
- string
- 'null'
description: PSA, BGS, CGC, SGC, TGA or null.
grade:
type:
- number
- string
- 'null'
description: The numeric grade, `Authentic`, or null when unread.
autoGrade:
type:
- string
- 'null'
description: Autograph grade when the label carries one.
certNumber:
type:
- string
- 'null'
description: The cert number read from the label or the grader's record.
certVerified:
type: boolean
description: true when the grader's record for that cert was found and agrees with the label.
priceAtGrade:
type:
- number
- 'null'
description: The card's price at exactly this grade when we price that grade; null otherwise (never a neighbouring grade's price).
verifyUrl:
type:
- string
- 'null'
description: The grader's own page for the cert (PSA, CGC and TGA open the cert; Beckett and SGC open their lookup page).
format: uri
frontImageUrl:
type:
- string
- 'null'
description: The grader's front slab photo through our image link, when they have one.
format: uri
backImageUrl:
type:
- string
- 'null'
description: The grader's back slab photo through our image link, when they have one.
format: uri
required:
- certVerified
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).'
ScanCard:
type: object
properties:
id:
type:
- integer
- 'null'
description: The catalog id, or null when the match is not in our catalog yet.
externalId:
type:
- string
- 'null'
description: Secondary id.
name:
type:
- string
- 'null'
description: Card name; a parallel is named in brackets.
playerName:
type:
- string
- 'null'
description: Player or character.
year:
type:
- integer
- string
- 'null'
setName:
type:
- string
- 'null'
description: Set name.
cardNumber:
type:
- string
- 'null'
description: Card number.
printRun:
type:
- integer
- 'null'
description: Print run for numbered cards.
parallel:
type: string
description: The parallel, or `Base`.
category:
type:
- string
- 'null'
description: Category.
imageUrl:
type:
- string
- 'null'
description: Picture through our image link, or null.
format: uri
confidence:
type:
- number
- 'null'
description: 0 to 1, three decimals.
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.
Cert:
type: object
properties:
grader:
type:
- string
- 'null'
description: PSA, BGS, CGC or SGC.
certNumber:
type:
- string
- 'null'
description: The cert number.
grade:
type:
- string
- 'null'
description: The grade on the grader's record.
player:
type:
- string
- 'null'
description: Player or character on the record.
year:
type:
- string
- 'null'
description: Year on the record.
cardNumber:
type:
- string
- 'null'
description: Card number on the record.
additionalProperties: true
description: The grading company's record for the cert, with whatever fields the grader reports (set, autograph grade, population).
CertResponse:
type: object
properties:
cert:
oneOf:
- $ref: '#/components/schemas/Cert'
- type: 'null'
card:
oneOf:
- allOf:
- $ref: '#/components/schemas/ScanCard'
- type: object
properties:
prices:
oneOf:
- $ref: '#/components/schemas/Prices'
- type: 'null'
description: null for keys on the pop + certs plan.
- type: 'null'
description: The matching card in our catalog with its prices at every grade, or null when the record did not match a card.
slab:
oneOf:
- $ref: '#/components/schemas/Slab'
- type: 'null'
description: 'The slab block: grade, cert, whether it verified, price at that grade, the grader''s own cert page and slab photos.'
additionalProperties: true
PopulationResponse:
type: object
properties:
card:
$ref: '#/components/schemas/CardRef'
population:
oneOf:
- type: object
properties:
totalPopulation:
type:
- integer
- 'null'
description: All graders together.
totalGems:
type:
- integer
- 'null'
description: Gem copies across graders.
asOf:
type:
- string
- 'null'
format: date-time
description: When the report was taken.
lastChange:
type:
- object
- integer
- string
- 'null'
graders:
type: object
additionalProperties:
$ref: '#/components/schemas/GraderPopulation'
description: 'Keyed by grader: PSA, BGS, SGC, CGC.'
- type: 'null'
description: null when we do not hold a report for this card yet.
required:
- card
- population
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.'
GraderPopulation:
type: object
properties:
totalGraded:
type:
- integer
- 'null'
description: Copies this grader has graded.
gems:
type:
- integer
- 'null'
description: Copies at the gem grade.
gemGrade:
type:
- string
- 'null'
description: The grade counted as gem for this grader, for example 10 or 9.5.
gemRate:
type:
- number
- 'null'
description: gems / totalGraded as a percentage.
ladder:
type:
- object
- array
- 'null'
description: Counts grade by grade, as the grader reports them.
autoTotal:
type:
- integer
- 'null'
description: Autograph copies graded.
nonAutoTotal:
type:
- integer
- 'null'
description: Non-autograph copies graded.
lastChange:
type:
- object
- integer
- string
- 'null'
description: The most recent change we recorded for this grader.
additionalProperties: true
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.
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