# Movie and TV ratings

> GET /3/movie/{movie_id}/ratings and GET /3/tv/{tv_id}/ratings return IMDb, Kinopoisk, Metacritic, Rotten Tomatoes and TMDB scores. Each source is an object with value and votes, or null when unknown. IMDb, Kinopoisk and TMDB are out of 10, the other two out of 100. Add append_to_response=ratings to merge it into details.

## Endpoint

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

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

Response:

```json
{
  "id": 550,
  "imdb": {
    "value": 8.8,
    "votes": 2669989
  },
  "kinopoisk": {
    "value": 8.7,
    "votes": null
  },
  "metacritic": {
    "value": 67,
    "votes": null
  },
  "rotten_tomatoes": {
    "value": 81,
    "votes": null
  },
  "tmdb": {
    "value": 8.437,
    "votes": 33042
  }
}
```

## Get ratings for a movie

`GET /3/movie/{movie_id}/ratings` returns the scores that rating sources publish for one movie. The reply has the movie `id` and five source keys: `imdb`, `kinopoisk`, `metacritic`, `rotten_tomatoes` and `tmdb`. Each source key holds an object with `value` and `votes`, or `null` when that source is not known for the title.

The sample loads Fight Club (id 550) and prints every source, marking the unknown ones.

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

```js
const res = await fetch('https://api.amodb.dev/3/movie/550/ratings', {
  headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` },
});
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const ratings = await res.json();
for (const source of ['imdb', 'kinopoisk', 'metacritic', 'rotten_tomatoes', 'tmdb']) {
  const score = ratings[source];
  console.log(source, score ? `${score.value} (votes: ${score.votes ?? 'unknown'})` : 'not known');
}
```

```python
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/550/ratings",
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
ratings = r.json()
for source in ("imdb", "kinopoisk", "metacritic", "rotten_tomatoes", "tmdb"):
    score = ratings[source]
    if score is None:
        print(source, "not known")
    else:
        print(source, score["value"], "votes:", score["votes"] or "unknown")
```

```php
<?php
$ch = curl_init('https://api.amodb.dev/3/movie/550/ratings');
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");
}
$ratings = json_decode($body, true);
foreach (['imdb', 'kinopoisk', 'metacritic', 'rotten_tomatoes', 'tmdb'] as $source) {
    $score = $ratings[$source];
    echo $source, ' ', $score === null ? 'not known' : $score['value'] . ' votes: ' . ($score['votes'] ?? 'unknown'), PHP_EOL;
}
```

```go
package main

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

type Score struct {
	Value float64 `json:"value"`
	Votes *int    `json:"votes"`
}

type Ratings struct {
	IMDb           *Score `json:"imdb"`
	Kinopoisk      *Score `json:"kinopoisk"`
	Metacritic     *Score `json:"metacritic"`
	RottenTomatoes *Score `json:"rotten_tomatoes"`
	TMDB           *Score `json:"tmdb"`
}

func show(name string, s *Score) {
	if s == nil {
		fmt.Println(name, "not known")
		return
	}
	if s.Votes == nil {
		fmt.Println(name, s.Value, "votes: unknown")
		return
	}
	fmt.Println(name, s.Value, "votes:", *s.Votes)
}

func main() {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3/movie/550/ratings", 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 r Ratings
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		panic(err)
	}
	show("imdb", r.IMDb)
	show("kinopoisk", r.Kinopoisk)
	show("metacritic", r.Metacritic)
	show("rotten_tomatoes", r.RottenTomatoes)
	show("tmdb", r.TMDB)
}
```

### 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         | Movie id. Not present when you use `append_to_response`.                                     |
| `imdb`            | object or null  | IMDb score, out of 10 with one decimal.                                                      |
| `kinopoisk`       | object or null  | Kinopoisk score, out of 10 with one decimal.                                                 |
| `metacritic`      | object or null  | Metacritic score, out of 100 as an integer.                                                  |
| `rotten_tomatoes` | object or null  | Rotten Tomatoes score, out of 100 as an integer.                                             |
| `tmdb`            | object or null  | TMDB user score, out of 10 with up to three decimals.                                        |
| `value`           | number          | Inside each source object. The score on that source's own scale.                             |
| `votes`           | integer or null | Inside each source object. Number of votes behind the score, or `null` when it is not known. |

Read the five source keys by name and skip any other key. The source keys always come in the same order: `imdb`, `kinopoisk`, `metacritic`, `rotten_tomatoes`, `tmdb`.

### Example response

```json
{
  "id": 550,
  "imdb": { "value": 8.8, "votes": 2669989 },
  "kinopoisk": { "value": 8.7, "votes": null },
  "metacritic": { "value": 67, "votes": null },
  "rotten_tomatoes": { "value": 81, "votes": null },
  "tmdb": { "value": 8.437, "votes": 33039 }
}
```

Only IMDb and TMDB come with a vote count here. Kinopoisk, Metacritic and Rotten Tomatoes have a score and a `null` vote count.

## Get ratings for a TV series

`GET /3/tv/{tv_id}/ratings` has the same keys and the same null rules. Pass the series id, for example `1396` for Breaking Bad.

| Method and path                   | Returns               | Parameters |
| --------------------------------- | --------------------- | ---------- |
| `GET /3/movie/{movie_id}/ratings` | `id` and five sources | `movie_id` |
| `GET /3/tv/{tv_id}/ratings`       | `id` and five sources | `tv_id`    |

```bash
curl -sf "https://api.amodb.dev/3/tv/1396/ratings" -H "Authorization: Bearer $AMODB_KEY"
```

Breaking Bad has no Metacritic score, so that source is `null`:

```json
{
  "id": 1396,
  "imdb": { "value": 9.5, "votes": 2671962 },
  "kinopoisk": { "value": 8.9, "votes": null },
  "metacritic": null,
  "rotten_tomatoes": { "value": 96, "votes": null },
  "tmdb": { "value": 9, "votes": 18863 }
}
```

## Add ratings to a details call

`append_to_response=ratings` works on movie and TV details. The object arrives under the `ratings` key next to the usual fields, without its own `id`. A list page needs one request per title instead of two.

| Request                                         | Where the scores appear                      |
| ----------------------------------------------- | -------------------------------------------- |
| `/3/movie/550?append_to_response=ratings`       | `ratings` in the movie record                |
| `/3/tv/1396?append_to_response=credits,ratings` | `ratings` and `credits` in the series record |

See [movie details](https://amodb.dev/movies/) and [TV series](https://amodb.dev/tv-series/) for the other values you can append.

## Sources and null values

A null source is not a zero score. It means the score is missing for the title.

| Key               | Source          | Scale    | Label to show   |
| ----------------- | --------------- | -------- | --------------- |
| `imdb`            | IMDb            | 0 to 10  | IMDb            |
| `kinopoisk`       | Kinopoisk       | 0 to 10  | Kinopoisk       |
| `metacritic`      | Metacritic      | 0 to 100 | Metacritic      |
| `rotten_tomatoes` | Rotten Tomatoes | 0 to 100 | Rotten Tomatoes |
| `tmdb`            | TMDB            | 0 to 10  | TMDB            |

Coverage varies by title, so code against null, not against a fixed list of sources. Any of the five keys can be null for any title.

## How ratings differ from vote_average

The `vote_average` and `vote_count` fields on a movie or series record are the TMDB user score. The `tmdb` key carries the same score in the same shape as the other four, so one call gives you all five sources in one format.

Each source has its own audience and scale. Do not average them and do not compare a value from one source with the vote count of another.

## Show ratings in an app

Render only the sources that are present. A short badge list is enough for a card. Add the scale so a reader can tell 8.8 out of 10 from 67 out of 100.

```js
const labels = {
  imdb: ['IMDb', 10],
  kinopoisk: ['Kinopoisk', 10],
  metacritic: ['Metacritic', 100],
  rotten_tomatoes: ['Rotten Tomatoes', 100],
  tmdb: ['TMDB', 10],
};

