# API ключевых слов

> Эндпоинты ключевых слов возвращают теги сюжета фильма или сериала, каждый в виде ID и названия. Можно получить одно ключевое слово, список фильмов с ним или передать ID в discover через with_keywords и собрать страницы тем. Фильмы отвечают в keywords, сериалы в results.

## Эндпоинт

`GET /3/movie/{movie_id}/keywords`

```bash
curl "https://api.amodb.dev/3/movie/550/keywords" \
  -H "Authorization: Bearer $AMODB_KEY"
```

Ответ:

```json
{
  "id": 550,
  "keywords": [
    {
      "id": 818,
      "name": "based on novel or book"
    },
    {
      "id": 825,
      "name": "support group"
    },
    {
      "id": 851,
      "name": "dual identity"
    }
  ]
}
```

## Получение ключевых слов фильма

`GET /3/movie/{movie_id}/keywords` возвращает теги сюжета одного фильма. Ключевое слово представляет собой короткую фразу, например тему, место действия или тип первоисточника, и имеет числовой ID, который можно использовать в [discover](https://amodb.dev/ru/discover/).

В примере загружаются ключевые слова фильма, после чего у discover запрашиваются самые популярные фильмы с первым из этих ключевых слов.

```bash
curl -s "https://api.amodb.dev/3/movie/550/keywords" \
  -H "Authorization: Bearer $AMODB_KEY"

curl -s "https://api.amodb.dev/3/discover/movie?with_keywords=818&sort_by=popularity.desc" \
  -H "Authorization: Bearer $AMODB_KEY"
```

```js
const base = 'https://api.amodb.dev/3';
const headers = { Authorization: `Bearer ${process.env.AMODB_KEY}` };

const kw = await fetch(`${base}/movie/550/keywords`, { headers });
if (!kw.ok) throw new Error(`keywords failed with ${kw.status}`);
const { keywords } = await kw.json();
console.log(keywords.map((k) => `${k.id}:${k.name}`).join(', '));

if (keywords.length > 0) {
  const first = keywords[0];
  const found = await fetch(`${base}/discover/movie?with_keywords=${first.id}&sort_by=popularity.desc`, { headers });
  if (!found.ok) throw new Error(`discover failed with ${found.status}`);
  const page = await found.json();
  console.log(first.name, page.results.slice(0, 5).map((m) => m.title));
}
```

```python
import os
import requests

base = "https://api.amodb.dev/3"
headers = {"Authorization": f"Bearer {os.environ['AMODB_KEY']}"}

r = requests.get(f"{base}/movie/550/keywords", headers=headers, timeout=10)
r.raise_for_status()
keywords = r.json()["keywords"]
print(", ".join(f"{k['id']}:{k['name']}" for k in keywords))

if keywords:
    first = keywords[0]
    d = requests.get(
        f"{base}/discover/movie",
        params={"with_keywords": first["id"], "sort_by": "popularity.desc"},
        headers=headers,
        timeout=10,
    )
    d.raise_for_status()
    print(first["name"], [m["title"] for m in d.json()["results"][:5]])
```

```php
<?php
function amodb_get(string $url): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AMODB_KEY')],
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($status !== 200) {
        throw new RuntimeException("amodb returned $status");
    }
    return json_decode($body, true);
}

$base = 'https://api.amodb.dev/3';
$keywords = amodb_get("$base/movie/550/keywords")['keywords'];
if ($keywords) {
    $first = $keywords[0];
    $found = amodb_get("$base/discover/movie?with_keywords={$first['id']}&sort_by=popularity.desc");
    $titles = array_column(array_slice($found['results'], 0, 5), 'title');
    echo $first['name'], ': ', implode(', ', $titles), PHP_EOL;
}
```

```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

var client = &http.Client{Timeout: 10 * time.Second}

func get(path string, out any) error {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3"+path, nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AMODB_KEY"))
	res, err := client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("amodb returned %s", res.Status)
	}
	return json.NewDecoder(res.Body).Decode(out)
}

type Keywords struct {
	Keywords []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"keywords"`
}

type Page struct {
	Results []struct {
		Title string `json:"title"`
	} `json:"results"`
}

