API Documentation

MediaCanon provides a free, open JSON API for movie and TV metadata. No API keys. No rate limits. Just data.

Base URL

https://mediacanon.org/api

All endpoints return JSON. The API is read-only.

Data Schemas

MCTitleLayer

Unified title response. For shows, includes season/episode metadata. For movies, includes runtime. title_id is the sole public identity.

{
  "title_id": number,
  "kind": "movie" | "show",
  "imdb_id": string | null,
  "display_name": string,
  "poster_url": string | null,
  "start_year": number | null,
  "end_year": number | null,
  "genres": string[],
  "rating": number | null,                // IMDb rating (0-10)
  "vote_count": number | null,            // IMDb vote count
  "runtime_minutes": number | null,       // Movies only
  "show": MCShowInfo | null,              // Shows only
  "watch_providers": WatchProviders | null,
  "networks": Network[] | null,           // Shows only
  "production_companies": ProductionCompany[] | null
}

The streaming decorator fields (watch_providers, networks, production_companies) are sourced from TMDB and populated lazily on first title view, then refreshed every 7 days. They may be null if the title has never been viewed, or {}/[] if TMDB has no data.

MCShowInfo

{
  "season_count": number,
  "episode_count": number,           // Total across all seasons
  "is_finished": boolean,
  "seasons": [
    { "season_number": number, "episode_count": number }
  ]
}

WatchProviders

Region-keyed availability map from TMDB. Top-level keys are ISO 3166-1 alpha-2 country codes (e.g. "US", "GB", "JP"). Each region may contain any of the offer-type arrays below; absent keys mean "not available under that offer type in that region".

{
  "US": {
    "flatrate": WatchProvider[],    // Subscription streaming
    "rent":     WatchProvider[],    // Paid rental
    "buy":      WatchProvider[],    // Paid purchase
    "ads":      WatchProvider[],    // Free with ads
    "free":     WatchProvider[]     // Free (no ads)
  },
  "GB": { ... },
  ...
}

WatchProvider

{
  "provider_id": number,
  "provider_name": string,          // "Netflix", "Disney Plus", ...
  "logo_path": string,              // Prepend https://image.tmdb.org/t/p/original
  "display_priority": number        // Lower = higher priority
}

Network

Commissioning network or streamer for a show. Shows only — always null for movies. Use the id field to detect streaming "Originals" (see table below).

{
  "id": number,
  "name": string,
  "logo_path": string | null,       // Prepend https://image.tmdb.org/t/p/original
  "origin_country": string          // ISO 3166-1 alpha-2
}

Known streaming-service network IDs for "Netflix Original"-style detection:

IDNetwork
213Netflix
2739Disney+
2552Apple TV+
1024Prime Video
49HBO
453Hulu

ProductionCompany

{
  "id": number,
  "name": string,
  "logo_path": string | null,       // Prepend https://image.tmdb.org/t/p/original
  "origin_country": string
}

MCEpisodeLayer

{
  "episode_number": number,
  "display_name": string | null,
  "image_url": string | null,
  "synopsis": string | null,
  "runtime_minutes": number | null,
  "air_date": string | null          // "YYYY-MM-DD" format
}

TitleSearchResult

Returned from search endpoints. title_id is the sole identity — no movie_id or show_id.

{
  "title_id": number,
  "type": "movie" | "show",
  "display_name": string,
  "start_year": number | null,
  "end_year": number | null,
  "imdb_id": string | null,
  "image_url": string | null,
  "num_votes": number | null,
  "average_rating": number | null,
  "original_language": string | null,
  "release_date": string | null
}

Title & Episodes

The primary API. title_id is the sole public identity — no internal IDs exposed.

GET /api/titles/:titleId

Get a title by title_id. Returns MCTitleLayer.

Response (show):

GET /api/titles/311356

