Web Function First draft
SPEC EXTENSION

Pagination

A standardized, flexible, and stateless contract for splitting large sets of results into discrete pages that supports multiple pagination strategies.

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, and previous.

  • 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 and previous 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 or previous 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 or previous keys.

  • Upon receiving the response from this new request, the client replaces its stored state with the new page, next, and previous 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"
    }
  }
}

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.