# Using the Population Reports API

## Overview

CardSight AI's Population Reports return graded population counts for cards, sets, and releases — sourced directly from each grading company (PSA, BGS, CGC, SGC, TAG, etc.). Three endpoints share the same family of schemas:

- `GET /v1/population/card/{card_id}` — population for a single card, broken down by **base** and each **parallel** that has data.
- `GET /v1/population/set/{set_id}` — population for an entire set, sourced from each grading company's authoritative per-set figures.
- `GET /v1/population/release/{release_id}` — population for an entire release, with both release-wide rollups and a per-set breakdown.

**Important conventions for these endpoints:**

- All field names in the response are **`snake_case`** (e.g. `card_id`, `total_population`, `qualified_population`, `last_synced_at`, `grading_companies`, `grading_types`). This differs from many catalog endpoints that mix camelCase — do not convert these to camelCase.
- Populations are nested **grading company → grading type → grade**.
- Every grade defined for a present company is enumerated. Grades with no recorded data appear as `population: 0, qualified_population: 0` — filter client-side if you only want non-zero rows.
- `population` counts unqualified examples; `qualified_population` counts examples graded with a qualifier (e.g. PSA "8Q"). Both are reported per grade.
- A grading company appears in a set/release response only when its source set has a confirmed link. A release with no confirmed links returns an empty `grading_companies` array.
- These endpoints echo only `*_id` and `*_name` fields. For full card / set / release metadata, call the matching catalog endpoint.

## Endpoint Details

### 1. Card Population

- **Method:** GET
- **URL:** `https://api.cardsight.ai/v1/population/card/{card_id}`
- **Authentication:** API Key required (`X-Api-Key` header)

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `card_id` | path | UUID | Yes | The card UUID |
| `grading_company_id` | query | UUID | No | Limit the response to a single grading company |

### 2. Set Population

- **Method:** GET
- **URL:** `https://api.cardsight.ai/v1/population/set/{set_id}`
- **Authentication:** API Key required

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `set_id` | path | UUID | Yes | The set UUID |
| `grading_company_id` | query | UUID | No | Limit the response to a single grading company |

### 3. Release Population

- **Method:** GET
- **URL:** `https://api.cardsight.ai/v1/population/release/{release_id}`
- **Authentication:** API Key required

| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
| `release_id` | path | UUID | Yes | The release UUID |
| `grading_company_id` | query | UUID | No | Limit the response to a single grading company |

## Using the CardSight AI SDK (Recommended)

### Node.js / TypeScript

```typescript
import { CardSightAI } from 'cardsightai'

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

// Card population
const cardResp = await client.population.card('550e8400-e29b-41d4-a716-446655440000')
if (cardResp.error) {
  console.error('Error:', cardResp.error)
} else {
  const data = cardResp.data
  console.log(`${data.card_name} — ${data.total_population} total graded`)

  if (data.base) {
    console.log(`Base card: ${data.base.total_population} graded`)
    for (const company of data.base.grading_companies) {
      console.log(`  ${company.name}: ${company.total_population}`)
    }
  }

  for (const parallel of data.parallels) {
    console.log(`${parallel.parallel_name}: ${parallel.total_population} graded`)
  }
}

// Set population (with grading company filter)
const setResp = await client.population.set(
  '550e8400-e29b-41d4-a716-446655440001',
  { grading_company_id: '550e8400-e29b-41d4-a716-4466554400ff' },
)
if (!setResp.error) {
  for (const company of setResp.data.grading_companies) {
    console.log(`${company.name}: ${company.total_population} graded in set`)
  }
}

// Release population
const releaseResp = await client.population.release(
  '550e8400-e29b-41d4-a716-446655440002',
)
if (!releaseResp.error) {
  for (const company of releaseResp.data.grading_companies) {
    console.log(`${company.name} across release: ${company.total_population}`)
    for (const set of company.sets) {
      console.log(`  ${set.set_name}: ${set.total_population}`)
    }
  }
}
```

### Python

```python
from cardsightai import CardSightAI

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

resp = client.population.card("550e8400-e29b-41d4-a716-446655440000")
if resp.error:
    print("Error:", resp.error)
else:
    data = resp.data
    print(f"{data['card_name']} — {data['total_population']} total graded")
    for parallel in data["parallels"]:
        print(f"  {parallel['parallel_name']}: {parallel['total_population']}")
```

## Direct REST (cURL)

