# Movers

> The cards whose composite index rose or fell the most over a window.

- **Endpoint**: `GET /v1/prices/movers`
- **Cost**: 3 credits
- **Minimum plan**: growth
- **Cache-Control**: `public, max-age=300`

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

From Developer up. On the trial the route answers `403 PLAN_REQUIRED` rather than an empty list, so a client can tell "not allowed" from "nothing moved".

`from` and `to` are the index at the two ends of the window, in EUR; `change_pct` is computed from them. The ranking is over the whole catalogue in one query, which is why it is the heaviest price route and priced accordingly.

## Query parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `window` | string | 30d | `7d`, `30d`, `90d` or `365d`. |
| `direction` | string | gainers | `gainers` or `losers`. |
| `min_value` | number | 1 | Ignore cards whose index at the end of the window is below this many euros. Percentages on a ten-cent card are noise. |
| `locale` | string | — | Restrict to one printing language. |
| `limit` | integer | 20 | Rows to return, 1 to 250. |

## 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/prices/movers" \
  --data-urlencode "window=90d" \
  --data-urlencode "min_value=5" \
  --data-urlencode "limit=3" \
  -H "X-Api-Key: $PTCG_API_KEY"
```

**TypeScript**

`request.ts`

```ts
const url = new URL("https://api.pokemontcgapi.com/v1/prices/movers");
url.searchParams.set("window", "90d");
url.searchParams.set("min_value", "5");
url.searchParams.set("limit", "3");

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/prices/movers",
    params={"window": "90d", "min_value": "5", "limit": "3"},
    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": [
    {
      "card_id": "adv1-002",
      "name": "Weezing",
      "set_code": "adv1",
      "from": 7.23,
      "to": 7.23,
      "change_pct": 0,
      "currency": "EUR"
    },
    {
      "card_id": "adv1-003",
      "name": "Sceptile",
      "set_code": "adv1",
      "from": 21.56,
      "to": 21.56,
      "change_pct": 0,
      "currency": "EUR"
    },
    {
      "card_id": "adv1-006",
      "name": "Beautifly",
      "set_code": "adv1",
      "from": 6.73,
      "to": 6.73,
      "change_pct": 0,
      "currency": "EUR"
    }
  ],
  "meta": {
    "window": "90d",
    "from": "2026-06-04",
    "to": "2026-09-02",
    "direction": "gainers",
    "min_value": 5,
    "count": 3,
    "source": "PTCG_INDEX"
  }
}
```

## 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 | `PLAN_REQUIRED` | The route exists and the key is valid, but the plan does not include this feature. Today that is `/v1/prices/movers` on the trial. |

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