AniList API Wrapper

Complete, type-safe Python client for the AniList GraphQL API.

v1.0.0 Python 3.10+ No API Key MIT

Introduction

The AniList Wrapper provides a clean, type-safe interface to the AniList GraphQL API. It handles rate limiting, pagination, OAuth2, and serialization — no API key needed for public data.

Zero Config

No API key. Import and go for all public anime/manga data.

Type-Safe

Pydantic v2 models for all 55+ GraphQL types with full alias support.

Rate Limited

Token bucket at ~80 req/min stays safely under AniList's ~90 cap.

Rich Data

Media, characters, staff, studios, airing schedules, trends, user lists.

Quick Start

from anilist import AniListClient, MediaSeason

client = AniListClient()

# Get by ID
anime = client.get_media(1)
print(anime.title.romaji)      # Cowboy Bebop
print(anime.average_score)     # 86

# Search
results = client.search_media("Attack on Titan")
for m in results.nodes:
    print(f"{m.title.english}: {m.average_score}%")

# Seasonal
summer = client.get_seasonal(2026, MediaSeason.SUMMER)
for m in summer.nodes:
    print(m.title.romaji)

# Characters
spike = client.get_character(1)
print(spike.name.full)         # Spike Spiegel

client.close()
from anilist import AniListClient

with AniListClient() as client:
    anime = client.get_media(1)
    trending = client.get_trending(per_page=10)
    characters = client.search_characters("Mikasa")

Installation

pip install anilist-wrapper

Or from source: git clone ... && pip install -e .

Dependencies: httpx >= 0.25.0, pydantic >= 2.0.0

API Methods 18 methods

get_media(id, *, media_type="ANIME") → Media | None

Get a single media entry by AniList ID.

anime = client.get_media(1)
manga = client.get_media(30002, media_type="MANGA")
search_media(query, *, ...) → MediaConnection

Search by title. Supports type, format, status, genre filters and all sort orders.

results = client.search_media("Cowboy Bebop", per_page=5, sort=MediaSort.POPULARITY_DESC)
for m in results.nodes:
    print(m.title.english, m.average_score)
print(results.page_info.total)  # total results

Known Issue AniList search endpoint occasionally returns HTTP 500 during high load.

get_seasonal(year, season, *, ...) → MediaConnection

Get media for a specific season.

from anilist import MediaSeason
summer = client.get_seasonal(2026, MediaSeason.SUMMER, per_page=20, sort=MediaSort.POPULARITY_DESC)
get_trending(*, media_type, page, per_page) → MediaConnection

Get currently trending anime or manga.

trending = client.get_trending(per_page=10)
trending_manga = client.get_trending(media_type="MANGA", per_page=5)
get_character(character_id) → Character | None

Get a character by ID with name, image, description, media appearances.

spike = client.get_character(1)
search_characters(query, *, page, per_page) → CharacterConnection

Search characters by name with pagination.

lelouch = client.search_characters("Lelouch", per_page=5)
get_staff(staff_id) → Staff | None

Get a staff member by ID.

director = client.get_staff(97091)
search_studios(query) → Studio | None

Search for a studio by name.

wit = client.search_studios("Wit Studio")
get_media_characters(media_id, *, ...) → CharacterConnection

Get characters for a media entry. Filter by role: "MAIN", "SUPPORTING", "BACKGROUND".

chars = client.get_media_characters(1, per_page=10, role="MAIN")
get_media_staff(media_id, *, page, per_page) → StaffConnection

Get staff (directors, animators, voice actors) for a media entry.

staff = client.get_media_staff(1, per_page=5)
get_media_studios(media_id) → StudioConnection

Get production studios for a media entry.

studios = client.get_media_studios(1)
for s in studios.nodes: print(s.name)
get_media_recommendations(media_id, *, page, per_page) → dict

Get recommendations based on a media entry.

recs = client.get_media_recommendations(16498, per_page=5)
for r in recs.get("nodes", []):
    mr = r["mediaRecommendation"]
    print(mr["title"]["romaji"], mr["averageScore"])
get_media_relations(media_id) → dict

Get sequels, prequels, adaptations, and other related media.

get_media_trends(media_id, *, page, per_page) → list[MediaTrend]

Get daily trend stats (trending, popularity, score over time).

trends = client.get_media_trends(1, per_page=30)
get_media_rankings(media_id) → list[dict]

