# Translations API

> The translations endpoints return every language version of a record in one response: title or name, overview, tagline and homepage for movies and series, name and overview for episodes. Each row carries iso_639_1, iso_3166_1 and english_name. Use it to read languages that the language parameter does not serve or to build a fallback chain.

## Endpoint

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

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

Response:

```json
{
  "id": 550,
  "translations": [
    {
      "iso_3166_1": "AR",
      "iso_639_1": "ar",
      "name": "العربية",
      "english_name": "Arabic",
      "data": {
        "title": "نادي القتال",
        "overview": "يعيش موظف مكتبي يعاني من الأرق والملل حياة روتينية فارغة، إلى أن يلتقي بالرجل الغامض تايلر ديردن، الذي يختلف عنه تمامًا ويؤمن بأن التحرر من قيود المجتمع يبدأ برفض الحياة التقليدية. تنشأ بينهما صداقة غريبة تقودهما إلى تأسيس نادٍ سري يتجمع في...",
        "homepage": "",
        "tagline": "نادي القتال هو نادي يوجد في العقل الباطن لكل واحد منا !",
        "runtime": 0
      }
    },
    {
      "iso_3166_1": "AZ",
      "iso_639_1": "az",
      "name": "Azərbaycan",
      "english_name": "Azerbaijani",
      "data": {
        "title": "Döyüş Klubu",
        "overview": "Xəyalları olmayan bir gənc yuxusuzluğa qarşı mübarizə aparır, bəlkə də boz və gündəlik həyatı ilə cansıxıcılığının nəticəsidir. Təyyarə gəzintisində o, çox xüsusi bir fəlsəfəyə sahib olan xarizmatik sabun satıcısı Tyler Durden ilə tanış olu...",
        "homepage": "",
        "tagline": "",
        "runtime": 0
      }
    },
    {
      "iso_3166_1": "BY",
      "iso_639_1": "be",
      "name": "Беларуская",
      "english_name": "Belarusian",
      "data": {
        "title": "Байцоўскі клуб",
        "overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground \"fight clubs\" forming in every town, until an eccentric gets in the wa...",
        "homepage": "",
        "tagline": "",
        "runtime": 0
      }
    }
  ]
}
```

## Get translations for a movie

`GET /3/movie/{movie_id}/translations` returns one row per language that has text for the movie. Each row holds a `data` object with the translated fields. The call takes no language parameter, because the whole point is to see all languages at once.

The example finds the Russian row and prints its title and the start of its overview.

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