function badges(ratings) {
  return Object.entries(labels)
    .filter(([key]) => ratings[key])
    .map(([key, [label, max]]) => `${label} ${ratings[key].value}/${max}`);
}
```

Show the label with every value. When all five are null, hide the block instead of showing empty badges.

## Errors

Failures keep the TMDB error body, where `success` is false and `status_code` names the problem. A title with no scores is not one of them.

| HTTP | status_code | When                                                               |
| ---- | ----------- | ------------------------------------------------------------------ |
| 401  | 7           | The key is missing or wrong.                                       |
| 402  | 36          | The plan period has ended.                                         |
| 404  | 34          | No movie or series with that id.                                   |
| 429  | 25          | Over your requests per minute. Wait for the `Retry-After` seconds. |

An unknown score is `null`, not an error. Details are on [errors](https://amodb.dev/errors/) and [rate limits](https://amodb.dev/rate-limits/).

## Practical notes

- Ratings are cached at the edge for 6 hours, like details. Refreshing more often returns the same body.
- A request with `append_to_response=ratings` counts as one request against your 20, 40 or 120 requests per minute.
- Store scores with the date you fetched them. Votes grow over time and the numbers move.
- To map an IMDb or Kinopoisk id to a title first, use [find by external id](https://amodb.dev/find-by-external-id/). For feelings rather than scores, see [moods](https://amodb.dev/moods/).

## Questions and answers

### Why is a source null?

Null means the score is missing or zero for that title. A new release or a regional film often has fewer sources than a famous one, so always handle null before you format a number. The votes field can be null too.

### How is this different from vote_average?

The vote_average field on a details record is the TMDB user score only. The ratings endpoint returns it again under the tmdb key, with three decimals, and adds the other four sources, each as value and votes.

### Can I average the five scores?

Do not. IMDb, Kinopoisk and TMDB are out of 10, while Metacritic and Rotten Tomatoes are out of 100. Each source has its own audience too, and some are null. Show the sources you have side by side with their labels.

### Does the ratings call cost extra requests?

It is one request like any other. With append_to_response=ratings the scores arrive inside a details call, so you use one request per minute slot instead of two.

## 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, the TV show endpoint](https://amodb.dev/tv-series/): TV show API reference for GET /3/tv/{series_id}: names, status, seasons, networks and the next episode, in TMDB v3 JSON with content ratings and providers.
- [Find by external id](https://amodb.dev/find-by-external-id/): Look up a movie, series or person by IMDb, TVDB, Wikidata or Kinopoisk id with the TMDB find endpoint, and read external ids and Wikidata facts for a title.
- [Mood meters](https://amodb.dev/moods/): Movie mood API with 0 to 10 mood meters per title. Read moods for a movie or series, merge them with append_to_response=moods and build badges or filters.
- [Rate limits, and TMDB API limits compared](https://amodb.dev/rate-limits/): TMDB API limits compared with amodb rate limits: 20, 40 or 120 requests per minute per key in a sliding 60 second window, no daily cap, 429 with Retry-After.
- [Quickstart](https://amodb.dev/quickstart/): Make your first amodb call: get a key, request movie details in curl, JavaScript, Python, PHP or Go, then load the poster from img.amodb.dev. Under 2 minutes.

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