Skip to content

Pagination

Some endpoints return results page by page. In the SDK each of them comes as a pair:

One page → Page All pages → async iterator
client.catalog.releases() client.catalog.iter_releases()
client.releases.list() client.releases.iter_list()

Both take the same filters; the iter_* twin just doesn't take page. Endpoints that return a short list without pages (releases.latest(), random(), recommended(), app.search_releases()) return a plain list.

The numbers in the examples below come from a real run on 2026-10-16; the catalog changes, so yours will differ.

Page

page = await client.catalog.releases(types=["MOVIE"], limit=20)
print(page.page, page.limit, page.total, page.total_pages, page.has_next_page)
# 1 20 148 8 True
for release in page.items:
    ...
Field Meaning
items the releases on this page
page number of this page, starting at 1
limit page size
total number of results on all pages
total_pages number of pages
has_next_page page < total_pages

limit is 1 to 50 — anything else is rejected by the API with a ValidationError; so is page below 1. A page past the last one is not an error, just an empty page.

Counting without fetching

total comes with every page, so to know how many releases match, ask for the smallest page possible:

page = await client.catalog.releases(types=["MOVIE"], limit=1)
print(page.total)  # 148 — one request, one release transferred

iter_*: all pages for you

async for release in client.catalog.iter_releases(publish_statuses=["IS_ONGOING"], limit=50):
    print(release.name.main)

The iterator requests page 1, yields its items, requests page 2 only when you've consumed page 1, and so on. It stops after the last page — or at the first empty page, so a total that changes while you iterate can't make it loop forever.

It's lazy, so breaking out stops the requests. Taking the first 10 releases of the whole catalog this way makes exactly one request:

first_ten = []
async for release in client.catalog.iter_releases(limit=50):
    first_ten.append(release)
    if len(first_ten) == 10:
        break

limit only changes the number of requests, not the result. Iterating the 33 ongoing releases took 4 requests with limit=10 and 1 with limit=50. Use limit=50 with iter_* unless you have a reason not to.

To collect everything into a list, use a comprehension:

ongoing = [r async for r in client.catalog.iter_releases(publish_statuses=["IS_ONGOING"], limit=50)]

Walking pages yourself

When you need the page boundaries — for your own UI, progress reporting, or to resume later — loop over releases() with has_next_page:

page_number = 1
while True:
    page = await client.catalog.releases(
        publish_statuses=["IS_ONGOING"], page=page_number, limit=20
    )
    handle(page.items)
    if not page.has_next_page:
        break
    page_number += 1

Fetching pages concurrently

iter_* is sequential: page 2 is requested after page 1 is consumed. If you need all pages fast, fetch the first one to learn total_pages, then the rest at once:

first = await client.catalog.releases(types=["MOVIE"], limit=50)
rest = await asyncio.gather(
    *(
        client.catalog.releases(types=["MOVIE"], page=n, limit=50)
        for n in range(2, first.total_pages + 1)
    )
)
movies = first.items + [release for page in rest for release in page.items]
# 148 movies, 3 requests

The client still keeps at most max_concurrency requests in flight and spaces them out, so this is safe to do with many pages.

Pages are separate snapshots

Each page is a separate request. If the data changes between them — a release gets a new episode and moves to the top of the default FRESH_AT_DESC order — an item can shift from one page to the next, showing up twice or not at all. This is how offset pagination works in general, sequential or concurrent. When it matters, sort by something stable (sorting="YEAR_ASC") and deduplicate by release.id.

Several releases by ID

client.releases.list() pages through the releases you ask for by ids/aliases — useful for a long list of IDs, e.g. a user's favorites stored elsewhere:

async for release in client.releases.iter_list(ids=[9542, 10172, 10297], limit=50):
    print(release.id, release.name.main)

The result doesn't follow the order of ids, and an ID given twice (or both as an ID and as an alias) is returned once.