```js
const url = 'https://api.amodb.dev/3/movie/550/translations';
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` } });
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { translations } = await res.json();
const ru = translations.find((t) => t.iso_639_1 === 'ru');
console.log(ru ? `${ru.data.title}: ${ru.data.overview.slice(0, 80)}` : 'no Russian row');
```

```python
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/550/translations",
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
ru = next((t for t in r.json()["translations"] if t["iso_639_1"] == "ru"), None)
print(f"{ru['data']['title']}: {ru['data']['overview'][:80]}" if ru else "no Russian row")
```

```php
<?php
$ch = curl_init('https://api.amodb.dev/3/movie/550/translations');
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");
}
foreach (json_decode($body, true)['translations'] as $t) {
    if ($t['iso_639_1'] === 'ru') {
        echo $t['data']['title'], ': ', mb_substr($t['data']['overview'], 0, 80), PHP_EOL;
        exit;
    }
}
echo 'no Russian row', PHP_EOL;
```

```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type Translations struct {
	Translations []struct {
		ISO6391 string `json:"iso_639_1"`
		Data    struct {
			Title    string `json:"title"`
			Overview string `json:"overview"`
		} `json:"data"`
	} `json:"translations"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3/movie/550/translations", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AMODB_KEY"))
	client := &http.Client{Timeout: 10 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		panic(res.Status)
	}
	var t Translations
	if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
		panic(err)
	}
	for _, row := range t.Translations {
		if row.ISO6391 == "ru" {
			text := []rune(row.Data.Overview)
			if len(text) > 80 {
				text = text[:80]
			}
			fmt.Printf("%s: %s\n", row.Data.Title, string(text))
			return
		}
	}
	fmt.Println("no Russian row")
}
```

### Parameters

| Parameter  | In    | Required   | Description                                               |
| ---------- | ----- | ---------- | --------------------------------------------------------- |
| `movie_id` | path  | yes        | Numeric movie id, for example `550`.                      |
| `api_key`  | query | one of two | Your key. Use this or the `Authorization: Bearer` header. |

### Response fields

| Field                         | Type    | Notes                                                                                                                                                                |
| ----------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                          | integer | The movie id.                                                                                                                                                        |
| `translations`                | array   | One row per language that has text, sorted by language code. Movie 550 returns 50 rows.                                                                              |
| `translations[].iso_3166_1`   | string  | Country code that goes with the language, such as `US` for `en` or `BR` for `pt`. For languages outside the 14 served ones it repeats the language code in capitals. |
| `translations[].iso_639_1`    | string  | Language code such as `ru` or `pt`. Unique in the array.                                                                                                             |
| `translations[].name`         | string  | The language written in that language.                                                                                                                               |
| `translations[].english_name` | string  | The language name in English.                                                                                                                                        |
| `translations[].data`         | object  | The translated fields. Keys depend on the resource, see the table below.                                                                                             |

### Example response

Two of the 50 rows are shown, and the overviews are cut.

```json
{
  "id": 550,
  "translations": [
    {
      "iso_3166_1": "US",
      "iso_639_1": "en",
      "name": "English",
      "english_name": "English",
      "data": {
        "title": "Fight Club",
        "overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy....",
        "homepage": "https://www.20thcenturystudios.com/movies/fight-club",
        "tagline": "Mischief. Mayhem. Soap.",
        "runtime": 0
      }
    },
    {
      "iso_3166_1": "RU",
      "iso_639_1": "ru",
      "name": "Русский",
      "english_name": "Russian",
      "data": {
        "title": "Бойцовский клуб",
        "overview": "Сотрудник страховой компании страдает хронической бессонницей и отчаянно пытается вырваться из мучительно скучной жизни. Однажд...",
        "homepage": "https://www.20thcenturystudios.com/movies/fight-club",
        "tagline": "«Интриги. Хаос. Мыло»",
        "runtime": 0
      }
    }
  ]
}
```

In amodb `runtime` is always 0 in a translation row. Read the runtime from the [movie details](https://amodb.dev/movies/) record.

## Translation endpoints and their data keys

All paths share the base `https://api.amodb.dev/3`. Every one returns `id` and a `translations` array of the row shape above. Only the keys inside `data` change.

| Method and path                                                                    | `data` keys                                                      |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `GET /movie/{movie_id}/translations`                                               | `title`, `overview`, `homepage`, `tagline`, `runtime` (always 0) |
| `GET /tv/{series_id}/translations`                                                 | `name`, `overview`, `homepage`, `tagline`                        |
| `GET /tv/{series_id}/season/{season_number}/translations`                          | none, the array is always empty                                  |
| `GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/translations` | `name`, `overview`                                               |

Series records list about as many languages as movies. Episode rows cover only the served languages that have text for that episode, from 2 to 14 in the samples checked. Treat a missing key like an empty value, and ignore keys you do not know. See [TV series](https://amodb.dev/tv-series/) for the parent records.

amodb does not offer translations for people or collections. A person biography follows the `language` parameter of the person details call, and a collection overview comes as stored.

## Fall back when text is missing

A title can exist in 14 languages and still have an empty overview in the one your user picked. Walk a chain of languages, take the first non-empty value and remember which language it came from.

```js
function pickText(translations, wanted, field) {
  for (const code of [...wanted, 'en']) {
    const row = translations.find((t) => t.iso_639_1 === code);
    const value = row?.data?.[field];
    if (value) return { code, value };
  }
  return null;
}

// Prefer Ukrainian, then Russian, then English.
// const hit = pickText(translations, ['uk', 'ru'], 'overview');
```

For a series, pass `'name'` as the field instead of `'title'`. Mark text from a fallback language in your interface, so the user knows why a Ukrainian page shows an English synopsis.

## Translations or the language parameter

| Need                                                         | Use                                        |
| ------------------------------------------------------------ | ------------------------------------------ |
| Show one language to one user                                | `language` on the details call             |
| List all languages of a title in an editor                   | This endpoint                              |
| Read a language that `language` does not serve, such as `ja` | This endpoint, pick the row by `iso_639_1` |
| Check which of the 14 served languages have text             | This endpoint, compare `iso_639_1`         |
| Build alternate language links for a page                    | This endpoint, read the codes present      |

For the tags `language` accepts, see [languages](https://amodb.dev/languages/). Any other tag falls back to English on a details call.

## Errors

| HTTP | status_code | When                                                                        |
| ---- | ----------- | --------------------------------------------------------------------------- |
| 401  | 7           | The key is missing or wrong.                                                |
| 402  | 36          | The plan period has ended.                                                  |
| 404  | 34          | No season or episode with that id, or an id that is not a number.           |
| 405  | 37          | You used a write method on a translations path.                             |
| 429  | 25          | You went over your requests per minute. Wait for the `Retry-After` seconds. |

A movie or series id that does not exist is not an error on this path: it answers 200 with `"translations": []`, while the details path answers 404. More on [errors](https://amodb.dev/errors/) and [rate limits](https://amodb.dev/rate-limits/).

## Practical notes

- One call replaces up to 14 details calls. Use it when you need more than two languages of the same title.
- The rows are sorted by language code, which is not a priority order. Pick rows by `iso_639_1`, never by array position.
- Details-type calls are cached at the edge for 6 hours, so a client cache of the same length costs nothing in freshness.
- Original titles in other scripts, such as Japanese or Korean, are always available in `original_title` and `original_name` on the details record.
- To add translations to a details call, pass `append_to_response=translations` and read the same array from the reply. It works for movies, series and episodes. The appended block has no `id`.

## Questions and answers

### When should I use translations instead of the language parameter?

Use language when you show one language to a user. Use translations when you need several languages of one title at once, for example an editor screen, alternate language pages or a coverage check. It also returns languages that language does not serve, such as ja or de.

### What is the difference between iso_639_1 and iso_3166_1?

iso_639_1 is the language code such as pt. iso_3166_1 is the country that goes with it, such as BR. For languages outside the 14 served ones it repeats the language code in capitals, for example DA for da, so always match on iso_639_1.

### Why is a field empty inside data?

An empty string or a zero means no text exists for that field in that language. Do not show it. Fall back to the next language in your chain, usually English, or to the original title.

### Which languages does the endpoint cover?

Movie and series records list every language the catalog holds, often 40 to 80 rows. That includes the 14 served ones: en, ru, uk, be, zh, pt, bg, he, cs, ro, fr, pl, it and es. Episodes list only some of those 14.

## Related pages

- [Movie details API](https://amodb.dev/movies/): Movie details API with the same paths and JSON as TMDB v3. Get one movie by id, add credits, ratings and moods with append_to_response, in 14 languages.
- [TV series API](https://amodb.dev/tv-series/): TV series API with the same paths and JSON as TMDB v3. Get a show by id with seasons, networks and the next episode, plus content ratings and watch providers.
- [Person details API](https://amodb.dev/people/): Person details API with the same paths and JSON as TMDB v3. Get an actor or crew member by id, with biography, filmography, external ids and the popular list.
- [Collections API](https://amodb.dev/collections/): Movie collections API with TMDB v3 paths: fetch a franchise by id with all its parts, read belongs_to_collection on a movie, and search collections by name.
- [Languages](https://amodb.dev/languages/): amodb serves titles, overviews and names in 14 languages. Pass a TMDB style tag such as ru-RU or pt-BR in language; any other tag falls back to English.
- [Multilingual movie metadata API](https://amodb.dev/multilingual-movie-metadata-api/): Multilingual movie metadata API with 14 languages. Use TMDB language tags, read all translations, fall back to English and handle Hebrew right-to-left.

Updated: 2026-10-18. Source: https://amodb.dev/translations/
