Introduction
This extension defines how an endpoint signals that it returns a paginated response, the structure of that response, and the logic clients must implement to navigate between pages. The design is intentionally strategy-agnostic, allowing the server to implement cursor, offset, or other pagination methods without changing the client-side contract.
Activation
By including the paginated
flag in the package definition, the server signals that it supports pagination. Clients should check for this flag to know when to apply the response parsing and navigation logic described in this document.
Request structure
The initial request to a paginated endpoint follows the standard endpoint specification. The request body may contain parameters for filtering, sorting, or controlling the number of results per page. An empty JSON object is valid for an initial request with default parameters.
Subsequent requests for different pages are constructed from the response, as described in a later section.
Response structure
When a request is successful, the response body from a paginated endpoint must be a JSON object containing the following three keys:
previous
: An object representing the request for the previous page, or null. If a previous page exists, the value will be a JSON object. This object represents the complete and exact request body that must be sent to retrieve the previous page by calling the same endpoint. If there is no previous page, the value will be null.page
: A JSON array containing the items for the current page. Each element in the array can be any JSON value. If there are no results for the current page, this will be an empty array.next
: An object representing the request for the next page, or null. If a next page exists, the value will be a JSON object. This object represents the complete and exact request body that must be sent to retrieve the next page by calling the same endpoint. If there is no next page, the value will be null.
Implementation logic
To properly interact with a paginated endpoint, a client must follow this explicit logic. The client should not attempt to construct, interpret, or modify the next
and previous
objects, and they must be treated as opaque tokens.
The client sends an initial request to the endpoint. The body of this request contains any desired filters or is an empty object for the default first page.
Upon receiving the response, the client should store the values of all three keys:
page
,next
, andprevious
.The data from the
page
array should be displayed to the user.The client's UI state for navigation controls (e.g., "Next" and "Previous" buttons) should be determined based on the
next
andprevious
objects.
To navigate to either the previous or next page, the client should follow these steps:
First, the client must check the stored value for the
next
orprevious
key from the most recent response.If the value is a JSON object, it signifies that a page is available and the corresponding "Next" or "Previous" buttons in the UI should be enabled. Similarly if the value is null, there is no page and the corresponding buttons should be disabled or hidden.
Once the user navigates, via one of the provided controls, the client invokes the same endpoint again. The body of this new request must be the complete, unmodified JSON object that was stored from the
next
orprevious
keys.Upon receiving the response from this new request, the client replaces its stored state with the new
page
,next
, andprevious
values and updates the UI accordingly.
Example interaction flow
This example illustrates the contract in action.
Initial request
POST /list-people
Content-Type: application/json;
Accept: application/json;
{
"filters": {
"first_name": "Joe"
}
}
Response
{
"previous": null,
"page": [
{ "person_id": "person_1LQBe84aAB", "first_name": "Joe", ... },
{ "person_id": "person_1M7CkWMdd3", "first_name": "Joe", ... },
{ "person_id": "person_d4N9Jk7IMs", "first_name": "Joe", ... },
{ "person_id": "person_dD0lzg5W4U", "first_name": "Joe", ... },
{ "person_id": "person_ghS2m1FgPI", "first_name": "Joe", ... },
{ "person_id": "person_IkY5O9KDrz", "first_name": "Joe", ... },
{ "person_id": "person_Jm21dr7Hd3", "first_name": "Joe", ... },
{ "person_id": "person_kH4BfUZ5jY", "first_name": "Joe", ... },
{ "person_id": "person_lNwWX2h5yi", "first_name": "Joe", ... },
{ "person_id": "person_MAhMIOhm1o", "first_name": "Joe", ... }
],
"next": {
"after": "person_MAhMIOhm1o",
"per_page": 10,
"filters": {
"first_name": "Joe"
}
}
}
POST /list-people
Content-Type: application/json;
Accept: application/json;
{
"filters": {
"first_name": "Joe"
}
}
{
"previous": null,
"page": [
{ "person_id": "person_1LQBe84aAB", "first_name": "Joe", ... },
{ "person_id": "person_1M7CkWMdd3", "first_name": "Joe", ... },
{ "person_id": "person_d4N9Jk7IMs", "first_name": "Joe", ... },
{ "person_id": "person_dD0lzg5W4U", "first_name": "Joe", ... },
{ "person_id": "person_ghS2m1FgPI", "first_name": "Joe", ... },
{ "person_id": "person_IkY5O9KDrz", "first_name": "Joe", ... },
{ "person_id": "person_Jm21dr7Hd3", "first_name": "Joe", ... },
{ "person_id": "person_kH4BfUZ5jY", "first_name": "Joe", ... },
{ "person_id": "person_lNwWX2h5yi", "first_name": "Joe", ... },
{ "person_id": "person_MAhMIOhm1o", "first_name": "Joe", ... }
],
"next": {
"after": "person_MAhMIOhm1o",
"per_page": 10,
"filters": {
"first_name": "Joe"
}
}
}
Based on this response the client displays the list of objects. "Previous" is disabled, and "Next" is enabled.
Next request
The client then uses the next object from the previous response as the body:
POST /list-people
Content-Type: application/json;
Accept: application/json;
{
"after": "person_MAhMIOhm1o",
"per_page": 10,
"filters": {
"first_name": "Joe"
}
}
And so on.