npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cardsightai

v4.1.0

Published

TypeScript SDK for CardSight AI REST API

Readme

CardSight AI Node.js SDK

NPM Version Node LTS License: MIT TypeScript

Official TypeScript/JavaScript SDK for CardSight AI REST API

The most comprehensive trading card identification and collection management platform, covering sports cards (Baseball, Football, Basketball, Hockey, MMA) and trading card games (Pokémon, Magic: The Gathering, One Piece). 15M+ Trading Cards • 8,250+ Identifiable Sets • AI-Powered Recognition • CardMagic Listing Images • Free Tier Available

Quick Links: Getting Started • Installation • Examples • API Documentation • Support


Features

  • Full TypeScript Support - Complete type safety with auto-generated types from OpenAPI
  • Multi-Card Detection - Identify multiple cards in a single image with confidence scores
  • Sports Cards & TCGs - Identification and catalog data for Baseball, Football, Basketball, Hockey, and MMA, plus Pokémon, Magic: The Gathering, and One Piece
  • CardMagic - Turn a phone photo into clean, listing-ready card images. No scanner or custom hardware required
  • Parallel Identification (beta) - Ranked parallel variant candidates with per-entry confidence tiers, launched for baseball
  • Flexible Metadata via Fields - Search and surface arbitrary card properties (HP, Rarity, Artist, Mana Cost, etc.) across any trading card game
  • Universal Compatibility - Works in Node.js, browsers, and edge runtimes
  • Dual Module System - Supports both ESM and CommonJS
  • Smart Error Handling - Typed errors carrying the HTTP status, response body, and request context
  • Minimal Dependencies - Only one runtime dependency (openapi-fetch)
  • 100% API Coverage - All CardSight AI endpoints fully implemented

Key Capabilities

| Feature | Description | Primary Methods | |---------|-------------|-----------------| | Card Identification | Identify multiple cards from images using AI; free pre-flight set identifiability lookups | identify.card(), identify.cardBySegment(), identify.sets.list(), identify.sets.check() | | Card Detection | Check if trading cards are present in an image | detect.card() | | CardMagic | Turn a phone photo of one or more cards into listing-ready card images | cardMagic.process() | | Catalog Search | Fuzzy search across cards, sets, releases, parallels | catalog.search(), catalog.cards.list() | | Random Catalog | Pack opening simulations with parallel odds | catalog.random.cards(), catalog.random.sets() | | Collections | Manage owned card collections with analytics | collections.create(), collections.cards.add() | | Collectors | Manage collector profiles with names | collectors.create(), collectors.update() | | Lists | Track wanted cards (wishlists) | lists.create(), lists.cards.add() | | Binders | Organize collection subsets | collections.binders.create() | | Pricing | Completed sales data for cards; candlestick price time series; free-text title search | pricing.get(), pricing.bulk(), pricing.timeseries(), pricing.search() | | Marketplace | Active marketplace listings for cards; free-text title search | marketplace.get(), marketplace.search() | | Population Reports | Graded population counts by card, set, or release | population.card(), population.set(), population.release() | | Grading | PSA, TAG, BGS, SGC grade information | grades.companies.list() | | AI Search | Natural language queries | ai.query() | | Autocomplete | Search suggestions for all entities | autocomplete.cards() | | Field Catalog | Browse flexible metadata fields (Artist, HP, Rarity, etc.) with usage counts — powers cross-TCG metadata search | catalog.fields.list(), catalog.fields.get() | | Release Calendar | Upcoming and recent card product releases across segments and manufacturers | releaseCalendar.list() |

Requirements

  • Node.js 22.0+ (uses native fetch)
  • TypeScript 5.0+ (optional, for TypeScript projects)
  • API Key from cardsight.ai (free tier available)

Installation

# npm
npm install cardsightai

# yarn
yarn add cardsightai

# pnpm
pnpm add cardsightai

Getting Started

Get Your Free API Key

Get started in minutes with a free API key from cardsight.ai - no credit card required!

Quick Start (< 5 minutes)

import { CardSightAI } from 'cardsightai';

// 1. Initialize the client
const client = new CardSightAI({ apiKey: 'your_api_key_here' });

// 2. Identify a card from an image
const imageFile = // ... a File, Blob, or ArrayBuffer
const result = await client.identify.card(imageFile);

// 3. Access the identification results
if (result.data?.success) {
  // The API can detect multiple cards in a single image
  const detection = result.data.detections?.[0]; // Get best match
  if (detection) {
    console.log(`Card: ${detection.card.name}`);       // Exact match only
    console.log(`Set: ${detection.card.releaseName}`);  // Exact + set-level match
    console.log(`Confidence: ${detection.confidence}`); // "High", "Medium", or "Low"
    console.log(`Total cards detected: ${result.data.detections.length}`);
  }
}

That's it! The SDK handles all API communication, type safety, and error handling automatically.

Usage Examples

Card Identification

The identification endpoint uses AI to detect cards in images. It can identify multiple cards in a single image and returns confidence levels for each detection. Identification covers sports cards (Baseball, Football, Basketball, Hockey, MMA) and trading card games (Pokémon, Magic: The Gathering, One Piece). Use identify.card() for baseball (the default segment) or identify.cardBySegment() to target any other segment by UUID, name, or shortname (e.g. 'football', 'magic').

import { CardSightAI } from 'cardsightai';
import { readFileSync } from 'fs';

const client = new CardSightAI({ apiKey: 'your_api_key' });

// From a File object (browser)
const fileInput = document.querySelector<HTMLInputElement>('input[type="file"]');
const file = fileInput?.files?.[0];
if (file) {
  await client.identify.card(file);
}

// From a Blob (browser/fetch)
const blob = await fetch('https://example.com/card.jpg').then(r => r.blob());
await client.identify.card(blob);

// From a file on disk (Node.js) — convert the Buffer to an ArrayBuffer
const imageBuffer = new Uint8Array(readFileSync('path/to/card.jpg')).buffer;
const result = await client.identify.card(imageBuffer);

// Process the results
if (result.data?.success && result.data.detections) {
  // Check if any cards were detected
  if (result.data.detections.length === 0) {
    console.log('No cards detected in image');
    return;
  }

  // Process all detected cards
  console.log(`Detected ${result.data.detections.length} card(s)`);

  for (const detection of result.data.detections) {
    console.log(`\nConfidence: ${detection.confidence}`);

    // card is always present — field completeness depends on match level
    if (detection.card.id) {
      // Exact match — all fields populated
      console.log(`  Name: ${detection.card.name}`);
      console.log(`  Year: ${detection.card.year}`);
      console.log(`  Manufacturer: ${detection.card.manufacturer}`);
      console.log(`  Set: ${detection.card.setName || detection.card.releaseName}`);
      console.log(`  Number: ${detection.card.number || 'N/A'}`);
      console.log(`  Card ID: ${detection.card.id}`);
    } else if (detection.card.setId) {
      // Set-level match — no specific card, but set info available
      console.log(`  Release: ${detection.card.releaseName}`);
      console.log(`  Set: ${detection.card.setName}`);
      console.log(`  Year: ${detection.card.year}`);
    } else {
      // No match — card detected in image but not identified
      console.log('  Could not identify this card');
    }
  }

  // Access request metadata
  console.log(`\nRequest ID: ${result.data.requestId}`);
  console.log(`Processing time: ${result.data.processingTime}ms`);

  // Check for server advisory messages (e.g., image quality warnings)
  if (result.data.messages?.length) {
    for (const msg of result.data.messages) {
      console.log(`[${msg.type}] ${msg.message}`);
    }
  }
}

