REST Lens
← Back to All Rules
Rule 48

Pagination

Category: Query Parameters & Response Design

Description

Collection endpoints that return multiple items must support pagination using a consistent, well-established pagination style to ensure scalability and performance.

What It Checks

This rule validates that GET operations returning arrays (collections) implement pagination using one of four standard styles:

  • Page Number Pagination: Uses page (required) and optionally size or per_page
  • Limit/Offset Pagination: Uses limit/page_size (required) and offset/page_start_index (required)
  • Cursor-Based Pagination: Uses cursor (required)
  • Time-Based Pagination: Uses since and until (required), optionally limit

The rule checks:

  • GET endpoints that return arrays/collections
  • Query parameters and headers for pagination support
  • Consistency of pagination style across the entire API
  • That all required parameters for a detected style are present

Why It Matters

Pagination is essential for scalable APIs and good user experience:

  • Performance: Without pagination, large collections can cause timeouts, excessive memory usage, and slow response times
  • Scalability: Pagination ensures your API can handle growing datasets without degrading performance
  • User Experience: Clients can request manageable chunks of data and navigate through results efficiently
  • Resource Management: Prevents overwhelming clients with massive payloads and reduces bandwidth consumption
  • Consistency: Using a single pagination style across your API makes it predictable and easier to integrate

Examples

Good ✅

Page Number Pagination

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          required: true
          schema:
            type: integer
            minimum: 1
        - name: per_page
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: Paginated list of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  page:
                    type: integer
                  total_pages:
                    type: integer

Limit/Offset Pagination

paths:
  /products:
    get:
      summary: List products
      parameters:
        - name: limit
          in: query
          required: true
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          required: true
          schema:
            type: integer
            minimum: 0
      responses:
        '200':
          description: Paginated products
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Product'
                  total:
                    type: integer

Cursor-Based Pagination

paths:
  /posts:
    get:
      summary: List posts with cursor pagination
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque cursor for pagination
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Paginated posts
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Post'
                  next_cursor:
                    type: string
                    nullable: true
                  prev_cursor:
                    type: string
                    nullable: true

Time-Based Pagination

paths:
  /events:
    get:
      summary: List events within time range
      parameters:
        - name: since
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: until
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        '200':
          description: Time-filtered events

Bad ❌

No Pagination Support

paths:
  /users:
    get:
      summary: List all users
      # Missing pagination parameters entirely
      responses:
        '200':
          description: All users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

Incomplete Pagination Parameters

paths:
  /products:
    get:
      summary: List products
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        # Missing 'offset' parameter - incomplete limit/offset style
      responses:
        '200':
          description: Products

Mixed Pagination Styles

paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
        - name: per_page
          in: query
          schema:
            type: integer
      # Page number style

  /products:
    get:
      summary: List products
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
        - name: offset
          in: query
          schema:
            type: integer
      # Limit/offset style - inconsistent with /users

Custom/Non-Standard Pagination

paths:
  /orders:
    get:
      summary: List orders
      parameters:
        - name: start_index  # Non-standard parameter name
          in: query
          schema:
            type: integer
        - name: count  # Non-standard parameter name
          in: query
          schema:
            type: integer

Configuration

This rule has no configuration options. It validates against the four standard pagination styles:

  1. Page Number: page + optional size/per_page
  2. Limit/Offset: limit/page_size + offset/page_start_index
  3. Cursor: cursor
  4. Time-Based: since + until + optional limit

The rule automatically normalizes parameter names (handles variations like pageNumber, page_number, etc.) and validates parameter types.

Common Violations

  1. Missing pagination entirely: Collection endpoints that return all items without pagination support

  2. Incomplete parameters: Using limit without offset, or page without making it clear how many items per page

  3. Inconsistent styles: Using page number pagination for some endpoints and cursor pagination for others

  4. Non-standard names: Using custom parameter names like start, count, pageNum instead of standard names

  5. Wrong parameter types: Using string type for page or limit instead of integer

How to Fix

1. Choose a Pagination Style

Select one of the four standard styles based on your needs:

  • Page Number: Best for simple use cases with stable data
  • Limit/Offset: Good for SQL-backed APIs with stable data
  • Cursor: Best for real-time feeds, infinite scroll, or frequently changing data
  • Time-Based: Ideal for time-series data or event logs

2. Add Required Parameters

Ensure all required parameters for your chosen style are present:

# Page Number Style
parameters:
  - name: page
    in: query
    required: true
    schema:
      type: integer
  - name: per_page
    in: query
    schema:
      type: integer

3. Use Standard Parameter Names

Stick to recognized names:

  • Page number: page, page_number
  • Page size: size, per_page
  • Limit: limit, page_size
  • Offset: offset, page_start_index
  • Cursor: cursor
  • Time range: since, until

4. Ensure Consistency Across API

Use the same pagination style for all collection endpoints:

# All collection endpoints should use the same style
/users → page + per_page
/products → page + per_page
/orders → page + per_page

5. Update Response Format

Include pagination metadata in responses:

{
  "data": [...],
  "page": 2,
  "per_page": 20,
  "total": 150,
  "total_pages": 8,
  "has_next": true,
  "has_previous": true
}

Or for cursor-based:

{
  "data": [...],
  "next_cursor": "eyJpZCI6MTIzfQ==",
  "prev_cursor": "eyJpZCI6OTh9"
}

Related Rules

  • Rule 1: Plural Collections - Ensures collection endpoints use plural nouns
  • Rule 19: Query Parameters for Filtering - Proper use of query parameters
  • Rule 52: Response Envelope Pattern - Structuring paginated responses