Get all rankings (#1 in Action, #3 in 1999, etc.) for a media.

get_airing_schedule(*, ...) → dict

Get upcoming episodes with Unix timestamp filtering.

schedule = client.get_airing_schedule(per_page=10, not_yet_aired=True)

Known Issue Returns HTTP 400 in some configurations. Fix pending.

get_user(id=None, name=None) → User | None

Get a user profile. Without args returns the authenticated viewer (requires OAuth).

user = client.get_user(name="SomeUser")
me = client.get_user()  # needs auth

Auth Required for viewer endpoint.

get_user_list(user_id, *, ...) → MediaListCollection

Get a user's anime or manga list. Requires OAuth for private lists.

alist = client.get_user_list(12345, media_type="ANIME", status=MediaListStatus.COMPLETED)
get_genres() → list[str]

Get all possible media genres.

genres = client.get_genres()
# ['Action', 'Adventure', 'Comedy', 'Drama', ...]
get_media_tags(*, status=1) → list[dict]

Get all media tags. status: 0=rejected, 1=pending, 2=accepted.

get_site_statistics() → dict

Get AniList site-wide anime/manga/user counts.

Known Issue Returns HTTP 400. Fix pending.

Enums

EnumValues
MediaFormatTV, TV_SHORT, MOVIE, SPECIAL, OVA, ONA, MUSIC, MANGA, NOVEL, ONE_SHOT
MediaStatusFINISHED, RELEASING, NOT_YET_RELEASED, CANCELLED, HIATUS
MediaSeasonWINTER, SPRING, SUMMER, FALL
MediaSortID, TITLE_ROMAJI, SCORE, POPULARITY, TRENDING, EPISODES, SEARCH_MATCH, FAVOURITES... (all with _DESC variants)
MediaSourceORIGINAL, MANGA, LIGHT_NOVEL, VISUAL_NOVEL, VIDEO_GAME, ANIME, WEB_NOVEL, LIVE_ACTION...
MediaListStatusCURRENT, PLANNING, COMPLETED, DROPPED, PAUSED, REPEATING
CharacterSortID, ROLE, SEARCH_MATCH, FAVOURITES, RELEVANCE (+ _DESC)
StaffSortID, ROLE, LANGUAGE, SEARCH_MATCH, FAVOURITES, RELEVANCE (+ _DESC)
UserTitleLanguageROMAJI, ENGLISH, NATIVE, ROMAJI_STYLISED, ENGLISH_STYLISED, NATIVE_STYLISED
ScoreFormatPOINT_100, POINT_10_DECIMAL, POINT_10, POINT_5, POINT_3

Models

All models are Pydantic v2 classes with populate_by_name=True — GraphQL camelCase fields auto-map to Python snake_case.

ModelKey Fields
Mediaid, title, format, status, episodes, chapters, average_score, popularity, trending, genres, cover_image, next_airing_episode, rankings...
MediaConnectionpage_info: PageInfo, nodes: list[Media]
PageInfototal, per_page, current_page, last_page, has_next_page
Characterid, name: CharacterName, image, description, gender, age, favourites, media
Staffid, name: StaffName, image, primary_occupations, years_active, favourites
Studioid, name, is_animation_studio, favourites, media
Userid, name, avatar, statistics, options, media_list_options, donator_tier
MediaListid, status, score, progress, notes, media
MediaTrendmedia_id, date, trending, average_score, popularity, in_progress, episode
AiringScheduleid, airing_at (Unix), time_until_airing, episode, media_id
FuzzyDateyear, month, day (all Optional[int])

Authentication

Public data requires zero auth. OAuth2 is only needed for user-specific operations (reading private lists, updating progress).

from anilist.auth import AniListAuth
from anilist import AniListClient

auth = AniListAuth(
    client_id="YOUR_CLIENT_ID",
    client_secret="YOUR_CLIENT_SECRET",
)

# Step 1: User visits this URL
print(auth.get_authorization_url())

# Step 2: Exchange the code from the callback
token = auth.exchange_code("code_from_callback_url")

# Step 3: Use with client
client = AniListClient(auth=auth)
me = client.get_user()  # Returns the authenticated viewer
print(me.name)
# Save token to disk
auth.save("token.json")

# Later: restore
auth = AniListAuth.load("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET", "token.json")
client = AniListClient(auth=auth)

# Or load manually
auth.load_token(
    access_token="...",
    refresh_token="...",
    expires_in=31536000,  # ~1 year
)

CLI Usage

# Search
anilist search "Attack on Titan"
anilist search "isekai" --format TV --sort POPULARITY_DESC

# Get by ID
anilist get 1
anilist get 1 --json | jq .title.romaji

# Seasonal
anilist seasonal --year 2026 --season SUMMER
anilist seasonal --year 2026 --season WINTER --per-page 5

# Trending
anilist trending
anilist trending --type MANGA

# Characters & Studios
anilist character "Spike Spiegel"
anilist studio "Wit Studio"

# Genres & Schedule
anilist genres
anilist schedule

# User (requires OAuth)
anilist user --name "someuser" --token YOUR_OAUTH_TOKEN

Rate Limiting

AniList allows ~90 requests per minute. The wrapper uses a token bucket algorithm defaulting to 80 req/min with burst support (up to 10 instantaneous).

# Stricter limit
client = AniListClient(rate_limit_rpm=60)

# More burst tolerance
from anilist.rate_limiter import RateLimiter
limiter = RateLimiter(rate=80, burst=20)

Exceptions

ExceptionWhen
AniListErrorBase class for all errors
GraphQLErrorAPI returns errors array in response
RateLimitErrorHTTP 429 — exceeded 90 req/min
AuthenticationErrorOAuth failure
NotFoundErrorResource not found
ValidationErrorInput validation failure

Limitations & Known Issues

IssueStatusDetails
search_media() 500 errors AniList Server AniList search endpoint occasionally returns HTTP 500 under load. This is upstream — retry after a few seconds.
get_airing_schedule() Fix Pending Returns 400 in some configurations. Query structure needs adjustment.
get_site_statistics() Fix Pending Returns 400. GraphQL query needs schema correction.
No async support Planned Currently synchronous only (httpx). Async client planned for v1.1.
Mutations not implemented Planned Read-only operations only. SaveMediaListEntry, ToggleFavourite, etc. not yet wrapped.
Cross-module model refs Resolved Connection-type fields (characters, staff, studios, recommendations) on Media use object type. Fully typed connections available via dedicated getter methods.
Rate limit 90 req/min Hard cap imposed by AniList. The wrapper stays at ~80 with burst tolerance.
No GraphQL query customization Planned Methods use fixed queries. Custom query builder planned for advanced use cases.

Architecture Constraints

Version 1.0.0 · MIT License · Built for Python 3.10+

Copied!