Skip to content

Repository files navigation

crunchyroll-api

An unofficial Crunchyroll API for Python — usable as a library (a module in another project) and as a standalone CLI.

It lets you:

  • Find series by name, TMDB ID, or IMDB ID (including metadata)
  • Log in and access your watchlist, history, continue watching, and custom lists (Crunchylists)
  • Select between profiles (multiprofile accounts)
  • Browse the full catalog, upcoming simulcast seasons, and the release calendar
  • Resolve episodes by season/episode number and read stream + subtitle metadata (for downloaders)

Unofficial and not affiliated with Crunchyroll LLC in any way. A premium account is recommended for full functionality. The Crunchyroll API is undocumented and changes occasionally — when it does, the values in endpoints.py may need adjusting.

📖 Full command and library reference: see GUIDE.md.

Installation

pip install -e .
# with development dependencies (pytest):
pip install -e ".[dev]"
# with encryption support (sealed credentials / encrypted token cache):
pip install -e ".[crypto]"

Requires Python ≥ 3.9. Only runtime dependency: requests.

Credentials

The library reads no .env file. Credentials are supplied by the calling program — either as constructor arguments or via a Config object. Where tokens are cached is up to the host program through a pluggable SessionStore.

Use as a library

from crunchyroll_api import CrunchyrollClient, CrunchyrollFinder, FileSessionStore

client = CrunchyrollClient(
    email="you@email.com",
    password="your-password",
    locale="de-DE",
    # optional: cache the login across runs
    session_store=FileSessionStore("./.crunchyroll.session.json"),
)
client.login()

# --- Search by name ---
for series in client.search_series("Frieren"):
    print(series.title, series.id, series.episode_count)

# --- Series details, seasons, episodes ---
series = client.get_series("GG5H5XQ0D")
seasons = client.get_seasons(series.id)
episodes = client.get_episodes(seasons[0].id)

# --- Select a profile ---
for p in client.list_profiles():
    print(p.profile_id, p.profile_name, p.is_primary)
client.switch_profile("<profile_id>")

# --- Watchlist / custom lists / history ---
watchlist = client.get_watchlist()
custom_lists = client.list_custom_lists()
items = client.get_custom_list(custom_lists[0]["list_id"])
history = client.get_history()

Search by TMDB ID / IMDB ID

Crunchyroll itself has no TMDB/IMDB IDs. The bridge uses TMDB to fetch titles + aliases for an ID, searches Crunchyroll with them, and returns the best fuzzy match. A free TMDB API key is required.

from crunchyroll_api import CrunchyrollClient, CrunchyrollFinder

client = CrunchyrollClient(email="...", password="...", locale="de-DE",
                           tmdb_api_key="YOUR_TMDB_KEY")
client.login()
finder = CrunchyrollFinder(client)

match = finder.find_by_tmdb(209867, media_type="tv")   # Frieren (TMDB tv)
if match:
    print(match.score, match.matched_title, match.series.id)

match = finder.find_by_imdb("tt22248376")

MatchResult contains the matched Series, a score (0–1), and the title that matched.

Custom session storage

Implement SessionStore (load/save/clear) to keep tokens in, e.g., a database or keyring:

from crunchyroll_api import SessionStore, CrunchyrollClient

class MyStore(SessionStore):
    def load(self): ...
    def save(self, data): ...
    def clear(self): ...

client = CrunchyrollClient(email="...", password="...", session_store=MyStore())

Use as a CLI (standalone)

The CLI takes credentials via flags or environment variables (CRUNCHYROLL_EMAIL, CRUNCHYROLL_PASSWORD, CRUNCHYROLL_LOCALE, TMDB_API_KEY, CRUNCHYROLL_SESSION_FILE).

# Search
crunchyroll-api --email a@b.de --password secret search "Frieren"

# With env vars + session cache
export CRUNCHYROLL_EMAIL=a@b.de CRUNCHYROLL_PASSWORD=secret CRUNCHYROLL_LOCALE=de-DE
export CRUNCHYROLL_SESSION_FILE=./.crunchyroll.session.json
crunchyroll-api search "Frieren" --type series --limit 10
crunchyroll-api seasons GG5H5XQ0D
crunchyroll-api episodes <season_id>

