# Mood meters

> GET /3/movie/{movie_id}/moods and GET /3/tv/{tv_id}/moods return a source and a results list. Each result has a mood name such as fear or humor, an avg on a 0 to 10 scale and the values behind it. Add append_to_response=moods to a details call to merge the meters without a second request.

## Endpoint

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

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

Response:

```json
{
  "id": 155,
  "source": "measured",
  "results": [
    {
      "name": "action",
      "avg": 3.9,
      "values": [
        3.6,
        3.4,
        4.2
      ]
    },
    {
      "name": "fear",
      "avg": 5.2,
      "values": [
        3.6,
        6,
        4.8
      ]
    },
    {
      "name": "humor",
      "avg": 0.9,
      "values": [
        2.4,
        1.8,
        0.4
      ]
    }
  ]
}
```

## Get mood meters for a movie

`GET /3/movie/{movie_id}/moods` returns the mood meters of one movie. A mood meter is a name and an average on a 0 to 10 scale. The reply carries the movie `id`, a `source` and a `results` array. Each result holds `name`, `avg` and `values`.

The sample loads the meters for The Dark Knight (id 155) and prints them from strongest to weakest.

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

```js
const res = await fetch('https://api.amodb.dev/3/movie/155/moods', {
  headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` },
});
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { source, results } = await res.json();
console.log('source:', source);
for (const m of [...results].sort((a, b) => b.avg - a.avg)) {
  console.log(m.name.padEnd(10), m.avg);
}
```

```python
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/155/moods",
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
data = r.json()
print("source:", data.get("source"))
for m in sorted(data["results"], key=lambda m: m["avg"], reverse=True):
    print(f"{m['name']:<10} {m['avg']}")
```

```php
<?php
$ch = curl_init('https://api.amodb.dev/3/movie/155/moods');
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");
}
$data = json_decode($body, true);
echo 'source: ', $data['source'] ?? 'none', PHP_EOL;
$results = $data['results'];
usort($results, fn ($a, $b) => $b['avg'] <=> $a['avg']);
foreach ($results as $m) {
    printf("%-10s %.1f\n", $m['name'], $m['avg']);
}
```

```go
package main

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

