Pagination
APIs often need to provide collections of data. However, collections can often be arbitrarily sized and also often grow over time, increasing lookup time as well as the size of the responses being sent over the wire. Therefore, it is important that collections be paginated.
Guidance
Section titled “Guidance”- Endpoints returning collections of data must be paginated.
- APIs should prefer cursor-based pagination to offset-based pagination. See Choosing a pagination strategy.
- Query parameters for pagination must follow the guidelines in AEP-106.
- The array of resources must be named
resultsand contain resources with no additional wrapping.
Choosing a pagination strategy
Section titled “Choosing a pagination strategy”This decision is not purely a UX decision, nor is it purely a technical one. UX requirements are a valid and important input, but must be weighed against dataset characteristics and performance rather than treated as the deciding factor in isolation. A great UX with offset pagination is of no use if the underlying dataset cannot support it reliably. Before choosing offset, teams must evaluate both user experience and technical limitations.
Use cursor-based pagination when:
- The dataset is large, unbounded, or expected to grow significantly over time.
- The underlying database is NoSQL or sharded. These databases are often designed around cursor-style access and may not support offset scanning at all or do so at significant performance cost.
- The data changes frequently, as offset pagination may produce duplicates or skip items between page requests.
- Sequential traversal (next/previous) is enough for the use case.
Use offset-based pagination when:
- The dataset is small, bounded, and unlikely to grow significantly.
- The underlying database is relational and the paginated query can be efficiently indexed.
- The data is stable and unlikely to change between page requests.
- Users must be able to jump to an arbitrary page, this is a validated user need and not just an assumed one.
- UX requirements genuinely call for it, and the above technical factors do not contradict it.
Cursor-based pagination
Section titled “Cursor-based pagination”Cursor-based pagination uses a pageToken which is an opaque pointer to a page
that must never be inspected or constructed by clients. It encodes the page
position (i.e., the unique identifier of the first or last page element), the
pagination direction, and the applied query filters to safely recreate the
collection.
When implementing cursor-based pagination:
- Request messages for collections should define an integer
pageSizequery parameter, allowing users to specify the maximum number of results to return.- The
pageSizefield must not be required. - If the request does not specify
pageSize, the API must choose an appropriate default. - The API may return fewer results than the number requested (including zero results), even if not at the end of the collection.
- The
- Request schemas for collections must define a
stringpageTokenquery parameter, allowing users to advance to the next page in the collection.- The
pageTokenfield must not be required. - If the user changes the
pageSizein a request for subsequent pages, the service must honor the new page size. - The user is expected to keep all other arguments to the method the same; if
any arguments are different, the API should return a
400 Bad Requesterror.
- The
- Response messages for collections must define a
stringnextPageTokenfield, providing the user with a page token that may be used to retrieve the next page.- The field containing pagination results must be an array containing a list of resources constituting a single page of results.
- If the end of the collection has been reached, the
nextPageTokenfield must be empty. This is the only way to communicate “end-of-collection” to users. - If the end of the collection has not been reached, the API must
provide a
nextPageToken.
- Responses should avoid including a total result count, since calculating it is a costly operation usually not required by clients.
Example:
GET /v1/publishers/123/books?pageSize=50&pageToken=abc123xyzresponds with:
{ "results": [ { "id": "456", "title": "Les Misérables", "author": "Victor Hugo" } // ... 49 more books ], "nextPageToken": "def456uvw"}Page Token Opacity
Section titled “Page Token Opacity”Page tokens provided by APIs must be opaque (but URL-safe) strings, and must not be user-parseable. This is because if users are able to deconstruct these, they will do so. Tokens must never be inspected or constructed by clients. Therefore, tokens must be encoded (encrypted) in a non-human-readable form.
Page tokens must be limited to providing an indication of where to continue the pagination process only. They must not provide any form of authorization to the underlying resources, and authorization must be performed on the request as with any other regardless of the presence of a page token.
Page Token Expiration
Section titled “Page Token Expiration”Some APIs store page tokens in a database internally. In this situation, APIs should expire page tokens a reasonable time after they have been sent, in order not to needlessly store large amounts of data that is unlikely to be used. It is not necessary to document this behavior.
Offset-based pagination
Section titled “Offset-based pagination”When implementing offset-based pagination:
- Request schemas for collections must define an integer
pageNumberquery parameter, allowing users to specify which page of results to return.- The
pageNumberfield must not be required and must default to1.
- The
- Request schemas for collections must define an integer
pageSizequery parameter, allowing users to specify the maximum number of results to return.- The
pageSizefield must not be required. - If the request does not specify
pageSize, the API must choose an appropriate default.
- The
- Response messages may include a
totalfield indicating the total number of results available, though this should be avoided if the calculation is expensive. - The API may return fewer results than the number requested (including zero results), even if not at the end of the collection.
Example:
GET /v1/publishers/123/books?pageSize=50&pageNumber=2responds with:
{ "results": [ { "id": "456", "title": "Les Misérables", "author": "Victor Hugo" } // ... 49 more books ], "total": 342}Small Collections
Section titled “Small Collections”All collections must return a paginated response structure, regardless of
size. For collections that will never meaningfully benefit from pagination,
endpoints may satisfy this requirement by returning all results in a single
response with an empty or absent nextPageToken, without implementing actual
pagination logic. In other words, just wrap the results in the pagination
envelope without actually implementing pagination.
However, if there is any reasonable chance the collection grows beyond a small size (typically a few hundred to low thousands of items), endpoints should implement true pagination from the start. Retrofitting pagination onto a collection that clients already consume as a single page is a breaking change.
Interface Definitions
Section titled “Interface Definitions”Cursor Pagination
Section titled “Cursor Pagination”paths: /publishers/{publisherId}/books: get: summary: List books for a publisher operationId: listBooks parameters: - name: publisherId in: path required: true schema: type: string - name: pageSize in: query required: false schema: type: integer default: 20 example: 50 description: > The maximum number of results to return. The API will choose an appropriate default if not specified. The API may return fewer results than requested, even if not at the end of the collection. - name: pageToken in: query required: false schema: type: string example: abc123xyz description: > An opaque, URL-safe token used to advance to the next page of results. This value is obtained from the `nextPageToken` field of a previous response. Must not be inspected or constructed by clients. responses: '200': description: A paginated list of books. content: application/json: schema: type: object properties: results: type: array items: $ref: '#/components/schemas/Book' description: | The list of books for the current page. nextPageToken: type: string description: > An opaque token used to retrieve the next page of results. If absent or empty, the end of the collection has been reached. Pass this value as the `pageToken` query parameter in a subsequent request to retrieve the next page.Offset Pagination
Section titled “Offset Pagination”paths: /publishers/{publisherId}/books: get: summary: List books for a publisher operationId: listBooks parameters: - name: publisherId in: path required: true schema: type: string - name: pageSize in: query required: false schema: type: integer default: 20 example: 50 description: > The maximum number of results to return. The API will choose an appropriate default if not specified. The API may return fewer results than requested, even if not at the end of the collection. - name: pageNumber in: query required: false schema: type: integer default: 1 example: 1 description: > The page number to return, 1-indexed. Defaults to 1 if not specified. responses: '200': description: A paginated list of books. content: application/json: schema: type: object properties: results: type: array items: $ref: '#/components/schemas/Book' description: | The list of books for the current page. total: type: integer description: > The total number of results available. This field is optional and may not always be present, as calculating it can be expensive.Rationale
Section titled “Rationale”Preferring cursor over offset
Section titled “Preferring cursor over offset”Cursor-based pagination is generally better and more efficient than offset-based pagination. Cursor-based pagination maintains consistent performance regardless of dataset size, while offset-based pagination degrades as offsets increase. Many NoSQL databases are optimized for cursor-based access patterns. Cursor-based pagination provides consistent results even when data changes between requests, preventing items from being skipped or duplicated. It is also better suited for collections that are frequently updated. These advantages make cursor-based pagination the preferred approach for most use cases.
Changelog
Section titled “Changelog”- 2026-02-23: Change guidance to allow both offset and cursor. Remove the token offset option. Add guidance on when to choose each method.
- 2026-01-30: Enforce
camelCase, notsnake_casefor query parameters - 2025-12-15: Added guidance on token-based offset pagination for new APIs, small collection handling, and clarified that new APIs must use cursor-based or token-based offset pagination only.
- 2025-12-10: Initial creation, adapted from Google AIP-158 and aep.dev AEP-158.