# Using the CardSight AI API - Price Trends (Time Series) API

## Overview
Chart how a card's price moves over time. The time series endpoint rolls a card's marketplace listings into daily, weekly, or monthly candles, and each candle carries descriptive statistics — mean, median, high, low, and count. Series are split by grade: `raw` holds candles for ungraded listings and `graded` holds one series per grade, grouped by grading company, so graded and ungraded prices never blend into one candle. Within each series, candles are keyed by listing type with the same bid/ask semantics as the Pricing Data endpoint: `auction` candles summarize completed auction sales (the bid side) and `fixed` candles summarize Buy It Now asking prices (the ask side, not necessarily completed sales). The parallel dimension is request-controlled: omit `parallel_id` and each series blends every parallel of that grade; pass "null" for base-card-only candles or a UUID for one parallel. A grouped outlier filter runs over the whole window per grade and parallel variant — a listing is only ever judged against other listings of its own variant — and each series reports how many listings it removed. Statistics are descriptive summaries of raw listings; they are not valuations. Available for Baseball, Basketball, Football, Hockey, Pokémon, Magic: The Gathering, and One Piece, with more segments coming soon.

## Endpoint Details
- **Method**: GET
- **URL**: `https://api.cardsight.ai/v1/pricing/{card_id}/timeseries`
- **Authentication**: API Key required (X-Api-Key header)

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| card_id | UUID | Yes | Card UUID (path parameter) |
| interval | string | Yes | Candle size: "daily" (UTC calendar day), "weekly" (ISO week, Monday start), or "monthly" (calendar month) |
| periods | integer | No | How many candles to look back, from 1 to 365. Defaults per interval when omitted: daily 90, weekly 52, monthly 24. Values above 365 are rejected; weekly values above 156 and monthly values above 120 are clamped to those caps, and the response echoes the effective value. |
| as_of_date | string | No | Viewpoint date (UTC, YYYY-MM-DD). The newest candle is the one containing this date and the window extends back `periods` candles. Defaults to today (UTC). |
| listing_type | string | No | Which listing-type series to compute: "auction" (completed auction sales, bid side), "fixed" (Buy It Now asking prices, ask side), or "both" (default). Stats are always split by type — this only restricts which types appear. |
| parallel_id | string | No | Filter by parallel variant. Pass a UUID for a specific parallel, "null" for base card only, or omit for all variants. When omitted, each series blends every parallel of that grade into the same candles. |
| grade_id | string | No | Filter by grade. Pass a UUID for a specific grade, "null" for ungraded only, or omit for all grades. Pinning a specific grade excludes ungraded listings, so `raw` comes back empty. |

## Using the CardSight AI SDK (Recommended)

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

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

// Daily candles for the last 90 days (the default period count for "daily")
const response = await client.pricing.timeseries('23084701-7511-4aa3-8831-8dbddf4c2c8d', {
  interval: 'daily'
})

// Always check for errors
if (response.error) {
  console.error('Error:', response.error)
} else {
  const series = response.data

  // Card context
  console.log(`${series.card.name}${series.card.number ? ` #${series.card.number}` : ''}`)
  console.log(`${series.card.set.year} ${series.card.set.release}`)

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

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

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

  // Graded candles, grouped by company → grade
  for (const company of series.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('23084701-7511-4aa3-8831-8dbddf4c2c8d', {
  interval: 'weekly',        // 'daily' | 'weekly' | 'monthly' (required)
  periods: 52,               // Defaults: daily 90, weekly 52, monthly 24
  as_of_date: '2026-09-01',  // Newest candle is the one containing this date (UTC)
  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
})
```

## Direct API Call (cURL)

### Weekly candles for the last 12 weeks
```bash
curl -X GET "https://api.cardsight.ai/v1/pricing/23084701-7511-4aa3-8831-8dbddf4c2c8d/timeseries?interval=weekly&periods=12" \
  -H "X-Api-Key: your-api-key"
```

### Monthly auction candles, base card only
```bash
curl -X GET "https://api.cardsight.ai/v1/pricing/23084701-7511-4aa3-8831-8dbddf4c2c8d/timeseries?interval=monthly&listing_type=auction&parallel_id=null" \
  -H "X-Api-Key: your-api-key"
```

## Example Response
```json
{
  "card": {
    "card_id": "23084701-7511-4aa3-8831-8dbddf4c2c8d",
    "name": "Shohei Ohtani",
    "number": "US1",
    "set": {
      "set_id": "550e8400-e29b-41d4-a716-446655440002",
      "name": "Base Set",
      "year": "2018",
      "release": "2018 Topps Update"
    },
    "parallel": null
  },
  "query": {
    "interval": "weekly",
    "periods": 12,
    "as_of_date": "2026-09-10",
    "listing_type": "both",
    "parallel_id": null,
    "grade_id": null
  },
  "raw": {
    "candles": [
      {
        "period_start": "2026-08-24",
        "types": {
          "auction": { "mean": 14.2, "median": 13.5, "high": 22.0, "low": 9.99, "count": 11 },
          "fixed": { "mean": 19.75, "median": 18.99, "high": 29.99, "low": 14.99, "count": 6 }
        }
      },
      {
        "period_start": "2026-08-31",
        "types": {
          "auction": { "mean": 15.1, "median": 14.0, "high": 24.5, "low": 10.5, "count": 9 }
        }
      }
    ],
    "totals": {
      "auction": { "total_count": 20, "filtered_count": 2 },
      "fixed": { "total_count": 6, "filtered_count": 0 }
    }
  },
  "graded": [
    {
      "company_name": "PSA",
      "company_id": "550e8400-e29b-41d4-a716-446655440020",
      "grades": [
        {
          "grade_value": "10",
          "grade_id": "550e8400-e29b-41d4-a716-446655440030",
          "candles": [
            {
              "period_start": "2026-08-31",
              "types": {
                "auction": { "mean": 92.5, "median": 90.0, "high": 110.0, "low": 80.0, "count": 4 }
              }
            }
          ],
          "totals": {
            "auction": { "total_count": 4, "filtered_count": 0 }
          }
        }
      ]
    }
  ]
}
```

## Response Fields

### Top-Level Response

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| card | object | Yes | Card context information |
| query | object | Yes | Echo of the query parameters that were applied (effective values) |
| raw | object | Yes | Candle series computed from ungraded listings only. Always present; empty candles/totals when the card has no ungraded listings in the window, or when `grade_id` pins a specific grade. |
| graded | array | Yes | Per-grade candle series grouped by grading company. Grades with no listings in the window are omitted; empty when the card has no graded listings in the window. |

### Card Context Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| card_id | UUID | Yes | Card UUID |
| name | string | Yes | Card name/subject |
| number | string | No | Card number in set |
| set | object | Yes | Set context: set_id, name, year, release |
| parallel | object | No | Parallel context (parallel_id, name) if filtered by parallel |

### Query Echo Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| interval | string | Yes | Candle size applied |
| periods | integer | Yes | Effective candle count (defaults applied when omitted, oversized values clamped) |
| as_of_date | string | Yes | Effective viewpoint date (UTC) the window ends on |
| listing_type | string | Yes | Listing type filter applied |
| parallel_id | string/null | No | Parallel UUID filter applied |
| grade_id | string/null | No | Grade UUID filter applied |

### Raw Section Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| candles | array | Yes | Chronological candles computed from ungraded listings only, oldest first. A candle with no listings in any requested type is omitted entirely. |
| totals | object | Yes | Whole-window counts for ungraded listings, keyed by listing type. A type with no listings across the window is omitted; a type can appear with total_count 0 when all of its listings were removed by the outlier filter. |

### Graded Section (Company Group)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| company_name | string | Yes | Grading company name (e.g., "PSA") |
| company_id | UUID | Yes | Grading company UUID |
| grades | array | Yes | Per-grade candle series for this company |

### Grade Group

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| grade_value | string | Yes | Grade value (e.g., "10", "9.5") |
| grade_id | UUID | Yes | Grade UUID |
| candles | array | Yes | Chronological candles computed from this grade's listings only, oldest first |
| totals | object | Yes | Whole-window counts for this grade, keyed by listing type |

### Candle Object

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| period_start | string | Yes | Candle start date (YYYY-MM-DD): the UTC calendar day, the Monday of the ISO week, or the 1st of the month, depending on interval |
| types | object | Yes | Stats keyed by listing type. Currently "auction" (completed auction sales — the bid side) and "fixed" (Buy It Now asking prices — the ask side). A type with no listings in this candle is absent from the map; new listing types appear as additive keys. |

### Candle Stats Object (inside `types`)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| mean | number | Yes | Arithmetic mean listing price in USD for this candle. Auction candles aggregate final sale prices; fixed candles aggregate Buy It Now asking prices. |
| median | number | Yes | Median listing price in USD for this candle |
| high | number | Yes | Highest listing price in USD in this candle |
| low | number | Yes | Lowest listing price in USD in this candle |
| count | integer | Yes | Number of listings in this candle for this listing type (after outlier filtering) |

### Type Totals Object (inside `totals`)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| total_count | integer | Yes | Listings included across all candles for this listing type (after outlier filtering); equals the sum of the per-candle counts. Can be 0 when every listing of that type was removed by the outlier filter. |
| filtered_count | integer | Yes | Listings removed by the outlier filter for this listing type. Pre-filter total = total_count + filtered_count. |

## Common Use Cases
- Draw a price chart for a card page — median line with a low/high band per day, week, or month
- Show the bid/ask spread over time by charting `auction` and `fixed` candles side by side
- Compare how a PSA 10 trends against the raw card without the two blending into one line
- Track a numbered parallel separately from the base card with `parallel_id`
- Power "up X% over 90 days" style stats from the first and last candles of a series
- Spot thin markets: a candle with `count: 1` is one listing, not a trend

## Tips for AI Assistants
- **Working with IDs:** These UUIDs (`card_id`, `set_id`, `parallel_id`, `grade_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
- `interval` is required; `periods` is optional and defaults to daily 90 / weekly 52 / monthly 24
- Do not pass `periods` above 365 — the request is rejected. Weekly above 156 and monthly above 120 are clamped instead, so read the effective value from `query.periods`
- Candles, listing types, and grades with no listings are **omitted, not zero-filled** — never assume every period is present or that `types.auction` exists on every candle
- A card with no listings in the window returns an empty `raw.candles` and an empty `graded` array as a success, not an error
- Think of `auction` as bid (what buyers paid) and `fixed` as ask (what sellers asked); the statistics summarize listings and are not a valuation
- `totals` covers the whole window: `total_count` is the sum of the per-candle counts, and `filtered_count` tells you how many listings the outlier filter removed. A type can appear in `totals` with `total_count` 0 when all of its listings were filtered
- The outlier filter is grouped per grade and parallel variant, so a listing is only judged against other listings of its own variant — a numbered parallel's high prices are not treated as outliers of the base card
- Pass `parallel_id: "null"` (the string) for base card only and `grade_id: "null"` for ungraded only; omit either to include everything
- Pinning `grade_id` to a specific grade excludes ungraded listings, so `raw` comes back empty in that case
- `as_of_date` lets you reproduce a chart as of a past date — the newest candle is the one containing that date
- For the individual listings behind a candle, call `GET /v1/pricing/{card_id}` (`client.pricing.get()`) with the same `listing_type`, `parallel_id`, and `grade_id` filters. That endpoint takes a `period` such as "90d" rather than `interval` and `periods`
- Available for Baseball, Basketball, Football, Hockey, Pokémon, Magic: The Gathering, and One Piece, with more segments coming soon
