Getting started

Pagination

Every list surface has three methods: list for one page, list_all for everything, iterate for a stream.

Every paginated collection in the SDK is exposed three ways. They hit the same endpoint and differ only in how much they buffer.

MethodReturnsRequestsUse when
listPage[T]Exactly oneYou are rendering a page, or you only need a count
list_alllist[T]As many as it takesYou need the whole set in memory anyway
iterateIterator[T], or AsyncIterator[T] on AsyncEnvlessAs many as it takes, lazilyThe set is large, or you can stop early
python
from envless import envless

page = envless.products.list(limit=50)

print(page['items'])
print(page['pagination']['limit'], page['pagination']['offset'])
print(page['pagination']['totalCount'], page['pagination']['hasMore'])
python
from envless import envless

everything = envless.products.list_all()
python
from envless import envless

for product in envless.products.iterate():
    if product['slug'] == 'billing':
        break

iterate is the only one that can stop early. Breaking out of the loop stops making requests, so scanning for one item in a large workspace costs one page, not all of them. On AsyncEnvless it is the same loop written async for.

Page is a TypedDict generic over the item type, so page['items'] is a list[ProductResource] to your type checker.

Options

List methods take these as keyword arguments, after their positional slugs. Each list accepts only the options its route reads, so an option a route ignores is a TypeError:

OptionTypeNotes
limitint1 to 100. Defaults to 25 on the API. iterate and list_all default to 100 to minimise round trips, and clamp anything higher.
offsetintRows to skip. iterate and list_all use it as a starting point and then advance it themselves.
searchstrProducts, product projects, projects, environments, invites, members, roles, keys and webhooks. Each method page says what it matches.
orderstrname, asc or desc, on products, product projects, projects, environments, keys and webhooks.
sort_bystrRoles only: name (the default), updated or created.
role_idstrMembers only: keeps the members holding that role.
statusstrWebhook deliveries only: pending, processing, succeeded or failed.
productstrProjects, environments, variables, variable history and versions: disambiguates a project slug that exists in more than one product.
timeoutfloatBounds each request of the walk, not the whole walk.

A limit outside 1 to 100, or a non-integer, is refused with 400 request.invalid_pagination rather than being clamped server-side. error.field names the offending parameter.

How the walk terminates

list_all and iterate stop on either of two conditions, which matters because it makes them safe against a server that keeps saying hasMore:

hasMore is False

The normal case.

A page comes back empty

Even if hasMore is still True. Without this a bad cursor would loop forever.

The offset advances by the number of items actually received, not by limit, so a short page does not skip rows. A failure on any page fails the whole list_all call, and the rows it had already collected are lost; iterate has already handed you the earlier items, so it raises at the page that failed.

Secondary collections

A namespace can paginate more than one collection. Those get their own trio, named after what they read.

NamespacePrimarySecondary
productslist, list_all, iteratelist_projects, list_all_projects, iterate_projects and access.list, access.list_all, access.iterate
variableslist, list_all, iteratelist_history, list_all_history, iterate_history
versionslist, list_all, iteratelist_entries, list_all_entries, iterate_entries
webhookslist, list_all, iteratelist_deliveries, list_all_deliveries, iterate_deliveries

Two endpoints look like lists but are not paginated, because the whole set is small and fixed. Both return a plain dict rather than a Page:

MethodReturns
roles.list_permissions()PermissionsResource with the full catalogue, a byResource index, and the system role sets
webhooks.list_event_types()EventTypesResource with eventTypes and totalCount

One collection carries an extra field

products.access.list() returns a ProductAccessPage, which is a normal page plus isPrivate. That flag tells you whether the grants matter at all: on a public product every member has access and the list is informational.

python
from envless import envless

access = envless.products.access.list('internal-tools')

if not access['isPrivate']:
    print('Public product, every member already has access')

print([entry['id'] for entry in access['items']])

access.list_all and access.iterate only stream items, so they drop isPrivate. If you need the flag, use access.list.

Counting without fetching

totalCount is the size of the full filtered set, not the page, so a single one-row request is enough to count.

python
from envless import envless

pagination = envless.members.list(limit=1)['pagination']

print(f"{pagination['totalCount']} members")