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

ParameterInRequiredDescription
movie_idpathyesNumeric movie id, for example 155.
api_keyqueryone of twoYour key. Use this or the Authorization: Bearer header.

Response fields

FieldTypeNotes
idintegerMovie id.
sourcestring or nullmeasured for viewer data, estimated when derived from genres, null when the title has no data.
resultsarrayMood meters sorted by name. Empty when nothing is known.
results[].namestringShort lowercase word such as fear.
results[].avgnumberMean on a 0 to 10 scale. One decimal for measured titles, a whole number for estimated ones.
results[].valuesarray of numbersUp 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 pathReturnsParameters
GET /movie/{movie_id}/moodsid, source and resultsmovie_id
GET /tv/{tv_id}/moodsid, source and resultstv_id
append_to_response=moodssource and results under moodsAdded to movie or TV details
curl
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.

RangeReading
0 to 2.9The mood is weak or absent.
3 to 5.9The mood is present.
6 to 7.9The mood is clear.
8 to 10The 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.

PatternRuleNotes
BadgeShow up to 3 moods with avg 6 or higherSort by avg first.
Meter barSet bar width to avg times 10 as a percentageThe scale is 0 to 10.
Filter chipMatch titles where the chosen mood is 7 or higherFetch meters per title, then filter locally.
JavaScript
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.

HTTPstatus_codeWhen
4017The key is missing or wrong.
40236The plan period has ended.
42925Over 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 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 for a score and with soundtracks for the music in the same title.