# TMDB / IMDB (TMDB key required)
crunchyroll-api --tmdb-key XYZ tmdb 209867 --media tv
crunchyroll-api --tmdb-key XYZ imdb tt22248376

# Account
crunchyroll-api profiles
crunchyroll-api watchlist --profile <profile_id>
crunchyroll-api continue
crunchyroll-api history
crunchyroll-api lists                 # all custom lists
crunchyroll-api lists --list-id <id>  # contents of a list

# Machine-readable output
crunchyroll-api --json search "Frieren"

Project layout

src/crunchyroll_api/
  __init__.py      public API
  client.py        CrunchyrollClient: auth, session, profiles, content, lists, streams
  config.py        Config + pluggable SessionStore (memory/file/custom)
  endpoints.py     all API endpoints (adjust centrally)
  models.py        dataclasses: Series, Season, Episode, Movie, Profile, Stream, ...
  tmdb.py          TMDB client (titles/aliases from a TMDB/IMDB ID)
  finder.py        CrunchyrollFinder: name/TMDB/IMDB -> fuzzy match
  crypto.py        optional encryption (sealed credentials, encrypted store)
  cli.py           command-line interface
tests/             offline tests (mocked HTTP calls, no network)

Tests

pip install -e ".[dev]"
pytest -q

The tests run fully offline (mocked HTTP responses) and cover the auth flow, CMS signing, parsing, fuzzy matching, catalog pagination, episode resolution, and stream parsing.

Notes & limits

  • Premium: watchlist, history, and premium-only content require an active subscription.
  • Cloudflare: Crunchyroll uses bot protection. On a block, CloudflareBlockError is raised — try a different user agent/IP.
  • TMDB matching is heuristic (title fuzzy match). For ambiguous titles the result may differ; min_score and rank_by_name() help with tuning.
  • Video streams are DRM-protected and are not decrypted by this library; the focus is on metadata, subtitle tracks, and account lists.

Feature summary

  • Full catalog: client.get_all_series() / iter_all_series() or CLI all-series fetch all series (paginated). A single page: client.browse(...).
  • Batch search: client.batch_search(["frieren", "one piece"]) or CLI search "frieren,one piece" (comma-separated).
  • Upcoming anime: client.list_simulcast_seasons() + client.get_season_anime("winter-2026") or CLI simulcast-seasons / simulcast <id>; weekly schedule via calendar.
  • Streams & subtitles: client.get_streams(episode_id) (manifest, audio, subtitle tracks), download_subtitle() (.ass/.vtt), invalidate_stream(). Video stays DRM.
  • Episode resolution: client.resolve_episode("Frieren", 1, 1) or find_episode(series_id, season, episode) — ideal for downloaders (S/E numbering).
  • Discovery: get_similar, list_categories, get_home_feed, get_news_feed, get_recommendations.
  • Robustness: retries + backoff and a GET cache (Config(max_retries=, cache_ttl=)), anonymous login login_anonymous().
  • Encrypted credentials: pass a sealed blob + key instead of plaintext (seal_credentials, CrunchyrollClient(sealed_credentials=, secret_key=)), plus EncryptedFileSessionStore for an encrypted token cache. Needs the [crypto] extra.
  • JSON export: any CLI output with --json (stdout) or -o file.json (file). In code: models are dataclasses, raw JSON under .raw.
crunchyroll-api all-series -o catalog.json
crunchyroll-api search "frieren,one piece,naruto" --json

See GUIDE.md for all commands and details.

Single-file build

Need just one file to embed in another project (e.g. an AniWorld downloader)?

python build_single_file.py     # -> dist/crunchyroll_api.py

Copy the bundled file (only requests as a dependency, CLI not included) and use from crunchyroll_api import CrunchyrollClient. Details in GUIDE.md section 8.

License

GNU General Public License v3.0 or later (GPLv3+). See LICENSE.

About

An inofficial API which I am use in other projects, wants to contribute here

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages