# Price history

> A time series for one card: one point per day, week or month, per source and variant.

- **Endpoint**: `GET /v1/cards/{id}/prices/history`
- **Cost**: 5 credits
- **Minimum plan**: free
- **Cache-Control**: `public, max-age=300`

Source: https://pokemontcgapi.com/docs/api/prices/history

Days with no observation are omitted, not interpolated: a straight line through a gap would be a claim we did not observe. Observations begin on 2026-07-28, the day the current ingest started, so a `from` before that returns the same series as `from=2026-07-28`.

The window is the plan’s: 7 days on the trial, 90 on Developer, the whole history on Growth and Professional. `meta.plan_window_days` reports the limit that applied (null when none did) and `meta.truncated` says whether `from` was moved. A series longer than the point cap is cut and flagged in `meta.capped` — pick a coarser `bucket` rather than paging.

## Path parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `id` **required** | string | — | Card id or alternate id. |

## Query parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `source` | string | — | One source code, e.g. `CARDMARKET`, `CARDTRADER`, `TCGPLAYER` or `PTCG_INDEX`. A chart that mixes sources means nothing, so filter to one. |
| `variant` | string | — | One variant, e.g. `MARKET`, for the same reason. |
| `locale` | string | — | Restrict to one printing language. |
| `printing` | string | — | Restrict to one printing: `HOLOFOIL`, `REVERSE_HOLO`, … |
| `from` | date | — | `YYYY-MM-DD`, inclusive. Defaults to the start of the plan’s window. |
| `to` | date | — | `YYYY-MM-DD`, inclusive. Defaults to today. |
| `bucket` | string | day | `day`, `week` or `month`. Coarser buckets keep a long window under the point cap. |

## Headers

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `X-Api-Key` **required** | string | — | Your API key. `Authorization: Bearer <key>` is accepted as an alias. |
| `If-None-Match` | string | — | Send back the `ETag` you stored. The API returns a strong ETag on every catalogue response, and a 304 is a header exchange: no body, no database row read. |

## Example request

**curl**

```bash
curl -s -G "https://api.pokemontcgapi.com/v1/cards/base1-4/prices/history" \
  --data-urlencode "source=TCGPLAYER" \
  --data-urlencode "variant=MARKET" \
  --data-urlencode "bucket=week" \
  -H "X-Api-Key: $PTCG_API_KEY"
```

**TypeScript**

`request.ts`

```ts
const url = new URL("https://api.pokemontcgapi.com/v1/cards/base1-4/prices/history");
url.searchParams.set("source", "TCGPLAYER");
url.searchParams.set("variant", "MARKET");
url.searchParams.set("bucket", "week");

const res = await fetch(url, {
  headers: { "X-Api-Key": process.env.PTCG_API_KEY ?? "" },
});

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}

const { data, meta } = await res.json();
```

**Python**

`request.py`

```python
import os, httpx

res = httpx.get(
    "https://api.pokemontcgapi.com/v1/cards/base1-4/prices/history",
    params={"source": "TCGPLAYER", "variant": "MARKET", "bucket": "week"},
    headers={"X-Api-Key": os.environ["PTCG_API_KEY"]},
)
res.raise_for_status()
payload = res.json()
```

## Example response

`200 OK` · `Cache-Control: public, max-age=300`

`response.json`

```json
{
  "data": [
    {
      "date": "2026-07-27",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": "HOLOFOIL",
      "amount": 800.43,
      "currency": "USD",
      "sample_n": 0
    },
    {
      "date": "2026-07-27",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": null,
      "amount": 800.43,
      "currency": "USD",
      "sample_n": 0
    },
    {
      "date": "2026-08-03",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": null,
      "amount": 818.65,
      "currency": "USD",
      "sample_n": 0
    },
    {
      "date": "2026-08-10",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": null,
      "amount": 825.38,
      "currency": "USD",
      "sample_n": 0
    },
    {
      "date": "2026-08-17",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": null,
      "amount": 855.52,
      "currency": "USD",
      "sample_n": 0
    },
    {
      "date": "2026-08-24",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": null,
      "amount": 855.52,
      "currency": "USD",
      "sample_n": 0
    },
    {
      "date": "2026-08-31",
      "source": "TCGPLAYER",
      "variant": "MARKET",
      "locale": "en",
      "printing": null,
      "amount": 868.56,
      "currency": "USD",
      "sample_n": 0
    }
  ],
  "meta": {
    "card_id": "base1-4",
    "from": "2026-06-04",
    "to": "2026-09-02",
    "bucket": "week",
    "count": 7,
    "truncated": false,
    "capped": false,
    "plan_window_days": null
  }
}
```

## Errors

Every error body carries `error.code`, `error.message` and `error.request_id`. Switch on the code, never on the message.

| Status | Code | When |
| --- | --- | --- |
| 401 | `MISSING_API_KEY` | No `X-Api-Key` header and no bearer token on a route that requires one. |
| 401 | `INVALID_API_KEY` | The key does not match any account. |
| 400 | `INVALID_PARAMETER` | A query parameter has the wrong type or an unsupported value — a non-integer `limit`, an unknown `lang`, an unknown `region`, a sort key that is not sortable. |
| 403 | `UPGRADE_REQUIRED` | A parameter asks for more than the plan allows: a `window` longer than the plan’s history, for example. `details.plan_window_days` says how much the plan gives. |

The full taxonomy, with what to do about each code, is on the [errors](https://pokemontcgapi.com/docs/errors) page.
