Skip to main content
Adopt

IPA-110: Pagination

APIs often need to provide collections of data, most commonly in the List standard method. Collections can grow arbitrarily — increasing response sizes and lookup times — so they must be paginated.

Guidance

Pagination requirement

Adding pagination to an existing unpaginated endpoint is a backward-incompatible change: clients that expect all results in a single response break. Pagination must be designed in from the start.

  1. API producers must provide pagination for operations that return collections.

    /orders:
    get:
    summary: List orders
    parameters:
    - name: pageNum
    in: query
    schema:
    type: integer
    - name: itemsPerPage
    in: query
    schema:
    type: integer
    responses:
    "200":
    content:
    application/json:
    schema:
    $ref: "#/components/schemas/PaginatedOrderList"
    Why:

    Collection endpoint exposes pageNum and itemsPerPage query parameters and returns a paginated envelope schema.

    /orders:
    get:
    summary: List orders
    responses:
    "200":
    content:
    application/json:
    schema:
    type: array
    items:
    $ref: "#/components/schemas/Order"
    Why:

    Returns an unbounded array with no pagination parameters. Adding pagination later is a breaking change for existing clients.

    1. Locate all GET operations that return arrays or collections.

    2. Verify that each operation exposes at least pageNum and itemsPerPage query parameters.

    3. Confirm the response schema is a Paginated-prefixed envelope containing a results array rather than a top-level array.

  2. List operations should return results within a Paginated-prefixed envelope object.

    components:
    schemas:
    PaginatedOrderList:
    type: object
    properties:
    results:
    type: array
    items:
    $ref: "#/components/schemas/Order"
    links:
    type: array
    items:
    $ref: "#/components/schemas/Link"
    totalCount:
    type: integer
    Why:

    The PaginatedOrderList name signals to consumers that the response is a paginated collection and follows the standard envelope structure.

    components:
    schemas:
    OrderList:
    type: object
    properties:
    results:
    type: array
    items:
    $ref: "#/components/schemas/Order"
    Why:

    OrderList omits the Paginated prefix, making it harder to distinguish paginated collection schemas from other list types at a glance.

    1. Find all GET operations that return collection responses.

    2. For each response schema, verify the schema name starts with Paginated.

    3. Flag any collection response schema that lacks the Paginated prefix.

Request parameters

itemsPerPage

The itemsPerPage parameter lets callers control the page size. It must remain optional so clients that omit it receive a sensible default.

  1. List operations should support an integer itemsPerPage query parameter that controls the maximum number of results returned per page.

    parameters:
    - name: itemsPerPage
    in: query
    required: false
    schema:
    type: integer
    Why:

    itemsPerPage is present as an optional integer query parameter.

    /orders:
    get:
    summary: List orders
    parameters: []
    Why:

    No itemsPerPage parameter is exposed, so callers cannot control page size.

    1. Find all GET operations that return paginated collections.

    2. Confirm each operation includes an itemsPerPage parameter with in: query and type: integer.

    3. Flag operations that lack itemsPerPage.
  2. The itemsPerPage parameter must not be required.

    - name: itemsPerPage
    in: query
    required: false
    schema:
    type: integer
    Why:

    required: false makes itemsPerPage optional; clients that omit it receive the default page size.

    - name: itemsPerPage
    in: query
    required: true
    schema:
    type: integer
    Why:

    Marking itemsPerPage as required forces every caller to specify a page size and breaks clients that don't.

  3. When itemsPerPage is absent or 0, the API must not return an error and must apply a default value of at least 1.

    - name: itemsPerPage
    in: query
    required: false
    schema:
    type: integer
    default: 100
    description: >
    Maximum number of results per page. Defaults to 100. Omitting this parameter
    or passing 0 applies the default page size.
    Why:

    Documents the default value and that 0 maps to the default, so clients know what to expect when they omit the parameter.

    - name: itemsPerPage
    in: query
    required: false
    schema:
    type: integer
    minimum: 1
    description: >
    Maximum number of results per page. Must be between 1 and 500.
    Why:

    Implying a minimum of 1 with no mention of 0 handling suggests that 0 is invalid and may produce a 400 error.

    1. Locate the itemsPerPage parameter definition and read its description and schema constraints.

    2. Verify that the description documents a default value and states that omitting the parameter or passing 0 is valid.

    3. If the implementation is accessible, send a request without itemsPerPage and confirm the response returns results with no error.

  4. When itemsPerPage exceeds the API's maximum permitted page size, the API should silently coerce it down to that maximum rather than returning an error.

    - name: itemsPerPage
    in: query
    required: false
    schema:
    type: integer
    maximum: 500
    description: >
    Maximum number of results per page. Values above 500 are clamped to 500.
    Why:

    Documenting coercion tells clients they won't get errors for oversized requests — the server handles the limit transparently.

    - name: itemsPerPage
    in: query
    required: false
    schema:
    type: integer
    maximum: 500
    description: >
    Maximum number of results per page. Values above 500 return a 400 Bad
    Request.
    Why:

    Returning an error for oversized values instead of coercing forces clients to know the exact maximum ahead of time.

    1. Read the itemsPerPage parameter description for any documented maximum or coercion behavior.

    2. Confirm the description states that values above the maximum are coerced, not rejected with an error.

    3. If the implementation is accessible, send a request with itemsPerPage set above the documented maximum and verify it returns results rather than an error.

pageNum

