# Filters and sorting

> Operators, every catalog filter, sorting, the rating window and presets.

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

The `/titles` catalog picks titles with filters and orders them with `sort`. The same grammar works in collections, relations, filmographies, shared titles and discovery feeds, and the filters work in the release calendar too: learn it once and you can filter any list of titles.

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "kind=anime" \
  --data-urlencode "country=jp" \
  --data-urlencode "votes_imdb.gte=50000" \
  --data-urlencode "sort=-rating_imdb" \
  --data-urlencode "limit=3" \
  --data-urlencode "fields=title,year,rating_imdb" \
  "https://api.kinodata.space/v1.3/titles"
```

Response `200`:

```json
{
  "has_more": true,
  "items": [
    {
      "imdb_id": "tt2560140",
      "kp_id": "749374",
      "tmdb_id": null,
      "tmdb_tv_id": "1429",
      "kinorium_id": "678289",
      "title_ru": "Атака титанов",
      "title_en": "Attack on Titan",
      "year": 2013,
      "rating_imdb": 9.1
    },
    {
      "imdb_id": "tt1355642",
      "kp_id": "452838",
      "tmdb_id": null,
      "tmdb_tv_id": "31911",
      "kinorium_id": "468024",
      "title_ru": "Стальной алхимик: Братство",
      "title_en": "Fullmetal Alchemist: Brotherhood",
      "year": 2009,
      "rating_imdb": 9.1
    },
    {
      "imdb_id": "tt14986406",
      "kp_id": "4789626",
      "tmdb_id": null,
      "tmdb_tv_id": "326913",
      "kinorium_id": "2794509",
      "title_ru": "Блич: Тысячелетняя кровавая война",
      "title_en": "Bleach: Thousand-Year Blood War",
      "year": 2022,
      "rating_imdb": 9
    }
  ],
  "limit": 3,
  "offset": 0,
  "total": null
}
```

Where:

- `kind=anime` and `country=jp` select Japanese anime;
- `votes_imdb.gte=50000` asks for at least 50,000 IMDb votes;
- `sort=-rating_imdb` orders from the highest IMDb rating down.

## Operators

An operator goes after the filter name:

| operator | meaning | example |
|---|---|---|
| none | equals; several comma-separated values mean any of them | `kind=movie,series` |
| `.any` | any of the listed values | `genre.any=drama,comedy` |
| `.all` | all of the listed values at once | `genre.all=drama,war` |
| `.not` | none of the listed values | `country.not=us` |
| `.gte`, `.lte` | at least, at most | `year.gte=1990&year.lte=1999` |

A number without an operator is an exact value: `year=1999` is the same as `year.gte=1999&year.lte=1999`. Conditions on different filters combine with "and".

## What you can filter

| group | filters |
|---|---|
| IDs | `imdb_id`, `kp_id`, `tmdb_id`, `tmdb_tv_id` |
| Kind | `kind`, `is_series`, `adult` |
| Dates | `year`, `release_date`, `premiere_world`, `premiere_ru`, `digital_release`, `released`, `start_year`, `end_year` |
| Ratings and votes | `rating`, `rating_imdb`, `rating_kp`, `rating_tmdb`, `rating_kr`, `rating_letterboxd`, `rating_rt`, `rating_metacritic`, `rating_critics`, `votes_imdb`, `votes_kp`, `votes_tmdb`, `votes_kr`, `votes_letterboxd`, `votes_total`, `popularity` |
| Content | `genre`, `tag`, `country`, `region`, `original_language`, `runtime` |
| Companies | `companies`, `company.kind`, `platform` |
| People | `person`, `person.role` |
| Collections and franchises | `collection`, `franchise` |
| Series | `seasons_total`, `episodes_total` |
| Money | `budget`, `box_office_us`, `revenue_world` |
| Awards | `wins_imdb`, `oscars_won` |
| Age rating | `age_limit`, `age_limit_max`, `mpaa` |
| Data completeness | `required`, `has_poster` |

[`GET /filters`](https://kinodata.space/en/docs/facets.md) returns the values for `genre`, `country`, `region`, `platform`, `tag` and `companies` with title counts, in the `value` field. [`GET /enums`](https://kinodata.space/en/docs/enums.md) returns the labels of every enum, the `sort` keys and the `fields` names.

## Sorting

```text
?sort=-rating_imdb          descending
?sort=+year                 ascending
?sort=-rating_imdb,+year    the second key breaks ties in the first
```

The sign is the direction; no sign means descending. Keys: `popularity`, `relevance`, `release_date`, `year`, `runtime`, `rating`, `rating_imdb`, `rating_kp`, `rating_tmdb`, `rating_kr`, `rating_letterboxd`, `rating_rt`, `rating_metacritic`, `rating_critics`, `votes_imdb`, `votes_kp`, `votes_tmdb`, `votes_kr`, `votes_letterboxd`, `votes_total`, `rating_weighted`, `divergence`, `budget`, `box_office_us`, `revenue_world`, `wins_imdb`, `oscars_won`, `random`. The money and award names match the title card.

You can send the `+` sign as is: `sort=+year` and `sort=%2Byear` work the same.

- **Without `sort`** the catalog goes by popularity: the vote total across all sources. Titles without votes, announcements included, come last.
- **With a search `q`** the default is relevance.
- **Sorting by a field** keeps only titles that have it. Collections, relations and filmographies are the exception: there, titles without the value stay at the end.
- **`sort=rating`** takes a title's first known rating (IMDb, then Kinopoisk, then TMDB) and puts titles with 50 or more votes above the rest.
- **`sort=rating_weighted`** is the average of the Kinopoisk, IMDb and TMDB ratings, weighted by vote count: a rating more people stand behind weighs more.
- **`sort=divergence`** orders by the gap between the Kinopoisk and IMDb ratings: with no sign or with `-`, largest gaps first; `+divergence` puts the closest ratings first. More in [Rating divergence](https://kinodata.space/en/docs/discover/divergence.md).
- **`sort=popularity`** orders by the vote total across all sources. The `popularity` filter and the card's `ratings.popularity` are a different number: TMDB's popularity index.
- **`sort=random`** shuffles the set. It can't be paged; repeat the request for a fresh draw.

## Ratings: set a vote threshold

The card has no overall score on purpose: sources have different scales and audiences, and an average would hide that. For sorting there is `rating_weighted`, an average weighted by vote count, but set a threshold with it too. Sorting by a single rating without a vote threshold lifts titles rated by a handful of people to the top: IMDb and TMDB accept votes before release, so even an unreleased film can get a 10 from one viewer. Set the threshold explicitly:

```text
?rating_imdb.gte=8&votes_imdb.gte=100000&sort=-rating_imdb
```

Every source with votes has a threshold: `votes_imdb`, `votes_kp`, `votes_tmdb`, `votes_kr` (Kinorium), `votes_letterboxd`.

### The rating window

When several audiences need to agree, use the window:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "rating_min=8.5" \
  --data-urlencode "rating_sources=imdb,kp" \
  --data-urlencode "rating_match=all" \
  --data-urlencode "votes_imdb.gte=500000" \
  --data-urlencode "sort=-votes_imdb" \
  --data-urlencode "limit=3" \
  --data-urlencode "fields=title,rating_imdb,rating_kp" \
  "https://api.kinodata.space/v1.3/titles"
```

