--- title: Pagination | Stainless description: Configure pagination helpers and auto-iterators for your SDK. Support cursor, offset, and page number pagination schemes with automatic next-page fetching. --- [\[reference\]](/docs/reference/config#pagination/index.md) Configuring pagination with Stainless SDKs generates a helper to fetch the next page and an auto-iterator to easily loop through items in your API. The helper makes it easy to manually paginate when needed and the auto-iterator makes consuming a list as natural as a for loop, with the iterator automatically fetching the next page when needed: ``` const iter: OffsetPage = await client.accounts.list() for await (const account in iter) { if (account.name === 'Michael') { console.log(account.id) } } ``` ### Pagination Scheme Stainless will only use recognized **pagination schemes** when generating SDKs. If a method isn’t being paginated, make sure that it has the required schema for the `request` and `response`, and that it matches the schema in the OpenAPI spec. If a method is improperly paginated, make sure that the [`x-stainless-pagination-property`](/docs/reference/config#pagination-property/index.md) is set correctly. The `request` and `response` must include all parameters and fields needed for pagination. The request might have a limit, offset, page number, page size, or start ID; the response might have the data, a link to the next page, or the next start ID. You can also include parameters that aren’t strictly necessary for pagination to work. For example, it might be appropriate to add a request parameter like `sort_by`, which might be an enum of the ways you can sort across your API. Here’s an example of a pagination scheme Stainless recognizes: stainless.yml ``` pagination: - # Name for the pagination scheme. # Stainless will use this name when generating code for pagination helpers. name: cursor_page # Type of pagination your API uses, used to set logic for fetching each page. # For the list of allowed types, see the "Pagination Type" section below. type: cursor_id request: # Specifies that paginated endpoints take a `starting_after` query param, # which pagination helpers pass cursor IDs to. starting_after: # This param name is the one your API uses. type: string x-stainless-pagination-property: # Defines the purpose of the param. purpose: next_cursor_id_param response: # Specifies that paginated responses have a `data` field # containing the contents of the requested page. data: type: array items: type: unknown x-stainless-pagination-property: purpose: items # Specifies that responses have a `next_cursor` field, # which pagination helpers will take the next cursor ID from. next_cursor: type: string x-stainless-pagination-property: purpose: next_cursor_field # Optional. Specifies where pagination parameters are located. # Can be `query` (default) or `body`. param_location: query ``` ### Pagination Type The first step in configuring pagination is to define the type of pagination your API is using. The types of pagination Stainless supports are: - `cursor`: A pagination scheme that uses a cursor (a string or a number to indicate the element to start from) to paginate through a list of items. - `cursor_id`: Similar to cursor pagination, but the cursor comes from an id property of the response items. - `cursor_url`: A pagination scheme that simply returns a URL to fetch from to retrieve the next page of results. - `offset`: A simple pagination scheme that uses an offset (the number of entries to skip) and limit (number of elements to fetch) to paginate through a list of items. - `page_number`: Similar to offset pagination, but instead of an offset, which indexes on the items, `page_number` indexes on chunks of elements to skip. If you haven’t yet built pagination for your API and are unsure which type of pagination to use, we recommend cursor\_id pagination as it provides the best balance of simplicity, debuggability, and flexibility. Cursor IDs map directly to your database’s natural ordering (primary keys or indexed fields), making them simple to implement and maintain. Unlike opaque cursor tokens, they’re human-readable and easy to debug. Unlike cursor URLs, they give clients the flexibility to construct requests programmatically while keeping payloads compact. Use cursor\_id unless you have specific requirements for complex pagination state that can’t be represented by a single identifier. Cursor Pagination ``` pagination: - name: my_cursor_page type: cursor request: next: type: string x-stainless-pagination-property: purpose: next_cursor_param limit: type: integer response: my_data: type: array items: type: object next: type: string x-stainless-pagination-property: purpose: next_cursor_field ``` Example OpenAPI Specification ``` openapi: 3.1.0 paths: /accounts: get: parameters: - name: next in: query schema: type: string - name: limit in: query schema: type: integer responses: '200': description: 'success!' content: application/json: schema: type: object properties: my_data: type: array items: type: object next: type: string ``` Cursor ID Pagination ``` pagination: - name: my_cursor_id_page type: cursor_id request: starting_after: type: string x-stainless-pagination-property: purpose: next_cursor_id_param ending_before: type: string x-stainless-pagination-property: purpose: previous_cursor_id_param limit: type: integer response: my_data: type: array items: type: object properties: id: type: string x-stainless-pagination-property: purpose: cursor_item_id required: - id ``` Example OpenAPI Specification ``` openapi: 3.1.0 paths: /accounts: get: parameters: - name: starting_after in: query schema: type: string - name: ending_before in: query schema: type: string - name: limit in: query schema: type: integer responses: '200': description: 'success!' content: application/json: schema: type: object properties: my_data: type: array items: type: object properties: id: type: string ``` Cursor URL Pagination ``` pagination: - name: cursor_url_page type: cursor_url request: page_size: type: integer response: data: type: array x-stainless-pagination-property: purpose: items next: type: string x-stainless-pagination-property: purpose: cursor_url_field from_header: Link ``` Example OpenAPI Specification ``` openapi: 3.1.0 paths: /paginated/cursor_url: get: description: Test case for cursor_url pagination parameters: - name: page_size in: query schema: type: integer responses: '200': description: OK content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/MyModel' next: type: string format: uri description: The URL for the next page required: - data - next_page ``` Offset Pagination While creating APIs with offset pagination is often simpler to implement, they can often end up being a source of problems for both you and your users. Consider: - If new resources are simultaneously being created or deleted that fall in the middle of the list based on the sort, you run the risk of missing or double scanning objects as you paginate through results. - Many database engines have poor performance for large offset values, since they often have to scan through the entire table to get to the offset. If you can, we’d recommend cursor-id-based pagination, which is more robust and performant. ``` pagination: - name: my_offset_page type: offset request: my_offset: type: integer description: The number of elements to skip. # this tells us to modify this param when getting the next page x-stainless-pagination-property: purpose: offset_count_param my_limit: type: integer description: The maximum number of elements to fetch. response: my_data: type: array items: type: object my_total: type: integer x-stainless-pagination-property: # total number of elements in the list purpose: offset_total_count_field my_count: type: integer x-stainless-pagination-property: # where to start the next page purpose: offset_count_start_field ``` Example OpenAPI Specification ``` openapi: 3.1.0 paths: /accounts: get: parameters: - name: my_offset in: query schema: type: integer - name: my_limit in: query schema: type: integer responses: '200': content: application/json: schema: type: object properties: my_data: type: array items: type: object my_total: type: integer my_count: type: integer ``` Page Number Pagination While creating APIs with page-number pagination is often simpler to implement, they can often end up being a source of problems for both you and your users. Consider: - If new resources are simultaneously being created or deleted that fall in the middle of the list based on the sort, you run the risk of missing or double scanning objects as you paginate through results. - Many database engines have poor performance for large offset values, since they often have to scan through the entire table to get to the offset. If you can, we’d recommend cursor-id-based pagination, which is more robust and performant. ``` pagination: - name: my_page_number_page type: page_number request: page: type: integer x-stainless-pagination-property: purpose: page_number_param page_size: type: integer response: data: type: array items: type: object page: type: integer x-stainless-pagination-property: purpose: current_page_number_field last_page: type: integer x-stainless-pagination-property: purpose: total_page_count_field ``` Example OpenAPI Specification ``` openapi: 3.1.0 paths: /accounts: get: description: Example case for page_number pagination parameters: - name: page in: query schema: type: integer - name: page_size in: query schema: type: integer responses: '200': description: OK content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/MyModel' last_page: type: integer description: The last page number page: type: integer description: The page number ``` ### Method level pagination By default, Stainless automatically enables pagination for methods whose names start with `list_`. For example, a method named `list_accounts` will automatically be paginated if it matches one of your configured pagination schemes. stainless.yml ``` resources: accounts: methods: list_accounts: get /accounts # Automatically paginated due to list_ prefix list_transactions: get /transactions # Also automatically paginated ``` If you have a paginated method that doesn’t follow the `list_` naming convention, you can explicitly enable pagination by setting `paginated: true` in the method configuration: stainless.yml ``` resources: accounts: methods: search: # Not using list_ prefix endpoint: get /accounts/search paginated: true # Explicitly enable pagination ``` When you have multiple pagination schemes configured, Stainless will automatically match each endpoint to the appropriate scheme based on the request parameters and response fields. If you need to explicitly specify which pagination scheme a method should use, you can provide the scheme name as a string: stainless.yml ``` pagination: - name: cursor_page type: cursor # ... scheme configuration - name: offset_page type: offset # ... scheme configuration resources: accounts: methods: list_accounts: endpoint: get /accounts paginated: cursor_page # Use the cursor_page scheme specifically list_transactions: endpoint: get /transactions paginated: offset_page # Use the offset_page scheme specifically ``` This is especially useful when: - Multiple pagination schemes could match an endpoint and you want to disambiguate - You want to ensure a method uses a specific pagination scheme and get an error if it doesn’t match - You have methods that don’t follow the `list_` naming convention but are still paginated ### Multiple pagination schemes The `pagination` section accepts multiple pagination schemes. We match each endpoint against the defined pagination schemes by making sure the relevant request parameters/response fields exist for that endpoint and have the correct type. In cases where it’s ambiguous or you want to explicitly assert that a method matches a specific pagination scheme, you can provide `paginated: ` in the method configuration (e.g., `paginated: my_offset_page`), and the generator reports an error if it doesn’t match that specific page. For more details on enabling pagination at the method level, see [Enabling Pagination on Methods](#enabling-pagination-on-methods). See the [config reference for pagination](/docs/reference/config#pagination/index.md) for various examples and edge cases. ### Other Pagination Properties #### Retrieving pagination properties from headers If your API returns pagination values in headers, you can use the `from_header` property to specify the header to read the value from. ``` pagination: - name: page_cursor_from_headers type: cursor request: ... response: my_cursor: type: string nullable: true x-stainless-pagination-property: purpose: next_cursor_field from_header: 'X-My-Cursor' ``` #### Retrieving pagination properties from nested items If your API returns pagination values nested within the response, you can configure Stainless to read them from the nested items. Example response: ``` { "data": { "items": [{}, {}] }, "pagination_object": { "next_page": "next_page_cursor", "previous_page": "previous_page_cursor" } } ``` ``` pagination: - name: page_cursor_nested_items type: cursor_id request: ... response: data: type: object properties: items: x-stainless-pagination-property: purpose: items type: array items: {} pagination_object: type: object properties: next_page: type: string x-stainless-pagination-property: purpose: next_cursor_id_param previous_page: type: string x-stainless-pagination-property: purpose: previous_cursor_id_param ``` #### Top level arrays in response You can use the `is_top_level_array` property to indicate that the response is a top-level array. Example response: ``` [ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ] ``` ``` pagination: - name: top_level_array type: offset request: ... response: items: type: array items: {} x-stainless-pagination-property: is_top_level_array: true ```