// Segment-specific identification (football, basketball, etc.)
const footballResult = await client.identify.cardBySegment('football', imageBuffer);
const basketballResult = await client.identify.cardBySegment('basketball', blob);

Response Structure

Each detection has a confidence level and a card object. The card is always present, but its fields are populated based on the match level:

  • Exact match: card.id present — all fields populated including name, number, and optionally parallelSuggestions (beta)
  • Set-level match: card.setId present but no card.id — release/set info available but no specific card
  • No match: card is an empty object {} — a card was detected in the image but couldn't be identified

Detections may also include a grading object when the card is inside a graded slab (see Grading/Slab Detection below).

// Exact card match with parallel variant
{
  success: true,
  requestId: "req_abc123",
  detections: [
    {
      confidence: "High",
      card: {
        id: "cd4e3a2f-8b9d-4c7e-a1b2-3d4e5f6g7h8i",
        segmentId: "seg-uuid",
        releaseId: "rel-uuid",
        setId: "set-uuid",
        year: "2023",
        manufacturer: "Topps",
        releaseName: "Chrome",
        setName: "Base Set",
        name: "Aaron Judge",
        number: "99",
        parallelSuggestions: [
          {
            id: "par_uuid",
            name: "Gold Refractor",
            numberedTo: 50,
            confidence: "High"
          }
        ]
      }
    }
  ],
  processingTime: 1250
}

// Mixed results - exact match, set-level match, and no match
{
  success: true,
  requestId: "req_xyz789",
  detections: [
    {
      confidence: "High",
      card: {
        id: "card-uuid",
        segmentId: "seg-uuid",
        releaseId: "rel-uuid",
        setId: "set-uuid",
        year: "2023",
        manufacturer: "Topps",
        releaseName: "Series 1",
        setName: "Base Set",
        name: "Mike Trout",
        number: "27"
      }
    },
    {
      confidence: "Medium",
      card: {
        segmentId: "seg-uuid",
        releaseId: "rel-uuid",
        setId: "set-uuid",
        year: "2024",
        manufacturer: "Panini",
        releaseName: "Prizm Football",
        setName: "Base Set"
      }
    },
    {
      confidence: "Low",
      card: {}
    }
  ],
  processingTime: 1500
}

// No cards detected - empty detections array
{
  success: true,
  requestId: "req_def456",
  detections: [],
  processingTime: 800
}

Checking Set Identifiability (free pre-flight)

Before spending a billed identify call, you can confirm whether a set is supported. These endpoints are free — they do not count toward your billed API usage.

// List every set the system can identify (paginated)
const { data } = await client.identify.sets.list({ take: 20, skip: 0 });
if (data) {
  console.log(`${data.total_count} identifiable sets`);
  for (const set of data.sets) {
    console.log(`${set.year} ${set.release_name} — ${set.set_name} (${set.segment_name})`);
  }
}

// Check whether a specific set is identifiable by its set ID
const { data: check } = await client.identify.sets.check(setId);
if (check?.is_identifiable) {
  console.log(`Set ${check.set_id} is identifiable`);
}

Card Detection (Presence Check)

The detection endpoint is a lightweight alternative to full identification — it checks whether trading cards are present in an image without identifying them. This is faster and cheaper when you only need to know if cards exist in the image.

import { CardSightAI } from 'cardsightai';
import { readFileSync } from 'fs';

const client = new CardSightAI({ apiKey: 'your_api_key' });

// Check if an image contains trading cards
const imageBuffer = new Uint8Array(readFileSync('path/to/image.jpg')).buffer;
const result = await client.detect.card(imageBuffer);

if (result.data) {
  console.log(`Cards detected: ${result.data.detected}`);   // true/false
  console.log(`Number of cards: ${result.data.count}`);      // 0, 1, 2, ...

  // Check for server advisory messages
  if (result.data.messages?.length) {
    for (const msg of result.data.messages) {
      console.log(`[${msg.type}] ${msg.message}`);
    }
  }
}

// Works with the same image types as identify
const blob = await fetch('https://example.com/card.jpg').then(r => r.blob());
const blobResult = await client.detect.card(blob);

Working with Identification Results

The SDK provides utility functions to simplify working with multi-card detection results:

import {
  getHighestConfidenceDetection,
  filterByConfidence,
  getDetectedCards,
  hasDetections,
  isExactMatch,
  isSetLevelMatch,
  getExactMatches,
  formatCardDisplay
} from 'cardsightai';

const result = await client.identify.card(imageFile);

// Get the highest confidence detection (best match)
const bestMatch = getHighestConfidenceDetection(result.data);
if (bestMatch) {
  console.log('Best match:', formatCardDisplay(bestMatch.card));
  // Output: "2023 Topps Series 1 Base Set Mike Trout #27"
}

// Check match levels
for (const detection of result.data?.detections || []) {
  if (isExactMatch(detection)) {
    console.log(`Exact: ${formatCardDisplay(detection.card)}`);
  } else if (isSetLevelMatch(detection)) {
    console.log(`Set-level: ${detection.card.releaseName} ${detection.card.setName}`);
  } else {
    console.log('Unidentified card detected');
  }
}

// Get only exact matches (detections with card.id)
const exactMatches = getExactMatches(result.data);
console.log(`${exactMatches.length} exact match(es)`);

// Get all exact-match card objects
const cards = getDetectedCards(result.data);
cards.forEach(card => {
  console.log(`- ${formatCardDisplay(card)}`);
});

// Filter by confidence level
const highConfidenceOnly = filterByConfidence(result.data, 'High');
const mediumAndAbove = filterByConfidence(result.data, 'Medium');

// Check if any cards were detected
if (hasDetections(result.data)) {
  console.log(`Found ${result.data.detections.length} card(s)`);
}

Flexible Metadata, Suggestions, and Numbered Cards (v3.4.2+)

Every detection's card now optionally includes three additional fields:

  • numberedTo?: number — print run for numbered base cards (e.g. 25 for a /25), independent of parallels
  • fields?: FieldValue[] — key/value metadata tailored to the TCG (e.g. HP, RARITY, ARTIST, MANA_COST), plus a CARD_LANGUAGE entry holding the ISO 639-1 code of the scanned card's language (e.g. "ja", "en") when it is detected (v3.8.1+)
  • suggestions?: CardSuggestion[] — alternative card candidates, best match first. Each entry is a full card record with the same fields as card. Only present when the detection confidence is Medium or Low (v4.0.0+)
import {
  getFieldValue,
  formatFieldValues,
  hasSuggestions,
  getSuggestions,
  isNumberedCard,
  getNumberedTo,
  formatCardDisplay
} from 'cardsightai';

const detection = result.data?.detections?.[0];
if (!detection) return;

// Pull a specific metadata value (case-insensitive key lookup)
const artist = getFieldValue(detection, 'ARTIST');

// Detected language of the scanned card as an ISO 639-1 code, when available
const language = getFieldValue(detection, 'CARD_LANGUAGE');  // e.g. "ja"
if (language && language !== 'en') {
  // Map the code to a display name with the built-in Intl API
  const label = new Intl.DisplayNames(['en'], { type: 'language' }).of(language);
  console.log(`Non-English printing: ${label} (${language})`);  // "Japanese (ja)"
}

// Format all metadata for display
console.log(formatFieldValues(detection, ' · '));
// e.g. "HP: 120 · RARITY: Holo Rare · ARTIST: Mitsuhiro Arita"

// Base-card print run (distinct from parallel print runs)
if (isNumberedCard(detection)) {
  console.log(`Limited to /${getNumberedTo(detection)}`);
}