Response `200`:

```json
{
  "has_more": true,
  "items": [
    {
      "imdb_id": "tt0468569",
      "kp_id": "111543",
      "tmdb_id": "155",
      "tmdb_tv_id": null,
      "kinorium_id": "352641",
      "title_ru": "Тёмный рыцарь",
      "title_en": "The Dark Knight",
      "rating_kp": 8.5,
      "rating_imdb": 9.1
    },
    {
      "imdb_id": "tt0111161",
      "kp_id": "326",
      "tmdb_id": "278",
      "tmdb_tv_id": null,
      "kinorium_id": "101209",
      "title_ru": "Побег из Шоушенка",
      "title_en": "The Shawshank Redemption",
      "rating_kp": 9.1,
      "rating_imdb": 9.3
    },
    {
      "imdb_id": "tt1375666",
      "kp_id": "447301",
      "tmdb_id": "27205",
      "tmdb_tv_id": null,
      "kinorium_id": "472809",
      "title_ru": "Начало",
      "title_en": "Inception",
      "rating_kp": 8.7,
      "rating_imdb": 8.8
    }
  ],
  "limit": 3,
  "offset": 0,
  "total": null
}
```

| parameter | meaning |
|---|---|
| `rating_min`, `rating_max` | The window's bounds. |
| `rating_sources` | Whose ratings to check: `kp`, `imdb`, `tmdb`, `kr` (Kinorium), `letterboxd`. The first three by default. |
| `rating_match` | `all`: every listed source must fall in the window. Without it, any one is enough. |

