Every paginated collection in the SDK is exposed three ways. They hit the same endpoint and differ only in how much they buffer.
| Method | Returns | Requests | Use when |
|---|---|---|---|
list | Page<T> | Exactly one | You are rendering a page, or you only need a count |
listAll | Array<T> | As many as it takes | You need the whole set in memory anyway |
iterate | AsyncGenerator<T> | As many as it takes, lazily | The set is large, or you can stop early |
const page = await envless.products.list({ limit: 50 })
page.items // Array<ProductResource>
page.pagination.limit
page.pagination.offset
page.pagination.totalCount
page.pagination.hasMoreconst everything = await envless.products.listAll()for await (const product of 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.
Options
Each list takes only the options its route reads, typed per method (ListOptions, SearchListOptions, SortedListOptions, ScopedListOptions, ScopedSortedListOptions, RoleListOptions, MemberListOptions and DeliveryListOptions), so passing one a route ignores is a type error.
| Option | Type | Notes |
|---|---|---|
limit | number | 1 to 100. Defaults to 25 on the API. iterate and listAll default to 100 to minimise round trips, and clamp anything higher. |
offset | number | Rows to skip. iterate and listAll use it as a starting point and then advance it themselves. |
search | string | Products, product projects, projects, environments, invites, members, roles, keys and webhooks. Each method page says what it matches. |
order | ListOrder | name, asc or desc, on products, product projects, projects, environments, keys and webhooks. |
sortBy | RoleSort | Roles only: name (the default), updated or created. |
roleId | string | Members only: keeps the members holding that role. |
status | DeliveryStatus | Webhook deliveries only: pending, processing, succeeded or failed. |
product | string | Projects, environments, variables, variable history and versions: disambiguates a project slug that exists in more than one product. |
signal | AbortSignal | Cancels the request, or the whole walk. |
A limit outside 1 to 100, or a non-integer, is rejected with 400 request.invalid_pagination rather than being clamped server-side. error.field names the offending parameter.
How the walk terminates
listAll 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.
Secondary collections
A namespace can paginate more than one collection. Those get their own trio, named after what they read.
| Namespace | Primary | Secondary |
|---|---|---|
products | list, listAll, iterate | listProjects, listAllProjects, iterateProjects and access.list, access.listAll, access.iterate |
variables | list, listAll, iterate | listHistory, listAllHistory, iterateHistory |
versions | list, listAll, iterate | listEntries, listAllEntries, iterateEntries |
webhooks | list, listAll, iterate | listDeliveries, listAllDeliveries, iterateDeliveries |
Two endpoints look like lists but are not paginated, because the whole set is small and fixed. Both return a plain object rather than a Page:
| Method | Returns |
|---|---|
roles.listPermissions() | PermissionsResource with the full catalogue, a byResource index, and the system role sets |
webhooks.listEventTypes() | 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.
const access = await envless.products.access.list('internal-tools')
if (!access.isPrivate) console.log('Public product, every member already has access')
access.items // Array<ProductAccessEntry>access.listAll 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.
const { pagination } = await envless.members.list({ limit: 1 })
console.log(`${pagination.totalCount} members`)