# Lists and pagination

> The shape of every list, pages and cursors, sections and choosing fields.

> Documentation index for agents: https://kinodata.space/en/llms.txt — the whole documentation in one file: https://kinodata.space/en/llms-full.txt

Every list in the API has the same shape: the catalog and search, charts, collections, cast, media, filmographies. Learn it once and you can read any list.

## The shape of a list

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "q=matrix" \
  --data-urlencode "limit=3" \
  --data-urlencode "fields=title,year,rating_imdb" \
  --data-urlencode "lang=en" \
  "https://api.kinodata.space/v1.3/titles"
```

Response `200`:

```json
{
  "has_more": true,
  "items": [
    {
      "imdb_id": "tt0133093",
      "kp_id": "301",
      "tmdb_id": "603",
      "tmdb_tv_id": null,
      "kinorium_id": "116780",
      "title_en": "The Matrix",
      "year": 1999,
      "rating_imdb": 8.7
    },
    {
      "imdb_id": "tt0234215",
      "kp_id": "299",
      "tmdb_id": "604",
      "tmdb_tv_id": null,
      "kinorium_id": "190315",
      "title_en": "The Matrix Reloaded",
      "year": 2003,
      "rating_imdb": 7.2
    },
    {
      "imdb_id": "tt0242653",
      "kp_id": "316",
      "tmdb_id": "605",
      "tmdb_tv_id": null,
      "kinorium_id": "196883",
      "title_en": "The Matrix Revolutions",
      "year": 2003,
      "rating_imdb": 6.7
    }
  ],
  "limit": 3,
  "offset": 0,
  "total": null
}
```

| field | what it is |
|---|---|
| `items` | The items of the list. |
| `total` | How many items there are in total. `null` when the API doesn't count them; see below. |
| `limit` | Items per page. If you asked for more than allowed, this is the real number. |
| `offset` | How many items were skipped from the start. |
| `has_more` | Whether there is a next page. |
| `next_cursor` | The cursor for the next page. Only when you page with a cursor. |

The resource's own fields come alongside: `roles` on cast and filmographies, `counts` on relations and texts, `collection` on a collection.

## When total is null

There are two kinds of lists.

- **The set is defined by the URL**: a title's cast, its relations and media, a person's filmography, a collection. Such a set is finite, and `total` is a number.
- **The set is defined by the query**: the catalog and search, the top chart, hidden gems, people search. Here `total` is `null`: an exact count of a filtered set would mean a second pass over the whole database on every page. Use `has_more` to tell whether there's more. Rating divergence and the release calendar do count `total`, filters included.

## Items per page

| list | default | maximum |
|---|---|---|
| Catalog and search, charts, discovery feeds, release calendar, collections, people search | 50 | 50 |
| Cast, media, texts, relations, episodes, filmography, shared titles | 1000 | 1000 |
| Sections, when a list is split into them | 10 per section | 50 per section |

A set defined by the URL comes back whole: it is the answer. A set defined by the query is sorted, and a page holds its top.

`limit` is a whole number from 1, `offset` from 0. A number above the maximum isn't an error: the API lowers it and returns the real value in `limit`. `limit=5000` on the catalog gives `"limit": 50`. Zero, a negative number or a non-number returns `400`.

## Pages: offset

```text
?limit=50&offset=100
```

`offset` is how many items to skip. The next page is `offset` plus `limit`, for as long as `has_more` is `true`.

## Deep paging: cursor

For long runs through the catalog, `/titles`, use a cursor: it doesn't drift when the database changes while you page. Send an empty `cursor=` and the response gets a `next_cursor`:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "sort=-rating_imdb" \
  --data-urlencode "votes_imdb.gte=100000" \
  --data-urlencode "limit=2" \
  --data-urlencode "fields=title,rating_imdb" \
  --data-urlencode "cursor=" \
  "https://api.kinodata.space/v1.3/titles"
```

Response `200`:

```json
{
  "has_more": true,
  "items": [
    {
      "imdb_id": "tt0903747",
      "kp_id": "404900",
      "tmdb_id": null,
      "tmdb_tv_id": "1396",
      "kinorium_id": "400796",
      "title_ru": "Во все тяжкие",
      "title_en": "Breaking Bad",
      "rating_imdb": 9.5
    },
    {
      "imdb_id": "tt5491994",
      "kp_id": "1007472",
      "tmdb_id": null,
      "tmdb_tv_id": "68595",
      "kinorium_id": "1605088",
      "title_ru": "Планета Земля 2",
      "title_en": "Planet Earth II",
      "rating_imdb": 9.4
    }
  ],
  "limit": 2,
  "next_cursor": "eyJzIjoicmF0aW5nX2ltZGIiLCJkIjp0cnVlLCJ2IjoiOS40MCIsImlkIjoyMjA4MDF9",
  "total": null
}
```

