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.
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
pageNumanditemsPerPagequery 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.
Locate all GET operations that return arrays or collections.
Verify that each operation exposes at least
pageNumanditemsPerPagequery parameters.Confirm the response schema is a
Paginated-prefixed envelope containing aresultsarray rather than a top-level array.
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: integerWhy:The
PaginatedOrderListname 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:OrderListomits thePaginatedprefix, making it harder to distinguish paginated collection schemas from other list types at a glance.Find all GET operations that return collection responses.
For each response schema, verify the schema name starts with
Paginated.Flag any collection response schema that lacks the
Paginatedprefix.
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.
List operations should support an integer
itemsPerPagequery parameter that controls the maximum number of results returned per page.parameters:
- name: itemsPerPage
in: query
required: false
schema:
type: integerWhy:itemsPerPageis present as an optional integer query parameter./orders:
get:
summary: List orders
parameters: []Why:No
itemsPerPageparameter is exposed, so callers cannot control page size.Find all GET operations that return paginated collections.
Confirm each operation includes an
itemsPerPageparameter within: queryandtype: integer.- Flag operations that lack
itemsPerPage.
The
itemsPerPageparameter must not be required.- name: itemsPerPage
in: query
required: false
schema:
type: integerWhy:required: falsemakesitemsPerPageoptional; clients that omit it receive the default page size.- name: itemsPerPage
in: query
required: true
schema:
type: integerWhy:Marking
itemsPerPageas required forces every caller to specify a page size and breaks clients that don't.When
itemsPerPageis absent or0, the API must not return an error and must apply a default value of at least1.- 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
0maps 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
1with no mention of0handling suggests that0is invalid and may produce a 400 error.Locate the
itemsPerPageparameter definition and read its description and schema constraints.Verify that the description documents a default value and states that omitting the parameter or passing
0is valid.If the implementation is accessible, send a request without
itemsPerPageand confirm the response returns results with no error.
When
itemsPerPageexceeds 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.
Read the
itemsPerPageparameter description for any documented maximum or coercion behavior.Confirm the description states that values above the maximum are coerced, not rejected with an error.
If the implementation is accessible, send a request with
itemsPerPageset 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.
List operations should support an integer
pageNumquery parameter that selects the page of results to return.parameters:
- name: pageNum
in: query
required: false
schema:
type: integerWhy:pageNumis present as an optional integer query parameter./orders:
get:
summary: List orders
parameters:
- name: itemsPerPage
in: query
schema:
type: integerWhy:pageNumis absent, so clients cannot navigate to a specific page beyond the first.Find all GET operations that return paginated collections.
Confirm each operation includes a
pageNumparameter within: queryandtype: integer.- Flag operations that lack
pageNum.
The
pageNumparameter must not be required.- name: pageNum
in: query
required: false
schema:
type: integer
default: 1Why:required: falsewith a documented default of1allows clients to omit the parameter and receive the first page.- name: pageNum
in: query
required: true
schema:
type: integerWhy:Marking
pageNumrequired forces clients to always specify a page number and breaks clients that expect first-page defaults.When
pageNumis absent or0, the API must not return an error and must default to page1. 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
0and 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: 1Why:A schema minimum of
1with no description implies0is invalid, leaving clients uncertain about default behavior.Locate the
pageNumparameter definition and review its description and schema constraints.Verify the description states that absent or
0values default to page1without error.If the implementation is accessible, send a request without
pageNumand 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.
The
includeCountparameter must not be required.- name: includeCount
in: query
required: false
schema:
type: boolean
default: trueWhy:Optional with a
truedefault means clients receivetotalCountunless they explicitly opt out.- name: includeCount
in: query
required: true
schema:
type: booleanWhy:Requiring
includeCountforces clients to explicitly opt in or out on every request, adding unnecessary friction.When
includeCountis absent, the API must not return an error and must default totrue.- 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
truedefault explicitly so clients know they receivetotalCountby 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.
Locate the
includeCountparameter definition and review its description.Verify the description states that omitting the parameter defaults to
true.If the implementation is accessible, send a request without
includeCountand confirmtotalCountis 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.
The response schema for a collection operation must define a
resultsproperty containing an array of the paginated resource.PaginatedOrderList:
type: object
required:
- results
properties:
results:
type: array
items:
$ref: "#/components/schemas/Order"
totalCount:
type: integerWhy:resultsis 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
datainstead ofresultsdiverges from the standard envelope, making client code inconsistent across APIs.Find all response schemas referenced by GET operations that return collections.
Verify each schema defines a
resultsproperty typed as an array.Flag any schema that uses a different property name for the items array.
The response schema should define a
linksproperty containing an array of navigation links to the next and previous pages.PaginatedOrderList:
type: object
properties:
results:
type: array
items:
$ref: "#/components/schemas/Order"
links:
type: array
items:
$ref: "#/components/schemas/Link"Why:The
linksarray follows the standard envelope structure and lets clients navigate pages without constructing URLs manually.PaginatedOrderList:
type: object
properties:
results:
type: array
items:
$ref: "#/components/schemas/Order"
nextPageUrl:
type: string
prevPageUrl:
type: stringWhy:Separate
nextPageUrl/prevPageUrlscalar fields diverge from the standardlinksarray and require clients to handle a non-standard structure.Find all paginated response schemas (those with a
resultsarray).Verify each schema defines a
linksproperty typed as an array.Flag any paginated schema that lacks a
linksarray.
A next-page link must only be included in the
linksarray when a next page is available or when the service cannot determine whether the end of the collection has been reached.# Response for the final page of results
links: []Why:No next-page link is emitted on the last page, so clients know they have reached the end of the collection without making an extra empty request.
# Response for the final page of results
links:
- href: "/orders?pageNum=6&itemsPerPage=25"
rel: nextWhy:Including a next-page link on the last page causes clients to make a redundant request only to receive an empty
resultsarray.Review the API documentation or implementation for how
linksis populated on the last page of a collection.Confirm that a
nextlink is absent when no further pages exist.If the implementation is accessible, request the final page and verify the response contains no
nextlink.
Depends onA previous-page link must be included in the
linksarray when a previous page exists.# Response for pageNum=2
links:
- href: "/orders?pageNum=1&itemsPerPage=25"
rel: prev
- href: "/orders?pageNum=3&itemsPerPage=25"
rel: nextWhy:Both
prevandnextlinks are present on page 2, giving clients bidirectional navigation without constructing URLs.# Response for pageNum=2
links:
- href: "/orders?pageNum=3&itemsPerPage=25"
rel: nextWhy:Omitting the
prevlink on page 2 forces clients to construct the previous-page URL themselves rather than following a standard link.Review the API documentation for how
linksis populated on non-first pages.Confirm that a
prevlink is present wheneverpageNumis greater than1.If the implementation is accessible, request page 2 or later and verify the response contains a
prevlink.
Depends onThe response may include an integer
totalCountfield giving the total number of resources in the backing collection.When
totalCountmay 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.
Locate
totalCount(or equivalently named total-count fields) in all paginated response schemas.Read the field description and check whether it mentions that the value may be an estimate or approximation.
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.
API producers should exercise caution when introducing support for
includeCount, since computingtotalCountfor large collections can be expensive and may affect overall API performance.