{
  "title_id": 311356,
  "kind": "show",
  "imdb_id": "tt0903747",
  "display_name": "Breaking Bad",
  "poster_url": "https://image.tmdb.org/t/p/w500/...",
  "start_year": 2008,
  "end_year": 2013,
  "genres": ["Crime", "Drama", "Thriller"],
  "rating": 9.5,
  "vote_count": 2581808,
  "runtime_minutes": null,
  "show": {
    "season_count": 5,
    "episode_count": 62,
    "is_finished": true,
    "seasons": [
      { "season_number": 1, "episode_count": 7 },
      { "season_number": 2, "episode_count": 13 },
      { "season_number": 3, "episode_count": 13 },
      { "season_number": 4, "episode_count": 13 },
      { "season_number": 5, "episode_count": 16 }
    ]
  },
  "watch_providers": {
    "US": {
      "flatrate": [
        {
          "provider_id": 8,
          "provider_name": "Netflix",
          "logo_path": "/pbpMk2JmcoNnQwx5JGpXngfoWtp.jpg",
          "display_priority": 0
        }
      ],
      "buy": [
        {
          "provider_id": 2,
          "provider_name": "Apple TV",
          "logo_path": "/peURlLlr8jggOwK53fJ5wdQl05y.jpg",
          "display_priority": 2
        }
      ]
    }
  },
  "networks": [
    {
      "id": 174,
      "name": "AMC",
      "logo_path": "/alqLicR1ZMHMaZGP3xRQxn9sq7p.png",
      "origin_country": "US"
    }
  ],
  "production_companies": [
    {
      "id": 11073,
      "name": "Sony Pictures Television",
      "logo_path": "/A7TKdAOpfHCCvqzgJkm4EXHHFr0.png",
      "origin_country": "US"
    }
  ]
}

Response (movie):

GET /api/titles/105378

{
  "title_id": 105378,
  "kind": "movie",
  "imdb_id": "tt0133093",
  "display_name": "The Matrix",
  "poster_url": "https://image.tmdb.org/t/p/w500/...",
  "start_year": 1999,
  "end_year": null,
  "genres": ["Action", "Sci-Fi"],
  "rating": 8.7,
  "vote_count": 2233202,
  "runtime_minutes": 136,
  "show": null
}

GET /api/titles/:titleId/episodes?season=N

Get all episodes for a season. Returns MCEpisodeLayer[], ordered by episode number. Complete season snapshot — no pagination.

ParamTypeDescription
seasonnumberSeason number (required)

Response:

GET /api/titles/311356/episodes?season=1

[
  {
    "episode_number": 1,
    "display_name": "Pilot",
    "image_url": "https://image.tmdb.org/t/p/w500/...",
    "synopsis": null,
    "runtime_minutes": 59,
    "air_date": "2008-01-20"
  },
  {
    "episode_number": 2,
    "display_name": "Cat's in the Bag...",
    "image_url": "https://image.tmdb.org/t/p/w500/...",
    "synopsis": null,
    "runtime_minutes": 49,
    "air_date": "2008-01-27"
  }
]

Errors

ScenarioStatusResponse
Unknown titleId404{"error": "title not found"}
Movie + ?season=N400{"error": "title is not a show"}
Season out of range404{"error": "season not found"}

Discover

Browse titles with filters, sorting, and pagination. Returns only titles with poster images.

GET /api/discover/carousels

Get the carousel-based discover feed: interleaved show/movie collections with preview titles (up to 30 each). Paginated by number of carousels.

ParamTypeDescription
pagenumberPage number (default: 1)
per_pagenumberCarousels per page, 1–50 (default: 10)

Response:

GET /api/discover/carousels?page=1&per_page=2

{
  "carousels": [
    {
      "name": "Top Drama Shows",
      "slug": "top-drama-shows",
      "description": "Highest rated drama series",
      "collection_id": 5,
      "engagement_count": 42.0,
      "total_count": 1250,
      "titles": [...]
    },
    {
      "name": "Best Action Movies",
      "slug": "best-action-movies",
      "collection_id": 8,
      "engagement_count": 38.0,
      "total_count": 890,
      "titles": [...]
    }
  ],
  "total": 24,
  "page": 1,
  "per_page": 2
}

