Public API reference
Version 1 JSON API for TCG catalog data, authenticated image proxies, recent eBay sold comps, condition-level guide prices, and developer feedback. Replace {host} with your API host (e.g. api.vendortools.net).
Overview
All routes live under /api/public/v1/. The API is organized by game (e.g. pokemon, pokemon-japan): use the game's slug as the first path segment after v1.
Almost all v1 routes require an API key and count toward your monthly quota. Exceptions: GET /health is public (no key, no quota). POST …/price-reports uses a key but does not consume quota. POST …/refresh-pricing charges one monthly call when your plan includes market pricing (any paid tier — Starter BUILDER, Advanced PRO, ENTERPRISE), same billing weight as a typical catalog GET.
GET /games lists active games for Free and Starter. Advanced and Enterprise accounts also see in-progress games (isActive: false) in that list and can call every game-scoped v1 route by slug.
Authentication
Create API keys in the portal dashboard after signing in. Send the full secret in either header:
Authorization: Bearer <vt_live_…>X-API-Key: <vt_live_…>- Query string (for browser embeds such as
<img src>):?api_key=<vt_live_…>
Keys are scoped to your portal user; we store a hash of the secret, not the secret itself. Portal auth (cookies) for the website is separate from API keys.
Catalog images
Expansion covers, card images, and sealed product images are returned as URLs on this API (not direct CDN links). Each time a client loads one of those URLs counts as a normal API request toward your quota and is logged like other v1 calls.
For server-side or JavaScript clients, send Authorization or X-API-Key as usual. For plain HTML images, append ?api_key=… to the image URL (browsers do not send custom headers on <img> requests). Treat query strings as sensitive: depending on browser and referrer policy, the full URL (including the key) can appear in Referer headers on downstream requests.
Image proxy routes
These return the raw image bytes (not JSON). Each request is a normal v1 call (quota + logs). Use the same auth headers or ?api_key= as other routes.
GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}/image· API key required
Expansion cover from the catalogimageUrl.GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}/cards/{cardId}/image/{imageIndex}· API key required
Card art by zero-based index (matchesimageUrlsorder).400INVALID_IMAGE_INDEXwhen the index is not a non-negative integer.GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}/sealed/{sealedSlug}/image· API key required
Sealed product image for that slug.
404 with IMAGE_NOT_FOUND when there is no allowed upstream URL on file.
Plans & field tiers
Your key's subscriptionTier (see GET /me) controls which optional fields are populated. Enum values: FREE, BUILDER (Starter), PRO (Advanced), ENTERPRISE.
- Market pricing (
ebayLastSolds,conditionPrices, list?include=marketPricing): included for every non-Free tier. Omitted (empty arrays) onFREEeven when clients request pricing. Some games exclude card-level eBay pricing entirely (e.g. digital-only titles); for those, rows stay empty androutes.refreshPricingis not returned on card GETs. - AI catalog copy (
catalogCardDescription,enterpriseCatalogDescriptionon cards, and the same fields when included on expansion card lists): Advanced (PRO) andENTERPRISEonly.FREEand Starter (BUILDER) omit them. - On-demand refresh (
POST …/refresh-pricing): requires a non-Free tier; then consumes one monthly quota unit per successful refresh (may return429QUOTA_EXCEEDED). ServermaxDurationis five minutes — use a generous client timeout.
Sold rows include compTier (standard vs unverified sold time), saleFormat, optional auctionRegion, and GBP price / currency. Guide prices use non-outlier standard sales; each row carries isOutlier where applicable.
Errors & quotas
401— Missing or invalid API key (body often includesINVALID_API_KEY).404— Unknown game, expansion, card, or sealed product. Bodies often include acodesuch asGAME_NOT_FOUND,CARD_NOT_FOUND, etc.429— Monthly quota exceeded; look forQUOTA_EXCEEDED.400— Validation (e.g. price report JSON,INVALID_JSON,INVALID_BODY, or imageINVALID_IMAGE_INDEX).403— Not allowed for your plan (e.g.MARKET_PRICING_NOT_IN_PLANonrefresh-pricingforFREEkeys). Does not consume refresh quota.429— Monthly quota exceeded on quota-backed routes (body may includeQUOTA_EXCEEDED,limit,used). Applies torefresh-pricingafter tier checks pass.
Global endpoints
These are not prefixed with a game slug.
Health
GET /api/public/v1/health· No API key (public) — always HTTP 200; JSON default, HTML with Accept: text/html
The response is always 200 OK so browsers and uptime panels always render the page; use ok and optional loadError for overall health (short, public-safe text only — no internal error details). The public-api-db row comes from a stored SELECT 1 that runs at most once per hour (same window as game checks); JSON includes polledAt for each route check. A live /games-shaped probe and per-game rows summarize catalog reachability from the API database (not unauthenticated HTTP to keyed game routes).
curl -sS "https://api.vendortools.net/api/public/v1/health" \
-H "Accept: application/json"{
"ok": true,
"service": "vendor-tools-public-api",
"version": "v1",
"generatedAt": "2026-05-13T21:05:13.189Z",
"routeChecks": [
{
"name": "database",
"path": "public-api-db",
"ok": true,
"statusCode": 200,
"responseMs": 139,
"polledAt": "2026-05-13T05:59:53.247Z"
},
{
"name": "catalog-games",
"path": "/api/public/v1/games",
"ok": true,
"statusCode": 200,
"responseMs": 98,
"polledAt": "2026-05-13T21:05:13.189Z"
}
],
"games": [
{
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG",
"isActive": true,
"lastCheckedAt": "2026-05-13T05:59:53.247Z",
"lastStatus": "up",
"lastResponseMs": 83,
"lastError": null,
"checks7d": 11,
"uptime7dPct": 100
},
{
"slug": "gundam",
"name": "Gundam",
"isActive": true,
"lastCheckedAt": "2026-05-13T05:59:53.247Z",
"lastStatus": "up",
"lastResponseMs": 81,
"lastError": null,
"checks7d": 7,
"uptime7dPct": 100
},
{
"slug": "gundam-japan",
"name": "Gundam Japan",
"isActive": true,
"lastCheckedAt": "2026-05-13T05:59:53.247Z",
"lastStatus": "up",
"lastResponseMs": 81,
"lastError": null,
"checks7d": 7,
"uptime7dPct": 100
},
{
"slug": "pokemon-tcg-pocket",
"name": "Pokémon TCG Pocket",
"isActive": true,
"lastCheckedAt": "2026-05-13T05:59:53.247Z",
"lastStatus": "up",
"lastResponseMs": 81,
"lastError": null,
"checks7d": 11,
"uptime7dPct": 100
}
]
}You can open /api/public/v1/health in a browser; most browsers send HTML in Accept and will show the HTML report.
List games
GET /api/public/v1/games· API key required
Active catalog games only. Use each slug in paths below. Each entry includes isActive and optional imageUrl (same-origin proxy; append ?api_key= for <img src>), or null when no cover is set.
{
"games": [
{
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG"
},
{
"slug": "gundam",
"name": "Gundam"
},
{
"slug": "gundam-japan",
"name": "Gundam Japan"
},
{
"slug": "pokemon-tcg-pocket",
"name": "Pokémon TCG Pocket"
}
],
"basePath": "/api/public/v1",
"note": "Use each slug as the first path segment after v1 for game-scoped routes."
}Current user (API key)
GET /api/public/v1/me· API key required
Illustrative values; call the endpoint with your key to see your user id, tier, and key metadata.
{
"user": {
"id": "cm4h2k3m0000abcd0000efgh",
"name": "Alex Developer",
"subscriptionTier": "BUILDER"
},
"key": {
"id": "cm4h2k3m0000ijkl0000mnop",
"prefix": "vt_live_a1b2",
"name": "Staging"
}
}Games & namespaces
Every game-scoped URL starts with /api/public/v1/{gameSlug}/. Slugs are lowercase. Inactive games are not returned from /games and return 404 if requested directly.
Games available on this host right now
cyberpunk-tcg— Cyberpunk TCGgundam— Gundamgundam-japan— Gundam Japanpokemon-tcg-pocket— Pokémon TCG Pocket
Game metadata
GET /api/public/v1/{gameSlug}· API key required
{
"game": {
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG",
"createdAt": "2026-04-27T08:51:48.165Z",
"updatedAt": "2026-05-05T20:33:41.755Z"
},
"routes": {
"self": "/api/public/v1/cyberpunk-tcg",
"prefix": "/api/public/v1/cyberpunk-tcg/",
"priceReports": "/api/public/v1/cyberpunk-tcg/price-reports"
}
}Catalog: expansions & cards
List expansions
GET /api/public/v1/{gameSlug}/expansions· API key required
Ordered by release date (newest first), then name. Includes per-expansion card and sealed counts.
{
"game": {
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG"
},
"expansions": [
{
"slug": "alpha-kit-set",
"name": "Alpha Kit Set",
"nameEn": null,
"nameJa": null,
"code": "ALPHA-KIT",
"releaseDate": null,
"series": null,
"imageUrl": null,
"cardCount": 31,
"sealedCount": 0,
"createdAt": "2026-04-27T08:57:16.578Z",
"updatedAt": "2026-05-05T20:21:32.407Z"
},
{
"slug": "promo-cards",
"name": "Promo Cards",
"nameEn": null,
"nameJa": null,
"code": "PROMO",
"releaseDate": null,
"series": null,
"imageUrl": null,
"cardCount": 1,
"sealedCount": 0,
"createdAt": "2026-04-27T08:56:29.591Z",
"updatedAt": "2026-05-05T20:49:38.761Z"
},
{
"slug": "spoiler-set",
"name": "Spoiler Set",
"nameEn": null,
"nameJa": null,
"code": "SPOILER",
"releaseDate": null,
"series": null,
"imageUrl": null,
"cardCount": 20,
"sealedCount": 0,
"createdAt": "2026-04-27T08:55:54.724Z",
"updatedAt": "2026-05-05T20:49:38.879Z"
},
{
"slug": "welcome-to-night-city",
"name": "Welcome To Night City",
"nameEn": null,
"nameJa": null,
"code": "WtNC",
"releaseDate": "2026-10-01",
"series": null,
"imageUrl": null,
"cardCount": 0,
"sealedCount": 3,
"createdAt": "2026-04-27T10:17:37.320Z",
"updatedAt": "2026-04-27T10:17:57.048Z"
}
]
}Sample above includes at most five expansions so the page stays readable; the live API returns the full list for the game.
One expansion
GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}· API key required
routes points to the card list and sealed pricing path for this set.
{
"game": {
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG"
},
"expansion": {
"slug": "alpha-kit-set",
"name": "Alpha Kit Set",
"nameEn": null,
"nameJa": null,
"code": "ALPHA-KIT",
"releaseDate": null,
"series": null,
"imageUrl": null,
"cardCount": 31,
"sealedCount": 0,
"createdAt": "2026-04-27T08:57:16.578Z",
"updatedAt": "2026-05-05T20:21:32.407Z"
},
"routes": {
"self": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set",
"expansionPrefix": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/",
"cards": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/cards",
"sealedProductPricing": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/sealed/{sealedSlug}"
}
}List cards in an expansion
GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}/cards· API key required
Omits cards marked excludeFromPublicPricing. Default payload: every card’s id, slug, name, primary imageUrl, and updatedAt in one JSON response (no pagination). Query include (comma-separated, case-insensitive):
cardDataor shorthandfull— fullcardDataplus allimageUrlsper card. Large sets can produce very large JSON.marketPricingor shorthandpricing—ebayLastSoldsandconditionPricesper card for non-Free tiers (empty on Free). Response may includeinclude.marketPricingPopulatedso clients can tell whether pricing arrays were filled.
Example: ?include=cardData,marketPricing. Advanced (PRO) / ENTERPRISE also receive description fields on list rows when the heavier payload is requested (same rules as card detail).
{
"game": {
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG"
},
"expansion": {
"slug": "alpha-kit-set",
"name": "Alpha Kit Set"
},
"cards": [
{
"id": "cmot3ru7z00ssf451xfawgddy",
"slug": "goro-takemura-foil-a030",
"name": "Goro Takemura",
"imageUrl": null,
"updatedAt": "2026-05-05T20:50:43.007Z"
},
{
"id": "cmot3ru9800stf451drqgmaiw",
"slug": "saburo-arasaka-foil-a029",
"name": "Saburo Arasaka",
"imageUrl": null,
"updatedAt": "2026-05-05T20:50:43.052Z"
},
{
"id": "cmot3ruaf00suf451ubi0flyy",
"slug": "yorinobu-arasaka-foil-a031",
"name": "Yorinobu Arasaka",
"imageUrl": null,
"updatedAt": "2026-05-05T20:50:43.095Z"
},
{
"id": "cmot3tli700svf451c59bqxl5",
"slug": "armored-minotaur-a007",
"name": "Armored Minotaur",
"imageUrl": null,
"updatedAt": "2026-05-05T20:52:05.023Z"
},
{
"id": "cmot3tljc00swf4515hqb8c5a",
"slug": "corpo-security-a016",
"name": "Corpo Security",
"imageUrl": null,
"updatedAt": "2026-05-05T20:52:05.064Z"
}
],
"routes": {
"self": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/cards",
"cardDetail": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/cards/{cardId}"
}
}Sample lists at most five cards; the API returns every in-catalog card for the expansion (excluding items marked private to pricing).
Example: replace {cardId} with the id from this list (cuid), not the slug.
Pricing: sold comps & condition guides
On non-Free tiers, card and sealed GETs expose a rolling window of recent sold rows and per-condition aggregates (empty when no data or when the game opts out of card-level eBay pricing). See Plans & field tiers for field-level rules.
Card detail (with pricing)
GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}/cards/{cardId}· API key required
cardData is game-specific JSON (numbers, variants, language fields, etc.). Cards excluded from public pricing return 404. routes includes list, listWithCardData, and, when your tier and game support it, refreshPricing.
{
"game": {
"slug": "cyberpunk-tcg",
"name": "Cyberpunk TCG"
},
"expansion": {
"slug": "alpha-kit-set",
"name": "Alpha Kit Set"
},
"card": {
"id": "cmot3ru7z00ssf451xfawgddy",
"slug": "goro-takemura-foil-a030",
"name": "Goro Takemura",
"imageUrls": [],
"cardData": {
"ram": 2,
"code": "a030",
"cost": 5,
"type": "Legend",
"power": 7,
"rules": "GO SOLO (Pay this card's cost to play it as a ready unit. It can attack this turn.) BLOCKER (When a rival units attacks, you may spend this unit to redirect the attack to this unit.)",
"colour": "Green",
"keywords": "GO SOLO,BLOCKER",
"subtitle": "Hands Unclean",
"expansion": "alpha-kit-set",
"illustrator": "Vincenzo Riccardi",
"variantName": "Foil",
"classification": "Arasaka,Corpo"
},
"updatedAt": "2026-05-05T20:50:43.007Z",
"catalogCardDescription": {
"set": {
"name": "Alpha Kit Set",
"slug": "alpha-kit-set",
"code": "ALPHA-KIT",
"series": null,
"releaseDate": null,
"nameEn": null
},
"history": null,
"playableInOfficialTcg": null,
"playableExplanation": null,
"simpleDescription": null
},
"enterpriseCatalogDescription": null
},
"ebayLastSolds": [],
"conditionPrices": [],
"routes": {
"self": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/cards/cmot3ru7z00ssf451xfawgddy",
"list": "/api/public/v1/cyberpunk-tcg/expansions/alpha-kit-set/cards"
}
}basis is either avg_last_3d (average of non-outlier sales in the recent window) or latest_sale when there is not enough in-window data.
Sealed product (with pricing)
GET /api/public/v1/{gameSlug}/expansions/{expansionSlug}/sealed/{sealedSlug}· API key required
The sealed item must be linked to this expansion. Slug is the catalog sealed slug for that game. Same ebayLastSolds / conditionPrices shape as cards.
Sealed product sample appears when a sealed item linked to the sample expansion exists in the catalog.
Refresh market pricing (on demand)
POST /api/public/v1/{gameSlug}/expansions/{expansionSlug}/cards/{cardId}/refresh-pricing· API key required
POST /api/public/v1/{gameSlug}/expansions/{expansionSlug}/sealed/{sealedSlug}/refresh-pricing· API key required
Paid tiers only (Starter BUILDER, Advanced PRO, ENTERPRISE) — same eligibility as populated ebayLastSolds on GET. Triggers a live eBay sold search (RapidAPI-backed), merges into the catalog, and updates snapshots. Each successful run consumes one monthly API call after quota is reserved (403 for FREE does not use quota; 429 when the monthly limit is already reached). Optional JSON body: { "keywords": "custom eBay search" } overrides server-built keywords. Server allows up to five minutes of work — set client timeouts accordingly. Card and sealed GET responses include routes.refreshPricing when your tier supports market pricing and the game exposes refresh for that item type.
Errors: 403 MARKET_PRICING_NOT_IN_PLAN, 429 QUOTA_EXCEEDED, 503 when RapidAPI is not configured on the host, 502 for upstream search failures.
Price reports
POST /api/public/v1/{gameSlug}/price-reports· API key required — key required; no quota use
Submit feedback for incorrect pricing or to flag listings. Does not count toward API quota. itemKind must be CARD or SEALED (uppercase). With CARD, send cardId (catalog id, not slug). With SEALED, send sealedProductId only — do not mix both ids. kind must be one of: incorrect_price, exclude_listing, exclude_all_pricing. For exclude_listing, ebayLastSoldId is required (the id from ebayLastSolds).
Request body (card example)
{
"itemKind": "CARD",
"cardId": "cmot3ru7z00ssf451xfawgddy",
"kind": "incorrect_price",
"message": "Example: last sale may be a damaged copy; NM guide seems high.",
"ebayLastSoldId": null
}Request body (exclude one listing)
{
"itemKind": "CARD",
"cardId": "cmot3ru7z00ssf451xfawgddy",
"kind": "exclude_listing",
"ebayLastSoldId": "—",
"message": "No eBay sold rows in catalog yet — after card detail returns ebayLastSolds, copy id here."
}Request body (sealed product)
{
"itemKind": "SEALED",
"sealedProductId": "—",
"kind": "incorrect_price",
"message": "Last comp looks like a damaged box.",
"ebayLastSoldId": null
}Response 201 Created
Shape after a successful POST (your response includes a new id and createdAt):
{
"id": "cm4h2k3m0000abcd0000price",
"gameSlug": "cyberpunk-tcg",
"kind": "incorrect_price",
"createdAt": "2026-05-13T21:05:15.366Z",
"routes": {
"self": "/api/public/v1/cyberpunk-tcg/price-reports"
}
}