Pagination
A list endpoint that returns one page is a list endpoint every caller writes a loop around. Octri generates that loop for them: callers get each item in turn, and pages are fetched only as they're needed.
Often you don't have to configure anything. Octri recognizes common pagination shapes in your spec, and you can state the shape yourself when it doesn't.
What callers write
These are from generated SDKs. Each one yields items, not pages, and stops when the API signals the end. Nothing is prefetched, so stopping early saves requests.
for await (const user of client.users.list({ limit: 100 })) {
console.log(user.id);
}The operation itself is a lazy AsyncGenerator.
Single pages and whole pages
The plain method still exists in most languages, for a caller who wants one page. client.users.list() in Python returns one UserPage, with its next_cursor.
Cursor-paginated operations can also generate a companion that yields a page at a time, for callers who need page-level data such as a count. It's named for the operation: listUsersPages in TypeScript, Kotlin, PHP, Dart and Swift, and list_users_pages in Python and Rust.
How Octri finds pagination
The generator checks an operation in this order. The first match wins.
| Order | Style | Detected from |
|---|---|---|
| First | Next URL | A next or next_url field holding a full URL |
| Second | Cursor | A cursor parameter and a matching response field |
| Third | Page number | A page parameter |
| Fourth | Offset | An offset parameter and a next_offset response field |
Every style also needs an array of results in the response. Octri looks for data, items, results, records, entries, values or objects, in that order, then the first array it finds.
These names are recognized, ignoring case:
| Role | Names |
|---|---|
| Cursor parameter | cursor, after, next_cursor, page_token, starting_after, start_cursor, continuation_token |
| Cursor in the response | next_cursor, nextCursor, cursor, next_page_token, nextPageToken, next, after, last_id, lastId |
| More to come | has_more, hasMore, has_next, hasNext, more, is_truncated, isTruncated |
| Page parameter | page, page_number, pageNumber |
| Next page in the response | next_page, nextPage |
| Total pages | total_pages, totalPages, page_count, pageCount, last_page, lastPage, pages |
| Offset parameter | offset, skip, start |
| Next offset in the response | next_offset, nextOffset |
An offset parameter with only a total in the response is not detected, because that alone doesn't reliably say where to stop. Add next_offset, or state the pagination yourself. Check your SDK Studio preview to see what was found.
Setting it in SDK Studio
Select an endpoint and open its pagination settings. Each is saved on that endpoint, under pagination.
Enable paginationon or offGenerates paginated access for this endpoint. Off also stops it being detected.
pagination: the settings below, or false
StrategychoiceHow the next page is asked for: cursor, offset or page.
pagination.strategy
Items fieldresponse fieldThe response field holding the array of items, such as data.
pagination.itemsField
Request cursor parameterquery parameterThe query parameter sent with the next-page cursor, such as after. For the other strategies this is Offset parameter or Page parameter.
pagination.nextField
Response cursor fieldresponse pathWhere the next cursor is in the response, such as meta.next, when it isn't named like the request parameter. Cursor strategy only.
pagination.responseField
Auto-paginate helperon or offGenerates the iterator. Off leaves the method returning one page.
pagination.autoPaginate
Pagination stylechoice per languageWhat the helper returns. Only TypeScript's output changes.
| Value | The method returns |
|---|---|
generator | A lazy AsyncGenerator that yields items |
paginator | A Paginator, with explicit page navigation |
byLang.<language>.paginationStyle
Octri checks these against the operation's real parameters and response when it builds. If a name doesn't match, Octri ignores your settings for that endpoint and falls back to detection, rather than generating code that can't work. So if the result isn't what you set, check the names first: Items field and the response field must exist in the response schema, and the parameter must be a query parameter on the operation.
Turning pagination off for an endpoint also stops it being detected.
Setting it in your spec
Put an x-pagination extension on the operation. It wins over detection, and it can express more than the settings above.
paths:
/users:
get:
operationId: listUsers
x-pagination:
type: cursor
results: $response.data
cursor:
request: $request.after
response: $response.last_id
hasMore: $response.has_moreresults is required. Then add the block for your type:
type | Field | Meaning |
|---|---|---|
cursor | request | The input parameter carrying the cursor |
response | The response field holding the next cursor. $response.data[-1].id uses the last item's ID | |
hasMore | Optional. Stops when false | |
offset | request | The offset parameter |
limit | Optional. The page-size parameter | |
response | Optional. The next-offset field | |
total | Optional. Stops at this total | |
page | request | The page-number parameter |
response | Optional. The next-page field | |
totalPages | Optional. Stops at this page | |
url | next | The field holding the next page's full URL |
previous | Optional. The previous page's URL |
Set x-pagination: false to opt one operation out of detection.
Choosing a style
| Style | Good for | Watch out for |
|---|---|---|
| Cursor | Most APIs. Results stay stable while data changes | Callers can't jump to a page |
| Next URL | APIs that already return links | Set it with x-pagination or rely on detection. SDK Studio's settings don't offer it |
| Page number | Small, slowly changing lists | Rows inserted mid-read shift the window, so items repeat or get skipped |
| Offset | Reporting over stable data | The same shifting problem, and deep offsets are often slow |