type Mood struct {
	Name   string    `json:"name"`
	Avg    float64   `json:"avg"`
	Values []float64 `json:"values"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3/movie/155/moods", 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 body struct {
		Source  string `json:"source"`
		Results []Mood `json:"results"`
	}
	if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
		panic(err)
	}
	fmt.Println("source:", body.Source)
	sort.Slice(body.Results, func(i, j int) bool { return body.Results[i].Avg > body.Results[j].Avg })
	for _, m := range body.Results {
		fmt.Printf("%-10s %.1f\n", m.Name, m.Avg)
	}
}
```

### Parameters

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

### Response fields

| Field              | Type             | Notes                                                                                                |
| ------------------ | ---------------- | ---------------------------------------------------------------------------------------------------- |
| `id`               | integer          | Movie id.                                                                                            |
| `source`           | string or null   | `measured` for viewer data, `estimated` when derived from genres, `null` when the title has no data. |
| `results`          | array            | Mood meters sorted by `name`. Empty when nothing is known.                                           |
| `results[].name`   | string           | Short lowercase word such as `fear`.                                                                 |
| `results[].avg`    | number           | Mean on a 0 to 10 scale. One decimal for measured titles, a whole number for estimated ones.         |
| `results[].values` | array of numbers | Up to 24 readings on the same 0 to 10 scale. Measured titles seen have 6, estimated titles have 5.   |

Names seen today are `action`, `fear`, `humor`, `pace`, `profanity`, `romance`, `sadness`, `sex` and `violence`. Not every title has all nine, so render names from the data and not from a fixed list.

### Example response

```json
{
  "id": 155,
  "source": "measured",
  "results": [
    { "name": "action", "avg": 3.9, "values": [3.6, 3.4, 4.2, 4.2, 4.8, 3] },
    { "name": "fear", "avg": 5.2, "values": [3.6, 6, 4.8, 6.4, 6.4, 3.8] },
    { "name": "humor", "avg": 0.9, "values": [2.4, 1.8, 0.4, 0.4, 0, 0.5] }
  ]
}
```

The list is cut to 3 of the 9 results.

### Measured and estimated titles

Fight Club (id 550) has no viewer data yet, so its meters are derived from its genres. Every `avg` is a whole number and `values` has 5 entries.

```json
{
  "id": 550,
  "source": "estimated",
  "results": [
    { "name": "action", "avg": 4, "values": [3, 4, 5, 4, 3] },
    { "name": "fear", "avg": 7, "values": [6, 7, 8, 7, 6] }
  ]
}
```

Check `source` if you want to treat estimated meters differently from measured ones.

## Get mood meters for a series

`GET /3/tv/{tv_id}/moods` has the same reply shape, with the series id in `id`. For Breaking Bad use `1396`.

| Method and path               | Returns                              | Parameters                   |
| ----------------------------- | ------------------------------------ | ---------------------------- |
| `GET /movie/{movie_id}/moods` | `id`, `source` and `results`         | `movie_id`                   |
| `GET /tv/{tv_id}/moods`       | `id`, `source` and `results`         | `tv_id`                      |
| `append_to_response=moods`    | `source` and `results` under `moods` | Added to movie or TV details |

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

When you append `moods` to a details call, the `moods` key sits at the top level of the record next to `title` or `name`. It holds `source` and `results` and has no `id` of its own. This costs one request, where two separate calls cost two.

## How to read the scale

The `avg` field is an intensity from 0 to 10. A higher number means the mood is stronger in the title.

| Range    | Reading                     |
| -------- | --------------------------- |
| 0 to 2.9 | The mood is weak or absent. |
| 3 to 5.9 | The mood is present.        |
| 6 to 7.9 | The mood is clear.          |
| 8 to 10  | The mood defines the title. |

The bands are a suggestion for your interface, not a field in the reply. The `values` array holds the readings behind the average, on the same scale.

## Build badges and filters

Moods fit three common interface patterns. Pick thresholds that suit your catalog and tune them.

| Pattern     | Rule                                              | Notes                                        |
| ----------- | ------------------------------------------------- | -------------------------------------------- |
| Badge       | Show up to 3 moods with `avg` 6 or higher         | Sort by `avg` first.                         |
| Meter bar   | Set bar width to `avg` times 10 as a percentage   | The scale is 0 to 10.                        |
| Filter chip | Match titles where the chosen mood is 7 or higher | Fetch meters per title, then filter locally. |

```js
function topMoods(results, min = 6, limit = 3) {
  return results
    .filter((m) => m.avg >= min)
    .sort((a, b) => b.avg - a.avg)
    .slice(0, limit)
    .map((m) => m.name);
}

function matchesMood(results, name, min = 7) {
  return results.some((m) => m.name === name && m.avg >= min);
}
```

An empty `results` array should render as no badges, not as an error.

## Errors

The error body has `success`, `status_code` and `status_message`, the same fields TMDB uses.

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

The moods endpoint does not answer 404 for an unknown id. It answers 200 with an empty `results` array, and `source` is `null` or missing. Appended to details, an unknown id is a 404 from the details call. The full list is on [errors](https://amodb.dev/errors/), and limits are on [rate limits](https://amodb.dev/rate-limits/).

## Practical notes

- Like other detail data, mood meters are cached at the edge for 6 hours. Fetching them more often returns the same body.
- A list of 20 titles needs 20 mood requests. If you already call details for each title, append `moods` and save the second call.
- Check `source` before you compare titles. Measured and estimated meters use the same scale but not the same precision.
- Store `name` and `avg` together with the title id in your own database if you filter often. A local filter beats a request per title.
- Combine meters with [ratings](https://amodb.dev/ratings/) for a score and with [soundtracks](https://amodb.dev/soundtracks/) for the music in the same title.

## Questions and answers

### What does a mood value mean?

The avg field is an intensity on a 0 to 10 scale, with one decimal for measured titles. An avg of 7 for fear means the title is scary, and 0 means the mood is absent. It is not a percentage and not a review score.

### Is the list of mood names fixed?

Do not assume it is. Names seen today are action, fear, humor, pace, profanity, romance, sadness, sex and violence, and a title can have fewer than nine. Render the name strings you get so a new one still shows up.

### What do measured and estimated mean in source?

Measured means the meters come from viewer data and carry decimals. Estimated means they are derived from the genres of the title, so avg is a whole number. A title with no data has an empty results array and a source of null.

### How do I filter a list of titles by mood?

Fetch the meters for each title in your list, either with the moods endpoint or with append_to_response on details, and keep the titles whose chosen mood passes your threshold. The filter runs in your code.

### Do TV series have moods too?

Yes. Use GET /3/tv/{tv_id}/moods, for example with series id 1396. The reply has the same shape as the movie endpoint.

## 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.
- [Movie and TV ratings](https://amodb.dev/ratings/): Movie ratings API that returns IMDb, Kinopoisk, Metacritic, Rotten Tomatoes and TMDB scores for a title in one call, with null for any source not known.
- [Soundtracks](https://amodb.dev/soundtracks/): Movie soundtrack API that returns the soundtrack album of a movie or TV series with its track list. Call the endpoint or add append_to_response=soundtracks.
- [Discover API](https://amodb.dev/discover/): Discover API with the same filters as TMDB v3. List movies and TV shows by genre, cast, company, keyword, year, rating, runtime and provider, with sorting.
- [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/moods/