// Alternative matches (only on Medium/Low confidence detections), best match first.
// Each suggestion is a full card record, so the display helpers work on it directly.
if (hasSuggestions(detection)) {
  console.log('Could also be:');
  for (const alt of getSuggestions(detection)) {
    console.log(`  • ${formatCardDisplay(alt)} (${alt.id ?? 'set-level only'})`);
    // e.g. "  • 1989 Upper Deck Upper Deck Base Set Ken Griffey Jr. #1 (card-uuid)"
  }
}

See Fields (Flexible Metadata System) for end-to-end Pokémon and Magic: The Gathering examples.

Parallel Variant Detection (beta)

The identify endpoint reports parallel variants (Refractors, Prizms, numbered parallels, etc.) as a ranked list of candidates in card.parallelSuggestions. The list is best match first, and each entry carries an optional confidence tier ("High" | "Medium" | "Low"). Ranking and confidence are independent — the engine's top pick is not always the entry with the highest confidence — and a missing confidence means "not assessed", not Low. When exactly one parallel was identified you get a single High-confidence entry; when several remain possible you get all of them. Base cards with no parallel evidence have no parallelSuggestions at all. Parallel identification is currently in beta and has launched for baseball.

import {
  isExactMatch,
  getParallelSuggestions,
  getBestParallelSuggestion,
  filterParallelSuggestionsByConfidence,
  formatParallelSuggestion
} from 'cardsightai';

const result = await client.identify.card(imageFile);

for (const detection of result.data?.detections || []) {
  if (!isExactMatch(detection)) continue;
  console.log(`Card: ${detection.card.name}`);

  // The engine's top-ranked parallel (undefined when there is no parallel evidence —
  // hasParallelSuggestions(detection) is the boolean form of the same check)
  const best = getBestParallelSuggestion(detection);
  if (!best) {
    console.log('  Type: Base Card');
    continue;
  }
  console.log(`  Best match: ${formatParallelSuggestion(best)}`);
  // Output: "Gold Refractor /50 - High confidence"

  // Only act on confirmed parallels
  if (best.confidence === 'High') {
    console.log(`  Parallel ID: ${best.id}`);
    if (best.numberedTo) {
      console.log(`  🔥 NUMBERED: Only ${best.numberedTo} exist!`);
    }
  }

  // Show every candidate the engine considered, in its ranking
  for (const candidate of getParallelSuggestions(detection)) {
    console.log(`  • ${formatParallelSuggestion(candidate)}`);
  }

  // Or only the ones assessed at Medium confidence or better
  // (entries with no confidence value are dropped — unassessed is not Low)
  const likely = filterParallelSuggestionsByConfidence(detection, 'Medium');
}

Parallel Suggestion Structure:

// Exact match with parallel evidence
{
  confidence: "High",
  card: {
    id: "card_uuid",
    segmentId: "seg_uuid",
    releaseId: "rel_uuid",
    setId: "set_uuid",
    name: "Mike Trout",
    year: "2023",
    // ... other card fields
    parallelSuggestions: [
      {
        id: "parallel_uuid",        // UUID of the parallel type
        name: "Gold Refractor",     // Human-readable name
        description?: "...",        // Optional additional details
        isPartial?: true,           // True if the parallel only applies to specific cards
        numberedTo?: 50,            // Print run for numbered parallels
        cards?: ["uuid1", "uuid2"], // Card UUIDs (only when isPartial is true)
        confidence?: "High"         // "High" | "Medium" | "Low"; omitted = not assessed
      }
      // ... further candidates, in the engine's ranking
    ]
  }
}

// Base cards have no parallelSuggestions array
{
  confidence: "High",
  card: {
    id: "card_uuid",
    name: "Aaron Judge",
    // ... other fields
    // parallelSuggestions is undefined
  }
}

Migrating from card.parallel (pre-4.0): the single parallel object is gone. The old helpers hasParallel(), getParallelInfo(), isNumberedParallel(), and formatParallelDisplay() still work — they now read the best-match entry (parallelSuggestions[0]) — but are deprecated. Note that hasParallel() is now true for any parallel evidence, including lower-confidence candidates; use getBestParallelSuggestion(detection)?.confidence === 'High' to keep the old "confirmed parallel" behaviour.

Grading/Slab Detection

When identifying a card that is inside a graded slab, the detection includes grading information:

import {
  hasGrading,
  getGradingInfo,
  formatGradingDisplay
} from 'cardsightai';

const result = await client.identify.card(imageFile);

for (const detection of result.data?.detections || []) {
  // Check if the card is in a graded slab — hasGrading(detection) is the boolean form;
  // reading the value lets TypeScript narrow the optional `grading` for the block below
  const grading = getGradingInfo(detection);
  if (grading) {
    console.log(`Grading Company: ${grading.company.name}`);
    console.log(`Detection Confidence: ${grading.confidence}`);
    console.log(`Display: ${formatGradingDisplay(detection)}`);
    // Output: "PSA 10 GEM MINT - High confidence"

    // Access detailed grade information
    if (grading.grade) {
      console.log(`  Grade: ${grading.grade.value}`);        // e.g., "10"
      console.log(`  Condition: ${grading.grade.condition}`); // e.g., "GEM MINT"
    }

    // Check for defect qualifiers (OC, MC, PD, ST)
    if (grading.qualifier) {
      console.log(`  Qualifier: ${grading.qualifier.code}`);  // e.g., "OC"
    }

    // Check for autograph grades
    if (grading.autoGrade) {
      console.log(`  Auto Grade: ${grading.autoGrade.value}`); // e.g., "10"
    }
  }
}

Grading Object Structure:

{
  confidence: "High",
  card: {
    id: "card_uuid",
    name: "Mike Trout",
    // ... other card fields
  },
  grading: {
    confidence: "High",        // Slab detection confidence
    company: {
      id: "company_uuid",     // Optional grading company UUID
      name: "PSA"             // Grading company name
    },
    grade: {                   // Optional - grade detected on the slab label
      id: "grade_uuid",       // Optional catalog UUID
      value: "10",            // Grade value
      condition: "GEM MINT"   // Grade condition
    },
    qualifier: {               // Optional - defect qualifier (e.g., OC, MC, PD, ST)
      id: "qualifier_uuid",   // Optional catalog UUID
      code: "OC"              // Qualifier code
    },
    autoGrade: {               // Optional - autograph grade
      id: "auto_grade_uuid",  // Optional catalog UUID
      value: "10",            // Autograph grade value
      condition: "MINT"       // Autograph grade condition
    }
  }
}

Card Parallel Utilities (Catalog)

Cards from catalog endpoints now include a parallels array listing all available parallel variants. Use these utilities to work with catalog card parallels:

import {
  getCardParallels,
  hasCardParallels,
  findParallelByName,
  getNumberedParallels,
  formatCardParallel,
  type CardParallel
} from 'cardsightai';

// Get a card from the catalog
const { data: card } = await client.catalog.cards.get('card_uuid');

// Check if the card has any parallel variants
if (card && hasCardParallels(card)) {
  // Get all parallels
  const parallels = getCardParallels(card);
  console.log(`This card has ${parallels.length} parallel variants`);

  // Format each parallel for display
  parallels.forEach(p => {
    console.log(`- ${formatCardParallel(p)}`);
    // Output: "Gold Refractor /50", "Black Prizm", "Orange /25"
  });

  // Get only numbered parallels (limited print runs)
  const numbered = getNumberedParallels(card);
  console.log(`${numbered.length} are numbered parallels`);

  // Find a specific parallel by name
  const gold = findParallelByName(card, 'Gold Refractor');
  if (gold) {
    console.log(`Found Gold Refractor with ID: ${gold.id}`);
  }
}