func main() {
	var kw Keywords
	if err := get("/movie/550/keywords", &kw); err != nil {
		panic(err)
	}
	if len(kw.Keywords) == 0 {
		return
	}
	first := kw.Keywords[0]
	var page Page
	if err := get(fmt.Sprintf("/discover/movie?with_keywords=%d&sort_by=popularity.desc", first.ID), &page); err != nil {
		panic(err)
	}
	fmt.Println(first.Name)
	for i, m := range page.Results {
		if i == 5 {
			break
		}
		fmt.Println(" ", m.Title)
	}
}
```

### Параметры

| Параметр   | Где   | Обязателен   | Описание                                                         |
| ---------- | ----- | ------------ | ---------------------------------------------------------------- |
| `movie_id` | path  | да           | Числовой ID фильма, например `550`.                              |
| `api_key`  | query | один из двух | Ваш ключ. Используйте его или заголовок `Authorization: Bearer`. |

### Поля ответа

| Поле              | Тип         | Примечания                                                                          |
| ----------------- | ----------- | ----------------------------------------------------------------------------------- |
| `id`              | целое число | ID фильма.                                                                          |
| `keywords`        | массив      | Объекты с `id` и `name`. Пуст, если у фильма нет ключевых слов.                     |
| `keywords[].id`   | целое число | ID ключевого слова. Используйте его в `with_keywords` и в путях ключевых слов ниже. |
| `keywords[].name` | строка      | Текст ключевого слова.                                                              |

### Пример ответа

```json
{
  "id": 550,
  "keywords": [
    { "id": 818, "name": "based on novel or book" },
    { "id": 825, "name": "support group" },
    { "id": 851, "name": "dual identity" }
  ]
}
```

В реальном ответе 14 ключевых слов, здесь оставлены первые 3. Названия ключевых слов не переводятся, поэтому `language` на них не влияет.

## Ключевые слова сериала

`GET /3/tv/{series_id}/keywords` принимает тот же ключ и возвращает такие же объекты, но массив называется `results`. В пути указывается ID сериала, например `1396`.

```json
{
  "id": 1396,
  "results": [
    { "id": 1508, "name": "new mexico" },
    { "id": 2231, "name": "drug dealer" }
  ]
}
```

В реальном ответе 29 записей, здесь оставлены первые 2. Клиент, который обслуживает оба типа медиа, может читать `data.keywords ?? data.results` и обрабатывать их одинаково.

## Записи ключевых слов и их фильмы

| Метод и путь                       | Возвращает                                                                            | Параметры          |
| ---------------------------------- | ------------------------------------------------------------------------------------- | ------------------ |
| `GET /keyword/{keyword_id}`        | `{ id, name }`                                                                        | нет                |
| `GET /keyword/{keyword_id}/movies` | Постраничный список фильмов с `id`, `page`, `results`, `total_pages`, `total_results` | `language`, `page` |

Оба пути используют числовой ID из массива ключевых слов. Список фильмов имеет ту же структуру элементов, что и другие [списки фильмов](https://amodb.dev/ru/trending-lists/), включая `genre_ids` и `poster_path`, а наверху добавлен `id` ключевого слова. Параметр `include_adult` принимается и игнорируется. Страница за пределами `total_pages` возвращает пустой массив `results`. Списка сериалов для ключевого слова нет, поэтому для сериалов используйте `/discover/tv?with_keywords=`.

## Объединение ключевых слов через discover

Для всего, что сложнее простого списка, фильтруйте в [discover](https://amodb.dev/ru/discover/). Он сортирует, разбивает на страницы, объединяет ключевые слова с другими фильтрами и работает как с фильмами, так и с сериалами.

| Цель                                  | Строка запроса                                     |
| ------------------------------------- | -------------------------------------------------- |
| Фильмы с одним ключевым словом        | `/discover/movie?with_keywords=818`                |
| Фильмы с обоими из двух ключевых слов | `/discover/movie?with_keywords=818,4565`           |
| Фильмы без ключевого слова            | `/discover/movie?without_keywords=818`             |
| Сериалы с ключевым словом             | `/discover/tv?with_keywords=9712`                  |
| Сериалы с любым из двух ключевых слов | `/discover/tv?with_keywords=818%7C9712`            |
| Одно ключевое слово внутри жанра      | `/discover/movie?with_keywords=818&with_genres=18` |

Запятая между ID означает «все из них». Чтобы подошел любой из них, поставьте между ID вертикальную черту, как в `with_keywords=818|4565`. Результаты discover кэшируются на edge в течение 30 минут.

## Как найти ID ключевого слова

Чтобы превратить текст в ID, вызовите `GET /3/search/keyword?query=heist`. Он возвращает постраничный список пар `id` и `name`, по 20 на странице. Полный набор путей поиска описан на странице [поиск](https://amodb.dev/ru/search/). Поиск ключевого слова, которому ничего не соответствует, сейчас отвечает 500 с `status_code` 11 вместо пустого списка. Ответы поиска кэшируются на 10 минут, но каждый вызов все равно учитывается в вашем лимите запросов в минуту, поэтому в поле с подсказками задерживайте отправку запросов (debounce).

## Ошибки

| HTTP | status_code | Когда                                                                             |
| ---- | ----------- | --------------------------------------------------------------------------------- |
| 401  | 7           | Ключ отсутствует или неверен.                                                     |
| 402  | 36          | Период тарифа закончился.                                                         |
| 404  | 34          | Нет ключевого слова с таким ID на `/keyword/{keyword_id}` и в его списке фильмов. |
| 405  | 37          | Вы использовали метод записи на пути ключевых слов.                               |
| 429  | 25          | Вы превысили лимит запросов в минуту. Подождите `Retry-After` секунд.             |
| 500  | 11          | Запрос `/search/keyword`, которому ничего не соответствует.                       |

Неизвестный ID фильма или сериала на путях ключевых слов не дает 404. Вы получите HTTP 200 с этим ID и пустым массивом.

Все коды описаны на странице [ошибки](https://amodb.dev/ru/errors/), а лимиты тарифов на странице [лимиты запросов](https://amodb.dev/ru/rate-limits/).

## Практические советы

- Храните `id` ключевого слова, а не название. Название могут переформулировать, а ID не меняется.
- Списки ключевых слов кэшируются вместе с деталями тайтла на 6 часов. Для страниц с тегами достаточно обновлять их раз в неделю.
- У одного тайтла короткий список тегов. Для страниц тем начинайте с ID ключевого слова и получайте его тайтлы через discover, а не наоборот.
- Используйте `without_keywords`, чтобы убрать лишнее, например тег, который не должен попадать на семейную страницу.
- Для малоизвестных тайтлов пустой массив является корректным ответом. Скройте строку с тегами, а не показывайте ошибку.

## Вопросы и ответы

### Почему в ответе для сериалов массив называется results, а для фильмов keywords?

Так устроен TMDB v3, и amodb сохраняет это поведение, чтобы существующие клиенты работали. Для фильмов приходит массив keywords. Для сериалов те же объекты лежат в results. Читайте data.keywords ?? data.results.

### Как найти ID ключевого слова по его названию?

Вызовите путь поиска ключевых слов с параметром query, например /3/search/keyword?query=heist. Он вернет постраничный список пар id и name. Выберите точное совпадение и сохраните ID.

### Можно ли использовать один ID ключевого слова для фильмов и сериалов?

Да. ID ключевого слова одинаков в discover для фильмов и в discover для сериалов, поэтому with_keywords работает на обоих путях. Храните один ID на тему и используйте его для обоих типов медиа.

### Как объединить несколько ключевых слов в одном запросе?

Перечислите ID в with_keywords через запятую, чтобы требовать все сразу, или через вертикальную черту, чтобы подошло любое. Исключайте ID через without_keywords. Следите, чтобы URL не превышал ограничение длины вашего HTTP-клиента.

## Связанные страницы

- [Discover API](https://amodb.dev/ru/discover/): Discover API с фильтрами TMDB v3. Фильмы и сериалы по жанру, актеру, студии, ключевому слову, году, рейтингу, длительности и провайдеру, с сортировкой.
- [API поиска](https://amodb.dev/ru/search/): API поиска с путями и JSON как в TMDB v3. Находите фильмы, сериалы, людей, коллекции, компании и ключевые слова по тексту, с фильтром по году и пагинацией.
- [API деталей фильма](https://amodb.dev/ru/movies/): API деталей фильма с теми же путями и JSON, что в TMDB v3. Получите фильм по ID, добавьте актеров, рейтинги и настроения через append_to_response, на 14 языках.
- [API сериалов](https://amodb.dev/ru/tv-series/): API сериалов с теми же путями и JSON, что в TMDB v3. Сериал по id с сезонами, телесетями и следующей серией, а также возрастные рейтинги и сервисы просмотра.
- [Жанры и справочники](https://amodb.dev/ru/genres/): API списка жанров с путями TMDB v3: ID жанров фильмов и сериалов с названиями, а также списки стран, языков, должностей, рейтингов и провайдеров.
- [Быстрый старт](https://amodb.dev/ru/quickstart/): Первый вызов amodb: получите ключ, запросите детали фильма через curl, JavaScript, Python, PHP или Go и загрузите постер с img.amodb.dev. Меньше 2 минут.

Обновлено: 2026-10-18. Source: https://amodb.dev/ru/keywords/
