Перейти к содержанию

Соглашения

Одна грамматика на все списки: выучив её на /titles, вы умеете фильтровать связи, фильмографию и подборки.

Конверт списка

Любой список приходит в одной форме:

{
  "items": [  ],
  "total": 19,
  "limit": 50,
  "offset": 0,
  "has_more": true
}

total — размер всей выборки до пагинации. Он приходит числом там, где выборку задаёт адрес: состав тайтла, его связи, фильмография человека, подборка целиком. Он приходит null там, где выборку задал запрос: точный счёт по фильтру — это второй полный агрегат по 1.38 млн строк, и платить за него на каждой странице незачем. Листать это не мешает: has_more отвечает на вопрос «есть ли ещё».

У чартов и витрин total не приходит вовсе — они бесконечны по построению.

Свои поля ресурса едут рядом с этими пятью, а не вместо них: у связей это counts, у каста — summary, у подборки — collection.

Пагинация

?limit=50&offset=100

Значения за границей клампятся, и ответ отдаёт эхом настоящее: ?limit=5000 на каталоге вернёт "limit": 50. Нечисловое значение — 400.

список по умолчанию максимум
/titles, чарты, витрины, подборки, поиск людей 50 50
связи, фильмография, каст, медиа, тексты, серии 1000 1000
секции при ?group= 10 на секцию 50 на секцию

Разница в том, кто задал набор. Если набор задан адресом — «состав этого тайтла», «фильмография этого человека» — набор и есть ответ, он приходит целиком. Если набор задан запросом, ответ ранжирован, и приходит его верх; глубже листайте offset или курсором.

Курсор

Для глубокой прокрутки каталога вместо offset берите курсор — он не «съезжает», когда данные меняются под вами:

curl -H "X-API-Key: $KEY" \
  "https://api.kinodata.space/v1.2/titles?sort=-rating_imdb&votes_imdb.gte=100000&cursor="

В ответе появится next_cursor; передайте его в следующий запрос. null означает, что выборка кончилась.

Сортировка

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

Знак — это направление; без знака подразумевается убывание. Допустимые ключи:

release_date, year, runtime, popularity, relevance, rating, rating_kp, rating_tmdb, rating_imdb, rating_rt, rating_metacritic, rating_critics, votes_kp, votes_tmdb, votes_imdb, budget, revenue, box_office, awards_won, oscars_won, random.

sort=rating берёт первую известную оценку тайтла — IMDb, затем Кинопоиск, затем TMDB — и ранжирует тайтлы с 50+ голосами выше остальных, чтобы десятка от одного человека не обошла девятку от миллиона. Агрегата «общий балл» нет намеренно: у источников разные шкалы и разная аудитория, и усреднение это скрывает. Нужен свой порог доверия — ставьте его явно: ?sort=-rating_imdb&votes_imdb.gte=100000.

sort=random перемешивает выборку. У случайного порядка нет якоря, поэтому cursor и offset с ним отвечают 400: каждый запрос — новая выборка.

Фильтры

Пять операторов, один список полей:

оператор смысл пример
(нет) равно; CSV = любое из 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.

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

группа поля
идентичность 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_rt, rating_metacritic, rating_critics, votes_kp, votes_tmdb, votes_imdb, popularity
содержание genre, tag, country, original_language, companies, platform, runtime
люди person, person.role
сериалы seasons_total, episodes_total
деньги budget, revenue, box_office
награды awards_won, oscars_won
возраст age_limit, age_limit_max
принадлежность collection
полнота required, hydrated

person= ищет по участию человека и принимает внешний ключ: ?person=tmdb:525&person.role=director.

required=poster,rating_imdb оставляет только тайтлы, у которых эти поля заполнены. Отдельный has_poster=true — то же самое для одного постера.

Пресеты

?preset=top | hidden_gems | trending | new | classics

Пресет — это набор фильтров под именем. Он раскрывается после явных параметров, поэтому ?preset=top&votes_imdb.gte=500000 ужесточает чарт, а не конфликтует с ним. Раскрытие каждого пресета публикует /filters.

Секции

Списки, элементы которых различаются по одному полю, приходят разделами:

?group=relation   на /titles/{type}/{id}/relations
?group=role       на /titles/{type}/{id}/cast, /persons/{type}/{id}/filmography
?group=kind       на /titles/{type}/{id}/texts
?group=type|class|language   на /titles/{type}/{id}/media

Секция сама устроена как список:

{
  "items": [
    { "relation": "similar", "label": "Похожие", "total": 93, "has_more": true, "items": [  ] },
    { "relation": "related", "label": "Связанные", "total": 8, "has_more": true, "items": [  ] }
  ],
  "total": 4, "limit": 10, "offset": 0, "has_more": false
}

Те же четыре слова на обоих уровнях, поэтому один разбор читает и то и другое. total секции — её настоящий размер, даже когда элементы обрезаны по limit.

?group=none даёт плоский список. Сужение до одного значения (?relation=, ?role=, ?kind=) — тоже плоский список, с полной пагинацией: это переход внутрь раздела.

Пагинация по сгруппированному ответу отвечает 400 — у секций нет единой последовательности, в которую можно сместиться.

У серий ?group=season устроен иначе: это не секции с вложенными сериями, а сводка по сезонам — строка на сезон с числом серий, датами и рейтингом.

Язык

?lang=ru   по умолчанию
?lang=en

Влияет на производные поля (title, overview, character) и на подписи перечислений (kind_label, genres[].name, названия секций). Исходные title_ru / title_en / title_original приходят всегда и не зависят от lang.

Выбор полей

?fields=title,ratings      только эти блоки
?fields=-akas,-tags        всё, кроме этих

На карточке тайтла именами служат её блоки, в списках — ключи карточки списка. Смешивать два режима в одном параметре нельзя: title,-akas400, потому что две трактовки неперечисленного противоречат друг другу. Неизвестное имя — тоже 400, со списком допустимых.

Выбор полей понимают /titles, подборка, связи и фильмография — списки, которые строит один и тот же движок выборки.

Даты и числа

Даты — YYYY-MM-DD, строкой. Год — целое. Рейтинги — числа в своей шкале, она указана рядом в scale (10 или 100). Пустое скалярное значение приходит как null, а не как 0 или "". Массивы пустые — [], не null.

Кэширование

Ответы отдают ETag и Cache-Control. Повторный запрос с If-None-Match получает 304 и не тратит тело. Сначала возьмите тег:

curl -sI -H "X-API-Key: $KINODATA_KEY" "https://api.kinodata.space/v1.2/titles/tmdb/27205" | grep -i etag

Затем пришлите его обратно — значение вместе с кавычками:

curl -s -o /dev/null -w '%{http_code}\n' -H "X-API-Key: $KINODATA_KEY" -H 'If-None-Match: "23930-981179597"' "https://api.kinodata.space/v1.2/titles/tmdb/27205"

Ответы сжимаются: с Accept-Encoding: gzip карточка «Начала» весит 7 КБ вместо 24 КБ.