# Фильтры и сортировка

> Операторы, все фильтры каталога, сортировка, окно оценок и пресеты.

> Оглавление документации для агентов: https://kinodata.space/ru/llms.txt — вся документация одним файлом: https://kinodata.space/ru/llms-full.txt

Каталог `/titles` выбирает тайтлы фильтрами и упорядочивает их параметром `sort`. Та же грамматика работает в подборке, связях, фильмографии, совместных работах и витринах, а фильтры — ещё и в календаре релизов: выучив её один раз, вы фильтруете любой список тайтлов.

```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"
```

Ответ `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
}
```

Где:

- `kind=anime` и `country=jp` — японское аниме;
- `votes_imdb.gte=50000` — не меньше 50 000 голосов на IMDb;
- `sort=-rating_imdb` — от высокой оценки IMDb к низкой.

## Операторы

К имени фильтра добавляется оператор:

| оператор | что значит | пример |
|---|---|---|
| без оператора | равно; несколько значений через запятую — любое из них | `kind=movie,series` |
| `.any` | любое из перечисленных | `genre.any=drama,comedy` |
| `.all` | все перечисленные сразу | `genre.all=drama,war` |
| `.not` | кроме перечисленных | `country.not=us` |
| `.gte`, `.lte` | не меньше, не больше | `year.gte=1990&year.lte=1999` |

Число без оператора — точное значение: `year=1999` то же, что `year.gte=1999&year.lte=1999`. Условия разных фильтров складываются через «и».

## Что можно фильтровать

| группа | фильтры |
|---|---|
| ID | `imdb_id`, `kp_id`, `tmdb_id`, `tmdb_tv_id` |
| Вид | `kind`, `is_series`, `adult` |
| Даты | `year`, `release_date`, `premiere_world`, `premiere_ru`, `digital_release`, `released`, `start_year`, `end_year` |
| Оценки и голоса | `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` |
| Содержание | `genre`, `tag`, `country`, `region`, `original_language`, `runtime` |
| Компании | `companies`, `company.kind`, `platform` |
| Люди | `person`, `person.role` |
| Подборки и франшизы | `collection`, `franchise` |
| Сериалы | `seasons_total`, `episodes_total` |
| Деньги | `budget`, `box_office_us`, `revenue_world` |
| Награды | `wins_imdb`, `oscars_won` |
| Возраст | `age_limit`, `age_limit_max`, `mpaa` |
| Полнота данных | `required`, `has_poster` |