Pass `next_cursor` to the next request with the same parameters until it comes back `null`. The cursor works in the `/titles` catalog and in the feeds built on it, the top chart and hidden gems. Other lists page with `offset` and answer `cursor` with `400`.

`sort=random` can't be paged: a random order has no next page. `cursor` and `offset` with it return `400`; just repeat the request for a fresh draw.

## Sections

Lists whose items differ by one property come split into sections. Each section is a list itself, with the same fields and a label in both languages:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "limit=1" \
  --data-urlencode "fields=title" \
  "https://api.kinodata.space/v1.3/titles/kp/301/relations"
```

Response `200`:

```json
{
  "counts": {
    "reference": 86,
    "referenced_in": 672,
    "related": 9,
    "similar": 74
  },
  "has_more": false,
  "items": [
    {
      "has_more": true,
      "items": [
        {
          "imdb_id": "tt1375666",
          "kp_id": "447301",
          "tmdb_id": "27205",
          "tmdb_tv_id": null,
          "kinorium_id": "472809",
          "title_ru": "Начало",
          "title_en": "Inception"
        }
      ],
      "label_en": "Similar",
      "label_ru": "Похожие",
      "relation": "similar",
      "total": 74
    },
    {
      "has_more": true,
      "items": [
        {
          "imdb_id": "tt0366179",
          "kp_id": "63744",
          "tmdb_id": "24358",
          "tmdb_tv_id": null,
          "kinorium_id": null,
          "title_ru": "Аниматрица: Второе возрождение – Часть 1",
          "title_en": "The Second Renaissance Part I"
        }
      ],
      "label_en": "Related",
      "label_ru": "Связанные",
      "relation": "related",
      "total": 9
    },
    {
      "has_more": true,
      "items": [
        {
          "imdb_id": "tt1431045",
          "kp_id": "462360",
          "tmdb_id": "293660",
          "tmdb_tv_id": null,
          "kinorium_id": "485426",
          "title_ru": "Дэдпул",
          "title_en": "Deadpool"
        }
      ],
      "label_en": "Referenced in",
      "label_ru": "Упоминается в",
      "relation": "referenced_in",
      "total": 672
    },
    {
      "has_more": true,
      "items": [
        {
          "imdb_id": "tt0060196",
          "kp_id": "349",
          "tmdb_id": "429",
          "tmdb_tv_id": null,
          "kinorium_id": "56474",
          "title_ru": "Хороший, плохой, злой",
          "title_en": "The Good, the Bad and the Ugly"
        }
      ],
      "label_en": "References",
      "label_ru": "Отсылки к",
      "relation": "reference",
      "total": 86
    }
  ],
  "limit": 1,
  "offset": 0,
  "total": 4
}
```

| list | default sections | other `group` values |
|---|---|---|
| Cast, `/titles/{type}/{id}/cast` | by role: `role` | `none` |
| Texts, `/titles/{type}/{id}/texts` | by kind: `kind` | `none` |
| Relations, `/titles/{type}/{id}/relations` | by relation: `relation` | `none` |
| Media, `/titles/{type}/{id}/media` | no sections | `type`, `class`, `language` |
| Filmography, `/persons/{type}/{id}/filmography` | no sections | `role` |
| Episodes, `/titles/{type}/{id}/episodes` | no sections | `season`: a per-season summary |

In a sectioned list `limit` applies to each section, and a section's `total` is its real size. For a flat list pass `group=none`. To page through one section, narrow the list to it: `role=actor`, `kind=fact`, `relation=similar`.

A sectioned list can't be paged: the sections share no single sequence. `offset` without narrowing returns `400`:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "limit=2" \
  --data-urlencode "offset=2" \
  "https://api.kinodata.space/v1.3/titles/kp/447301/cast"
```

Response `400`:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "grouped lists have no single sequence to page through — narrow to one value to page inside it, or ask for group=none",
    "field": "offset"
  }
}
```

## Only the fields you need

The `fields` parameter keeps only what you list.

- **On a title card** the names are card blocks: `fields=title,ratings`. You can also drop what you don't need: `fields=-akas,-tags`. The two can't be mixed in one parameter: `fields=title,-akas` returns `400`.
- **In lists** the names are list item fields: `fields=title,year,rating_imdb` or `fields=-genres,-countries`. `title` keeps `title_ru` and `title_en`, `poster_url` keeps `poster_url_ru` and `poster_url_en`.

A title's IDs always come back, even if you didn't list them: without them there's nothing to link the item to. Fields keep the same order as without `fields`. An unknown name returns `400` with the list of allowed names.

`fields` works on the title card, in every list of titles (the catalog, collections, relations, filmographies, shared titles, discovery feeds and the release calendar) and in people search. Cast, media, texts and episodes don’t take it: there `fields` returns `400`.

## Next

- [Filters and sorting](https://kinodata.space/en/docs/filters.md): how to pick and order items.
- [Catalog and search](https://kinodata.space/en/docs/titles.md): the main list in the API.
