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
| Parameter | In | Required | Description |
|---|---|---|---|
series_id | path | yes | Numeric TV id, for example 1396. |
season_number | path | yes | Integer season number. 0 returns specials. |
api_key | query | one of two | Your key. Use this or the Authorization: Bearer header. |
language | query | no | TMDB tag such as en-US or fr-FR. 14 base languages are served. Default en-US. |
append_to_response | query | no | Season level values: credits, external_ids, images, videos. Other names are ignored. |
Response fields
| Field | Type | Notes |
|---|---|---|
id, season_number | integer | Season id and its number inside the show. |
name, overview | string | Localized text, empty when no translation exists. |
air_date | string or null | Date of the first episode, YYYY-MM-DD. |
poster_path | string or null | Append to https://img.amodb.dev/t/p/{size}. |
vote_average | number | Aggregate user score for the season. |
episodes | array | Every 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:
{
"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.
| Field | Type | Notes |
|---|---|---|
id | integer | Episode id. |
show_id | integer | Series id. Present in the season reply and on the series pointers, not in the single episode reply. |
season_number, episode_number | integer | Position inside the show. Use them to build the request path. |
name, overview | string | Localized text. |
air_date | string or null | Date only, no time or time zone. Null when unscheduled. |
episode_type | string | standard for regular episodes. To spot a finale, compare episode_number with the episode_count of that season in the series record. |
runtime | integer or null | Minutes. |
production_code | string | Studio code, often empty. |
still_path | string or null | Episode still. Use w300 or original from https://img.amodb.dev/t/p/{size}. |
vote_average, vote_count | number, integer | Aggregate user score. |
crew, guest_stars | array | Credits 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.
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 path | Returns |
|---|---|
GET /tv/{series_id}/season/{season_number}/credits | id, cast and crew for the season |
GET /tv/{series_id}/season/{season_number}/images | id and an empty posters array |
GET /tv/{series_id}/season/{season_number}/videos | id and an empty results array |
GET /tv/{series_id}/season/{season_number}/external_ids | id plus tvdb_id, wikidata_id and other ids, all null |
GET /tv/{series_id}/season/{season_number}/translations | id and an empty translations array |
GET .../episode/{episode_number}/credits | id, an empty cast, crew and guest_stars |
GET .../episode/{episode_number}/images | id and a stills array with the episode still |
GET .../episode/{episode_number}/videos | id and an empty results array |
GET .../episode/{episode_number}/external_ids | id plus imdb_id, tvdb_id and other ids, all null |
GET .../episode/{episode_number}/translations | id 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
| HTTP | status_code | When |
|---|---|---|
| 401 | 7 | The key is missing or wrong. |
| 402 | 36 | The plan period has ended. |
| 404 | 34 | The series, season or episode does not exist, for example season 0 on a show without specials, or the path is not offered. |
| 405 | 37 | You called a write method. |
| 429 | 25 | Over 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 nullruntimeand an emptyoverview. Render a placeholder instead of failing. - Stills are wide frames, not posters. Ask for
w300on lists andoriginalonly when you need a full size view.