Значения для `genre`, `country`, `region`, `platform`, `tag` и `companies` вместе с числом тайтлов отдаёт [`GET /filters`](https://kinodata.space/ru/docs/facets.md) — в поле `value`. Подписи всех перечислений, ключи `sort` и имена `fields` — [`GET /enums`](https://kinodata.space/ru/docs/enums.md).

## Сортировка

```text
?sort=-rating_imdb          по убыванию
?sort=+year                 по возрастанию
?sort=-rating_imdb,+year    второй ключ решает при равенстве первого
```

Знак — направление, без знака — по убыванию. Ключи: `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`. Имена денег и наград — те же, что в карточке тайтла.

Знак `+` можно передать как есть: `sort=+year` и `sort=%2Byear` работают одинаково.

- **Без `sort`** каталог идёт по популярности — по сумме голосов всех источников. Тайтлы без голосов, в том числе анонсы, стоят в конце.
- **С поиском `q`** по умолчанию — по релевантности.
- **Сортировка по полю** оставляет только тайтлы, у которых оно есть. Исключение — подборка, связи и фильмография: там тайтлы без значения остаются в конце.
- **`sort=rating`** берёт первую известную оценку тайтла — IMDb, затем Кинопоиск, затем TMDB — и ставит тайтлы с 50 и больше голосами выше остальных.
- **`sort=rating_weighted`** — средняя оценка Кинопоиска, IMDb и TMDB, взвешенная по числу голосов: оценка, за которой больше людей, весит больше.
- **`sort=divergence`** — по величине расхождения оценок Кинопоиска и IMDb: без знака и с `-` — сильные расхождения первыми, `+divergence` — самые близкие оценки первыми. Подробнее — [Расхождения оценок](https://kinodata.space/ru/docs/discover/divergence.md).
- **`sort=popularity`** — по сумме голосов всех источников. Фильтр `popularity` и поле `ratings.popularity` в карточке — другое число: индекс популярности TMDB.
- **`sort=random`** перемешивает выборку. Листать её нельзя — повторите запрос за новой.

## Оценки: ставьте порог голосов

Общего балла в карточке нет намеренно: у источников разные шкалы и аудитории, и среднее это скрыло бы. Для сортировки есть `rating_weighted` — среднее, взвешенное по числу голосов, — но и с ним ставьте порог. Сортировка по одной оценке без порога голосов поднимает наверх тайтлы, которые оценили несколько человек: IMDb и TMDB принимают голоса и до премьеры, поэтому десятку от одного зрителя может получить даже невышедший фильм. Ставьте порог явно:

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

Порог есть у каждого источника с голосами: `votes_imdb`, `votes_kp`, `votes_tmdb`, `votes_kr` (Кинориум), `votes_letterboxd`.

### Окно оценок

Когда важно мнение нескольких аудиторий сразу, используйте окно:

```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"
```

Ответ `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
}
```

| параметр | что значит |
|---|---|
| `rating_min`, `rating_max` | Границы окна. |
| `rating_sources` | Чьи оценки проверять: `kp`, `imdb`, `tmdb`, `kr` (Кинориум), `letterboxd`. По умолчанию первые три. |
| `rating_match` | `all` — в окно попадают все названные источники. Без него хватает любого. |

## Люди и компании

`person` выбирает тайтлы с участием человека. ID передаётся с каталогом через двоеточие: `kp:41477`, `imdb:nm0634240`, `tmdb:525`, `kinorium:476400`. Несколько людей через запятую — любой из них, повтор параметра — все сразу: `person=kp:37859&person=kp:41477` — тайтлы, где есть и ДиКаприо, и Нолан. `person.role` уточняет роль и без `person` даёт `400`:

```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"
```

Ответ `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` ищет по названию компании, `company.kind` — по её роли: `production`, `studio`, `network`, `special_effects`, `distributor_theatrical`, `distributor_online`, `dubbing`. Вместе они отвечают на вопрос «где HBO была телеканалом»: `companies=HBO&company.kind=network`.

## Возраст и выход

- `age_limit` — российский возрастной ценз: 0, 6, 12, 16, 18. Список через запятую (`age_limit=12,16`) или диапазон (`age_limit.gte=16`). `age_limit_max=12` — то же, что `age_limit.lte=12`. Тайтлы без ценза в выборку не попадают: неизвестный ценз — не ноль.
- `mpaa` — американский сертификат: `G`, `PG`, `PG-13`, `R`, `NC-17` и семейство `TV-*`. У тайтла один сертификат, поэтому для «любой из» пишите `mpaa=R,NC-17`.
- `released=true` — вышедшие, `released=false` — предстоящие. Тайтл считается предстоящим, если дата выхода или год позже сегодняшних.

## Готовые наборы: preset

`preset` — набор фильтров под именем. Явные параметры уточняют его: `preset=top&votes_imdb.gte=500000` строже обычного топа. `order=asc` переворачивает сортировку пресета — `preset=top&order=asc` начинается с самых низких оценок, — а `sort` заменяет её целиком.

| пресет | что внутри |
|---|---|
| `top` | `sort=-rating_imdb`, `votes_imdb.gte=100000`, есть оценка Кинопоиска и постер |
| `hidden_gems` | всё, кроме короткого метра и ТВ-шоу: 1 000–50 000 голосов на Кинопоиске и 1 000–25 000 на IMDb, без концертов, стендапов, спектаклей, музыки и документального кино, `sort=-rating_weighted` |
| `trending` | вышедшие, `sort=-popularity` |
| `new` | вышли за последние 30 дней, `sort=-release_date` |
| `classics` | до 1980 года, от 50 000 голосов на IMDb, `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"
```

Ответ `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
}
```

## Подборки и франшизы

- `collection` — тайтлы нашей подборки по её `slug`: `collection=popular-titles`. Список подборок — `GET /collections`. Несуществующая подборка — ошибка `400` со списком существующих.
- `franchise` — тайтлы франшизы по части её названия в TMDB: `franchise=matrix`.

## Полнота данных

`required` оставляет тайтлы, у которых заполнены перечисленные поля: `required=poster,rating_imdb` или `required=kinorium_id`. `has_poster=true` — то же для одного постера.

## В других списках

Те же фильтры и `sort` работают в подборке, связях, фильмографии, совместных работах и витринах, а фильтры — ещё и в календаре релизов. Например, фильмы Кристофера Нолана от 100 000 голосов — по оценке IMDb:

```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"
```

Ответ `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
}
```

Пресеты тоже работают: `preset=top` в фильмографии ставит первыми работы с лучшей оценкой IMDb. В фильмографии и совместных работах порядок по умолчанию — от новых работ к старым, `order=asc` его переворачивает.

## Ошибки в запросе

Неизвестный фильтр или оператор — ошибка `400`, а не тишина: незаметно отброшенный фильтр выглядел бы как фильтр, под который подошло всё. Где замена очевидна, API подсказывает её в `hint`. Фильтра `status` нет — статус выхода спрашивают через `released`:

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

Ответ `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"
  }
}
```

Параметр с одним значением нельзя повторять: `kind=movie&kind=series` — тоже `400`. Несколько значений пишутся через запятую — `kind=movie,series`. Исключение — `person`: его повтор значит «все эти люди».

## Дальше

- [Каталог и поиск](https://kinodata.space/ru/docs/titles.md) — все параметры `/titles`.
- [Списки и пагинация](https://kinodata.space/ru/docs/lists.md) — как листать результат.
