# Using the CardSight AI API - Card Identification API

## Overview
Identify trading cards from photos using CardSight AI's computer vision. Upload an image and get back detailed card information including player name, year, manufacturer, release, and set. The AI can detect multiple cards in a single image and automatically detects each card's segment (sport/category), so a single photo may contain cards from different segments (e.g., baseball and basketball). It also automatically detects graded slabs, identifying the grading company (PSA, BGS, CGC, SGC, TAG, and others). For exact matches it reports possible parallel variants (Refractors, Prizms, numbered parallels) as a ranked list with a confidence tier on each candidate (beta, live for baseball), and on Medium/Low confidence detections it suggests alternative cards so you can pick the right one. Supports baseball, football, basketball, hockey, MMA, Pokémon, Magic: The Gathering, and One Piece, with more segments coming soon.

## Endpoint Details
- **Mixed / Auto-detect**: POST `https://api.cardsight.ai/v1/identify/card` — detects each card's segment automatically. Use this **only when you don't know the segment** or a single photo genuinely contains cards from different segments (e.g., baseball and Pokémon together). Because the AI has to infer each card's segment before identifying it, results can be **less accurate** than the segment-specific route.
- **Segment-Specific**: POST `https://api.cardsight.ai/v1/identify/card/{segment}` (e.g., `/v1/identify/card/football`) — scopes identification to one known segment. **Strongly recommended whenever you know the segment** (Baseball, Pokémon, One Piece, etc.) for enhanced accuracy and speed.
- **Authentication**: API Key required (X-Api-Key header)
- **Content-Type**: `multipart/form-data` or direct binary (`image/jpeg`, `image/png`, `image/webp`)

> **Recommended:** If you know the segment you're scanning, use the segment-specific route (`/v1/identify/card/{segment}`) — it delivers enhanced accuracy. The mixed/auto-detect route (`/v1/identify/card`) has to infer each card's segment before identifying it, which can lower accuracy, so reach for it only when the segment is unknown or a single photo genuinely mixes segments.

> **Note:** Segment names in the URL path are case-insensitive (e.g., `football`, `Football`, and `FOOTBALL` are all valid). Short codes work too — MMA cards use `/v1/identify/card/mma`.

## Constraints
- **Maximum file size**: 20MB
- **Supported formats**: JPEG, PNG, WebP, HEIF, HEIC

## Using the CardSight AI SDK (Recommended)

### Node.js / TypeScript
```typescript
import {
  CardSightAI,
  getBestParallelSuggestion,
  getParallelSuggestions,
  formatParallelSuggestion,
  hasSuggestions,
  getSuggestions,
  formatCardDisplay
} from 'cardsightai'
import * as fs from 'fs'

const client = new CardSightAI({ apiKey: 'your-api-key' })

// From a file path
const imageBuffer = fs.readFileSync('path/to/card-image.jpg')
const file = new File([imageBuffer], 'card.jpg', { type: 'image/jpeg' })

// Mixed identification — auto-detects each card's segment; use only when the segment is unknown or a photo mixes segments
const response = await client.identify.card(file)

// Segment-specific identification (e.g., football) — strongly recommended when you know the segment (more accurate)
const footballResponse = await client.identify.cardBySegment('football', file)

// Always check for errors
if (response.error) {
  console.error('Error:', response.error)
} else {
  console.log('Success:', response.data.success)
  console.log('Request ID:', response.data.requestId)
  console.log('Processing Time:', response.data.processingTime, 'ms')

  // Process each detected card
  for (const detection of response.data.detections || []) {
    console.log('Confidence:', detection.confidence)

    // card is always present — completeness varies by match level
    const card = detection.card
    console.log('Year:', card.year)
    console.log('Release:', card.releaseName)

    if (card.id) {
      // Exact card match found in catalog
      console.log('Card ID:', card.id)
      console.log('Player:', card.name)
      console.log('Number:', card.number)
    }

    // Parallel candidates (beta) — ranked best match first, each with its own confidence.
    // Absent entirely when there is no parallel evidence (a base card).
    const bestParallel = getBestParallelSuggestion(detection)
    if (bestParallel) {
      console.log('Best parallel:', formatParallelSuggestion(bestParallel))
      // e.g. "Gold Refractor /50 - High confidence"
      if (bestParallel.confidence === 'High') {
        console.log('Confirmed parallel ID:', bestParallel.id)
      }
      for (const candidate of getParallelSuggestions(detection)) {
        console.log('  candidate:', formatParallelSuggestion(candidate))
      }
    }

    // Alternative matches — only present on Medium/Low confidence detections.
    // 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'})`)
      }
    }

    // Check if a graded slab was detected
    if (detection.grading) {
      console.log('Graded by:', detection.grading.company.name)
      console.log('Slab confidence:', detection.grading.confidence)
    }
  }
}
```

### Python
```python
from cardsightai import CardSightAI

