How does OpenAPI describe pagination in API responses?

How does OpenAPI describe pagination in API responses? #

Pagination is one of the most common patterns in REST APIs. Any endpoint that returns a collection of resources — users, orders, products, events — must handle the case where that collection is larger than what should be returned in a single response. OpenAPI doesn’t prescribe a single pagination pattern, but it provides the schema and parameter tooling to describe any pagination approach accurately. This article walks through how to describe the most common pagination styles in OpenAPI documents.

Why Pagination Matters in API Design #

Without pagination, APIs that return large datasets force clients to receive potentially millions of records in a single response — exhausting memory, network bandwidth, and timeout budgets. Pagination divides results into manageable pages, letting clients request them incrementally. From an OpenAPI perspective, pagination affects:

  • Query parameters — the inputs clients use to control which page they receive.
  • Response body schemas — the shape of the paginated envelope, including the data array and metadata like total counts and cursors.
  • Response headers — some APIs communicate pagination metadata via headers rather than the body.
  • Link headers — the Link HTTP header (RFC 5988) is a standard way to communicate next/previous page URLs.

Offset-Based Pagination #

Offset pagination is the simplest approach: clients provide an offset (how many records to skip) and a limit (how many records to return).

Parameters #

parameters:
  - name: limit
    in: query
    description: Maximum number of results to return (1–100)
    schema:
      type: integer
      minimum: 1
      maximum: 100
      default: 20

  - name: offset
    in: query
    description: Number of results to skip before returning results
    schema:
      type: integer
      minimum: 0
      default: 0

Response Schema #

components:
  schemas:
    UserListResponse:
      type: object
      required: [data, total, limit, offset]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        total:
          type: integer
          description: Total number of users matching the query
          example: 1523
        limit:
          type: integer
          description: Maximum number of results per page
          example: 20
        offset:
          type: integer
          description: Number of results skipped
          example: 40

Offset pagination is easy to implement and understand, but it has well-known drawbacks: if records are inserted or deleted between pages, clients may see duplicates or miss records. For most use cases, it remains a pragmatic choice.

Page-Based Pagination #

Page-based pagination uses page (1-indexed page number) and per_page (page size) parameters instead of raw offsets.

Parameters #

parameters:
  - name: page
    in: query
    description: Page number (1-indexed)
    schema:
      type: integer
      minimum: 1
      default: 1

  - name: per_page
    in: query
    description: Number of results per page
    schema:
      type: integer
      minimum: 1
      maximum: 100
      default: 25

Response Schema with Pagination Metadata #

components:
  schemas:
    PaginationMeta:
      type: object
      required: [current_page, per_page, total_pages, total_count]
      properties:
        current_page:
          type: integer
          example: 2
        per_page:
          type: integer
          example: 25
        total_pages:
          type: integer
          example: 61
        total_count:
          type: integer
          example: 1523

Cursor-Based Pagination #

Cursor-based (or keyset) pagination is the most scalable approach for large, frequently-updated datasets. Instead of an offset, the API returns a cursor — an opaque token representing the position in the dataset — and the client passes this cursor to get the next page.

Parameters #

parameters:
  - name: cursor
    in: query
    description: >
      Opaque cursor returned in the previous response. Omit for the first page.      
    schema:
      type: string
      example: "eyJpZCI6MTAwfQ=="

  - name: limit
    in: query
    description: Maximum number of results to return
    schema:
      type: integer
      minimum: 1
      maximum: 100
      default: 20

Response Schema #

components:
  schemas:
    UserCursorResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        next_cursor:
          type: string
          nullable: true
          description: >
            Cursor for the next page. Null if this is the last page.            
          example: "eyJpZCI6MTIwfQ=="
        has_more:
          type: boolean
          description: Whether there are more results after this page
          example: true

The cursor is opaque to the client — it should not be parsed or constructed. This is important to document clearly in the description fields.

Many APIs (including GitHub’s API) use the HTTP Link header (RFC 5988) to communicate pagination URLs rather than embedding them in the response body.

responses:
  "200":
    description: A paginated list of repositories
    headers:
      Link:
        description: >
          Pagination links. May include `next`, `prev`, `first`, and `last` relations.
          Example: `<https://api.example.com/users?page=3>; rel="next",
          <https://api.example.com/users?page=1>; rel="first"`          
        schema:
          type: string
      X-Total-Count:
        description: Total number of results across all pages
        schema:
          type: integer
    content:
      application/json:
        schema:
          type: array
          items:
            $ref: '#/components/schemas/User'

Reusable Pagination Components #

For APIs with multiple paginated endpoints, defining reusable parameters and schemas in components avoids repetition and keeps the document consistent:

components:
  parameters:
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

    CursorParam:
      name: cursor
      in: query
      schema:
        type: string

  schemas:
    CursorPage:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: {}
        next_cursor:
          type: string
          nullable: true
        has_more:
          type: boolean

Individual operations then reference these shared components:

paths:
  /users:
    get:
      parameters:
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        "200":
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - properties:
                      data:
                        items:
                          $ref: '#/components/schemas/User'

Documenting Pagination Behavior in Descriptions #

Schema annotations alone don’t fully describe pagination semantics. Operation and parameter description fields should explain:

  • Which pagination style is used and why.
  • The behavior of cursors — whether they expire, and how long they remain valid.
  • Sort order — pagination is typically only meaningful when results are in a stable, deterministic order.
  • Maximum page size — what happens if the client requests more than the maximum.
  • Empty results — whether an empty data array or a null cursor signals the end of results.

Industry Standards: JSON:API and HAL #

Some teams adopt standardized envelope formats that include pagination:

Both are describable in OpenAPI using their respective schemas in components.

Conclusion #

OpenAPI’s parameter and schema machinery handles every common pagination pattern — offset, page-based, and cursor-based — with precision. The key is consistency: choosing one pagination style across all collection endpoints, defining reusable parameter and response schemas in components, and investing in clear descriptions that explain not just what the parameters do but how pagination works as a whole. Well-documented pagination is one of the most impactful improvements an API team can make for developer experience.


Last updated on April 30, 2026.

This website is not affiliated with the OpenAPI Initiative.