```bash
# Card population
curl -X GET "https://api.cardsight.ai/v1/population/card/550e8400-e29b-41d4-a716-446655440000" \
  -H "X-API-Key: YOUR_API_KEY"

# Set population, filtered to a single grading company
curl -X GET "https://api.cardsight.ai/v1/population/set/550e8400-e29b-41d4-a716-446655440001?grading_company_id=550e8400-e29b-41d4-a716-4466554400ff" \
  -H "X-API-Key: YOUR_API_KEY"

# Release population
curl -X GET "https://api.cardsight.ai/v1/population/release/550e8400-e29b-41d4-a716-446655440002" \
  -H "X-API-Key: YOUR_API_KEY"
```

## Response Structure

### Shared building blocks

All three endpoints share these nested object shapes.

**`PopulationGradeEntry`** — required: `id`, `grade`, `population`, `qualified_population`

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | UUID | Yes | Grade UUID |
| `grade` | string | Yes | Grade value (e.g. `"10"`, `"9.5"`) |
| `condition` | string \| null | No | Condition descriptor (e.g. `"Gem Mint"`) |
| `population` | integer | Yes | Count of unqualified graded examples |
| `qualified_population` | integer | Yes | Count of qualified graded examples (e.g. PSA `"8Q"`) |

**`PopulationGradingType`** — required: `id`, `name`, `total_population`, `grades`

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | UUID | Yes | Grading type UUID |
| `name` | string | Yes | Grading type name (e.g. `"Standard"`) |
| `total_population` | integer | Yes | Sum of `population + qualified_population` across every grade for this type |
| `grades` | `PopulationGradeEntry[]` | Yes | Every grade for this type (zero-filled when no data) |

**Grading-company population** — used in two flavors:

- `VariantGradingCompanyPopulation` — used in card responses (per base / per parallel).
- `AggregatedGradingCompanyPopulation` — used in set responses; same shape.

Both have required fields: `id`, `name`, `last_synced_at`, `total_population`, `grading_types`.

| Field | Type | Description |
|---|---|---|
| `id` | UUID | Grading company UUID |
| `name` | string | Grading company name (e.g. `"PSA"`) |
| `last_synced_at` | ISO 8601 string | Most recent population sync timestamp for this company in this aggregation |
| `total_population` | integer | Sum across every grading type for this company in this aggregation |
| `grading_types` | `PopulationGradingType[]` | Per-type breakdown |

### Card endpoint — `CardPopulationResponse`

Required: `card_id`, `card_name`, `total_population`, `parallels`.

| Field | Type | Required | Description |
|---|---|---|---|
| `card_id` | UUID | Yes | Card UUID (echoed from the request) |
| `card_name` | string | Yes | Card name (echoed for visual confirmation only) |
| `total_population` | integer | Yes | Top-level total: base + every parallel, every company, every grade |
| `base` | `CardBasePopulation` | **No** | Population for the base card (no parallel applied). **Absent when no company has data for the base** |
| `parallels` | `CardParallelPopulation[]` | Yes | One entry per parallel that has any data. Parallels with no data are omitted. May be `[]` |

`CardBasePopulation` (required: `total_population`, `grading_companies`):

| Field | Type | Description |
|---|---|---|
| `total_population` | integer | Total across all grading companies for the base card |
| `grading_companies` | `VariantGradingCompanyPopulation[]` | Per-company breakdown |

`CardParallelPopulation` (required: `parallel_id`, `parallel_name`, `total_population`, `grading_companies`):

| Field | Type | Description |
|---|---|---|
| `parallel_id` | UUID | Parallel UUID |
| `parallel_name` | string | Parallel name (echoed for visual confirmation only) |
| `total_population` | integer | Total across all companies for this parallel |
| `grading_companies` | `VariantGradingCompanyPopulation[]` | Per-company breakdown |

#### Example card response

```json
{
  "card_id": "550e8400-e29b-41d4-a716-446655440000",
  "card_name": "Ken Griffey Jr. — 1989 Upper Deck #1",
  "total_population": 12340,
  "base": {
    "total_population": 11200,
    "grading_companies": [
      {
        "id": "550e8400-e29b-41d4-a716-4466554400ff",
        "name": "PSA",
        "last_synced_at": "2026-04-30T14:22:09Z",
        "total_population": 9800,
        "grading_types": [
          {
            "id": "550e8400-e29b-41d4-a716-446655441111",
            "name": "Standard",
            "total_population": 9800,
            "grades": [
              { "id": "...", "grade": "10", "population": 412, "qualified_population": 7 },
              { "id": "...", "grade": "9", "population": 5120, "qualified_population": 38 }
            ]
          }
        ]
      }
    ]
  },
  "parallels": [
    {
      "parallel_id": "550e8400-e29b-41d4-a716-446655442222",
      "parallel_name": "Refractor",
      "total_population": 1140,
      "grading_companies": [ /* same shape as base.grading_companies */ ]
    }
  ]
}
```

