TV seasons and episodes API

Read one season with every episode at GET /3/tv/{series_id}/season/{season_number}, or a single episode one level deeper. The JSON matches TMDB v3 with air_date, episode_number, runtime and still_path. Each episode carries crew and guest_stars, season 0 holds specials, and append_to_response adds credits, images and videos.

Get a season

GET /3/tv/{series_id}/season/{season_number} returns one season and its full episodes array in a single reply. Season numbers start at 1, and 0 is reserved for specials. Take the series id and the season numbers from the TV series record.

The example reads season 1 of Breaking Bad. The code prints one line per episode.

curl -sSf "https://api.amodb.dev/3/tv/1396/season/1?language=en-US" \
  -H "Authorization: Bearer $AMODB_KEY"
const url = 'https://api.amodb.dev/3/tv/1396/season/1?language=en-US';
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` } });
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const season = await res.json();
for (const ep of season.episodes) {
  console.log(String(ep.episode_number).padStart(2, '0'), ep.name, ep.air_date, `${ep.runtime ?? '?'} min`);
}
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/tv/1396/season/1",
    params={"language": "en-US"},
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
season = r.json()
for ep in season["episodes"]:
    print(f"{ep['episode_number']:02d}", ep["name"], ep["air_date"], ep["runtime"] or "?")
<?php
$url = 'https://api.amodb.dev/3/tv/1396/season/1?language=en-US';
$ch = curl_init($url);
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");
}
$season = json_decode($body, true);
foreach ($season['episodes'] as $ep) {
    printf("%02d %s %s\n", $ep['episode_number'], $ep['name'], $ep['air_date']);
}
package main

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

type Season struct {
	Name     string `json:"name"`
	Episodes []struct {
		EpisodeNumber int    `json:"episode_number"`
		Name          string `json:"name"`
		AirDate       string `json:"air_date"`
		Runtime       int    `json:"runtime"`
	} `json:"episodes"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3/tv/1396/season/1?language=en-US", 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 s Season
	if err := json.NewDecoder(res.Body).Decode(&s); err != nil {
		panic(err)
	}
	for _, e := range s.Episodes {
		fmt.Printf("%02d %s %s %d min\n", e.EpisodeNumber, e.Name, e.AirDate, e.Runtime)
	}
}

Parameters

ParameterInRequiredDescription
series_idpathyesNumeric TV id, for example 1396.
season_numberpathyesInteger season number. 0 returns specials.
api_keyqueryone of twoYour key. Use this or the Authorization: Bearer header.
languagequerynoTMDB tag such as en-US or fr-FR. 14 base languages are served. Default en-US.
append_to_responsequerynoSeason level values: credits, external_ids, images, videos. Other names are ignored.

Response fields

FieldTypeNotes
id, season_numberintegerSeason id and its number inside the show.
name, overviewstringLocalized text, empty when no translation exists.
air_datestring or nullDate of the first episode, YYYY-MM-DD.
poster_pathstring or nullAppend to https://img.amodb.dev/t/p/{size}.
vote_averagenumberAggregate user score for the season.
episodesarrayEvery episode, with the fields listed in the next section.

Example response

GET /3/tv/1396/season/1?language=en-US. The season has 7 episodes, so episodes is cut to one entry, and the crew and guest_stars arrays of that entry are left out:

JSON
{
  "air_date": "2008-01-20",
  "episodes": [
    {
      "air_date": "2008-01-20",
      "episode_number": 1,
      "episode_type": "standard",
      "id": 62085,
      "name": "Pilot",
      "overview": "When an unassuming high school chemistry teacher discovers he has a rare form of lung cancer, he decides to...",
      "production_code": "",
      "runtime": 59,
      "season_number": 1,
      "show_id": 1396,
      "still_path": "/ydlY3iPfeOAvu8gVqrxPoMvzNCn.jpg",
      "vote_average": 0,
      "vote_count": 0
    }
  ],
  "name": "Season 1",
  "overview": "High school chemistry teacher Walter White's life is suddenly transformed by a dire medical diagnosis. Stre...",
  "id": 3572,
  "poster_path": "/91EIXdWxmp2mHNRsSuDW3WyDJLj.jpg",
  "season_number": 1,
  "vote_average": 0
}

