# Using the CardSight AI API - Random Catalog (Pack Opening & Discovery)

## Overview
The Random Catalog endpoints return *random* matching results instead of paginated sorted results. They power pack-opening simulations, discovery carousels, and "card of the day" features. The random cards endpoint uniquely supports `includeParallels`, which applies weighted parallel odds so some pulls convert to parallel variants with real print-run numbers attached.

## Endpoint Details

### Random Cards
- **Method**: GET
- **URL**: `https://api.cardsight.ai/v1/catalog/cards/random`
- **Authentication**: API Key required (X-Api-Key header)

### Random Sets
- **Method**: GET
- **URL**: `https://api.cardsight.ai/v1/catalog/sets/random`
- **Authentication**: API Key required (X-Api-Key header)

### Random Releases
- **Method**: GET
- **URL**: `https://api.cardsight.ai/v1/catalog/releases/random`
- **Authentication**: API Key required (X-Api-Key header)

## Parameters (Random Cards — `GET /v1/catalog/cards/random`)

Per the OpenAPI spec, `setId` and `releaseId` are **mutually exclusive**.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| count | integer | No | Number of random cards to return. Min 1, max **200**, default **1**. If fewer cards match filters, returns all available. |
| includeParallels | boolean | No | Enable weighted parallel conversion odds. Default `false`. |
| setId | UUID | No | Limit random pull to a specific set. Mutually exclusive with `releaseId`. |
| releaseId | UUID | No | Limit random pull to a specific release. Mutually exclusive with `setId`. |
| name | string | No | Filter by player/subject name (partial, case-insensitive). |
| number | string | No | Exact card number match. |
| year | string | No | Exact release year (e.g. `"2023"`). Overrides `min_year`/`max_year` when set. |
| min_year | string | No | Release year lower bound (inclusive). |
| max_year | string | No | Release year upper bound (inclusive). |
| setName | string | No | Filter by set name (partial, case-insensitive). |
| releaseName | string | No | Filter by release name (partial, case-insensitive). |
| manufacturer | string | No | Manufacturer UUID **or** exact name (e.g. `"Topps"`). |
| attributeId | UUID | No | Filter by attribute UUID (Rookie Card, Autograph, etc.). |
| attributeShortName | string | No | Filter by attribute short code, e.g. `"RC"`. Case-sensitive. |
| field | string[] | No | Metadata key/value filters. Repeat param; all must match (AND). Format: `KEY:VALUE`, e.g. `field=CASTING_COST:2&field=COLOR:Red`. Keys normalized to uppercase; values match case-insensitively. |
| sort | string | No | One of `name`, `release`, `set`, `year`. |
| order | string | No | `asc` (default) or `desc`. |

## Parameters (Random Sets — `GET /v1/catalog/sets/random`)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| count | integer | No | Number of random sets. Min 1, max 200, default 1. |
| releaseId | UUID | No | Limit random pull to a specific release. |
| name | string | No | Filter by set name (partial, case-insensitive). |
| year | string | No | Exact release year. Overrides `min_year`/`max_year` when set. |
| min_year | string | No | Release year lower bound (inclusive). |
| max_year | string | No | Release year upper bound (inclusive). |
| manufacturer | string | No | Manufacturer UUID or exact name. |
| is_identifiable | string | No | `"true"` returns only AI-identifiable sets, `"false"` returns non-identifiable. |
| sort | string | No | `name` or `year`. |
| order | string | No | `asc` (default) or `desc`. |

## Parameters (Random Releases — `GET /v1/catalog/releases/random`)

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| count | integer | No | Number of random releases. Min 1, max 200, default 1. |
| segment | string | No | Segment UUID or exact name (case-insensitive). |
| manufacturer | string | No | Manufacturer UUID or exact name (case-insensitive). |
| year | string | No | Exact release year. Overrides `min_year`/`max_year` when set. |
| min_year | string | No | Release year lower bound (inclusive). |
| max_year | string | No | Release year upper bound (inclusive). |
| name | string | No | Filter by release name (partial, case-insensitive). |
| is_identifiable | string | No | `"true"` returns releases with at least one identifiable set. |
| sort | string | No | `year` or `name`. |
| order | string | No | `asc` (default) or `desc`. |

## Using the CardSight AI SDK (Recommended)

### Node.js / TypeScript
```typescript
import { CardSightAI } from 'cardsightai'

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

// Pack opening simulation with parallel odds
const pack = await client.catalog.random.cards({
  setId: '550e8400-e29b-41d4-a716-446655440003',
  count: 10,
  includeParallels: true,
})

if (!pack.error) {
  for (const card of pack.data.cards) {
    if (card.isParallel) {
      const stamp = card.numberedTo ? ` /${card.numberedTo}` : ''
      console.log(`PARALLEL: ${card.name} — ${card.parallelName}${stamp}`)
    } else {
      console.log(`Base: ${card.name} #${card.number ?? ''}`)
    }
  }
}

// Discovery — five random 2023 releases (year is a STRING)
const randomReleases = await client.catalog.random.releases({
  count: 5,
  year: '2023',
})

// Random sets from a specific release
const randomSets = await client.catalog.random.sets({
  releaseId: '550e8400-e29b-41d4-a716-446655440001',
  count: 6,
})