### Set endpoint — `SetPopulationResponse`

Required: `set_id`, `set_name`, `grading_companies`.

| Field | Type | Required | Description |
|---|---|---|---|
| `set_id` | UUID | Yes | Set UUID (echoed from the request) |
| `set_name` | string | Yes | Set name (echoed for visual confirmation only) |
| `grading_companies` | `AggregatedGradingCompanyPopulation[]` | Yes | One entry per grading company with confirmed data. May be `[]` |

> **No top-level `total_population` field.** If you need a set-wide total, sum `grading_companies[*].total_population` yourself.

### Release endpoint — `ReleasePopulationResponse`

Required: `release_id`, `release_name`, `grading_companies`.

| Field | Type | Required | Description |
|---|---|---|---|
| `release_id` | UUID | Yes | Release UUID (echoed from the request) |
| `release_name` | string | Yes | Release name (echoed for visual confirmation only) |
| `grading_companies` | `ReleaseGradingCompanyPopulation[]` | Yes | One entry per grading company with confirmed data. May be `[]` |

`ReleaseGradingCompanyPopulation` (required: `id`, `name`, `last_synced_at`, `total_population`, `grading_types`, `sets`):

| Field | Type | Description |
|---|---|---|
| `id` | UUID | Grading company UUID |
| `name` | string | Grading company name |
| `last_synced_at` | ISO 8601 string | Most recent population sync timestamp |
| `total_population` | integer | Total across every set and grading type in this release for this company |
| `grading_types` | `PopulationGradingType[]` | Release-wide rollup across all sets |
| `sets` | `ReleaseSetRollup[]` | Per-set rollup. Sets with no data for this company are omitted |

`ReleaseSetRollup` (required: `set_id`, `set_name`, `total_population`, `grading_types`):

| Field | Type | Description |
|---|---|---|
| `set_id` | UUID | Set UUID |
| `set_name` | string | Set name (echoed for visual confirmation only) |
| `total_population` | integer | Total across all grading types for this set within the company |
| `grading_types` | `PopulationGradingType[]` | Per-type breakdown for this set within the company |

> **No top-level `total_population` field.** Sum `grading_companies[*].total_population` for a release-wide total.

## Common Use Cases

- Show graded population alongside a card's catalog details to inform pricing and rarity.
- Surface set-level scarcity by showing how many examples have been graded across all companies.
- Compare grading company coverage for a release (some releases will have PSA data only, others will include BGS / SGC / CGC).
- Power "this card is rare in PSA 10" callouts by reading the matching grade row's `population`.

## Tips for AI Assistants

- **Working with IDs:** These UUIDs (`release_id`, `set_id`, 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 `error` before reading `data`.
- **Field names are `snake_case`**: `card_id`, `set_id`, `release_id`, `total_population`, `qualified_population`, `last_synced_at`, `grading_companies`, `grading_types`, `parallel_id`, `parallel_name`, `set_name`, `release_name`. Do not camelCase them.
- `qualified_population` is **separate** from `population`. To get total examples graded at a particular grade, use `population + qualified_population`.
- Every grade defined for a present company is returned, including ones with `0` populations. Filter client-side if you only want non-zero rows.
- `CardPopulationResponse.base` is **optional** — check `if (data.base)` before reading it.
- `CardPopulationResponse.parallels` is required but may be an empty array.
- `SetPopulationResponse` and `ReleasePopulationResponse` **do not include a top-level `total_population`** — compute it by summing `grading_companies[*].total_population`.
- An empty `grading_companies` array means no grading company has a confirmed link to this set/release. This is not an error — display an empty state.
- These endpoints echo only `*_id` and `*_name`. For full metadata (year, manufacturer, parallels, attributes, card lists), call the catalog endpoint (`/v1/catalog/cards/{id}`, `/v1/catalog/sets/{id}`, `/v1/catalog/releases/{id}`).
- Use `?grading_company_id={uuid}` to filter to a single company. The list of company UUIDs is available from `GET /v1/grades/companies`.
- `last_synced_at` is an ISO 8601 timestamp. Population data from one company can lag another — display the timestamp if accuracy matters.