## People and companies

`person` picks titles a person worked on. Pass the ID with its catalog and a colon: `kp:41477`, `imdb:nm0634240`, `tmdb:525`, `kinorium:476400`. Several people separated by commas means any of them; repeating the parameter means all of them: `person=kp:37859&person=kp:41477` gives titles with both DiCaprio and Nolan. `person.role` narrows the role and returns `400` without `person`:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "person=kp:41477" \
  --data-urlencode "person.role=director" \
  --data-urlencode "released=true" \
  --data-urlencode "sort=-year" \
  --data-urlencode "limit=3" \
  --data-urlencode "fields=title,year" \
  "https://api.kinodata.space/v1.3/titles"
```

Response `200`:

```json
{
  "has_more": true,
  "items": [
    {
      "imdb_id": "tt33764258",
      "kp_id": "6385370",
      "tmdb_id": "1368337",
      "tmdb_tv_id": null,
      "kinorium_id": "11772913",
      "title_ru": "Одиссея",
      "title_en": "The Odyssey",
      "year": 2026
    },
    {
      "imdb_id": "tt15398776",
      "kp_id": "4664634",
      "tmdb_id": "872585",
      "tmdb_tv_id": null,
      "kinorium_id": "2828885",
      "title_ru": "Оппенгеймер",
      "title_en": "Oppenheimer",
      "year": 2023
    },
    {
      "imdb_id": "tt6723592",
      "kp_id": "1236063",
      "tmdb_id": "577922",
      "tmdb_tv_id": null,
      "kinorium_id": "2023303",
      "title_ru": "Довод",
      "title_en": "Tenet",
      "year": 2020
    }
  ],
  "limit": 3,
  "offset": 0,
  "total": null
}
```

`companies` searches by company name, `company.kind` by the company's role: `production`, `studio`, `network`, `special_effects`, `distributor_theatrical`, `distributor_online`, `dubbing`. Together they answer "where was HBO the network": `companies=HBO&company.kind=network`.

## Age ratings and release

- `age_limit` is the Russian age rating: 0, 6, 12, 16, 18. A comma-separated list (`age_limit=12,16`) or a range (`age_limit.gte=16`). `age_limit_max=12` is the same as `age_limit.lte=12`. Titles without a rating never match: an unknown rating isn't zero.
- `mpaa` is the US certificate: `G`, `PG`, `PG-13`, `R`, `NC-17` and the `TV-*` family. A title has one certificate, so for "any of" write `mpaa=R,NC-17`.
- `released=true` is out already, `released=false` is upcoming. A title counts as upcoming if its release date or year is later than today.

## Ready-made sets: preset

A `preset` is a named set of filters. Explicit parameters refine it: `preset=top&votes_imdb.gte=500000` is stricter than the usual top. `order=asc` reverses the preset’s sort (`preset=top&order=asc` starts with the lowest ratings), while `sort` replaces it entirely.

| preset | what's inside |
|---|---|
| `top` | `sort=-rating_imdb`, `votes_imdb.gte=100000`, has a Kinopoisk rating and a poster |
| `hidden_gems` | everything but shorts and TV shows: 1,000–50,000 Kinopoisk votes and 1,000–25,000 IMDb votes, no concerts, stand-up, stage recordings, music or documentaries, `sort=-rating_weighted` |
| `trending` | released, `sort=-popularity` |
| `new` | released in the last 30 days, `sort=-release_date` |
| `classics` | up to 1980, 50,000+ IMDb votes, `sort=-rating_imdb` |

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "preset=top" \
  --data-urlencode "limit=3" \
  --data-urlencode "fields=title,rating_imdb" \
  "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
    },
    {
      "imdb_id": "tt0795176",
      "kp_id": "279548",
      "tmdb_id": null,
      "tmdb_tv_id": "1044",
      "kinorium_id": "379535",
      "title_ru": "Планета Земля",
      "title_en": "Planet Earth",
      "rating_imdb": 9.4
    }
  ],
  "limit": 3,
  "offset": 0,
  "total": null
}
```

## Collections and franchises

