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.
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.
curl -sf "https://api.amodb.dev/3/movie/155/moods" \
-H "Authorization: Bearer $AMODB_KEY"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);
}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
$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']);
}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
{
"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.
{
"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 |
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. |
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, and limits are on 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
moodsand save the second call. - Check
sourcebefore you compare titles. Measured and estimated meters use the same scale but not the same precision. - Store
nameandavgtogether with the title id in your own database if you filter often. A local filter beats a request per title. - Combine meters with ratings for a score and with soundtracks for the music in the same title.