client = CardSightAI(api_key='your-api-key')

# Mixed identification — auto-detects each card's segment; use only when the segment is unknown or a photo mixes segments
with open('path/to/card-image.jpg', 'rb') as f:
    response = client.identify.card(f)

# Segment-specific identification (e.g., football) — strongly recommended when you know the segment (more accurate)
with open('path/to/card-image.jpg', 'rb') as f:
    response = client.identify.card_by_segment('football', f)

print(f"Success: {response.success}")
print(f"Request ID: {response.request_id}")

for detection in response.detections:
    print(f"Confidence: {detection.confidence}")

    # card is always present — completeness varies by match level
    card = detection.card
    print(f"Year: {card.year}")
    print(f"Release: {card.release_name}")

    if card.id:
        print(f"Found: {card.name} - {card.year}")

    # Check if a graded slab was detected
    if detection.grading:
        print(f"Graded by: {detection.grading.company.name}")
        print(f"Slab confidence: {detection.grading.confidence}")
```

## Direct API Call (cURL)

### Mixed / Auto-detect (unknown or mixed segments)
```bash
curl -X POST "https://api.cardsight.ai/v1/identify/card" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: multipart/form-data" \
  -F "image=@path/to/card-image.jpg"
```

### Segment-Specific (Football)
```bash
curl -X POST "https://api.cardsight.ai/v1/identify/card/football" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: multipart/form-data" \
  -F "image=@path/to/card-image.jpg"