- `collection` picks the titles of one of our collections by its `slug`: `collection=popular-titles`. The list of collections is `GET /collections`. An unknown collection returns `400` with the list of existing ones.
- `franchise` picks a franchise's titles by part of its TMDB name: `franchise=matrix`.

## Data completeness

`required` keeps titles that have every listed field filled: `required=poster,rating_imdb` or `required=kinorium_id`. `has_poster=true` does the same for the poster alone.

## In other lists

The same filters and `sort` work in collections, relations, filmographies, shared titles and discovery feeds, and the filters work in the release calendar too. For example, Christopher Nolan's movies with 100,000 votes or more, by IMDb rating:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "kind=movie" \
  --data-urlencode "votes_imdb.gte=100000" \
  --data-urlencode "sort=-rating_imdb" \
  --data-urlencode "limit=3" \
  --data-urlencode "fields=title,rating_imdb" \
  "https://api.kinodata.space/v1.3/persons/kp/41477/filmography"
```

Response `200`:

```json
{
  "has_more": true,
  "items": [
    {
      "imdb_id": "tt0468569",
      "kp_id": "111543",
      "tmdb_id": "155",
      "tmdb_tv_id": null,
      "kinorium_id": "352641",
      "title_ru": "Тёмный рыцарь",
      "title_en": "The Dark Knight",
      "rating_imdb": 9.1
    },
    {
      "imdb_id": "tt1375666",
      "kp_id": "447301",
      "tmdb_id": "27205",
      "tmdb_tv_id": null,
      "kinorium_id": "472809",
      "title_ru": "Начало",
      "title_en": "Inception",
      "rating_imdb": 8.8
    },
    {
      "imdb_id": "tt0816692",
      "kp_id": "258687",
      "tmdb_id": "157336",
      "tmdb_tv_id": null,
      "kinorium_id": "384511",
      "title_ru": "Интерстеллар",
      "title_en": "Interstellar",
      "rating_imdb": 8.7
    }
  ],
  "limit": 3,
  "offset": 0,
  "roles": [
    {
      "role": "director",
      "label_ru": "Режиссёры",
      "label_en": "Directors",
      "count": 19
    },
    {
      "role": "actor",
      "label_ru": "Актёры",
      "label_en": "Actors",
      "count": 2
    },
    {
      "role": "self",
      "label_ru": "В роли себя",
      "label_en": "Self",
      "count": 90
    },
    {
      "role": "archive",
      "label_ru": "Архивные кадры",
      "label_en": "Archive Footage",
      "count": 12
    },
    {
      "role": "writer",
      "label_ru": "Сценаристы",
      "label_en": "Writers",
      "count": 20
    },
    {
      "role": "producer",
      "label_ru": "Продюсеры",
      "label_en": "Producers",
      "count": 22
    },
    {
      "role": "cinematographer",
      "label_ru": "Операторы",
      "label_en": "Cinematographers",
      "count": 5
    },
    {
      "role": "composer",
      "label_ru": "Композиторы",
      "label_en": "Composers",
      "count": 1
    },
    {
      "role": "editor",
      "label_ru": "Монтажёры",
      "label_en": "Editors",
      "count": 5
    },
    {
      "role": "crew",
      "label_ru": "Съёмочная группа",
      "label_en": "Crew",
      "count": 3
    }
  ],
  "total": 18
}
```

Presets work too: `preset=top` in a filmography puts the best IMDb-rated titles first. In filmographies and shared titles the default order runs from newest to oldest, and `order=asc` reverses it.

## Mistakes in a request

An unknown filter or operator returns `400` instead of being ignored: a silently dropped filter would look like a filter that everything matched. When the fix is obvious, the API suggests it in `hint`. There is no `status` filter; ask about release status with `released`:

```bash
curl -G -H "X-API-Key: $KINODATA_KEY" \
  --data-urlencode "status=released" \
  "https://api.kinodata.space/v1.3/titles"
```

Response `400`:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "unknown query parameter \"status\"",
    "field": "status",
    "hint": "there is no status filter; the card carries type.status, the catalog filters by released"
  }
}
```

A parameter that takes one value can’t be repeated: `kind=movie&kind=series` returns `400` too. Put several values in one parameter, separated by commas: `kind=movie,series`. The exception is `person`: repeating it means “all of these people”.

## Next

- [Catalog and search](https://kinodata.space/en/docs/titles.md): every `/titles` parameter.
- [Lists and pagination](https://kinodata.space/en/docs/lists.md): how to page through the result.
