API reference
Everything below is importable from the top-level aniliberty package unless noted.
Client
aniliberty.AniLiberty
classAniLiberty(
*,
base_url: str = DEFAULT_BASE_URL,
timeout: float = DEFAULT_TIMEOUT,
max_concurrency: int = DEFAULT_MAX_CONCURRENCY,
headers: Mapping[str, str] | None = None,
http_client: httpx.AsyncClient | None = None,
token: str | None = None,
cache_dir: str | os.PathLike[str] | None = None,
cache_ttl: float | None = None,
)Async client for the AniLibria API.
Groups the API into resource namespaces that follow the tags of its OpenAPI spec
(client.app, ...). Use it as an async context manager so the connection pool is
closed when you're done, or call aclose() yourself.
| Name | Type | Description |
|---|---|---|
http | The low-level client every resource uses. | |
app | Service endpoints — API status and quick release search. | |
releases | Anime releases — latest, random, recommended, by ID/alias, members. | |
episodes | Episodes of releases, with HLS links. | |
catalog | The release catalog; | |
schedule | The weekly airing schedule. | |
genres | Genres, and the releases of each. | |
franchises | Franchises — groups of related releases. | |
torrents | Torrents, | |
teams | AniLibria's own teams, roles and members. | |
media | Front-page promo banners and AniLibria's YouTube videos. | |
auth | Logging in and out — by password, one-time code or social network. | |
me | The logged-in user's own account — profile, favorites, collections, watch progress and history (needs a session). |
token
attributepropertytoken: str | NoneThe session token sent with every request, or None when not logged in.
Set by client.auth.login() (and the other login methods), cleared by
client.auth.logout(). Save it to reuse the session later as
AniLiberty(token=...); assign None to stop sending it without ending the
session. It is a secret — don't log it.
aclose
methodasyncaclose() -> NoneClose the connection pool. Safe to call more than once.
SOURCE ↗Resources
The namespaces of an AniLiberty instance: client.app, client.releases,
client.releases.ratings, client.episodes, client.catalog (with client.catalog.references), client.schedule, client.genres, client.franchises, client.torrents, client.teams, client.media, client.auth (with client.auth.otp, client.auth.social, client.auth.password), client.me (with client.me.favorites, client.me.collections and their references, client.me.views). Not meant to be created directly — import from aniliberty.resources only
for type annotations.
aniliberty.resources.AppResource
classAppResource(http: HttpClient) BaseResource
Service endpoints of the API, available as client.app.
status
methodasyncstatus() -> ApiStatusGet the status of the API.
A cheap request that is handy as a health check. The response also lists the hosts
the API is available at, which can be used as base_url mirrors.
| Type | Description |
|---|---|
ApiStatus | The API status. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
search_releases
methodasyncsearch_releases(query: str, *, refresh: bool = False) -> list[Release]Quick search of releases by name — the search box of the site.
Matches Russian and English names and aliases, fuzzily: "frieren",
"Фрирен" and "sousou-no-frieren" find the same release. There is no paging
and no limit: the API returns a short list (at most 20 releases in every query
checked), in an order it chooses. For filtered, paginated search use the catalog
instead.
| Name | Type | Description | Default |
|---|---|---|---|
query | str | Search text. | |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.ReleasesResource
classReleasesResource(http: HttpClient) BaseResource
Anime releases, available as client.releases.
For filtering and sorting the whole catalog, see client.catalog; this namespace
covers single releases and the ready-made selections.
limit on every method here must be between 1 and 50 — the API answers anything else
with a 422, raised as ValidationError (see docs/api-notes.md).
| Name | Type | Description |
|---|---|---|
ratings | The logged-in user's own scores of releases (needs a session). |
latest
methodasynclatest(
*,
limit: int | None = None,
refresh: bool = False,
) -> builtins.list[Release]Get the latest releases — the ones that got new episodes most recently.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | How many releases to return, 1 to 50. Omitted, the API picks the number. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[Release] | The releases, with |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
random
methodasyncrandom(*, limit: int | None = None) -> builtins.list[Release]Get random releases.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | How many releases to return, 1 to 50. | None |
| Type | Description |
|---|---|
builtins.list[Release] | The releases, with |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
recommended
methodasyncrecommended(
*,
release: int | Release | None = None,
limit: int | None = None,
) -> builtins.list[Release]Get recommended releases, optionally ones similar to a given release.
| Name | Type | Description | Default |
|---|---|---|---|
release | int | Release | None | Release ID, or a | None |
limit | int | None | How many releases to return, 1 to 50. | None |
| Type | Description |
|---|---|
builtins.list[Release] | The releases, with |
| Type | Description |
|---|---|
TypeError |
|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
list
methodasynclist(
*,
ids: Sequence[int] | None = None,
aliases: Sequence[str] | None = None,
page: int | None = None,
limit: int | None = None,
refresh: bool = False,
) -> Page[Release]Get several releases at once by their IDs and/or aliases.
Releases given both by ID and by alias are returned once. The order of the result
doesn't follow the order of ids/aliases. To fetch all pages, use
iter_list().
| Name | Type | Description | Default |
|---|---|---|---|
ids | Sequence[int] | None | Release IDs. | None |
aliases | Sequence[str] | None | Release aliases. | None |
page | int | None | Page number, starting at 1. | None |
limit | int | None | Page size, 1 to 50. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
ValueError | Neither |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_list
methoditer_list(
*,
ids: Sequence[int] | None = None,
aliases: Sequence[str] | None = None,
limit: int | None = None,
refresh: bool = False,
) -> AsyncIterator[Release]Iterate over all releases matching ids/aliases, fetching page by page.
Same as calling list() with page=1, 2, ... until the last page; the next page
is requested only once the current one has been consumed.
| Name | Type | Description | Default |
|---|---|---|---|
ids | Sequence[int] | None | Release IDs. | None |
aliases | Sequence[str] | None | Release aliases. | None |
limit | int | None | Page size, 1 to 50 — affects how many requests are made, not the result. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
AsyncIterator[Release] | An async iterator over the releases. |
| Type | Description |
|---|---|
ValueError | Neither |
get
methodasyncget(release: int | str | Release, *, refresh: bool = False) -> ReleaseGet one release by its ID or alias.
| Name | Type | Description | Default |
|---|---|---|---|
release | int | str | Release | Release ID ( | |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
NotFoundError | No release with this ID or alias. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
members
methodasyncmembers(
release: int | str | Release,
*,
refresh: bool = False,
) -> builtins.list[ReleaseMember]Get the people who worked on a release (voice actors, translators, ...).
| Name | Type | Description | Default |
|---|---|---|---|
release | int | str | Release | Release ID, alias, or a | |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[ReleaseMember] | The release's members. |
| Type | Description |
|---|---|
NotFoundError | No release with this ID or alias. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
timecodes
methodasynctimecodes(release: int | str | Release) -> builtins.list[EpisodeView]Get the logged-in user's watch records of a release's episodes. Needs a session.
Records appear about a minute after client.me.views.save_timecodes().
| Name | Type | Description |
|---|---|---|
release | int | str | Release | Release ID, alias, or a |
| Type | Description |
|---|---|
builtins.list[EpisodeView] | One record per episode the user has started; empty if none. |
| Type | Description |
|---|---|
NotFoundError | No release with this ID or alias. |
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.RatingsResource
classRatingsResource(http: HttpClient) BaseResource
The logged-in user's own scores of releases, available as client.releases.ratings.
Needs a session (client.auth.login() or AniLiberty(token=...)); without a valid
one every method raises AuthRequiredError. Releases are identified by numeric ID only:
the API answers an alias with a 404, so a str is rejected.
The release's overall Release.rating is computed by the API on its own schedule: it
didn't change within several minutes of a score being set (see docs/api-notes.md).
get
methodasyncget(release: int | Release) -> UserRatingGet the user's score for a release.
| Name | Type | Description |
|---|---|---|
release | int | Release | Release ID, or a |
| Type | Description |
|---|---|
UserRating | The score; |
| Type | Description |
|---|---|
TypeError |
|
AuthRequiredError | No valid session. |
NotFoundError | No release with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
set
methodasyncset(release: int | Release, *, score: int) -> UserRatingRate a release, or change the user's score for it.
| Name | Type | Description |
|---|---|---|
release | int | Release | Release ID, or a |
score | int | Whole number from 1 to 10. |
| Type | Description |
|---|---|
UserRating | The saved score. |
| Type | Description |
|---|---|
TypeError |
|
ValueError |
|
AuthRequiredError | No valid session. |
NotFoundError | No release with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
remove
methodasyncremove(release: int | Release) -> UserRatingWithdraw the user's score for a release. A no-op if there is none.
| Name | Type | Description |
|---|---|---|
release | int | Release | Release ID, or a |
| Type | Description |
|---|---|
UserRating | The rating afterwards, with |
| Type | Description |
|---|---|
TypeError |
|
AuthRequiredError | No valid session. |
NotFoundError | No release with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.EpisodesResource
classEpisodesResource(http: HttpClient) BaseResource
Episodes of releases, available as client.episodes.
Episode IDs come from Release.episodes (client.releases.get()/list()) or
Release.latest_episode (client.releases.latest()).
get
methodasyncget(episode: str | Episode) -> EpisodeGet one episode, with its HLS links and its release.
| Name | Type | Description |
|---|---|---|
episode | str | Episode | Episode ID, a UUID string such as
|
| Type | Description |
|---|---|
NotFoundError | No episode with this ID (also for IDs that aren't UUIDs). |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
timecode
methodasynctimecode(episode: str | Episode) -> EpisodeViewGet the logged-in user's watch record of an episode. Needs a session.
Records appear about a minute after client.me.views.save_timecodes(); for the
positions saved just now use client.me.views.timecodes().
| Name | Type | Description |
|---|---|---|
episode | str | Episode | Episode ID (a UUID string), or an |
| Type | Description |
|---|---|
EpisodeView | Where the user stopped in the episode and whether they finished it. |
| Type | Description |
|---|---|
NotFoundError | The user has no record for this episode (yet), or there is no such episode. |
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.CatalogResource
classCatalogResource(http: HttpClient) BaseResource
The release catalog with filters and sorting, available as client.catalog.
How the filters combine (checked against the live API, see docs/api-notes.md):
genres: a release must have all of the given genres (AND);types,seasons,age_ratings,publish_statuses,production_statuses: any of the given values (OR);- different filters: all of them must match (AND);
year_from/year_to: inclusive on both ends;search: the same matching asclient.app.search_releases(), and likewise capped at about 20 matches in total, whatever the other filters and the page size.
Without sorting, releases come in "FRESH_AT_DESC" order (recently updated first).
| Name | Type | Description |
|---|---|---|
references | Values the catalog filters accept. |
releases
methodasyncreleases(
*,
genres: Sequence[int | Genre] | None = None,
types: Sequence[ReleaseType | ValueDescription[str]] | None = None,
seasons: Sequence[Season | ValueDescription[str]] | None = None,
year_from: int | None = None,
year_to: int | None = None,
search: str | None = None,
sorting: CatalogSorting | SortingOption | None = None,
age_ratings: Sequence[AgeRatingValue | ValueDescription[str]] | None = None,
publish_statuses: Sequence[PublishStatus | ValueDescription[str]] | None = None,
production_statuses: Sequence[ProductionStatus | ValueDescription[str]] | None = None,
page: int | None = None,
limit: int | None = None,
refresh: bool = False,
) -> Page[Release]Get one page of catalog releases matching the filters.
Every filter is optional; without any, this is the whole catalog. Values can be
passed as plain strings/IDs or as the items client.catalog.references returns.
To go through all pages, use iter_releases().
| Name | Type | Description | Default |
|---|---|---|---|
genres | Sequence[int | Genre] | None | Genre IDs (or | None |
types | Sequence[ReleaseType | ValueDescription[str]] | None | Release types, e.g. | None |
seasons | Sequence[Season | ValueDescription[str]] | None | Seasons, e.g. | None |
year_from | int | None | Earliest release year, inclusive. | None |
year_to | int | None | Latest release year, inclusive. | None |
search | str | None | Text to search in names (capped at about 20 matches by the API). | None |
sorting | CatalogSorting | SortingOption | None | Sort order, e.g. | None |
age_ratings | Sequence[AgeRatingValue | ValueDescription[str]] | None | Age ratings, e.g. | None |
publish_statuses | Sequence[PublishStatus | ValueDescription[str]] | None |
| None |
production_statuses | Sequence[ProductionStatus | ValueDescription[str]] | None |
| None |
page | int | None | Page number, starting at 1. A page past the last one is empty. | None |
limit | int | None | Page size, 1 to 50. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
ValueError | A filter value isn't one the API knows, or |
TypeError | A list filter got a single string ( |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_releases
methoditer_releases(
*,
genres: Sequence[int | Genre] | None = None,
types: Sequence[ReleaseType | ValueDescription[str]] | None = None,
seasons: Sequence[Season | ValueDescription[str]] | None = None,
year_from: int | None = None,
year_to: int | None = None,
search: str | None = None,
sorting: CatalogSorting | SortingOption | None = None,
age_ratings: Sequence[AgeRatingValue | ValueDescription[str]] | None = None,
publish_statuses: Sequence[PublishStatus | ValueDescription[str]] | None = None,
production_statuses: Sequence[ProductionStatus | ValueDescription[str]] | None = None,
limit: int | None = None,
refresh: bool = False,
) -> AsyncIterator[Release]Iterate over all catalog releases matching the filters, fetching page by page.
Takes the same filters as releases(). The next page is requested only once the
current one has been consumed, so breaking out of the loop early stops the requests.
The whole catalog is a couple of thousand releases — iterate all of it only when you
really need everything.
| Name | Type | Description | Default |
|---|---|---|---|
genres | Sequence[int | Genre] | None | See | None |
types | Sequence[ReleaseType | ValueDescription[str]] | None | See | None |
seasons | Sequence[Season | ValueDescription[str]] | None | See | None |
year_from | int | None | See | None |
year_to | int | None | See | None |
search | str | None | See | None |
sorting | CatalogSorting | SortingOption | None | See | None |
age_ratings | Sequence[AgeRatingValue | ValueDescription[str]] | None | See | None |
publish_statuses | Sequence[PublishStatus | ValueDescription[str]] | None | See | None |
production_statuses | Sequence[ProductionStatus | ValueDescription[str]] | None | See | None |
limit | int | None | Page size, 1 to 50 — affects how many requests are made, not the result. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
AsyncIterator[Release] | An async iterator over the releases. |
| Type | Description |
|---|---|
ValueError | Invalid filter values — raised right away, not when iteration starts. |
TypeError | A list filter got a single string or a value of the wrong type. |
aniliberty.resources.CatalogReferencesResource
classCatalogReferencesResource(http: HttpClient) BaseResource
Values the catalog filters accept, available as client.catalog.references.
Each method returns the list the site shows in its catalog filters. Use them instead of
hardcoding values: the value (or id for genres, the number for years) of an item is
exactly what the corresponding catalog filter takes.
The lists are small and change rarely — fetching them once per program run is enough.
SOURCE ↗genres
methodasyncgenres(*, refresh: bool = False) -> list[Genre]Get the genres the catalog can be filtered by.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
types
methodasynctypes(*, refresh: bool = False) -> list[ValueDescription[str]]Get the release types, e.g. value="TV", description="ТВ".
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[ValueDescription[str]] | The release types. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
seasons
methodasyncseasons(*, refresh: bool = False) -> list[ValueDescription[str]]Get the seasons, e.g. value="winter", description="Зима".
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[ValueDescription[str]] | The seasons. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
years
methodasyncyears(*, refresh: bool = False) -> list[int]Get the years that have releases in the catalog, oldest first.
Only years with at least one release are listed, so the range can have gaps.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[int] | The years. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response wasn't a list of integers. |
age_ratings
methodasyncage_ratings(*, refresh: bool = False) -> list[AgeRating]Get the age ratings, e.g. value="R16_PLUS", label="16+".
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[AgeRating] | The age ratings ( |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
publish_statuses
methodasyncpublish_statuses(
*,
refresh: bool = False,
) -> list[ValueDescription[str]]Get the airing statuses, e.g. value="IS_ONGOING", description="Онгоинг".
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[ValueDescription[str]] | The publish statuses. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
production_statuses
methodasyncproduction_statuses(
*,
refresh: bool = False,
) -> list[ValueDescription[str]]Get the dubbing statuses, e.g. value="IS_IN_PRODUCTION".
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[ValueDescription[str]] | The production statuses. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
sorting
methodasyncsorting(*, refresh: bool = False) -> list[SortingOption]Get the ways the catalog can be sorted, e.g. value="FRESH_AT_DESC".
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[SortingOption] | The sorting options. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.ScheduleResource
classScheduleResource(http: HttpClient) BaseResource
The weekly airing schedule of ongoing releases, available as client.schedule.
now
methodasyncnow(*, refresh: bool = False) -> ScheduleNowGet the releases scheduled for yesterday, today and tomorrow.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
ScheduleNow | The schedule for the three days. Each item says whether this week's episode is |
ScheduleNow | already out ( |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
week
methodasyncweek(*, refresh: bool = False) -> list[ScheduledRelease]Get the schedule for the whole current week.
The list is not ordered by weekday; group or sort it by
item.release.publish_day.value (1 = Monday ... 7 = Sunday) as needed.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[ScheduledRelease] | All releases in this week's schedule. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.GenresResource
classGenresResource(http: HttpClient) BaseResource
Anime genres with their images and release counts, available as client.genres.
To filter the catalog by several genres at once, use client.catalog.releases(genres=...);
releases() here lists the releases of one genre.
list
methodasynclist(*, refresh: bool = False) -> builtins.list[Genre]Get all genres.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[Genre] | Every genre, with |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
get
methodasyncget(genre: int | Genre, *, refresh: bool = False) -> GenreGet one genre.
| Name | Type | Description | Default |
|---|---|---|---|
genre | int | Genre | Genre ID, or a | |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
Genre | The genre, with |
| Type | Description |
|---|---|
NotFoundError | No genre with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
random
methodasyncrandom(*, limit: int | None = None) -> builtins.list[Genre]Get random genres.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | How many genres to return. A value outside 1..(number of genres) isn't an error: the API returns all genres instead. | None |
| Type | Description |
|---|---|
builtins.list[Genre] | The genres, in random order. |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
releases
methodasyncreleases(
genre: int | Genre,
*,
page: int | None = None,
limit: int | None = None,
refresh: bool = False,
) -> Page[Release]Get one page of the releases of a genre.
The order is the API's own (neither by update date nor by year). For sorting and for
combining genres with other filters, use client.catalog.releases(genres=[...]).
| Name | Type | Description | Default |
|---|---|---|---|
genre | int | Genre | Genre ID or | |
page | int | None | Page number, starting at 1. The API treats 0 and negative numbers as 1. | None |
limit | int | None | Page size, 1 to 30 here (not 50 as in the catalog). Omitted, the API uses 15. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
NotFoundError | No genre with this ID. |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_releases
methoditer_releases(
genre: int | Genre,
*,
limit: int | None = None,
refresh: bool = False,
) -> AsyncIterator[Release]Iterate over all releases of a genre, fetching page by page.
| Name | Type | Description | Default |
|---|---|---|---|
genre | int | Genre | Genre ID or | |
limit | int | None | Page size, 1 to 30 — affects how many requests are made, not the result. | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
AsyncIterator[Release] | An async iterator over the releases. |
| Type | Description |
|---|---|
TypeError |
|
aniliberty.resources.FranchisesResource
classFranchisesResource(http: HttpClient) BaseResource
Franchises — groups of related releases, available as client.franchises.
list
methodasynclist(*, refresh: bool = False) -> builtins.list[Franchise]Get all franchises (a few hundred, in one response).
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[Franchise] | Every franchise, without |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
get
methodasyncget(franchise: str | Franchise, *, refresh: bool = False) -> FranchiseGet one franchise with its releases.
| Name | Type | Description | Default |
|---|---|---|---|
franchise | str | Franchise | Franchise ID (a UUID string), or a | |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
Franchise | The franchise, with |
| Type | Description |
|---|---|
NotFoundError | No franchise with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
random
methodasyncrandom(*, limit: int | None = None) -> builtins.list[Franchise]Get random franchises.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | How many franchises to return. Omitted, 0, or more than there are, the API returns all franchises (a few hundred) — pass a limit. | None |
| Type | Description |
|---|---|
builtins.list[Franchise] | The franchises, without |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
for_release
methodasyncfor_release(
release: int | Release,
*,
refresh: bool = False,
) -> builtins.list[Franchise]Get the franchises a release belongs to.
| Name | Type | Description | Default |
|---|---|---|---|
release | int | Release | Release ID, or a | |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
TypeError |
|
NotFoundError | No release with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.TorrentsResource
classTorrentsResource(http: HttpClient) BaseResource
Torrents of releases, their .torrent files and RSS feeds, as client.torrents.
A torrent is identified by its numeric id or its info hash; every method that takes
a torrent also accepts a Torrent object.
list
methodasynclist(
*,
page: int | None = None,
limit: int | None = None,
) -> Page[Torrent]Get one page of all torrents.
| Name | Type | Description | Default |
|---|---|---|---|
page | int | None | Page number, starting at 1. | None |
limit | int | None | Page size, 1 to 50. | None |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_list
methoditer_list(*, limit: int | None = None) -> AsyncIterator[Torrent]Iterate over all torrents (a few thousand), fetching page by page.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | Page size, 1 to 50 — affects how many requests are made, not the result. | None |
| Type | Description |
|---|---|
AsyncIterator[Torrent] | An async iterator over the torrents. |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error, while iterating. |
TransportError | A request failed without a response, while iterating. |
get
methodasyncget(torrent: int | str | Torrent) -> TorrentGet one torrent by its ID or info hash.
| Name | Type | Description |
|---|---|---|
torrent | int | str | Torrent | Torrent ID, info hash (any letter case), or a |
| Type | Description |
|---|---|
Torrent | The torrent, with |
| Type | Description |
|---|---|
NotFoundError | No torrent with this ID or hash. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
for_release
methodasyncfor_release(release: int | Release) -> builtins.list[Torrent]Get the torrents of a release.
| Name | Type | Description |
|---|---|---|
release | int | Release | Release ID, or a |
| Type | Description |
|---|---|
builtins.list[Torrent] | The release's torrents; an empty list if it has none. |
| Type | Description |
|---|---|
TypeError |
|
NotFoundError | No release with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
file
methodasyncfile(torrent: int | str | Torrent) -> TorrentFileDownload a .torrent file into memory.
The files checked were 22 to 388 KB. To save one to disk without holding it in
memory, use download().
| Name | Type | Description |
|---|---|---|
torrent | int | str | Torrent | Torrent ID, info hash, or a |
| Type | Description |
|---|---|
TorrentFile | The file's bytes and the file name the API suggests. |
| Type | Description |
|---|---|
NotFoundError | No torrent with this ID or hash. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
download
methodasyncdownload(
torrent: int | str | Torrent,
*,
path: str | os.PathLike[str],
) -> PathDownload a .torrent file to disk, streaming it in chunks.
The file is written to a temporary <name>.part next to the target and renamed
only once complete, so a failed download never leaves a truncated .torrent
behind. An existing file at the target path is overwritten.
| Name | Type | Description |
|---|---|---|
torrent | int | str | Torrent | Torrent ID, info hash, or a |
path | str | os.PathLike[str] | Where to save it: a file path, or an existing directory — then the file is
named as the API suggests (falling back to |
| Type | Description |
|---|---|
Path | The path of the saved file. |
| Type | Description |
|---|---|
NotFoundError | No torrent with this ID or hash. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
OSError | The file couldn't be written. |
rss
methodasyncrss(*, limit: int | None = None) -> strGet the RSS feed of the latest torrents, as XML text.
The feed is meant for torrent clients and feed readers; each <item> has the
torrent's hash (<guid>), <torrentId>, <releaseId> and an <enclosure>
linking to the .torrent file. For the same data as models, use list().
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | How many torrents to include. Omitted, the API includes 10. | None |
| Type | Description |
|---|---|
str | The RSS 2.0 document. |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
rss_for_release
methodasyncrss_for_release(release: int | Release) -> strGet the RSS feed of a release's torrents, as XML text.
| Name | Type | Description |
|---|---|---|
release | int | Release | Release ID, or a |
| Type | Description |
|---|---|
str | The RSS 2.0 document. |
| Type | Description |
|---|---|
TypeError |
|
NotFoundError | No release with this ID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
aniliberty.resources.TeamsResource
classTeamsResource(http: HttpClient) BaseResource
AniLibria's own teams, roles and members, available as client.teams.
Each endpoint returns its whole list in one response (a few dozen teams and roles, a couple of hundred members); there is no pagination or filtering, so filter in Python.
SOURCE ↗list
methodasynclist(*, refresh: bool = False) -> builtins.list[Team]Get all teams.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[Team] | Every team, ordered by |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
roles
methodasyncroles(*, refresh: bool = False) -> builtins.list[TeamRole]Get all roles team members can have.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[TeamRole] | Every role, ordered by |
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
users
methodasyncusers(*, refresh: bool = False) -> builtins.list[TeamMember]Get all team members, each with their team and roles.
| Name | Type | Description | Default |
|---|---|---|---|
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
builtins.list[TeamMember] | Every member. Not sorted by team: the API interleaves teams, so group by |
builtins.list[TeamMember] |
|
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.MediaResource
classMediaResource(http: HttpClient) BaseResource
Front-page promo banners and AniLibria's YouTube videos, available as client.media.
The ads endpoints that live under /media as well (/media/vasts,
/media/manifest.xml, spec tag Реклама.Vasts) are deliberately not covered.
promotions
methodasyncpromotions() -> list[Promotion]Get the promo banners shown on the site's front page.
| Type | Description |
|---|---|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
videos
methodasyncvideos(
*,
limit: int | None = None,
refresh: bool = False,
) -> list[Video]Get the latest videos from AniLibria's YouTube playlists, newest first.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | How many videos to return, 1 to 50. Omitted, the API returns 10.
0 or a negative number returns all videos (over a thousand, more than
1 MB of JSON); more than 50 is a | None |
refresh | bool | Fetch from the API even if the response is cached (see
| False |
| Type | Description |
|---|---|
list[Video] | The videos, ordered by |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.AuthResource
classAuthResource(http: HttpClient) _SessionResource
Logging in and out, available as client.auth.
A successful login stores the session token on the client (client.token), so
account methods work right after it. To reuse a session across runs, save
client.token and pass it back as AniLiberty(token=...) — logging in on every run
piles up sessions on the account.
| Name | Type | Description |
|---|---|---|
otp | Logging a device in with a one-time code. | |
social | Logging in with a social network. | |
password | Recovering a forgotten password. |
login
methodasynclogin(*, login: str, password: str) -> AuthTokenLog in with a login (or e-mail) and password, and use the session from now on.
| Name | Type | Description |
|---|---|---|
login | str | The account's login or e-mail; the login is case-insensitive. |
password | str | The account's password. |
| Type | Description |
|---|---|
AuthToken | The new session's token, also stored as |
| Type | Description |
|---|---|
AuthRequiredError | Wrong login or password (HTTP 401); the API's reason is in
|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. Not retried: login is a
|
ResponseParseError | The response didn't match |
logout
methodasynclogout() -> NoneEnd the client's session and stop sending its token.
Only this session is ended; other sessions of the account (other devices, other
logins) stay valid. client.token is None afterwards, also when the API
rejects the token as already invalid.
| Type | Description |
|---|---|
AuthRequiredError | The client has no token, or the API no longer accepts it (logged out or expired) — the session is over either way (HTTP 403). |
APIError | The API answered with another error; the token is kept. |
TransportError | The request failed without a response; the token is kept. |
aniliberty.resources.OtpResource
classOtpResource(http: HttpClient) _SessionResource
Logging a device in with a one-time code, available as client.auth.otp.
Meant for devices where typing a password is awkward (a TV app):
- The device calls
issue(device_id=...)and shows the code. - The user enters the code on a device where they are logged in, which calls
accept(code). - The device calls
login(code=..., device_id=...)and gets its own session.
A code lives 5 minutes and logs in once.
SOURCE ↗issue
methodasyncissue(*, device_id: str) -> IssuedOtpIssue a one-time code for a device. No session needed.
Asking again for the same device_id while the code is alive returns the same code.
| Name | Type | Description |
|---|---|---|
device_id | str | Any string identifying the device, e.g. a UUID it keeps; the same
value must be passed to |
| Type | Description |
|---|---|
IssuedOtp | The code and the seconds it has left. |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
accept
methodasyncaccept(code: str | int) -> NoneApprove a one-time code with the client's session, letting its device log in.
| Name | Type | Description |
|---|---|---|
code | str | int | The code the other device shows. Pass it as the string |
| Type | Description |
|---|---|
AuthRequiredError | The client has no valid session. |
NotFoundError | No live code like that. |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
login
methodasynclogin(*, code: str | int, device_id: str) -> AuthTokenLog the device in with an accepted code, and use the session from now on.
Sent without the client's current token, if it has one: the API refuses this request from a logged-in caller (403). The new token replaces the old one.
| Name | Type | Description |
|---|---|---|
code | str | int | The code from |
device_id | str | The same |
| Type | Description |
|---|---|
AuthToken | The new session's token, also stored as |
| Type | Description |
|---|---|
NotFoundError | No live code for this device — wrong code or |
ServerError | The code hasn't been accepted yet — the API answers 500 to that. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.SocialAuthResource
classSocialAuthResource(http: HttpClient) _SessionResource
Logging in with a social network, available as client.auth.social.
login(provider)returns an OAuthurland astatekey.- The user opens
urlin a browser and logs in with the social network. authenticate(state)exchanges the key for a session.
login
methodasynclogin(provider: SocialProvider) -> SocialLoginStart logging in with a social network.
| Name | Type | Description |
|---|---|---|
provider | SocialProvider |
|
| Type | Description |
|---|---|
SocialLogin | The OAuth page to send the user to, and the key to finish with. |
| Type | Description |
|---|---|
ValueError |
|
APIError | The API answered with an error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
authenticate
methodasyncauthenticate(state: str) -> AuthTokenFinish a social login, and use the session from now on.
| Name | Type | Description |
|---|---|---|
state | str |
|
| Type | Description |
|---|---|
AuthToken | The new session's token, also stored as |
| Type | Description |
|---|---|
NotFoundError | Unknown |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.PasswordResource
classPasswordResource(http: HttpClient) BaseResource
Recovering a forgotten password, available as client.auth.password.
forget
methodasyncforget(*, email: str) -> NoneAsk the API to e-mail a password reset link.
The answer is the same whether or not an account has this e-mail, so it can't be used to check for one.
| Name | Type | Description |
|---|---|---|
email | str | The account's e-mail. |
| Type | Description |
|---|---|
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
reset
methodasyncreset(*, token: str, password: str) -> NoneSet a new password with the token from the reset e-mail.
Doesn't log in: call client.auth.login() with the new password afterwards.
| Name | Type | Description |
|---|---|---|
token | str | The token from the link in the e-mail sent by |
password | str | The new password (sent as both |
| Type | Description |
|---|---|
NotFoundError | Unknown or used |
ValidationError | The password doesn't meet the API's rules, or a field is empty. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
aniliberty.resources.MeResource
classMeResource(http: HttpClient) BaseResource
The logged-in user's own account, available as client.me.
Every method needs a session: log in with client.auth.login() or pass
AniLiberty(token=...). Without a valid one the API answers 403, raised as
AuthRequiredError.
| Name | Type | Description |
|---|---|---|
favorites | The user's favorite releases. | |
collections | The user's collections (planned, watched, watching, ...). | |
views | The user's watch progress (timecodes) and history. |
profile
methodasyncprofile() -> UserProfileGet the logged-in user's profile.
| Type | Description |
|---|---|
UserProfile | The profile, with the account's e-mail and tracker passkey. |
| Type | Description |
|---|---|
AuthRequiredError | No session token, or the API doesn't accept it (logged out or expired). |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.FavoritesResource
classFavoritesResource(http: HttpClient) BaseResource
The logged-in user's favorite releases, available as client.me.favorites.
Needs a session (client.auth.login() or AniLiberty(token=...)); without a valid
one every method raises AuthRequiredError.
The API checks little here (see docs/api-notes.md): add() accepts IDs of releases
that don't exist — they end up in ids(), though never in releases() — and adding a
release twice or removing one that isn't there is silently a no-op.
| Name | Type | Description |
|---|---|---|
references | Values the favorites filters accept. |
ids
methodasyncids() -> list[int]Get the IDs of all releases in favorites, in the order they were added.
Cheaper than releases() for checking whether a release is a favorite.
| Type | Description |
|---|---|
list[int] | Release IDs. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response wasn't a list of integers. |
add
methodasyncadd(*releases: int | Release) -> list[int]Add releases to favorites.
| Name | Type | Description | Default |
|---|---|---|---|
*releases | int | Release | Release IDs or | () |
| Type | Description |
|---|---|
list[int] | IDs of all releases in favorites afterwards, like |
| Type | Description |
|---|---|
ValueError | No releases given. |
TypeError | A value is neither an |
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. Not retried: it's a
|
ResponseParseError | The response wasn't a list of integers. |
remove
methodasyncremove(*releases: int | Release) -> list[int]Remove releases from favorites.
| Name | Type | Description | Default |
|---|---|---|---|
*releases | int | Release | Release IDs or | () |
| Type | Description |
|---|---|
list[int] | IDs of the releases left in favorites, like |
| Type | Description |
|---|---|
ValueError | No releases given. |
TypeError | A value is neither an |
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response wasn't a list of integers. |
releases
methodasyncreleases(
*,
genres: Sequence[int | Genre] | None = None,
types: Sequence[ReleaseType | ValueDescription[str]] | None = None,
years: Sequence[int] | None = None,
search: str | None = None,
sorting: FavoritesSorting | SortingOption | None = None,
age_ratings: Sequence[AgeRatingValue | ValueDescription[str]] | None = None,
page: int | None = None,
limit: int | None = None,
) -> Page[Release]Get one page of favorite releases matching the filters.
The filters combine like the catalog's: a release must have all of genres
and any of the types, years and age_ratings; different filters must
all match. To go through all pages, use iter_releases().
| Name | Type | Description | Default |
|---|---|---|---|
genres | Sequence[int | Genre] | None | Genre IDs (or | None |
types | Sequence[ReleaseType | ValueDescription[str]] | None | Release types, e.g. | None |
years | Sequence[int] | None | Release years; any of them. A range works too: | None |
search | str | None | Text to search in names. | None |
sorting | FavoritesSorting | SortingOption | None | Sort order. Default: | None |
age_ratings | Sequence[AgeRatingValue | ValueDescription[str]] | None | Age ratings, e.g. | None |
page | int | None | Page number, starting at 1. A page past the last one is empty. | None |
limit | int | None | Page size, 1 to 50. Default: 25. | None |
| Type | Description |
|---|---|
ValueError | A filter value isn't one the API knows (it would silently return nothing or ignore the filter instead). |
TypeError | A list filter got a single value ( |
AuthRequiredError | No valid session. |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_releases
methoditer_releases(
*,
genres: Sequence[int | Genre] | None = None,
types: Sequence[ReleaseType | ValueDescription[str]] | None = None,
years: Sequence[int] | None = None,
search: str | None = None,
sorting: FavoritesSorting | SortingOption | None = None,
age_ratings: Sequence[AgeRatingValue | ValueDescription[str]] | None = None,
limit: int | None = None,
) -> AsyncIterator[Release]Iterate over all favorite releases matching the filters, fetching page by page.
Takes the same filters as releases(); the next page is requested only once the
current one has been consumed.
| Name | Type | Description | Default |
|---|---|---|---|
genres | Sequence[int | Genre] | None | See | None |
types | Sequence[ReleaseType | ValueDescription[str]] | None | See | None |
years | Sequence[int] | None | See | None |
search | str | None | See | None |
sorting | FavoritesSorting | SortingOption | None | See | None |
age_ratings | Sequence[AgeRatingValue | ValueDescription[str]] | None | See | None |
limit | int | None | Page size, 1 to 50 — affects how many requests are made, not the result. | None |
| Type | Description |
|---|---|
AsyncIterator[Release] | An async iterator over the releases. |
| Type | Description |
|---|---|
ValueError | Invalid filter values — raised right away, not when iteration starts. |
TypeError | A list filter got a single value or a value of the wrong type. |
aniliberty.resources.FavoritesReferencesResource
classFavoritesReferencesResource(http: HttpClient) _OwnListReferencesResource
Values the favorites filters accept, available as client.me.favorites.references.
Unlike client.catalog.references, the lists only contain what occurs in the user's
own favorites: the genres, types, years and age ratings of the releases added there. With
empty favorites they are empty — except sorting(), which is always the full list.
sorting
methodasyncsorting() -> list[SortingOption]Get the ways favorites can be sorted, e.g. value="CREATED_AT_DESC".
| Type | Description |
|---|---|
list[SortingOption] | All sorting options, whatever is in favorites. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
genres
methodasyncgenres() -> list[Genre]Get the genres of the releases in the list.
| Type | Description |
|---|---|
list[Genre] | Genres with |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
types
methodasynctypes() -> list[ValueDescription[str]]Get the release types in the list, e.g. value="TV", description="ТВ".
| Type | Description |
|---|---|
list[ValueDescription[str]] | The release types. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
years
methodasyncyears() -> list[int]Get the release years in the list, newest first.
| Type | Description |
|---|---|
list[int] | The years. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response wasn't a list of integers. |
age_ratings
methodasyncage_ratings() -> list[AgeRating]Get the age ratings in the list, e.g. value="R16_PLUS", label="16+".
| Type | Description |
|---|---|
list[AgeRating] | The age ratings ( |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.CollectionsResource
classCollectionsResource(http: HttpClient) BaseResource
The user's collections, available as client.me.collections.
There are five collections (CollectionType): "PLANNED", "WATCHED",
"WATCHING", "POSTPONED" and "ABANDONED". A release is in at most one of them:
adding it to another collection moves it there. Collections are separate from favorites.
Needs a session (client.auth.login() or AniLiberty(token=...)); without a valid
one every method raises AuthRequiredError. As with favorites, the API accepts IDs of
releases that don't exist, and removing a release that isn't in any collection is a
no-op.
The API also applies collection changes in the background (see docs/api-notes.md):
a release removed right after being added can be back about a minute later, and removing
a release can take minutes to stick. To be sure a change held, check ids() again a
minute later.
| Name | Type | Description |
|---|---|---|
references | Values the collections filters accept. |
ids
methodasyncids() -> list[CollectionItem]Get every release in the collections, with the collection it's in.
| Type | Description |
|---|---|
list[CollectionItem] | One item per release, in the order the releases were added. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
add
methodasyncadd(
*releases: int | Release,
to: CollectionType,
) -> list[CollectionItem]Put releases into a collection, moving them out of the one they were in.
| Name | Type | Description | Default |
|---|---|---|---|
*releases | int | Release | Release IDs or | () |
to | CollectionType | The collection, e.g. |
| Type | Description |
|---|---|
list[CollectionItem] | Every release in the collections afterwards, like |
| Type | Description |
|---|---|
ValueError | No releases given, or |
TypeError | A release is neither an |
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
remove
methodasyncremove(*releases: int | Release) -> list[CollectionItem]Take releases out of whatever collection they are in.
| Name | Type | Description | Default |
|---|---|---|---|
*releases | int | Release | Release IDs or | () |
| Type | Description |
|---|---|
list[CollectionItem] | The releases left in the collections, like |
| Type | Description |
|---|---|
ValueError | No releases given. |
TypeError | A release is neither an |
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
releases
methodasyncreleases(
collection: CollectionType,
*,
genres: Sequence[int | Genre] | None = None,
types: Sequence[ReleaseType | ValueDescription[str]] | None = None,
years: Sequence[int] | None = None,
search: str | None = None,
age_ratings: Sequence[AgeRatingValue | ValueDescription[str]] | None = None,
page: int | None = None,
limit: int | None = None,
) -> Page[Release]Get one page of the releases in a collection, matching the filters.
Releases come most recently added first; the API offers no other order. The filters
combine like the favorites' ones: all of genres, any of the types, years
and age_ratings. To go through all pages, use iter_releases().
| Name | Type | Description | Default |
|---|---|---|---|
collection | CollectionType | The collection, e.g. | |
genres | Sequence[int | Genre] | None | Genre IDs (or | None |
types | Sequence[ReleaseType | ValueDescription[str]] | None | Release types, e.g. | None |
years | Sequence[int] | None | Release years; any of them. A range works too: | None |
search | str | None | Text to search in names. | None |
age_ratings | Sequence[AgeRatingValue | ValueDescription[str]] | None | Age ratings, e.g. | None |
page | int | None | Page number, starting at 1. A page past the last one is empty. | None |
limit | int | None | Page size, 1 to 50. Default: 25. | None |
| Type | Description |
|---|---|
ValueError |
|
TypeError | A list filter got a single value or a value of the wrong type. |
AuthRequiredError | No valid session. |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_releases
methoditer_releases(
collection: CollectionType,
*,
genres: Sequence[int | Genre] | None = None,
types: Sequence[ReleaseType | ValueDescription[str]] | None = None,
years: Sequence[int] | None = None,
search: str | None = None,
age_ratings: Sequence[AgeRatingValue | ValueDescription[str]] | None = None,
limit: int | None = None,
) -> AsyncIterator[Release]Iterate over all releases in a collection matching the filters, page by page.
Takes the same arguments as releases(); the next page is requested only once the
current one has been consumed.
| Name | Type | Description | Default |
|---|---|---|---|
collection | CollectionType | See | |
genres | Sequence[int | Genre] | None | See | None |
types | Sequence[ReleaseType | ValueDescription[str]] | None | See | None |
years | Sequence[int] | None | See | None |
search | str | None | See | None |
age_ratings | Sequence[AgeRatingValue | ValueDescription[str]] | None | See | None |
limit | int | None | Page size, 1 to 50 — affects how many requests are made, not the result. | None |
| Type | Description |
|---|---|
AsyncIterator[Release] | An async iterator over the releases. |
| Type | Description |
|---|---|
ValueError | Invalid |
TypeError | A list filter got a single value or a value of the wrong type. |
aniliberty.resources.CollectionsReferencesResource
classCollectionsReferencesResource(http: HttpClient) _OwnListReferencesResource
Values the collections filters accept, available as client.me.collections.references.
The lists cover all the user's collections together — the API has no per-collection
variant (a type_of_collection parameter is ignored). Collections can't be sorted, so
unlike favorites there is no sorting().
genres
methodasyncgenres() -> list[Genre]Get the genres of the releases in the list.
| Type | Description |
|---|---|
list[Genre] | Genres with |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
types
methodasynctypes() -> list[ValueDescription[str]]Get the release types in the list, e.g. value="TV", description="ТВ".
| Type | Description |
|---|---|
list[ValueDescription[str]] | The release types. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
years
methodasyncyears() -> list[int]Get the release years in the list, newest first.
| Type | Description |
|---|---|
list[int] | The years. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response wasn't a list of integers. |
age_ratings
methodasyncage_ratings() -> list[AgeRating]Get the age ratings in the list, e.g. value="R16_PLUS", label="16+".
| Type | Description |
|---|---|
list[AgeRating] | The age ratings ( |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
aniliberty.resources.ViewsResource
classViewsResource(http: HttpClient) BaseResource
The user's watch progress and history, available as client.me.views.
Needs a session (client.auth.login() or AniLiberty(token=...)); without a valid
one every method raises AuthRequiredError.
The API stores timecodes in two steps (see docs/api-notes.md):
save_timecodes()shows up intimecodes()at once, but inhistory(),client.episodes.timecode()andclient.releases.timecodes()only about a minute later.remove_timecodes()likewise takes about a minute to reach the history, andtimecodes()can keep listing the deleted entries after that; deleting them once more after the minute clears them.
timecodes
methodasynctimecodes(*, since: datetime | None = None) -> list[Timecode]Get the user's timecodes — the position in every episode they started.
| Name | Type | Description | Default |
|---|---|---|---|
since | datetime | None | Only timecodes saved at or after this moment. Must be timezone-aware
(e.g. | None |
| Type | Description |
|---|---|
list[Timecode] | The timecodes, in no documented order (updating one doesn't move it). |
| Type | Description |
|---|---|
ValueError |
|
AuthRequiredError | No valid session. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
save_timecodes
methodasyncsave_timecodes(*timecodes: Timecode) -> NoneSave the position in one or more episodes, replacing what was saved for them.
| Name | Type | Description | Default |
|---|---|---|---|
*timecodes | Timecode | One | () |
| Type | Description |
|---|---|
ValueError | No timecodes given. |
TypeError | A value isn't a |
AuthRequiredError | No valid session. |
ValidationError | An episode ID isn't a UUID. An ID of an episode that doesn't exist is accepted, and dropped later by the API. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
remove_timecodes
methodasyncremove_timecodes(*episodes: str | Episode | Timecode) -> NoneDelete the saved position in one or more episodes (and their history entries).
Episodes without a timecode are skipped. See the class docs: the deletion takes about
a minute, and timecodes() may need a second call after that.
| Type | Description |
|---|---|
ValueError | No episodes given. |
TypeError | A value isn't an episode ID, |
AuthRequiredError | No valid session. |
ValidationError | An episode ID isn't a UUID. |
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
history
methodasynchistory(
*,
page: int | None = None,
limit: int | None = None,
) -> Page[HistoryEntry]Get one page of the watch history — one entry per episode, most recent first.
| Name | Type | Description | Default |
|---|---|---|---|
page | int | None | Page number, starting at 1. A page past the last one is empty. | None |
limit | int | None | Page size, 1 to 50. Default: 25. | None |
| Type | Description |
|---|---|
Page[HistoryEntry] | One page of entries, each with its episode and the episode's release. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session. |
ValidationError |
|
APIError | The API answered with another error. |
TransportError | The request failed without a response. |
ResponseParseError | The response didn't match |
iter_history
methoditer_history(*, limit: int | None = None) -> AsyncIterator[HistoryEntry]Iterate over the whole watch history, fetching page by page.
| Name | Type | Description | Default |
|---|---|---|---|
limit | int | None | Page size, 1 to 50 — affects how many requests are made, not the result. | None |
| Type | Description |
|---|---|
AsyncIterator[HistoryEntry] | An async iterator over the entries, most recent first. |
| Type | Description |
|---|---|
AuthRequiredError | No valid session — raised once iteration starts. |
ValidationError |
|
APIError | The API answered with another error, while iterating. |
TransportError | A request failed without a response, while iterating. |
Pagination
aniliberty.Page
classPage() AniLibertyModel, Generic[T]
One page of results from a paginated endpoint.
Paginated endpoints answer with {"data": [...], "meta": {"pagination": {...}}} (see
docs/api-notes.md); Page flattens that into the fields below. Pages are numbered
from 1. To walk through all pages, use the matching iter_*() method instead of
calling the paginated one in a loop.
| Name | Type | Description |
|---|---|---|
items | list[T] | Items on this page. |
page | int | Number of this page, starting at 1. |
limit | int | Page size that was requested ( |
total | int | Number of items on all pages. |
total_pages | int | Number of pages. |
has_next_page
attributepropertyhas_next_page: boolWhether there are more pages after this one.
SOURCE ↗Models
Releases
aniliberty.Release
classRelease() AniLibertyModel
An anime release.
Spec schema: models.anime.releases.v1.release plus the extra fields each endpoint adds.
List endpoints fill only part of the extras: genres comes with every endpoint except
Episode.release, background_covers with random() and get(),
members/sponsors with get() and list(), episodes with get(),
list() and Episode.release, latest_episode with latest() only. An extra
the endpoint doesn't send is an empty list or None.
| Name | Type | Description |
|---|---|---|
id | int | Release ID. |
alias | str | URL slug, e.g. |
name | ReleaseName | Release names. |
type | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | Release type; |
year | int | None | Release year. |
season | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | Release season; |
poster | Image | None | Release poster. |
description | str | None | Synopsis, in Russian. |
notification | str | None | Important notice shown on the release page, if any. |
age_rating | Annotated[AgeRating | None, EMPTY_VALUE_AS_NONE] | Age rating. |
publish_day | Annotated[ValueDescription[int] | None, EMPTY_VALUE_AS_NONE] | Weekday new episodes come out on; |
is_ongoing | bool | Whether the anime is still airing in its home country. |
is_in_production | bool | Whether AniLibria is still dubbing it. |
is_blocked_by_geo | bool | Whether it's blocked in the requester's region. |
is_blocked_by_copyrights | bool | Whether it's blocked by the copyright holder. |
episodes_total | int | None | Number of episodes, if known. |
average_duration_of_episode | int | None | Average episode length, in minutes, if known. |
external_player | str | None | Link to an external player, if any. |
fresh_at | datetime | None | When the release was last bumped (e.g. by a new episode). |
created_at | datetime | None | When the release was added. |
updated_at | datetime | None | When the release was last edited. |
added_in_users_favorites | int | How many users added it to favorites. |
added_in_planned_collection | int | How many users put it in their "planned" collection. |
added_in_watched_collection | int | How many users put it in "watched". |
added_in_watching_collection | int | How many users put it in "watching". |
added_in_postponed_collection | int | How many users put it in "postponed". |
added_in_abandoned_collection | int | How many users put it in "abandoned". |
shikimori | ExternalRating | None | Entry on Shikimori. |
mal | ExternalRating | None | Entry on MyAnimeList. |
rating | ReleaseRating | None | Score from AniLibria users. |
genres | list[Genre] | Genres of the release. |
background_covers | list[ImageSet] | Wide cover images for a page background. |
members | list[ReleaseMember] | People who worked on the release. |
sponsors | list[Sponsor] | Sponsors shown on the release page. |
episodes | list[Episode] | All episodes of the release, in order. |
latest_episode | Episode | None | The newest episode — only filled in by |
aniliberty.ReleaseName
classReleaseName() AniLibertyModel
Names of a release.
| Name | Type | Description |
|---|---|---|
main | str | The main (Russian) title. |
english | str | None | The English or romanized title. |
alternative | str | None | Other titles, comma-separated, if any. |
aniliberty.AgeRating
classAgeRating() ValueDescription[str]
Age rating of a release, e.g. value="R16_PLUS", label="16+".
value is one of R0_PLUS, R6_PLUS, R12_PLUS, R16_PLUS, R18_PLUS
(spec enum enums.anime.releases.release.ageRating).
| Name | Type | Description |
|---|---|---|
label | str | None | Short label, e.g. |
is_adult | bool | None | Whether the release is for adults only. |
aniliberty.ReleaseRating
classReleaseRating() AniLibertyModel
Score given to a release by AniLibria users.
| Name | Type | Description |
|---|---|---|
average | float | None | Average score from 1 to 10, |
votes | int | Number of people who rated it. |
distribution | dict[int, int] | Number of votes per score, for every score from 1 to 10. |
aniliberty.UserRating
classUserRating() AniLibertyModel
The logged-in user's own score for a release, from client.releases.ratings.
Spec schema: models.anime.releases.v1.release.rating.own.
| Name | Type | Description |
|---|---|---|
release_id | int | Release ID. |
score | int | None | The user's score, 1 to 10; |
aniliberty.ExternalRating
classExternalRating() AniLibertyModel
A release's entry and score on an external site (Shikimori or MyAnimeList).
All fields are None when the release isn't linked to that site.
| Name | Type | Description |
|---|---|---|
id | int | None | ID of the anime on the external site. |
url | str | None | Link to the anime's page there. |
rating | float | None | Average score there, from 1 to 10. |
votes | int | None | Number of people who rated it there. |
aniliberty.ReleaseMember
classReleaseMember() AniLibertyModel
A person who worked on a release (voice actor, translator, timer, ...).
Spec schema: models.anime.releases.v1.release.member.
| Name | Type | Description |
|---|---|---|
id | str | Member entry ID (a UUID string). |
nickname | str | The member's nickname. |
role | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | What they did. |
user | LinkedAccount | None | Linked site account, if any (usually |
aniliberty.Sponsor
classSponsor() AniLibertyModel
A sponsor shown on a release page.
Spec schema: models.anime.sponsors.v1.sponsor.
| Name | Type | Description |
|---|---|---|
id | str | Sponsor ID. |
title | str | None | Sponsor name. |
description | str | None | Sponsor description. |
url | str | None | Sponsor link. |
url_title | str | None | Text of the sponsor link. |
aniliberty.Genre
classGenre() AniLibertyModel
Episodes
aniliberty.Episode
classEpisode() AniLibertyModel
An episode of a release, with links to its HLS streams.
Spec schema: models.anime.releases.v1.release.episode. HLS links are absolute URLs of
.m3u8 playlists on the video CDN, returned exactly as the API sends them — their query
string (country, ads flags, ...) is part of the link. Downloading the video itself is out
of scope for the SDK: hand the playlist to a player or to ffmpeg.
| Name | Type | Description |
|---|---|---|
id | str | Episode ID (a UUID string) — pass it to |
release_id | int | ID of the release the episode belongs to. |
ordinal | int | float | Episode number. Usually an |
sort_order | int | Position of the episode in the release's list. |
name | str | None | Episode title, in Russian, if it has one (most don't). |
name_english | str | None | Episode title in English, if any. |
duration | int | Length, in seconds. |
opening | Annotated[SkipRange | None, BeforeValidator(_empty_skip_as_none)] | Where the opening is, |
ending | Annotated[SkipRange | None, BeforeValidator(_empty_skip_as_none)] | Where the ending is, |
preview | Image | None | Still image from the episode. Often present with all paths |
hls_480 | str | None | HLS playlist, 480p. |
hls_720 | str | None | HLS playlist, 720p. |
hls_1080 | str | None | HLS playlist, 1080p — missing on some episodes. |
rutube_id | str | None | ID of the episode on Rutube, if it's published there. |
youtube_id | str | None | ID of the episode on YouTube, if it's published there. |
updated_at | datetime | None | When the episode was last updated. |
release | Release | None | The release with all its episodes — only filled in by
|
aniliberty.SkipRange
classSkipRange() AniLibertyModel
Where an opening or ending is, so a player can offer to skip it.
The spec describes the numbers as "seconds from the start or the end of the episode";
in every live sample checked they were seconds from the start (an ending of a
1502-second episode at 1326–1502), see docs/api-notes.md.
| Name | Type | Description |
|---|---|---|
start | int | None | Where the segment starts, in seconds. |
stop | int | None | Where the segment ends, in seconds. |
Franchises
aniliberty.Franchise
classFranchise() AniLibertyModel
A franchise: a group of related releases (seasons, movies, spin-offs).
Spec schema: models.anime.franchises.v1.franchise. The totals are precomputed by the
API and can be missing or stale — two franchises with total_releases=None still had
two releases each — so count franchise_releases when the exact number matters.
| Name | Type | Description |
|---|---|---|
id | str | Franchise ID (a UUID string). |
name | str | Name in Russian. |
name_english | str | None | Name in English or romanized. |
image | Image | None | Franchise cover image (its paths can be |
rating | float | None | Franchise rating — |
first_year | int | None | Year of the first release. |
last_year | int | None | Year of the latest release. |
total_releases | int | None | Number of releases. |
total_episodes | int | None | Number of episodes across all releases. |
total_duration | str | None | Total watch time as human-readable Russian text, e.g. |
total_duration_in_seconds | int | None | Total watch time, in seconds. |
franchise_releases | list[FranchiseRelease] | The releases, ordered by |
aniliberty.FranchiseRelease
classFranchiseRelease() AniLibertyModel
One release of a franchise, with its place in the franchise.
Spec schema: models.anime.franchises.v1.franchise.release.
| Name | Type | Description |
|---|---|---|
id | str | ID of this release-to-franchise link (a UUID string). |
franchise_id | str | ID of the franchise. |
release_id | int | ID of the release. |
sort_order | int | Position of the release in the franchise, starting at 1 — in every franchise checked, the chronological order. |
release | Release | The release itself (without |
Torrents
aniliberty.Torrent
classTorrent() AniLibertyModel
A torrent of a release.
Spec schema: models.anime.torrents.v1.torrent.
| Name | Type | Description |
|---|---|---|
id | int | Torrent ID. |
hash | str | Info hash (40 hex characters); |
label | str | Full title, e.g. |
filename | str | Name of the |
description | str | Which episodes it contains, e.g. |
magnet | str | Magnet link. |
size | int | Total size of the files the torrent downloads, in bytes (not the size of the
|
type | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | Source, e.g. |
quality | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | Resolution, e.g. |
codec | Annotated[TorrentCodec | None, EMPTY_VALUE_AS_NONE] | Video codec. |
color | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | Color depth, |
bitrate | int | None | Bitrate in kbit/s — usually |
is_hardsub | bool | Whether the subtitles are burned into the video. |
seeders | int | Number of seeders. |
leechers | int | Number of leechers. |
completed_times | int | How many times it was downloaded completely. |
sort_order | int | Position among the release's torrents. |
created_at | datetime | None | When the torrent was added. |
updated_at | datetime | None | When the torrent was last updated. |
release | Release | None | The release (without |
torrent_members | list[TorrentMember] | People credited on the torrent; often empty. |
aniliberty.TorrentCodec
classTorrentCodec() ValueDescription[str]
Video codec of a torrent, e.g. value="x265/HEVC", label="HEVC".
value is one of AV1, x264/AVC, x265/HEVC, x265hq/HEVC-HQ (spec enum
enums.anime.torrents.torrent.codec).
| Name | Type | Description |
|---|---|---|
label | str | None | Short label the site shows, e.g. |
label_color | str | None | Color of that label as |
label_is_visible | bool | None | Whether the site shows the label. |
aniliberty.TorrentMember
classTorrentMember() AniLibertyModel
A person credited on a torrent (e.g. who encoded the HEVC version).
| Name | Type | Description |
|---|---|---|
id | str | Member entry ID (a UUID string). |
nickname | str | The member's nickname. |
role | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | What they did; |
external_url | str | None | The member's own link (e.g. a Telegram channel), if any. |
user | LinkedAccount | None | Linked site account, if any. |
aniliberty.TorrentFile
classTorrentFile() AniLibertyModel
A downloaded .torrent file, from client.torrents.file().
| Name | Type | Description |
|---|---|---|
filename | str | None | The file name the API suggests (from |
content | bytes | The file's bytes. |
Teams
aniliberty.Team
classTeam() AniLibertyModel
An AniLibria team — one of the "leagues" the project's members are grouped into.
Spec schema: models.teams.v1.team.
| Name | Type | Description |
|---|---|---|
id | str | Team ID (a UUID string). |
title | str | Team name in Russian, e.g. |
description | str | None | Short description in Russian, e.g. |
sort_order | int | Position of the team in the list, from 0 (project leadership) up. |
aniliberty.TeamRole
classTeamRole() AniLibertyModel
A role a team member can have: voice actor, translator, timer, ...
Spec schema: models.teams.v1.team.role.
| Name | Type | Description |
|---|---|---|
id | str | Role ID (a UUID string). |
title | str | Role name in Russian, e.g. |
color | str | None | The role's display color as a CSS hex string, e.g. |
sort_order | int | Position of the role in the list. |
aniliberty.TeamMember
classTeamMember() AniLibertyModel
A member of an AniLibria team, with the team and their roles.
Spec schema: items of responses.api.v1.teams.users (models.teams.v1.team.user
with team, user and roles added).
| Name | Type | Description |
|---|---|---|
id | str | Member ID (a UUID string). |
nickname | str | The member's nickname in the team — the same name that appears in a
release's |
is_intern | bool | Whether the member is an intern. |
is_vacation | bool | Whether the member is on vacation. |
sort_order | int | Position of the member within their team, from 1. |
team | Team | The team the member belongs to. |
roles | list[TeamRole] | The member's roles (at least one for every member at the time of writing). |
user | LinkedAccount | None | The linked site account — |
Media
aniliberty.Promotion
classPromotion() AniLibertyModel
A promo banner from the site's front page: a featured release, a news post or an ad.
Spec schema: models.media.promotions.v1.promotion. What a promotion links to
depends on its kind: a featured release has release and no url; a news post or
an ad has url (and usually url_label) and no release.
| Name | Type | Description |
|---|---|---|
id | str | Promotion ID (a UUID string). |
title | str | None | Banner title, e.g. the release name — can be |
description | str | None | Banner text — can be |
image | Image | None | Banner image. |
url | str | None | External link the banner points to (absolute), or |
url_label | str | None | Text for the link button, e.g. |
release | Release | None | The featured release (with |
is_ad | bool | Whether this is a paid ad rather than the site's own promo. |
has_overlay | bool | Whether the site darkens the image under the text. |
ad_erid | str | None | Ad registry token (ERID) of an ad — |
ad_origin | str | None | Advertiser of an ad — |
aniliberty.Video
classVideo() AniLibertyModel
A video from one of AniLibria's YouTube playlists: trailers, reviews, announcements.
Spec schema: items of responses.v1.media.videos (models.media.videos.v1.video.content
with origin added).
| Name | Type | Description |
|---|---|---|
id | int | Video ID on the site. |
title | str | Video title. |
url | str | Link to watch the video ( |
video_id | str | The video's YouTube ID. |
image | Image | None | Preview image (its paths can be |
views | int | None | View count, as last synced from YouTube — can be |
comments | int | None | Comment count, as last synced from YouTube — can be |
is_announce | bool | Whether the video is a season announcement. |
origin | VideoOrigin | None | The playlist the video comes from. |
created_at | datetime | None | When the video was published. |
updated_at | datetime | None | When the site last synced the video's data. |
aniliberty.VideoOrigin
classVideoOrigin() AniLibertyModel
Where a video comes from: a YouTube playlist the site tracks.
Spec schema: models.media.videos.v1.video.origin.
| Name | Type | Description |
|---|---|---|
id | str | Origin ID (a UUID string). |
title | str | None | Playlist or channel name, e.g. |
description | str | None | Playlist description in Russian. |
url | str | None | Playlist URL. |
type | Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] | Source type. |
is_announce | bool | Whether the playlist holds season announcements. |
Schedule
aniliberty.ScheduledRelease
classScheduledRelease() AniLibertyModel
A release in the airing schedule, with where it stands this week.
Spec schema: models.anime.schedule.v1.releaseInSchedule. The weekday a release comes
out on is release.publish_day (value 1 = Monday ... 7 = Sunday).
| Name | Type | Description |
|---|---|---|
release | Release | The release, with |
published_release_episode | Episode | None | The episode that came out during the current week
(Monday to Sunday), or |
next_release_episode_number | int | None | Number of the next episode to come out, |
full_season_is_released | bool | Whether all episodes of the season are already out. |
aniliberty.ScheduleNow
classScheduleNow() AniLibertyModel
Releases scheduled for yesterday, today and tomorrow.
Spec schema: responses.v1.anime.schedule.now. Each list holds the releases whose
publish_day is that day of the week.
| Name | Type | Description |
|---|---|---|
yesterday | list[ScheduledRelease] | Releases that came out (or were due) yesterday. |
today | list[ScheduledRelease] | Releases due today. |
tomorrow | list[ScheduledRelease] | Releases due tomorrow. |
Catalog
aniliberty.SortingOption
classSortingOption() ValueDescription[str]
A way to sort a list, from client.catalog.references.sorting() or
client.me.favorites.references.sorting().
value is what the matching sorting filter takes, e.g. "FRESH_AT_DESC".
| Name | Type | Description |
|---|---|---|
label | str | None | Short name, in Russian, e.g. |
Catalog models (spec tags Аниме.Каталог and Аниме.Каталог.Справочники).
ReleaseType
attributeReleaseType = Literal['TV', 'ONA', 'WEB', 'OVA', 'OAD', 'MOVIE', 'DORAMA', 'SPECIAL']Release type. Spec: enums.anime.releases.release.type.
Season
attributeSeason = Literal['winter', 'spring', 'summer', 'autumn']Release season. Spec: enums.anime.releases.release.season.
AgeRatingValue
attributeAgeRatingValue = Literal['R0_PLUS', 'R6_PLUS', 'R12_PLUS', 'R16_PLUS', 'R18_PLUS']Age rating. Spec: enums.anime.releases.release.ageRating.
PublishStatus
attributePublishStatus = Literal['IS_ONGOING', 'IS_NOT_ONGOING']Whether the anime is still airing. Spec: enums.anime.catalog.filter.publishStatus.
ProductionStatus
attributeProductionStatus = Literal['IS_IN_PRODUCTION', 'IS_NOT_IN_PRODUCTION']Whether AniLibria is still dubbing it. Spec: enums.anime.catalog.filter.productionStatus.
CatalogSorting
attributeCatalog sort order. Spec: enums.anime.catalog.filter.sorting.
Common
aniliberty.ValueDescription
classValueDescription() AniLibertyModel, Generic[T]
A machine value with its human-readable (Russian) description.
The API returns many enumerations this way — a release's type, season, publish day, a
torrent's quality, ... — e.g. {"value": "TV", "description": "ТВ"} or
{"value": 5, "description": "Пятница"} (publish_day uses weekday numbers). Fields
with extra keys next to these two (age_rating's label/is_adult, a torrent
codec's label_*) use subclasses.
| Name | Type | Description |
|---|---|---|
value | T | The machine value — use it for comparisons and catalog filters. |
description | str | None | The value's description in Russian, for display. |
aniliberty.Image
classImage()An image in its original format plus optimized (WebP) variants.
Spec schema: commons.v1.models.components.image.withOptimized — used for release
posters, episode previews, genre and franchise images, user avatars.
| Name | Type | Description |
|---|---|---|
optimized | ImageSet | None | The same image in WebP, usually smaller. |
aniliberty.ImageSet
classImageSet() AniLibertyModel
Links to one image in several sizes.
The API sends relative paths such as /storage/releases/posters/1/abc.jpg; they
resolve against the site's origin, not the /api/v1 base URL (see
docs/api-notes.md). The raw paths are kept in src/preview/thumbnail, and
the *_url properties return absolute URLs on the host the client talked to (or on
https://aniliberty.top for a model built by hand).
Any of the paths can be None — e.g. a user without an avatar gets an image object
whose paths are all None.
| Name | Type | Description |
|---|---|---|
src | str | None | Original image path. Only posters and episode previews have it. |
preview | str | None | Path of the medium-size image. |
thumbnail | str | None | Path of the small image. |
aniliberty.LinkedAccount
classLinkedAccount() AniLibertyModel
The site account linked to a person in the credits or in a team.
The user of ReleaseMember, TorrentMember and TeamMember — set when the
person has an account on the site, None otherwise. Spec schemas:
models.teams.v1.team.user.account and the user of release/torrent members.
| Name | Type | Description |
|---|---|---|
id | int | User ID on the site. |
nickname | str | None | The account's nickname. Only team members' accounts have it (it can differ
from the team nickname); for release and torrent members it's |
avatar | Image | None | The account's avatar (its paths can be |
Building blocks shared by many models: value + description pairs and images.
EMPTY_VALUE_AS_NONE
attributeEMPTY_VALUE_AS_NONE = BeforeValidator(_empty_value_as_none)Turns {"value": null, ...} into None for an optional ValueDescription field.
The API sometimes sends a value + description object with both keys null — seen on
season of releases in the schedule — instead of omitting it. Annotate such fields as::
season: Annotated[ValueDescription[str] | None, EMPTY_VALUE_AS_NONE] = NoneService
aniliberty.ApiStatus
classApiStatus() AniLibertyModel
Status of the API, returned by client.app.status().
Spec schema: responses.v1.app.status.
| Name | Type | Description |
|---|---|---|
is_alive | bool | Whether the API reports itself as working. |
available_api_endpoints | list[str] | Hosts the API can be reached at, e.g.
|
request | RequestInfo | None | What the API knows about the caller, if it sent that. |
aniliberty.RequestInfo
classRequestInfo() AniLibertyModel
What the API knows about the client that sent the request (geolocated by IP).
Spec schema: responses.v1.app.status → request. All fields are optional: the spec
marks none of them required, and geolocation can fail.
| Name | Type | Description |
|---|---|---|
ip | str | None | The client's IP address as seen by the API. |
country | str | None | Country name, in English (e.g. |
iso_code | str | None | ISO 3166-1 alpha-2 country code (e.g. |
timezone | str | None | IANA time zone name (e.g. |
Authentication
aniliberty.AuthToken
classAuthToken() AniLibertyModel
A session token, returned by the login methods of client.auth.
Spec schemas: responses.v1.accounts.users.auth.login,
responses.api.v1.accounts.otp.login,
responses.v1.accounts.users.auth.social.authenticate.
The token is an opaque string (32 characters at the time of writing) with no expiry
inside; the API has no refresh endpoint. Every login creates a new, independent session,
and client.auth.logout() ends only the client's own.
| Name | Type | Description |
|---|---|---|
token | str | The session token. Keep it secret — it gives full access to the account. |
aniliberty.IssuedOtp
classIssuedOtp() AniLibertyModel
aniliberty.Otp
classOtp() AniLibertyModel
A one-time code for logging in a device without typing the password.
Spec schema: models.accounts.otp.v1.otp.
| Name | Type | Description |
|---|---|---|
code | str | The code to show to the user, as a string — it can start with |
device_id | str | The device the code was issued for. |
user_id | int | None | The account that accepted the code; |
expired_at | datetime | When the code expires. |
aniliberty.SocialLogin
classSocialLogin() AniLibertyModel
Where to send the user to log in with a social network.
Spec schema: responses.v1.accounts.users.auth.social.login.
| Name | Type | Description |
|---|---|---|
url | str | The OAuth page of the social network, to open in a browser. |
state | str | Key to pass to |
Authentication models (spec tags Аккаунты.Пользователи.Авторизация.* and
Аккаунты.ОдноразовыеПароли).
SocialProvider
attributeSocialProvider = Literal['vk', 'google', 'patreon', 'discord']Social networks that can be used to log in.
SOURCE ↗Account
aniliberty.UserProfile
classUserProfile() AniLibertyModel
The logged-in user's own profile, returned by client.me.profile().
Spec schema: models.users.v1.user. email and torrents.passkey are only sent
for the user's own profile.
| Name | Type | Description |
|---|---|---|
id | int | User ID. |
login | str | Login, used to log in (together with |
email | str | None | E-mail of the account. |
nickname | str | None | Display name; defaults to the login for a new account. |
avatar | Image | None | Avatar; all paths are |
torrents | TorrentStats | None | Tracker passkey and traffic. |
is_banned | bool | None | Whether the user is banned on the site. |
created_at | datetime | None | When the account was created. |
is_with_ads | bool | None | Whether ads are shown to the user — in the spec, but not sent by the API at the time of writing (see the three flags below). |
is_with_video_ads | bool | None | Whether video ads are shown (not in the spec). |
is_with_content_ads | bool | None | Whether ads are shown among the site's content (not in the spec). |
is_with_video_ads_always | bool | None | An ad flag the API doesn't document (not in the spec); it
matches the |
aniliberty.TorrentStats
classTorrentStats() AniLibertyModel
The account's torrent tracker data.
Spec schema: models.users.v1.user → torrents.
| Name | Type | Description |
|---|---|---|
passkey | str | None | The account's tracker passkey — the |
uploaded | int | None | Total uploaded, in bytes; |
downloaded | int | None | Total downloaded, in bytes; |
aniliberty.CollectionItem
classCollectionItem() AniLibertyModel
A release in one of the user's collections, from client.me.collections.ids().
Spec schema: responses.api.v1.accounts.users.me.collections.ids.item — the API sends
a [release_id, collection] pair, e.g. [9839, "WATCHED"]; it's validated into this
model (a dict with the field names works too).
| Name | Type | Description |
|---|---|---|
release_id | int | Release ID. |
collection | CollectionType | The collection the release is in. A release is in one collection at most. |
User account models (spec tags Аккаунты.Пользователи.Мое.*).
FavoritesSorting
attributeSort order of favorites; CREATED_AT_* is when the release was added to favorites.
CollectionType
attributeCollectionType = Literal['PLANNED', 'WATCHED', 'WATCHING', 'POSTPONED', 'ABANDONED']A user's collection: planned, watched, watching, postponed or abandoned.
SOURCE ↗Watch progress
aniliberty.Timecode
classTimecode() AniLibertyModel
Where the user stopped in an episode, and whether they finished it.
What client.me.views.save_timecodes() takes and client.me.views.timecodes()
returns. Spec schema: responses.v1.accounts.users.me.views.timecodes.item — the API
sends an [episode_id, time, is_watched] triple, which is validated into this model (a
dict with the field names works too).
| Name | Type | Description |
|---|---|---|
release_episode_id | str | Episode ID (a UUID string), as in |
time | float | Position in the episode, in seconds from its start (fractions allowed, not negative). |
is_watched | bool | Whether the episode counts as watched. |
aniliberty.EpisodeView
classEpisodeView() AniLibertyModel
The stored watch record of one episode.
Returned by client.episodes.timecode() and client.releases.timecodes(). Spec
schema: models.accounts.users.v1.user.view. Records are written by the API about a
minute after client.me.views.save_timecodes() (see docs/api-notes.md).
| Name | Type | Description |
|---|---|---|
id | int | Record ID. |
release_episode_id | str | Episode ID (a UUID string). |
time | float | Position in the episode, in seconds from its start. |
is_watched | bool | Whether the episode counts as watched. |
updated_at | datetime | None | When the record was last saved. |
user_id | int | None | The user the record belongs to. |
aniliberty.HistoryEntry
classHistoryEntry()Exceptions
aniliberty.AniLibertyError
classAniLibertyError() Exception
Base class for all errors raised by the SDK.
SOURCE ↗aniliberty.APIError
classAPIError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
)The API responded with an error status.
Raised as is for statuses without a dedicated subclass (e.g. 400, 405, 409); otherwise one
of the subclasses below is raised, so except APIError catches all of them.
| Name | Type | Description |
|---|---|---|
status_code | HTTP status code of the response. | |
message | Error message — the | |
method | HTTP method of the failed request. | |
url | Full URL of the failed request. | |
payload | The decoded JSON body of the response, or its text if it wasn't JSON. |
aniliberty.NotFoundError
classNotFoundError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
)The requested resource doesn't exist (404).
The API's own message for this status is an internal Laravel lookup string such as
"No query results for model [...]" — it's kept in payload, while message is a
plain "Not found".
aniliberty.ValidationError
classValidationError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
errors: dict[str, list[str]] | None = None,
)The API rejected the request parameters (422).
Not to be confused with pydantic.ValidationError: this one means the request was
invalid, e.g. limit above the endpoint's maximum. A response that doesn't fit the SDK's
models raises ResponseParseError instead.
| Name | Type | Description |
|---|---|---|
errors | dict[str, list[str]] | Messages per parameter, as the API returns them (in Russian), e.g.
|
aniliberty.RateLimitError
classRateLimitError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
retry_after: float | None = None,
)The API answered 429 Too Many Requests.
| Name | Type | Description |
|---|---|---|
retry_after | The response's |
aniliberty.AuthRequiredError
classAuthRequiredError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
)The request needs a valid session, or the credentials were wrong (401/403 JSON).
The API answers 403 "This action is unauthorized." to an account endpoint called
without a session token, with a malformed one, or with one that was logged out or has
expired — it doesn't say which. Log in again (client.auth.login()) or pass a fresh
token to AniLiberty. Wrong credentials at client.auth.login() are a 401; the
API's reason is in message.
aniliberty.ServerError
classServerError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
)The API (or the CDN in front of it) failed with a 5xx status.
SOURCE ↗aniliberty.AccessBlockedError
classAccessBlockedError(
message: str,
*,
status_code: int,
method: str,
url: str,
payload: Any = None,
)Something in front of the API answered with an HTML page instead of JSON.
The SDK always sends Accept: application/json, and with that header the API itself
answers with JSON even for errors (see docs/api-notes.md). An HTML page therefore
comes from the Cloudflare edge in front of the API — a bot challenge or a block of the
network/IP. Resending the same request gets the same answer, so this is not retried.
HTML on a 5xx status is not this error but ServerError: Cloudflare renders its own
origin errors (502, 520, ...) as HTML pages too, and those are worth retrying.
aniliberty.TransportError
classTransportError(message: str, *, method: str, url: str)The request failed before any response arrived (connection error, timeout, ...).
The underlying httpx exception is available as __cause__.
| Name | Type | Description |
|---|---|---|
method | HTTP method of the failed request. | |
url | Full URL of the failed request. |
aniliberty.ResponseParseError
classResponseParseError(message: str, *, url: str, payload: Any = None)A successful response couldn't be decoded or didn't match the SDK's model.
Usually means the API changed its response format. The raw data is kept so it isn't
lost: payload holds the decoded JSON (or the body text if it wasn't JSON), and the
original json/pydantic exception is available as __cause__.
| Name | Type | Description |
|---|---|---|
url | Full URL of the request. | |
payload | Decoded JSON body, or the body text if it wasn't valid JSON. |
Low-level HTTP client
Available as client.http; client.http.request(...) is the escape hatch for endpoints the
SDK doesn't cover yet.
aniliberty.HttpClient
classHttpClient(
*,
base_url: str = DEFAULT_BASE_URL,
timeout: float = DEFAULT_TIMEOUT,
headers: Mapping[str, str] | None = None,
http_client: httpx.AsyncClient | None = None,
token: str | None = None,
max_concurrency: int = DEFAULT_MAX_CONCURRENCY,
request_delay: float = DEFAULT_REQUEST_DELAY,
max_retries: int = DEFAULT_MAX_RETRIES,
retry_base_delay: float = DEFAULT_RETRY_BASE_DELAY,
max_retry_after: float = DEFAULT_MAX_RETRY_AFTER,
cache_dir: str | os.PathLike[str] | None = None,
cache_ttl: float | None = None,
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
clock: Callable[[], float] = time.monotonic,
cache_clock: Callable[[], float] = time.time,
)Sends requests to the API and turns responses into JSON or SDK exceptions.
Every resource of AniLiberty goes through this class; it is also the escape hatch for
endpoints the SDK doesn't cover yet: await client.http.request("GET", "/teams/roles")
returns the decoded JSON as is, with the same headers, limits, retries and error handling
as the rest of the SDK.
Request paths are relative to base_url and joined as strings, so
"/app/status" with the default base URL becomes
https://aniliberty.top/api/v1/app/status. Absolute URLs are not accepted.
Being polite to the API is built in:
- Concurrency — at most
max_concurrencyrequests are in flight at once, however many coroutines call the client (e.g. viaasyncio.gather). - Pacing — consecutive requests start at least
request_delayseconds apart. - Retries — 429 is retried for any method, 5xx and network errors/timeouts only for
idempotent methods (
IDEMPOTENT_METHODS). The delay is the response'sRetry-Afterwhen it has one, otherwiseretry_base_delaydoubled per attempt (1 s, 2 s, 4 s by default). Other 4xx and HTML block pages are never retried.
With cache_dir set, request(..., cache=True) keeps the decoded JSON of successful
GET responses on disk and answers repeated identical requests from there until
cache_ttl passes — see request() for what is and isn't cached.
base_url
attributepropertybase_url: strThe API base URL, without a trailing slash.
SOURCE ↗site_url
attributepropertysite_url: strOrigin of base_url (scheme and host), e.g. https://aniliberty.top.
Relative paths in responses, such as image paths, resolve against it.
SOURCE ↗cache_dir
attributepropertycache_dir: Path | NoneThe response cache directory, or None when caching is off.
token
attributepropertytoken: str | NoneSession token sent as Authorization: Bearer <token>, or None for none.
Set it to start or stop sending the token; it replaces an Authorization header
given in headers. Public endpoints work with or without it (even with an expired
one); account endpoints answer 403 without a valid one.
closed
attributepropertyclosed: boolWhether aclose() has been called.
aclose
methodasyncaclose() -> NoneClose the underlying httpx.AsyncClient, unless it was passed in by the caller.
Safe to call more than once.
SOURCE ↗url
methodurl(path: str) -> strReturn the full URL for an API path such as "/app/status".
request
methodasyncrequest(
method: str,
path: str,
*,
params: Mapping[str, Any] | None = None,
json: Any = None,
with_token: bool = True,
cache: bool = False,
refresh: bool = False,
) -> AnySend a request and return the decoded JSON body.
Retries and limits described on the class apply.
A response is read from and saved to the cache only when all of these hold: the
client has a cache_dir, cache=True, the method is GET, and no session
token goes with the request — so nothing that depends on the logged-in user (or
carries their torrent passkey) ever lands on disk. The cache key is the method, the
full URL and the query parameters in sorted order. Only successful responses with a
body are cached; errors are raised as usual and never stored.
| Name | Type | Description | Default |
|---|---|---|---|
method | str | HTTP method, e.g. | |
path | str | Path relative to | |
params | Mapping[str, Any] | None | Query parameters, passed to | None |
json | Any | JSON request body. | None |
with_token | bool | Whether to send | True |
cache | bool | Whether the response may be read from and saved to the cache (see above). The SDK's own methods pass it for endpoints whose data doesn't change from call to call. | False |
refresh | bool | Skip reading the cache and fetch from the API; the fresh response still replaces the cached one. | False |
| Type | Description |
|---|---|
Any | The decoded JSON body ( |
| Type | Description |
|---|---|
APIError | The API answered with an error status — one of its subclasses for 404, 422, 429, 401/403, 5xx and HTML pages. 429 and 5xx are raised only once retries are exhausted. |
TransportError | The request failed without a response (network error, timeout), after retries for idempotent methods. |
ResponseParseError | A successful response wasn't valid JSON. |
RuntimeError | The client has been closed. |
request_raw
methodasyncrequest_raw(
method: str,
path: str,
*,
params: Mapping[str, Any] | None = None,
) -> httpx.ResponseSend a request for a non-JSON resource (a file, an XML feed) and return the response.
Same limits, retries and error handling as request(); the body is fully read, so
use stream() instead for large downloads.
| Name | Type | Description | Default |
|---|---|---|---|
method | str | HTTP method, e.g. | |
path | str | Path relative to | |
params | Mapping[str, Any] | None | Query parameters, passed to | None |
| Type | Description |
|---|---|
httpx.Response | The successful |
| Type | Description |
|---|---|
APIError | The API answered with an error status — see |
TransportError | The request failed without a response. |
RuntimeError | The client has been closed. |
stream
methodasyncstream(
method: str,
path: str,
*,
params: Mapping[str, Any] | None = None,
) -> AsyncIterator[httpx.Response]Send a request and stream the response body, for downloads.
Limits and retries apply until the response headers arrive: a 429/5xx is retried
before anything is yielded, and an error status raises like request(). The body is
then read by the caller (response.aiter_bytes()); the response is closed when the
async with block exits.
| Name | Type | Description | Default |
|---|---|---|---|
method | str | HTTP method, e.g. | |
path | str | Path relative to | |
params | Mapping[str, Any] | None | Query parameters, passed to | None |
| Type | Description |
|---|---|
AsyncIterator[httpx.Response] | The successful |
| Type | Description |
|---|---|
APIError | The API answered with an error status — see |
TransportError | The request failed without a response. |
RuntimeError | The client has been closed. |