# List sealed products

> Booster boxes, elite trainer boxes, tins, blisters and collections: 2,088 products with names in six locales, images and prices.

- **Endpoint**: `GET /v1/sealed`
- **Cost**: 1 credit
- **Minimum plan**: free
- **Cache-Control**: `public, max-age=60, s-maxage=300, stale-while-revalidate=600`

Source: https://pokemontcgapi.com/docs/api/sealed/list

`id` on a sealed product is its `sku` — a readable, stable string such as `evolving-skies-booster-box` — and the same value opens `/v1/sealed/{id}` and `/v1/sealed/{id}/prices`. `languages` lists the languages the product physically exists in, which is a different thing from the locales of its name.

`meta.total_count` is always present: 2,088 rows is small enough that counting them costs an index scan, so you get the number instead of guessing from `has_more`.

> **Unknown parameters are ignored**
>
> A query parameter this endpoint does not know is dropped silently — it is not an error and it does not change the result. A typo in a filter name therefore returns a full, unfiltered page rather than a 400, so check the parameter names in the table above before concluding that a filter does nothing.

## Query parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `q` | string | — | Free text on the product name — in the locale given by `lang`, if any — or an exact `sku`. Not the card query grammar: there are no fields or ranges here, because "evolving skies booster box" is the whole question. |
| `set` | string | — | Set code, slug or alternate id, as on `/v1/sets/{code}`. Products of a set that is not in the catalogue carry `set_code: null` and cannot be reached this way. |
| `kind` | string | — | Product kind, as printed in `kind`: `BOOSTER_BOX`, `ETB`, `TIN`, `BLISTER`, `COLLECTION`, `BOOSTER_PACK`, `BOOSTER_BUNDLE`, `STARTER_DECK`, `BUILD_AND_BATTLE`, `PREMIUM_COLLECTION`, `SLEEVED_BOOSTER` and twelve more. Read the values off the list rather than hardcoding them; an unknown kind returns an empty page, not an error. |
| `lang` | string | — | Locale for `name`: `en`, `ja`, `fr`, `de`, `es`, `it`. A product without a name in that locale falls back to English. |
| `orderBy` | string | -release_date | `release_date`, `name`, `sku`, `index_eur`, `last_price_at`, `updated_at` or `set`, with a leading `-` for descending. Every order ends on `sku` as the tiebreaker. |
| `limit` | integer | 50 | Rows per page, 1 to 250. Above 250 you get `LIMIT_EXCEEDED` — follow `links.next` instead of raising it. |
| `cursor` | string | — | Opaque keyset cursor from `links.next`. Never construct one; it carries the sort order it was issued for and is rejected if `orderBy` changes mid-run. |

## 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/sealed" \
  --data-urlencode "q=evolving skies" \
  --data-urlencode "kind=BOOSTER_BOX" \
  --data-urlencode "limit=2" \
  -H "X-Api-Key: $PTCG_API_KEY"
```

**TypeScript**

`request.ts`

```ts
const url = new URL("https://api.pokemontcgapi.com/v1/sealed");
url.searchParams.set("q", "evolving skies");
url.searchParams.set("kind", "BOOSTER_BOX");
url.searchParams.set("limit", "2");

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/sealed",
    params={"q": "evolving skies", "kind": "BOOSTER_BOX", "limit": "2"},
    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=60, s-maxage=300, stale-while-revalidate=600`

`response.json`

```json
{
  "data": [
    {
      "id": "evolving-skies-6-booster-box-case",
      "sku": "evolving-skies-6-booster-box-case",
      "slug": "evolving-skies-6-booster-box-case",
      "name": "Evolving Skies 6 Booster Box Case",
      "kind": "BOOSTER_BOX",
      "set_code": "evs",
      "set_name": "Evolving Skies",
      "image_url": "https://media.rarebit.app/boxes/evolving-skies-6-booster-box-case-normal.webp",
      "release_date": null,
      "pack_count": 216,
      "languages": [],
      "index_eur": 15495,
      "last_price_at": "2026-09-02T00:38:55.286Z",
      "created_at": "2026-05-23T12:43:56.128Z",
      "updated_at": "2026-09-02T15:56:54.473Z"
    },
    {
      "id": "evolving-skies-booster-box",
      "sku": "evolving-skies-booster-box",
      "slug": "evolving-skies-booster-box",
      "name": "Evolving Skies Booster Box",
      "kind": "BOOSTER_BOX",
      "set_code": "evs",
      "set_name": "Evolving Skies",
      "image_url": "https://media.rarebit.app/boxes/evolving-skies-booster-box-normal.webp",
      "release_date": null,
      "pack_count": 36,
      "languages": [],
      "index_eur": 1750,
      "last_price_at": "2026-09-02T00:36:37.317Z",
      "created_at": "2026-05-23T12:43:56.069Z",
      "updated_at": "2026-09-02T15:56:54.473Z"
    }
  ],
  "meta": {
    "limit": 2,
    "count": 2,
    "total_count": 3,
    "has_more": true
  },
  "links": {
    "next": "https://api.pokemontcgapi.com/v1/sealed?cursor=eyJrIjpbbnVsbF0sImlkIjoiZXZvbHZpbmctc2tpZXMtYm9vc3Rlci1ib3giLCJzIjoicmVsZWFzZV9kYXRlOmRlc2Msc2t1OmFzYyJ9&kind=BOOSTER_BOX&limit=2&q=evolving+skies"
  }
}
```

## 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_CURSOR` | The cursor is malformed, or was issued for a different `orderBy` than the one on this request. |
| 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. |
| 400 | `LIMIT_EXCEEDED` | `limit` is above 250, or a batch request carries more than 100 ids. |

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