Each carousel's slug can be used with GET /api/collections/:slug to fetch all titles for that collection.

GET /api/discover

Discover titles with optional filters. Paginated, up to 100 results per page.

ParamTypeDescription
typestringFilter by type: movie or show
genrestringFilter by genre name (e.g. Action, Horror, Sci-Fi)
countrystringFilter by origin country (ISO 3166-1 code, e.g. US, KR, JP)
langstringFilter by original language (ISO 639-1 code, e.g. en, ja)
sortstringSort order: most_rated, top_rated, newest, trending, popular, hidden_gems, a-z (default: most rated by IMDb vote count)
year_minnumberMinimum year filter
rating_minnumberMinimum average rating filter
min_votesnumberMinimum IMDb vote count filter
pagenumberPage number (default: 1)
limitnumberResults per page, 1–100 (default: 100)

Response:

GET /api/discover?country=KR&type=movie&sort=top_rated&page=1

{
  "titles": [
    {
      "title_id": 484052,
      "type": "movie",
      "display_name": "Parasite",
      "start_year": 2019,
      "image_url": "https://image.tmdb.org/t/p/w500/...",
      "average_rating": 8.5,
      "num_votes": 940000,
      "tmdb_popularity": 19.4,
      "genres": ["Comedy", "Drama", "Thriller"]
    }
  ],
  "total": 156,
  "page": 1,
  "per_page": 100
}

Collections

Curated lists of titles, designed for LLM-generated discovery.

GET /api/collections

List all active collections, ordered by pinned status and click popularity.

Response: Collection[]

GET /api/collections

[
  {
    "id": 1,
    "name": "Classic Anime",
    "slug": "classic-anime",
    "description": "Beloved anime series and films",
    "pinned": true,
    "active": true,
    "engagement_count": 42.0
  }
]

GET /api/collections/:slug

Get a collection by slug or ID, including its matched titles.

Response:

GET /api/collections/classic-anime

{
  "collection": {
    "id": 1,
    "name": "Classic Anime",
    "slug": "classic-anime",
    "description": "Beloved anime series and films",
    "pinned": true,
    "active": true,
    "engagement_count": 42.0
  },
  "titles": [...],
  "total": 25
}

Usage Examples

cURL

# Title & Episodes
curl https://mediacanon.org/api/titles/311356
curl https://mediacanon.org/api/titles/311356/episodes?season=1

# Search
curl https://mediacanon.org/api/titles?q=matrix
curl https://mediacanon.org/api/titles?type=show&lang=ja&page=2

# Discover
curl https://mediacanon.org/api/discover?genre=Horror&sort=top_rated
curl https://mediacanon.org/api/discover?country=JP&type=movie&page=2
curl https://mediacanon.org/api/discover/carousels

# Collections
curl https://mediacanon.org/api/collections
curl https://mediacanon.org/api/collections/classic-anime

JavaScript

// Get title details
const title = await fetch('https://mediacanon.org/api/titles/311356').then(r => r.json());
console.log(title.display_name, title.kind);

// Get episodes for season 1
const episodes = await fetch('https://mediacanon.org/api/titles/311356/episodes?season=1').then(r => r.json());
episodes.forEach(ep => console.log(ep.episode_number, ep.display_name));

Python

import requests

title = requests.get('https://mediacanon.org/api/titles/311356').json()
print(title['display_name'], title['kind'])

episodes = requests.get('https://mediacanon.org/api/titles/311356/episodes?season=1').json()
for ep in episodes:
    print(ep['episode_number'], ep['display_name'])

Data Stats

  • ~892,000 movies
  • ~362,000 TV shows
  • ~7.5 million episodes

Data sourced from IMDb datasets. Episode images, air dates, runtimes, and synopses from TMDB.

Rate Limits

None. Be reasonable.