Every episode in the season reply also carries crew and guest_stars arrays, with the entry shape described on credits. runtime can be null for an unaired episode.

Get an episode

GET /3/tv/{series_id}/season/{season_number}/episode/{episode_number} returns one episode. The season reply already holds every episode with crew and guest_stars, so use this endpoint to refresh one record without reloading the season. It accepts language and append_to_response with credits, external_ids, images, translations and videos. A request such as /3/tv/1396/season/1/episode/1?append_to_response=credits costs one request.

FieldTypeNotes
idintegerEpisode id.
show_idintegerSeries id. Present in the season reply and on the series pointers, not in the single episode reply.
season_number, episode_numberintegerPosition inside the show. Use them to build the request path.
name, overviewstringLocalized text.
air_datestring or nullDate only, no time or time zone. Null when unscheduled.
episode_typestringstandard for regular episodes. To spot a finale, compare episode_number with the episode_count of that season in the series record.
runtimeinteger or nullMinutes.
production_codestringStudio code, often empty.
still_pathstring or nullEpisode still. Use w300 or original from https://img.amodb.dev/t/p/{size}.
vote_average, vote_countnumber, integerAggregate user score.
crew, guest_starsarrayCredits for this episode, see credits.

Next and last episode on the series record

The series endpoint returns next_episode_to_air and last_episode_to_air. Each one is an episode summary with the fields above, plus show_id and minus crew and guest_stars. The next one is null for ended shows and for returning shows without a confirmed date. The sample reads South Park, a returning show.

JavaScript
const headers = { Authorization: `Bearer ${process.env.AMODB_KEY}` };
const res = await fetch('https://api.amodb.dev/3/tv/2190', { headers });
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const next = (await res.json()).next_episode_to_air;
console.log(next ? `Next: S${next.season_number}E${next.episode_number} on ${next.air_date}` : 'No upcoming episode');

For a countdown, compare air_date with the viewer's local date. The value carries no time of day, so show a date and not a clock time.

Season and episode sub-resources

Season and episode paths have their own sub-resources. Several of them return empty data today.

Method and pathReturns
GET /tv/{series_id}/season/{season_number}/creditsid, cast and crew for the season
GET /tv/{series_id}/season/{season_number}/imagesid and an empty posters array
GET /tv/{series_id}/season/{season_number}/videosid and an empty results array
GET /tv/{series_id}/season/{season_number}/external_idsid plus tvdb_id, wikidata_id and other ids, all null
GET /tv/{series_id}/season/{season_number}/translationsid and an empty translations array
GET .../episode/{episode_number}/creditsid, an empty cast, crew and guest_stars
GET .../episode/{episode_number}/imagesid and a stills array with the episode still
GET .../episode/{episode_number}/videosid and an empty results array
GET .../episode/{episode_number}/external_idsid plus imdb_id, tvdb_id and other ids, all null
GET .../episode/{episode_number}/translationsid and a translations array with name and overview per language

Season and episode credits are covered on credits, and stills on images. amodb does not offer the TMDB changes paths or an endpoint for the episodes of one episode group.

Errors

HTTPstatus_codeWhen
4017The key is missing or wrong.
40236The plan period has ended.
40434The series, season or episode does not exist, for example season 0 on a show without specials, or the path is not offered.
40537You called a write method.
42925Over your requests per minute. Wait for the Retry-After seconds.

See errors for the full body format and rate limits for the windows.

Practical notes

  • Seasons and episodes are cached for 6 hours. Re-read the current season once or twice a day while a show airs.
  • A 10 season show needs 10 season calls. On the 20 requests per minute plan that uses half of one minute's allowance, and a repeat run within 6 hours is answered from the edge cache.
  • An unaired episode can have a null air_date, a null runtime and an empty overview. Render a placeholder instead of failing.
  • Stills are wide frames, not posters. Ask for w300 on lists and original only when you need a full size view.