Note: These utilities are for catalog cards (card.parallels[]). For identification results, use hasParallelSuggestions(), getBestParallelSuggestion(), etc. which work with the detected card.parallelSuggestions array (see Parallel Variant Detection). A ParallelSuggestion has the same id / name / numberedTo shape as a CardParallel, so formatCardParallel() accepts either.

CardMagic (Listing-Ready Card Images)

CardMagic turns a phone photo into clean, listing-ready card images. No scanner or custom hardware is needed: snap a picture of one or more cards and CardMagic finds each card, straightens it, squares it up at standard trading-card proportions, and returns an image ready for a marketplace listing.

import { CardSightAI, CardSightAIError, getCardMagicInfo } from 'cardsightai';
import { readFileSync, writeFileSync } from 'fs';

const client = new CardSightAI({ apiKey: 'your_api_key' });

// Send the original photo from the phone, not a downscaled or rotated copy
const photo = new Uint8Array(readFileSync('path/to/IMG_1234.HEIC')).buffer;

try {
  const result = await client.cardMagic.process(photo, {
    mode: 'process',      // default: straightened and squared up, as if scanned
    outputFormat: 'jpeg', // or 'png'
    longEdge: 2000        // long side in pixels (32-2100); omit for the photo's native size
  });

  if (result.data) {
    // The response is binary: result.data is a Blob, and metadata comes from the headers
    const info = getCardMagicInfo(result.response);
    const bytes = Buffer.from(await result.data.arrayBuffer());

    if (info.isZip) {
      // Two or more cards: a zip of card_0, card_1, ... in reading order
      writeFileSync('cards.zip', bytes);
      console.log(`${info.count} cards processed`);
    } else {
      // One card: the image itself
      writeFileSync('card.jpg', bytes);
      console.log(`Card image: ${info.width}x${info.height}px`);
    }
  }
} catch (error) {
  if (error instanceof CardSightAIError && error.status === 422) {
    console.log('No card found in the photo'); // error.response.code === 'NO_CARD_FOUND'
  } else {
    throw error;
  }
}

In the browser, pass the File from a camera input directly:

import { CardSightAI, getCardMagicInfo } from 'cardsightai';

const client = new CardSightAI({ apiKey: 'your_api_key' });

// <input type="file" accept="image/*" capture="environment">
const input = document.querySelector<HTMLInputElement>('input[type="file"]');
const photoFile = input?.files?.[0];

if (photoFile) {
  const { data, response } = await client.cardMagic.process(photoFile, { outputFormat: 'png' });
  const preview = document.querySelector<HTMLImageElement>('#preview');

  if (data && preview && !getCardMagicInfo(response).isZip) {
    preview.src = URL.createObjectURL(data);
  }
}

Options (all optional). Yes/no options take the strings 'true' and 'false', matching the API.