```

## Example Response (Exact Card Match)
```json
{
  "success": true,
  "requestId": "98cc3c7d-09a1-4433-aa9a-1939a419d411",
  "detections": [
    {
      "confidence": "High",
      "card": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "segmentId": "660e8400-e29b-41d4-a716-446655440001",
        "releaseId": "770e8400-e29b-41d4-a716-446655440002",
        "setId": "880e8400-e29b-41d4-a716-446655440003",
        "name": "Shohei Ohtani",
        "number": "1",
        "year": "2023",
        "manufacturer": "Topps",
        "releaseName": "Topps Chrome",
        "setName": "Base Set",
        "parallelSuggestions": [
          {
            "id": "550e8400-e29b-41d4-a716-446655440001",
            "name": "Refractor",
            "numberedTo": 299,
            "confidence": "High"
          }
        ]
      },
      "grading": {
        "confidence": "High",
        "company": {
          "id": "11bfc982-39bc-4813-99fc-70483a4dd653",
          "name": "PSA"
        }
      }
    }
  ],
  "processingTime": 1215
}
```

## Example Response (Medium Confidence with Alternatives and Several Possible Parallels)
```json
{
  "success": true,
  "requestId": "c3d4e5f6-a7b8-9012-cdef-123456789abc",
  "detections": [
    {
      "confidence": "Medium",
      "card": {
        "id": "550e8400-e29b-41d4-a716-446655440010",
        "segmentId": "660e8400-e29b-41d4-a716-446655440001",
        "releaseId": "770e8400-e29b-41d4-a716-446655440002",
        "setId": "880e8400-e29b-41d4-a716-446655440003",
        "name": "Mike Trout",
        "number": "27",
        "year": "2023",
        "manufacturer": "Topps",
        "releaseName": "Topps Chrome",
        "setName": "Base Set",
        "parallelSuggestions": [
          { "id": "550e8400-e29b-41d4-a716-446655440020", "name": "Refractor", "confidence": "Medium" },
          { "id": "550e8400-e29b-41d4-a716-446655440021", "name": "Prism Refractor", "confidence": "High" },
          { "id": "550e8400-e29b-41d4-a716-446655440022", "name": "Sepia Refractor" }
        ],
        "suggestions": [
          {
            "id": "550e8400-e29b-41d4-a716-446655440030",
            "segmentId": "660e8400-e29b-41d4-a716-446655440001",
            "releaseId": "770e8400-e29b-41d4-a716-446655440004",
            "setId": "880e8400-e29b-41d4-a716-446655440005",
            "name": "Mike Trout",
            "number": "27",
            "year": "2023",
            "manufacturer": "Topps",
            "releaseName": "Topps Chrome Update",
            "setName": "Base Set"
          }
        ]
      }
    }
  ],
  "processingTime": 1340
}
```

> **Note:** In the example above the engine ranks "Refractor" first, but "Prism Refractor" carries the higher confidence — ranking and confidence are independent. "Sepia Refractor" has no `confidence`, which means it was not assessed, not that it is Low.

## Example Response (Set-Level Match with Graded Slab)
```json
{
  "success": true,
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "detections": [
    {
      "confidence": "Medium",
      "card": {
        "segmentId": "660e8400-e29b-41d4-a716-446655440001",
        "releaseId": "770e8400-e29b-41d4-a716-446655440002",
        "setId": "880e8400-e29b-41d4-a716-446655440003",
        "year": "2023",
        "manufacturer": "Topps",
        "releaseName": "Topps Chrome",
        "setName": "Base Set"
      },
      "grading": {
        "confidence": "Medium",
        "company": {
          "name": "BGS"
        }
      }
    }
  ],
  "processingTime": 1215
}
```

> **Note:** The `grading` field is only present when a graded slab is detected in the image. If the card is not in a slab, this field will be absent.

## Response Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| success | boolean | Yes | Whether identification completed successfully |
| requestId | string | Yes | Unique ID for tracking this request |
| detections | array | No | Array of detected cards (can be multiple) |
| processingTime | number | No | Processing time in milliseconds |

### Detection Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| confidence | string | Yes | "High" (90-100%), "Medium" (75-89%), or "Low" (50-74%) |
| card | object | Yes | Card details — completeness varies by match level |
| grading | object | No | Grading company info — only present when a graded slab is detected |

### Card Object (always present, completeness varies)

| Field | Type | Description |
|-------|------|-------------|
| id | string | UUID of identified card. Present only for exact card matches. |
| segmentId | string | UUID of the segment. Present for exact card and set-level matches. |
| releaseId | string | UUID of the release. Present for exact card and set-level matches. |
| setId | string | UUID of the set. Present for exact card and set-level matches. |
| year | string | Release year from catalog |
| manufacturer | string | Card manufacturer (Topps, Panini, etc.) |
| releaseName | string | Release/product name |
| setName | string | Set name |
| name | string | Player or subject name. Present only for exact card matches. |
| number | string | Card number. Present only for exact card matches. |
| description | string | Descriptive text for the card when available. Omitted if no description exists. |
| numberedTo | number | Print run for numbered cards (e.g., 25 for a /25 card). Omitted if the card is not numbered. |
| attributes | array | Notable attributes of the card (e.g., ["Rookie", "Autograph"]). Omitted if the card has no attributes. |
| variationOf | string | UUID of the parent card when this card is a variation. Omitted if the card is not a variation. |
| parallelSuggestions | array | (beta) Possible parallels, best match first, each with an optional `confidence`. Present whenever there is any parallel evidence; omitted for base cards. |
| suggestions | array | Alternative card matches, best match first. Each entry is a full card record with the same fields as `card`. Included only when `confidence` is Medium or Low. |
| fields | array | Key-value card properties tailored to the segment. Omitted when the card has no fields. |

### Field Values Object (inside `fields`)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| key | string | Yes | Property name (e.g., "HP", "RARITY", "ARTIST", "MANA_COST") |
| value | string | Yes | Property value |

TCG cards (Pokémon, One Piece, Magic: The Gathering) may include a `CARD_LANGUAGE` entry whose
value is the ISO 639-1 code of the scanned card's language (e.g., `"ja"`, `"en"`). It is present
only when a language is detected, so always handle its absence.

```json
"fields": [
  { "key": "HP", "value": "120" },
  { "key": "CARD_LANGUAGE", "value": "ja" }
]
```

### Parallel Suggestion Object (inside `parallelSuggestions`, beta)

The array is the identification engine's ranking, best match first. `confidence` is the strength of evidence behind that individual entry and does not re-order the list, so a later entry may carry a higher confidence than an earlier one. You get a single High-confidence entry when exactly one parallel was identified, or several entries when more than one remains possible. Base cards with no parallel evidence have no `parallelSuggestions` at all. Parallel identification is currently in beta and has launched for baseball.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | UUID | Yes | Unique identifier for the parallel type (the variant, not an individual card) |
| name | string | Yes | Parallel name (e.g., "Gold Refractor", "Black Prizm") |
| description | string | No | Additional details about the parallel |
| numberedTo | number | No | Limited print run number (e.g., 299 for /299) |
| isPartial | boolean | No | True if parallel only applies to specific cards in the set |
| cards | array | No | Card UUIDs that have this parallel (only when isPartial is true) |
| confidence | string | No | "High", "Medium", or "Low" — how strongly this parallel is supported for the scanned card. A missing value means not assessed, not Low. |

### Card Suggestion Object (inside `suggestions`)

Each suggestion is a full card record with the same fields as the Card Object above (`id`, `segmentId`, `releaseId`, `setId`, `year`, `manufacturer`, `releaseName`, `setName`, `name`, `number`, `description`, `numberedTo`, `attributes`, `variationOf`, `fields`). Completeness varies the same way: an entry with an `id` is an exact card, an entry with only `setId`/`releaseId` is a set-level alternative. The array is only present when the detection `confidence` is Medium or Low.

### Grading Object (when a graded slab is detected)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| confidence | string | Yes | How confident the AI is that a slab was detected: "High", "Medium", or "Low" |
| company | object | Yes | The grading company that graded the card |

### Company Object (inside grading)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | string | No | UUID of the grading company (when identified in the catalog) |
| name | string | Yes | Name of the grading company (e.g., "PSA", "BGS", "CGC", "SGC", "TAG") |

## Common Use Cases
- Identify cards from photos for inventory management
- Quick card lookup by image instead of manual search
- Batch processing of card collections
- Mobile app card scanning features
- Identify cards from a known segment (Baseball, Pokémon, One Piece, etc.) using the segment-specific endpoint for enhanced accuracy
- Identify mixed lots or photos spanning multiple segments in a single request via the auto-detect endpoint
- Detect graded slabs and identify the grading company automatically

## Tips for AI Assistants
- **Working with IDs:** These UUIDs (`releaseId`, `setId`, and card `id`) let you look up catalog entities and chain calls together. Caching them briefly to fulfill a user's request is expected and fine — it's why the image and other free endpoints exist. If you need to save cards for the long term, add them to a **list or collection**: those are your own stable resources, purpose-built for holding cards. If you need a bulk or offline copy of catalog data, reach out to us at `support@cardsight.ai` so we can help with your use case.
- The SDK returns `{ data, error }` - always check for errors first
- Multiple cards can be detected in a single image
- `card` is always present in each detection — no need to check for its existence
- Completeness of `card` indicates match level: all fields = exact match, IDs/year/release only = set-level match, empty object = no match
- There is no `aiIdentification` field — all identification data is in the `card` object
- The `confidence` field indicates match certainty: "High" (90-100%), "Medium" (75-89%), "Low" (50-74%)
- `card.parallelSuggestions` (beta) replaced the old single `card.parallel` object in API v4 — it is a ranked array, best match first. Use `parallelSuggestions[0]` (or the SDK's `getBestParallelSuggestion()`) for the engine's top pick, and check its `confidence === "High"` before treating a parallel as confirmed
- Ranking and confidence are independent — a lower-ranked parallel can carry a higher confidence, and a missing `confidence` means "not assessed", not Low
- Base cards have no `parallelSuggestions` array at all — always check for its presence
- `card.suggestions` lists alternative cards (full card records, best match first) and is only sent on Medium/Low confidence detections — offer them to the user as "could also be" choices rather than silently picking the top match
- Parallel identification is in beta and currently live for baseball; expect it to expand to other segments
- The `grading` field is only present when a graded slab is detected — always check `if (detection.grading)` before accessing
- `grading.company.name` contains the grading company name (e.g., "PSA", "BGS", "CGC", "SGC", "TAG")
- `grading.company.id` is optional — it's present when the company is recognized in the catalog
- Slab detection works automatically — no extra parameters needed
- Use `requestId` for debugging and support requests
- Compress large images before uploading for faster processing
- **Prefer the segment-specific route whenever you know the segment.** If you know what you're scanning (Baseball, Pokémon, One Piece, etc.), call `client.identify.cardBySegment(segment, file)` → `POST /v1/identify/card/{segment}`. Scoping to a known segment gives noticeably more accurate results (and is faster).
- **Use the base/mixed route (`client.identify.card(file)` → `POST /v1/identify/card`) only when you have to** — i.e., the segment is unknown or a single photo genuinely contains cards from different segments. It auto-detects each card's segment, but because it has to infer the segment first, accuracy can be lower than the segment-specific route.
- Segment names are case-insensitive in the URL path
- The multipart form field is named `image` (e.g., `-F "image=@card.jpg"`)