// Player-focused random pulls — the param is `name`, not playerName
const playerRandom = await client.catalog.random.cards({
  name: 'Mike Trout',
  count: 3,
})

// Flexible-metadata filter — Pokémon Fire-type cards with HP 120
// `field` is repeatable; each entry is KEY:VALUE, AND-combined
const firePokemon = await client.catalog.random.cards({
  count: 10,
  field: ['TYPE:Fire', 'HP:120'],
})
```

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

client = CardSightAI(api_key='your-api-key')
pack = client.catalog.random.cards(set_id='550e8400-...', count=10, include_parallels=True)

for card in pack.cards:
    if card.is_parallel:
        print(f"PARALLEL: {card.name} — {card.parallel_name}")
    else:
        print(f"Base: {card.name}")
```

## Direct API Call (cURL)
```bash
# Pack opening with parallel odds
curl -X GET "https://api.cardsight.ai/v1/catalog/cards/random?setId=550e8400-e29b-41d4-a716-446655440003&count=10&includeParallels=true" \
  -H "X-Api-Key: your-api-key"

# Player-focused random (name, not playerName)
curl -X GET "https://api.cardsight.ai/v1/catalog/cards/random?name=Mike%20Trout&count=3" \
  -H "X-Api-Key: your-api-key"

# Multiple field filters are AND-combined — repeat the parameter
curl -X GET "https://api.cardsight.ai/v1/catalog/cards/random?count=10&field=TYPE:Fire&field=HP:120" \
  -H "X-Api-Key: your-api-key"

# Random sets from 2023
curl -X GET "https://api.cardsight.ai/v1/catalog/sets/random?year=2023&count=6" \
  -H "X-Api-Key: your-api-key"

# Random 2023 releases
curl -X GET "https://api.cardsight.ai/v1/catalog/releases/random?count=5&year=2023" \
  -H "X-Api-Key: your-api-key"
```

## Example Response (Random Cards with Parallel Pull)
```json
{
  "cards": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440010",
      "releaseId": "550e8400-e29b-41d4-a716-446655440001",
      "setId": "550e8400-e29b-41d4-a716-446655440003",
      "name": "Shohei Ohtani",
      "number": "1",
      "setName": "Base Set",
      "releaseName": "2023 Topps Chrome Baseball",
      "releaseYear": "2023"
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440011",
      "releaseId": "550e8400-e29b-41d4-a716-446655440001",
      "setId": "550e8400-e29b-41d4-a716-446655440003",
      "name": "Mike Trout",
      "number": "27",
      "setName": "Base Set",
      "releaseName": "2023 Topps Chrome Baseball",
      "releaseYear": "2023",
      "isParallel": true,
      "parallelId": "550e8400-e29b-41d4-a716-446655440099",
      "parallelName": "Gold Refractor",
      "numberedTo": 50
    }
  ],
  "count": 10
}
```

## Parallel Odds Model
When `includeParallels=true` is set on `random.cards`, each card runs through a weighted roll:

1. **Numbered Parallels** (e.g. /1, /10, /50) — individual rolls, rarest to most common, boosted odds relative to base rates
2. **Unlimited Parallels** (e.g. Refractor, Rainbow) — one collective roll; if it succeeds, one parallel is randomly chosen
3. **Base Card** — returned if no parallel roll succeeds

Parallel pulls carry four extra fields on the card:

| Field | Type | Description |
|-------|------|-------------|
| isParallel | boolean | Always true for parallel pulls |
| parallelId | UUID | Parallel variant ID |
| parallelName | string | Human-readable parallel name |
| numberedTo | number/null | Print-run limit (null for unlimited parallels) |

## Common Use Cases
- Building a pack-opening simulator or card-of-the-day feature
- Seeding a homepage carousel with fresh picks on every render
- Discovery screens highlighting unfamiliar releases or sets
- Generating sample imagery for a player, manufacturer, or year
- Teaching users how parallel odds work in real products

## 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.
- `setId` and `releaseId` are **mutually exclusive** on `random.cards` — don't pass both.
- `count` minimum is 1, maximum is **200**, server default is **1**. Always pass `count` explicitly; defaulting to 1 is rarely what a pack-opening UX wants.
- If fewer cards match filters than requested, the server returns what's available — always trust the `count` field in the response rather than assuming you got `count` items.
- Without `includeParallels=true`, `random.cards` only returns base cards; `isParallel` is not set on base results.
- Numbered parallels use the same `numberedTo` semantics as the rest of the API (`/50` means print run of 50). Unlimited parallels return `numberedTo: null`.
- To filter `random.cards` by player, pass `name`, NOT `playerName`. Same endpoint uses `manufacturer` (UUID or name), not `manufacturerId`.
- All three endpoints accept `year` as a **string** (`"2023"`), not a number.
- `field` on `random.cards` enables cross-TCG metadata filtering: repeat the param (`field=HP:120&field=RARITY:Rare`), AND-combined, keys normalized to uppercase, values case-insensitive.
- `order` defaults to `asc` on all three endpoints; pass `desc` explicitly when you want the reverse.
- Use `random.sets` and `random.releases` for "discover something new" UX — they are not substitutes for paginated list endpoints.