| Option | Values | Default | Effect | |--------|--------|---------|--------| | mode | 'process' | 'crop' | 'process' | process straightens each card and squares it up at standard trading-card proportions, as if scanned. crop returns each card as it appears in the photo, trimmed to the card | | paddingPercent | 0-50 | 5 | Margin around the card, as a percent of the card size on each side | | paddingFill | 'background' or '#RRGGBB' | 'background' | Fill the margin with the photo's real surroundings or a solid color | | autoLevels | 'true' | 'false' | 'true' | Restore contrast and remove color cast. Use 'false' when the photo's own color matters | | outputFormat | 'jpeg' | 'png' | 'jpeg' | Encoding of the returned images | | longEdge | 32-2100 | Photo's native size | Length of the long side of every returned image in pixels, padding included | | corners | 'true' | 'false' | 'false' | Add close-ups of each card's four corners for judging condition (600x600 each, showing 14 mm of card plus 2.5 mm beyond it on a light 1 mm grid, in the photo's original color) and a 1210x1210 sheet combining them |

What comes back:

| Photo contains | corners | Response (result.data Blob) | |----------------|-----------|-------------------------------| | One card | off | The image (image/jpeg or image/png). getCardMagicInfo() reports its width and height | | Two or more cards | off | application/zip with card_0.<ext>, card_1.<ext>, ... in reading order (top to bottom, then left to right) | | One or more cards | 'true' | Always application/zip. Each card_N.<ext> is followed by card_N_top-left, card_N_top-right, card_N_bottom-right, card_N_bottom-left, and card_N_corners (all four on one sheet) | | No card | any | Throws CardSightAIError with status 422 and code NO_CARD_FOUND |

getCardMagicInfo(response) returns { contentType, isZip, count?, width?, height? }, parsed from the Content-Type, X-CardMagic-Count, X-CardMagic-Width, and X-CardMagic-Height headers. width and height are only set for single-image responses.

Tips:

  • Raw cards give the best results. Cards in toploaders or grading-company slabs may crop poorly.
  • Accepts JPEG, PNG, WebP, and HEIC/HEIF photos up to 20MB and 8192px per side.
  • Send the original photo, including its orientation flag. Don't downscale or rotate it first.
  • The SDK has no zip dependency. Unzip multi-card results with the library of your choice, or pass the zip straight through to your users.

Catalog Search

Search across cards, sets, releases, and parallels with a single query:

// Global fuzzy search
const results = await client.catalog.search({
  q: 'Ken Griffey Jr',  // Required search query
  take: 10,             // Limit results
  skip: 0               // Pagination offset
});

// Filter by entity type
const cardResults = await client.catalog.search({
  q: 'Topps Chrome',
  type: 'set',          // 'card' | 'set' | 'release' | 'parallel'
  year: '2023'
});

// Filter by segment, manufacturer, year range
const footballCards = await client.catalog.search({
  q: 'Patrick Mahomes',
  segment: 'football',
  min_year: '2020',
  max_year: '2024'
});

// Slash notation: append a standalone "/N" term to hard-filter to cards and
// parallels serial-numbered to that value. Matched results expose `numberedTo`.
const numbered = await client.catalog.search({
  q: 'aaron judge /25'   // only cards/parallels numbered to /25
});

// Process results
if (results.data) {
  console.log(`Found ${results.data.total_count} results`);
  for (const result of results.data.results) {
    // `relevance` is opaque and order-only — compare it between results, not across requests
    console.log(`[${result.type}] ${result.name} (relevance: ${result.relevance})`);
    if (result.segmentName) console.log(`  Segment: ${result.segmentName}`);
    if (result.setName) console.log(`  Set: ${result.setName}`);
    if (result.year) console.log(`  Year: ${result.year}`);
    if (result.cardNumber) console.log(`  Card #${result.cardNumber}`);
    if (result.numberedTo) console.log(`  Numbered to /${result.numberedTo}`);
    // Present on every result only when close-spelling (fuzzy) matching engaged;
    // "exact" results always sort before "fuzzy" ones
    if (result.matchKind === 'fuzzy') console.log('  (fuzzy match)');
  }

  // Advisory messages (e.g. an unrecognized query parameter was ignored)
  for (const msg of results.data.messages ?? []) {
    console.log(`[${msg.type}] ${msg.message}`);
  }
}

Fields (Flexible Metadata System)

Every trading card game has different metadata: Pokémon cards have HP and Rarity, while Magic: The Gathering cards have Mana Cost and Artist. Rather than hard-coding columns per game, CardSight exposes a flexible Fields system — any card, set, release, or segment can carry key/value metadata, and the catalog exposes it as a first-class browsable entity. One SDK surface works across every TCG, no per-game branching required.

Browse available fields, sorted by how prevalent they are:

// usageCount tells you how many catalog entities (cards, sets, releases, segments) carry each field
const fields = await client.catalog.fields.list({
  sort: 'usageCount',
  order: 'desc',
  take: 20
});

fields.data?.fields.forEach(f => {
  console.log(`${f.name} (${f.key}) — used on ${f.usageCount} entities`);
});
// e.g. "Artist (ARTIST) — used on 48,231 entities"
//      "Rarity (RARITY) — used on 39,104 entities"
//      "Hit Points (HP) — used on 11,520 entities"

Example — find rare Pokémon cards:

Identification responses now include a fields array on every detected card, so you can surface rarity directly from a scan:

import { CardSightAI, getFieldValue, isExactMatch } from 'cardsightai';

const client = new CardSightAI({ apiKey: 'your_api_key_here' });
const result = await client.identify.cardBySegment('pokemon', pokemonCardImage);
const detection = result.data?.detections?.[0];

if (detection && isExactMatch(detection)) {
  const rarity = getFieldValue(detection, 'RARITY');
  const hp = getFieldValue(detection, 'HP');
  const language = getFieldValue(detection, 'CARD_LANGUAGE');  // ISO 639-1, e.g. "ja"
  console.log(`${detection.card.name} — ${rarity} (HP: ${hp}) [${language}]`);
  // e.g. "Charizard — Holo Rare (HP: 120) [ja]"

  if (rarity?.toLowerCase().includes('rare')) {
    // Route to a higher-value pricing lookup, flag for user review, etc.
    const pricing = await client.pricing.get(detection.card.id);
    // ...
  }
}

Example — surface the artist on Magic: The Gathering cards:

import { CardSightAI, formatFieldValues, getFieldValue } from 'cardsightai';

const client = new CardSightAI({ apiKey: 'your_api_key_here' });
const result = await client.identify.cardBySegment('magic', mtgCardImage);
const detection = result.data?.detections?.[0];

if (detection) {
  const artist = getFieldValue(detection, 'ARTIST');
  const manaCost = getFieldValue(detection, 'MANA_COST');
  console.log(`${detection.card.name} by ${artist} — ${manaCost}`);
  // e.g. "Black Lotus by Christopher Rush — {0}"

  // Or show every field at once:
  console.log(formatFieldValues(detection, ' · '));
  // e.g. "MANA_COST: {0} · ARTIST: Christopher Rush · RARITY: Rare"
}

Related utility helpers:

| Helper | Purpose | |--------|---------| | hasFields(detection) | Check whether a detection has any field values | | getFields(detection) | Return the full FieldValue[] array | | getFieldValue(detection, key) | Look up a single value by key (case-insensitive) | | formatFieldValues(detection, separator?) | Format all fields as a display string | | hasSuggestions(detection) | Check for alternative card candidates (Medium/Low confidence only) | | getSuggestions(detection) | Get the CardSuggestion[] array — full card records, best match first | | isNumberedCard(detection) | Check for a base-card print run (independent of parallels) | | getNumberedTo(detection) | Get the base-card print run number |

The CardDetails type on every detection also exposes numberedTo (e.g. 25 for a /25 card) and suggestions (alternative full card records on Medium/Low confidence detections) alongside the new fields array — see Working with Identification Results for full details.

Pricing (Completed Sales)

Get completed sales pricing data for cards, grouped into raw (ungraded) and graded sections:

// Get pricing for a single card
const pricing = await client.pricing.get('card_uuid');

if (pricing.data) {
  // Card context
  console.log(`Card: ${pricing.data.card.name}`);
  console.log(`Set: ${pricing.data.card.set.name} (${pricing.data.card.set.year})`);

  // Raw (ungraded) sales
  console.log(`\nUngraded sales: ${pricing.data.raw.count}`);
  for (const sale of pricing.data.raw.records) {
    console.log(`  $${sale.price} - ${sale.date} (${sale.source})`);
  }

  // Graded sales (grouped by company → grade)
  for (const company of pricing.data.graded) {
    console.log(`\n${company.company_name}:`);
    for (const grade of company.grades) {
      console.log(`  Grade ${grade.grade_value}: ${grade.count} sales`);
      for (const sale of grade.records) {
        console.log(`    $${sale.price} - ${sale.date}`);
      }
    }
  }

  // Metadata
  console.log(`\nTotal records: ${pricing.data.meta.total_records}`);
  console.log(`Last sale: ${pricing.data.meta.last_sale_date}`);
}

// Filter by parallel, grade, time period, and listing type
const filtered = await client.pricing.get('card_uuid', {
  parallel_id: 'parallel_uuid',  // Specific parallel (omit for all)
  grade_id: 'grade_uuid',        // Specific grade (omit for all)
  period: '90d',                  // Any combo: "7d", "2w", "3m", "1y", "all"
  listing_type: 'both',          // auction, fixed, both
  limit: 50                       // Max records per section
});

// Page backward through price history
// Each call returns the most-recent listings up to a 500-row cap ending at `as_of_date`
// (default: today, US Eastern). When the cap is hit, a warning is returned in `messages`.
const firstPage = await client.pricing.get('card_uuid', { period: 'all' });

if (firstPage.data) {
  // Advisory messages (e.g. the row cap was hit and more listings exist in the window)
  for (const msg of firstPage.data.messages ?? []) {
    console.log(`[${msg.type}] ${msg.message}`);
  }

  // To fetch older listings, anchor the next call at the oldest date returned
  // (records are most-recent first, so the oldest raw record is last).
  // The boundary day may repeat a few rows (duplicates, never gaps).
  const oldest = firstPage.data.raw.records.at(-1)?.date;
  if (oldest) {
    const olderPage = await client.pricing.get('card_uuid', {
      period: 'all',
      as_of_date: oldest    // 'YYYY-MM-DD'; future dates are clamped to today
    });
  }
}

// Bulk pricing for multiple cards (up to 100)
const bulk = await client.pricing.bulk({
  card_ids: ['card_uuid_1', 'card_uuid_2', 'card_uuid_3'],
  period: '90d',
  listing_type: 'both',
  limit: 25                       // Optional: most-recent listings per card (default 25, max 100)
});

if (bulk.data) {
  console.log(`Requested: ${bulk.data.meta.requested}`);
  console.log(`Successful: ${bulk.data.meta.successful}`);
  console.log(`Failed: ${bulk.data.meta.failed}`);

  for (const result of bulk.data.results) {
    if (result.success && result.data) {
      console.log(`${result.data.card.name}: ${result.data.meta.total_records} sales`);
    } else if (result.error) {
      console.log(`${result.card_id}: ${result.error.message}`);
    }
  }
}

Price Time Series (Candlestick Rollups)

Chart price trends over time with per-bucket descriptive statistics (mean, median, high, low, count). Series are split by grade — raw for ungraded listings and graded per company → grade — so graded and ungraded prices never blend into one candle. Within each series, candles are keyed by listing type: auction (completed sales, the bid side) and fixed (Buy It Now asking prices, the ask side). Statistics are summaries of listings, not valuations.

// Daily candles for the last 90 days (the default period count for "daily")
const series = await client.pricing.timeseries('card_uuid', { interval: 'daily' });

if (series.data) {
  // Effective values (after defaults and clamping) are echoed back
  const { interval, periods, as_of_date } = series.data.query;
  console.log(`${interval} × ${periods} buckets ending ${as_of_date}`);

  // Ungraded (raw) candles, oldest first
  for (const candle of series.data.raw.candles) {
    const auction = candle.types.auction;   // absent when the bucket has no auction listings
    if (auction) {
      console.log(`${candle.period_start}: median $${auction.median} (n=${auction.count})`);
    }
  }

  // Whole-window counts, including how many listings the outlier filter removed
  for (const [type, totals] of Object.entries(series.data.raw.totals)) {
    console.log(`${type}: ${totals.total_count} kept, ${totals.filtered_count} filtered`);
  }

  // Graded candles, grouped by company → grade
  for (const company of series.data.graded) {
    for (const grade of company.grades) {
      console.log(`${company.company_name} ${grade.grade_value}: ${grade.candles.length} candles`);
    }
  }
}

// Weekly candles for one year: base card only, one grade, auctions only
const weekly = await client.pricing.timeseries('card_uuid', {
  interval: 'weekly',        // 'daily' | 'weekly' | 'monthly' (required)
  periods: 52,               // Defaults: daily 90, weekly 52, monthly 24.
                             // Values above 365 are rejected; weekly > 156 and monthly > 120 are clamped.
  as_of_date: '2026-09-01',  // Newest bucket is the one containing this date (UTC); defaults to today
  listing_type: 'auction',   // 'auction' | 'fixed' | 'both'
  parallel_id: 'null',       // UUID for one parallel, 'null' for base card only, omit for all
  grade_id: 'grade_uuid'     // UUID for one grade, 'null' for ungraded only, omit for all
});

Buckets, listing types, and grades with no listings are omitted rather than zero-filled. A card with no listings in the window returns an empty raw section and an empty graded array as a success. Pinning grade_id to a specific grade excludes ungraded listings, so raw comes back empty in that case.

Pricing Search (Free-Text Title)

Search completed sales by listing title when you don't have a card ID. Returns a flat, relevance-ranked list that spans multiple cards and may include listings never matched to a canonical card:

const results = await client.pricing.search({
  q: 'Ken Griffey Jr 1989 Upper Deck',  // Required, 2–300 characters
  period: '90d',                         // Optional: "7d", "2w", "3m", "1y", "all"
  listing_type: 'both',                  // Optional: auction, fixed, both
  limit: 25                              // Optional: default 100, max 500
});

if (results.data) {
  console.log(`Found ${results.data.meta.total_records} sales`);

  for (const sale of results.data.results) {
    console.log(`$${sale.price} - ${sale.title ?? 'Untitled'} (${sale.source})`);

    // matched_card is present when the listing was matched to a canonical card
    if (sale.matched_card) {
      console.log(`  ${sale.matched_card.name} - ${sale.matched_card.set.name}`);
    }
    // grade is present for graded sales
    if (sale.grade) {
      console.log(`  ${sale.grade.company_name} ${sale.grade.grade_value}`);
    }
  }

  // Breakdown by data source
  for (const source of results.data.meta.sources) {
    console.log(`${source.source}: ${source.count}`);
  }
}

Marketplace (Active Listings)

Get currently active marketplace listings for cards:

// Get active listings for a card
const listings = await client.marketplace.get('card_uuid');

if (listings.data) {
  // Raw (ungraded) active listings
  console.log(`Ungraded listings: ${listings.data.raw.count}`);
  for (const listing of listings.data.raw.records) {
    console.log(`  ${listing.title} - $${listing.price} (${listing.source})`);
    if (listing.url) console.log(`    ${listing.url}`);
    if (listing.bid_count) console.log(`    Bids: ${listing.bid_count}`);
  }

  // Graded active listings (grouped by company → grade)
  for (const company of listings.data.graded) {
    console.log(`\n${company.company_name}:`);
    for (const grade of company.grades) {
      console.log(`  Grade ${grade.grade_value}: ${grade.count} listings`);
    }
  }
}

// Filter by parallel, grade, and listing type
const filtered = await client.marketplace.get('card_uuid', {
  parallel_id: 'parallel_uuid',
  grade_id: 'grade_uuid',
  listing_type: 'fixed',  // auction, fixed (buy-it-now), both
  limit: 25
});

Marketplace Search (Free-Text Title)

Search active marketplace listings by title when you don't have a card ID. Same flat, relevance-ranked shape as pricing search (active listings instead of completed sales):

const results = await client.marketplace.search({
  q: 'Ken Griffey Jr 1989 Upper Deck',  // Required, 2–300 characters
  listing_type: 'both',                  // Optional: auction, fixed, both
  limit: 25                              // Optional: default 100, max 500
});

if (results.data) {
  console.log(`Found ${results.data.meta.total_records} active listings`);

  for (const listing of results.data.results) {
    console.log(`$${listing.price} - ${listing.title} (${listing.source})`);
    if (listing.url) console.log(`  ${listing.url}`);
    if (listing.bid_count) console.log(`  Bids: ${listing.bid_count}`);
    if (listing.matched_card) {
      console.log(`  ${listing.matched_card.name} - ${listing.matched_card.set.name}`);
    }
  }
}

Catalog Operations

Search and retrieve cards, sets, releases, and other catalog data:

// Search for specific cards
const cards = await client.catalog.cards.list({
  year: '2023',
  manufacturer: 'Topps',
  name: 'Aaron Judge',
  take: 10,  // Limit results
  skip: 0    // Pagination offset
});

// Get a specific card by ID
const card = await client.catalog.cards.get('card_uuid');

// Search sets
const sets = await client.catalog.sets.list({
  year: '2023',
  manufacturer: 'Topps',
  take: 20
});

// Get cards in a specific set
const setCards = await client.catalog.sets.cards('set_uuid');

// Search releases (product lines like "Chrome", "Series 1")
const releases = await client.catalog.releases.list({
  name: 'Chrome',
  min_year: '2020',
  max_year: '2024'
});

// Get manufacturers
const manufacturers = await client.catalog.manufacturers();

// Get segments (Baseball, Football, etc.)
const segments = await client.catalog.segments();

// Get all parallels/variations
const parallels = await client.catalog.parallels.list();

// Get detailed parallel information by ID
const parallel = await client.catalog.parallels.get('parallel_uuid');
// Returns: id, name, description, numberedTo, isPartial, setId, setName,
//          releaseId, releaseName, releaseYear, cards (for partial parallels)

// Cards include their available parallels directly
const { data: cardDetail } = await client.catalog.cards.get('card_uuid');
if (cardDetail?.parallels && cardDetail.parallels.length > 0) {
  console.log('Available parallels:');
  cardDetail.parallels.forEach(p => {
    // Each parallel has: id, name, numberedTo (optional)
    const display = p.numberedTo ? `${p.name} /${p.numberedTo}` : p.name;
    console.log(`- ${display}`);
  });
}

// Get catalog statistics
const stats = await client.catalog.statistics();
console.log(`Total cards: ${stats.data?.cards.total}`);
console.log(`Total sets: ${stats.data?.sets.total}`);

Release Calendar

Browse upcoming and recent card product releases, sorted by release date (newest first). Useful for "coming soon" pages, recent-release feeds, and pre-order discovery.

// Next page of upcoming 2026 Panini releases
const calendar = await client.releaseCalendar.list({
  manufacturer: 'Panini',   // UUID or name (case-insensitive)
  year: '2026',
  take: 20,
  skip: 0
});

calendar.data?.release_calendar.forEach(entry => {
  const preOrder = entry.pre_order_date ?? 'N/A';
  console.log(`${entry.name} — releases ${entry.release_date} (pre-order: ${preOrder})`);
});

console.log(`Total upcoming: ${calendar.data?.total_count}`);

Filters: segment, manufacturer, year (all accept UUIDs or case-insensitive names). Each entry includes id, name, year, release_date, pre_order_date, segment_id, and manufacturer_id.

Random Catalog (Pack Opening & Discovery)

The random endpoints enable pack opening simulations and discovery features by returning random results instead of paginated sorted results:

// Pack opening simulation - Get 10 random cards from a set with parallel odds
const pack = await client.catalog.random.cards({
  setId: 'set_uuid',
  count: 10,
  includeParallels: true  // Enable parallel conversion odds
});

// Process the pack
if (pack.data?.cards) {
  pack.data.cards.forEach(card => {
    if (card.isParallel) {
      console.log(`🌟 PARALLEL: ${card.name} - ${card.parallelName}`);
      if (card.numberedTo) {
        console.log(`   Numbered to ${card.numberedTo}!`);
      }
    } else {
      console.log(`Base: ${card.name} #${card.number}`);
    }
  });
}

// Discovery feature - Get 5 random releases from 2023
const randomReleases = await client.catalog.random.releases({
  count: 5,
  year: '2023'
});

// Get random sets from a specific release
const randomSets = await client.catalog.random.sets({
  releaseId: 'release_uuid',
  count: 6
});

// Player collection building - Get random player cards
const randomPlayerCards = await client.catalog.random.cards({
  name: 'Mike Trout',
  count: 3
});

// Random cards with filters (no parallels)
const randomCards = await client.catalog.random.cards({
  year: '2024',
  manufacturer: 'manufacturer_uuid',
  count: 20
});

Parallel Odds System

When includeParallels: true is set on catalog.random.cards(), each card has a weighted probability of converting to a parallel variant:

  1. Numbered Parallels (e.g., /1, /10, /50): Individual rolls with boosted odds, rarest to most common
  2. Unlimited Parallels (e.g., Refractor, Rainbow): One collective roll, then random selection if successful
  3. Base Cards: Returned if no parallel rolls succeed

Parallel cards include additional fields:

  • isParallel: true
  • parallelId: string - UUID of the parallel
  • parallelName: string - Name like "Gold Refractor"
  • numberedTo: number | null - Serial number limit (e.g., 50 for /50)

Note: setId and releaseId are mutually exclusive on the cards endpoint.

Collection Management

Manage personal card collections with full CRUD operations:

// Create a new collection
const collection = await client.collections.create({
  name: 'My Vintage Cards',
  description: 'Pre-1980 baseball cards',
  collectorId: 'collector_uuid'  // Required: links to a collector profile
});

// Add cards to collection with detailed information.
// Signature: add(collectionId, cardItem | cardItem[])
await client.collections.cards.add(collection.data!.id, [
  {
    cardId: 'card_uuid',
    quantity: 1,
    buyPrice: '50.00',    // Store purchase price
    buyDate: '2024-01-15', // Track purchase date
    gradeId: 'grade_uuid'  // Optional: PSA 10, BGS 9.5, etc.
  }
]);

// Update collection card (e.g., after selling)
await client.collections.cards.update(
  'collection_uuid',
  'card_uuid',
  {
    sellPrice: '150.00',
    soldDate: '2024-10-01'
  }
);

// Get collection analytics
const analytics = await client.collections.analytics('collection_uuid');
console.log(`Total cards: ${analytics.data?.overview.totalCards}`);
console.log(`Total invested: ${analytics.data?.financials.totalInvested}`);
console.log(`Total realized gains: ${analytics.data?.financials.totalRealizedGains}`);

// Get collection breakdown by various categories
const breakdown = await client.collections.breakdown('collection_uuid', {
  groupBy: 'year'  // Options: 'release', 'year', 'grade', 'player', 'manufacturer'
});

// List all collections for a collector
const collections = await client.collections.list({
  collectorId: 'collector_uuid',
  take: 10
});

Binders (Collection Organization)

Organize collections into binders (subsets):

// Create a binder within a collection
const binder = await client.collections.binders.create(
  'collection_uuid',
  {
    name: 'Hall of Famers',
    description: 'Cards of HOF players'
  }
);

// Add a collection card to a binder
await client.collections.binders.cards.add(
  'collection_uuid',
  'binder_uuid',
  {
    collectionCardId: 'card1_uuid'
  }
);

// List cards in a binder
const binderCards = await client.collections.binders.cards.list(
  'collection_uuid',
  'binder_uuid'
);

Lists (Want Lists / Wishlists)

Track cards you want to acquire:

// Create a want list
const list = await client.lists.create({
  collectorId: 'collector_uuid',
  name: 'Rookies to Find',
  description: '2024 rookie cards I need'
});

// Add cards to the list
await client.lists.cards.add('list_uuid', [
  { cardId: 'card_uuid_1' },
  { cardId: 'card_uuid_2' }
]);

// Get all cards in a list
const listCards = await client.lists.cards.list('list_uuid');

// Remove card from list when acquired
await client.lists.cards.delete('list_uuid', 'card_uuid');

Grading Information

Access grading company data and grade values:

// Get all grading companies (PSA, BGS, SGC, etc.)
const companies = await client.grades.companies.list();

// Get grading types for a company (e.g., PSA Regular, PSA DNA)
const types = await client.grades.companies.types('PSA');

// Get specific grades for a grading type
const { data: grades } = await client.grades.companies.grades('PSA', 'psa_regular');

// Grades include condition descriptors
grades?.grades?.forEach(grade => {
  // grade.grade = "10", grade.condition = "GEM MINT"
  console.log(`${grade.grade} - ${grade.condition}`);
  // Output: "10 - GEM MINT", "9 - MINT", etc.
});

AI-Powered Search

Use natural language to search the catalog:

// Ask questions in natural language
const response = await client.ai.query({
  query: 'Show me Mike Trout rookie cards worth over $100'
});

// The AI understands context and intent
const response2 = await client.ai.query({
  query: 'What are the most valuable cards from 2023 Topps Chrome?'
});

Autocomplete

Provide search suggestions for users:

// Get card name suggestions (pass the query string directly)
const suggestions = await client.autocomplete.cards('aaron'); // User typed "aaron"
// Returns: ["Aaron Judge", "Hank Aaron", "Aaron Nola", ...]

// Autocomplete for other entities
const sets = await client.autocomplete.sets('chrome');
const manufacturers = await client.autocomplete.manufacturers('top');
const releases = await client.autocomplete.releases('series');
const segments = await client.autocomplete.segments('base');
const years = await client.autocomplete.years('2023');

Image Retrieval

Get card images directly:

// Get card image (returns binary data by default)
const imageData = await client.images.getCard('card_uuid');

// Get image as base64 JSON
const imageJson = await client.images.getCard('card_uuid', { format: 'json' });

// Get image with placeholder fallback (returns a default image instead of 404)
const imageWithFallback = await client.images.getCard('card_uuid', { default: 'true' });

// Get collection card images
const collectionImage = await client.collections.cards.getImage(
  'collection_uuid',
  'card_uuid'
);

// Get thumbnail
const thumbnail = await client.collections.cards.getThumbnail(
  'collection_uuid',
  'card_uuid'
);

Feedback System

Submit feedback to improve the platform:

// Report identification issues
await client.feedback.identify('identification_request_id', {
  feedback_type: 'data_error',  // 'data_error' | 'missing_data' | 'suggestion' | 'bug' | 'other'
  message: 'Wrong year detected'
});

// Submit general feedback
await client.feedback.general({
  feedback_type: 'bug',  // 'data_error' | 'missing_data' | 'suggestion' | 'bug' | 'other'
  message: 'Search not finding parallel cards'
});

// Report issues with specific entities
await client.feedback.card('card_uuid', {
  feedback_type: 'data_error',  // 'data_error' | 'missing_data' | 'suggestion' | 'bug' | 'other'
  message: 'Player name is misspelled'
});

// Look up a previously submitted item by its unique_id
const submitted = await client.feedback.get('feedback_unique_id');
console.log(submitted.data?.data.status);  // e.g. 'new'

Each response carries a review status. Newly submitted feedback starts as 'new'; every other value is set by the review team:

| Status | Meaning | |--------|---------| | new | Just submitted, not yet triaged | | under_review | Actively being investigated | | confirmed_bug | Confirmed as a bug | | enhancement_backlog | Accepted as an enhancement; in the backlog | | enhancement_planned | Accepted as an enhancement; planned | | released | The resulting change has been released | | not_an_issue | Reviewed and determined not to be an issue | | closed | Closed with no further action |

The values not_reviewed, fixed, wont_fix, duplicate, and need_info are deprecated and only appear on feedback submitted before August 2026. They remain in the exported FeedbackStatus union so an exhaustive switch still compiles.

TypeScript Support

The SDK provides complete TypeScript support with auto-generated types:

import {
  CardSightAI,
  CardSightAIError,
  IdentifyResult,
  CardDetection,
  DetectedCard,
  ParallelSuggestion,
  CardSuggestion,
  DetailedParallel,
  CatalogSearchResponse,
  SearchResult,
  SlabGradingDetail,
  SlabCompany,
  SlabGrade,
  SlabQualifier,
  SlabAutoGrade,
  ServerMessage,
  PricingResponse,
  PricingRecord,
  BulkPricingResponse,
  MarketplaceResponse,
  MarketplaceRecord,
  TimeseriesResponse,
  CandlePeriod,
  CandleStats,
  PricingSearchResponse,
  PricingSearchRecord,
  MarketplaceSearchResponse,
  MarketplaceSearchRecord,
  FeedbackResponse,
  FeedbackStatus,
  CardMagicProcessParams,
  CardMagicInfo
} from 'cardsightai';

// All methods are fully typed
const client = new CardSightAI({ apiKey: 'key' });

// TypeScript knows the exact shape of responses
const result = await client.catalog.cards.get('id');
if (result.data) {
  // TypeScript knows all available fields
  console.log(result.data.name);
  console.log(result.data.releaseYear);
  console.log(result.data.releaseName);
}

// Use types in your functions
function processDetection(detection: CardDetection): void {
  if (detection.confidence === 'High' && detection.card.id) {
    console.log(`High confidence exact match: ${detection.card.name}`);
  }
}

Advanced Type Usage

The SDK provides access to all OpenAPI-generated types for advanced use cases:

import type { paths, components } from 'cardsightai';

// Access named component schemas
type Card = components['schemas']['Card'];
type Set = components['schemas']['Set'];
type Release = components['schemas']['Release'];
type IdentifyResponse = components['schemas']['IdentifyCardResponse'];

// The SDK is path-based (consistent with openapi-fetch): access request and
// response types straight from `paths` by URL + HTTP method.
type IdentifyCardOperation = paths['/v1/identify/card']['post'];
type GetCardsOperation = paths['/v1/catalog/cards']['get'];

// Extract a specific response type from a path
type HealthResponse = paths['/health']['get']['responses']['200']['content']['application/json'];
type CardsListResponse = paths['/v1/catalog/cards']['get']['responses']['200']['content']['application/json'];

// Extract the query params for an endpoint
type CardListParams = paths['/v1/catalog/cards']['get']['parameters']['query'];

The OpenAPI specification now uses named schemas in components.schemas for better type reusability and clearer documentation. All types are structurally compatible with previous versions.

Error Handling

The SDK provides detailed error information:

import { CardSightAIError, AuthenticationError } from 'cardsightai';

try {
  const result = await client.identify.card(imageFile);
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof CardSightAIError) {
    console.error(`API Error ${error.status}: ${error.message}`);

    // Access detailed error information
    console.error('Request details:', error.request);
    console.error('Response:', error.response);

    // Some errors are retryable
    if (error.status === 503) {
      // Service temporarily unavailable, retry later
    }
  }
}