The pageNum parameter lets callers select a specific page of results. The offset is calculated as (pageNum - 1) × itemsPerPage, so page 1 returns the first page.

  1. List operations should support an integer pageNum query parameter that selects the page of results to return.

    parameters:
    - name: pageNum
    in: query
    required: false
    schema:
    type: integer
    Why:

    pageNum is present as an optional integer query parameter.

    /orders:
    get:
    summary: List orders
    parameters:
    - name: itemsPerPage
    in: query
    schema:
    type: integer
    Why:

    pageNum is absent, so clients cannot navigate to a specific page beyond the first.

    1. Find all GET operations that return paginated collections.

    2. Confirm each operation includes a pageNum parameter with in: query and type: integer.

    3. Flag operations that lack pageNum.
  2. The pageNum parameter must not be required.

    - name: pageNum
    in: query
    required: false
    schema:
    type: integer
    default: 1
    Why:

    required: false with a documented default of 1 allows clients to omit the parameter and receive the first page.

    - name: pageNum
    in: query
    required: true
    schema:
    type: integer
    Why:

    Marking pageNum required forces clients to always specify a page number and breaks clients that expect first-page defaults.

  3. When pageNum is absent or 0, the API must not return an error and must default to page 1. The offset is calculated as (pageNum - 1) × itemsPerPage.

    - name: pageNum
    in: query
    required: false
    schema:
    type: integer
    default: 1
    description: >
    Page number to return, starting at 1. Defaults to 1. Omitting this parameter
    or passing 0 returns the first page. Offset is calculated as (pageNum - 1) ×
    itemsPerPage.
    Why:

    Documents that 0 and absent both map to page 1, and explains the offset formula so clients understand the semantics.

    - name: pageNum
    in: query
    required: false
    schema:
    type: integer
    minimum: 1
    Why:

    A schema minimum of 1 with no description implies 0 is invalid, leaving clients uncertain about default behavior.

    1. Locate the pageNum parameter definition and review its description and schema constraints.

    2. Verify the description states that absent or 0 values default to page 1 without error.

    3. If the implementation is accessible, send a request without pageNum and confirm the response returns first-page results.

includeCount

The optional includeCount parameter lets callers opt out of the totalCount field when they don't need it. Computing total counts for large collections can be expensive, so this parameter must remain optional with a safe default.

  1. The includeCount parameter must not be required.

    - name: includeCount
    in: query
    required: false
    schema:
    type: boolean
    default: true
    Why:

    Optional with a true default means clients receive totalCount unless they explicitly opt out.

    - name: includeCount
    in: query
    required: true
    schema:
    type: boolean
    Why:

    Requiring includeCount forces clients to explicitly opt in or out on every request, adding unnecessary friction.

  2. When includeCount is absent, the API must not return an error and must default to true.

    - name: includeCount
    in: query
    required: false
    schema:
    type: boolean
    default: true
    description: >
    When true, includes totalCount in the response. Defaults to true. Omitting
    this parameter is equivalent to passing true.
    Why:

    Documents the true default explicitly so clients know they receive totalCount by default without specifying the parameter.

    - name: includeCount
    in: query
    required: false
    schema:
    type: boolean
    description: >
    When true, includes totalCount in the response.
    Why:

    Omits what happens when the parameter is absent, leaving clients uncertain whether omitting it returns a count.

    1. Locate the includeCount parameter definition and review its description.

    2. Verify the description states that omitting the parameter defaults to true.

    3. If the implementation is accessible, send a request without includeCount and confirm totalCount is present in the response.

Response structure

Every paginated response must expose a results array. A links array for navigation and a totalCount integer are strongly recommended.

  1. The response schema for a collection operation must define a results property containing an array of the paginated resource.

    PaginatedOrderList:
    type: object
    required:
    - results
    properties:
    results:
    type: array
    items:
    $ref: "#/components/schemas/Order"
    totalCount:
    type: integer
    Why:

    results is a required array property in the response schema, following the standard envelope convention.

    PaginatedOrderList:
    type: object
    properties:
    data:
    type: array
    items:
    $ref: "#/components/schemas/Order"
    Why:

    Using data instead of results diverges from the standard envelope, making client code inconsistent across APIs.

    1. Find all response schemas referenced by GET operations that return collections.

    2. Verify each schema defines a results property typed as an array.

    3. Flag any schema that uses a different property name for the items array.

  2. The response may include an integer totalCount field giving the total number of resources in the backing collection.

  3. When totalCount may be an estimate, the API should explicitly document that in the field description.

    totalCount:
    type: integer
    description: >
    Approximate total number of orders. This value may be an estimate for large
    collections and should not be used for precise pagination boundaries.
    Why:

    The description explicitly calls out that the count is approximate, so clients do not treat it as exact when calculating page boundaries.

    totalCount:
    type: integer
    description: Total number of orders.
    Why:

    No indication that the count may be approximate. Clients that rely on this for pagination math may encounter off-by-one errors or missing results.

    1. Locate totalCount (or equivalently named total-count fields) in all paginated response schemas.

    2. Read the field description and check whether it mentions that the value may be an estimate or approximation.

    3. If the underlying data store uses approximate counts, flag any description that does not disclose this.

Total count considerations

Calculating an exact total count for a large collection can be computationally expensive, especially on document databases where count operations must scan the full collection.

  1. API producers should exercise caution when introducing support for includeCount, since computing totalCount for large collections can be expensive and may affect overall API performance.