Configuration

import { CardSightAI } from 'cardsightai';

// Basic configuration
const basicClient = new CardSightAI({
  apiKey: 'your_api_key'  // Required
});

// Advanced configuration
const advancedClient = new CardSightAI({
  apiKey: 'your_api_key',
  baseUrl: 'https://api.cardsight.ai',  // Custom API endpoint
  timeout: 30000,  // Request timeout in milliseconds
  headers: {  // Additional headers
    'X-Custom-Header': 'value'
  }
});

// Using environment variables
// Set CARDSIGHTAI_API_KEY in your environment
const envClient = new CardSightAI();  // Automatically uses env variable

Environment Variables

The SDK supports the following environment variables:

CARDSIGHTAI_API_KEY=your_api_key_here  # API key for authentication

API Endpoint Coverage

The SDK provides 100% coverage of all CardSight AI REST API endpoints:

| Category | Endpoints | SDK Methods | |----------|-----------|------------| | Health | 2 | health.check(), health.checkAuth() | | Identification | 4 | identify.card(), identify.cardBySegment(), identify.sets.list(), identify.sets.check() | | Detection | 1 | detect.card() | | CardMagic | 1 | cardMagic.process() | | Catalog | 20 | catalog.search(), catalog.cards.*, catalog.sets.*, catalog.releases.*, catalog.fields.*, catalog.random.* | | Release Calendar | 1 | releaseCalendar.list() | | Collections | 23 | collections.*, collections.cards.*, collections.binders.* | | Collectors | 5 | collectors.* | | Lists | 8 | lists.*, lists.cards.* | | Pricing | 4 | pricing.get(), pricing.bulk(), pricing.timeseries(), pricing.search() | | Marketplace | 2 | marketplace.get(), marketplace.search() | | Grades | 3 | grades.companies.* | | Autocomplete | 6 | autocomplete.* | | AI | 1 | ai.query() | | Images | 3 | images.*, collections.cards.getImage() | | Feedback | 8 | feedback.* | | Subscription | 1 | subscription.get() |

Building from Source

# Clone the repository
git clone https://github.com/cardsightai/cardsightai-sdk-node.git
cd cardsightai-sdk-node

# Install dependencies
npm install

# Generate types from OpenAPI spec
npm run generate

# Build the SDK
npm run build

# Run tests
npm test

Testing

# Run unit tests
npm test

# Run integration tests (requires API key)
export CARDSIGHTAI_API_KEY=your_api_key
npm run test:integration

Browser Support

The SDK works in modern browsers with native fetch support:

<!-- Use via CDN -->
<script type="module">
  import { CardSightAI } from 'https://cdn.jsdelivr.net/npm/cardsightai/+esm';

  const client = new CardSightAI({ apiKey: 'your_api_key' });
  // Use the client...
</script>

Edge Runtime Support

The SDK is compatible with edge runtimes like Cloudflare Workers and Vercel Edge Functions:

import { CardSightAI } from 'cardsightai';

// Cloudflare Worker example
export default {
  async fetch(request: Request) {
    const client = new CardSightAI({ apiKey: 'your_api_key' });

    // Process request...
    const result = await client.catalog.cards.list({ take: 10 });

    return Response.json(result);
  }
};

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT

Support


Built with ❤️ by CardSight AI