# Pagination pattern defined and consistent

- **Pattern:** `ab-000363` (`api-design.contract-quality.pagination-contract`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000363
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000363)

## Why it matters

An API where `/api/users` uses cursor-based pagination and `/api/orders` uses offset/limit forces every consumer to write different pagination logic per endpoint. Worse, an endpoint with no pagination at all returns unbounded result sets that will eventually OOM the server or time out for large collections. iso-25010:2011 compatibility.interoperability is directly impacted: a generic list component cannot be built once and reused, and any SDK wrapping the API must special-case every list endpoint.

## Severity rationale

High because inconsistent pagination patterns prevent generic client-side list handling and unbounded collections create reliability failures at scale.

## Remediation

Define one pagination response shape as a generic type and apply it to every list endpoint:

```ts
interface PaginatedResponse<T> {
  data: T[]
  meta: {
    page: number
    limit: number
    total: number
    totalPages: number
  }
}

// Use identically across all list endpoints:
// GET /api/users?page=1&limit=20
// GET /api/orders?page=2&limit=10
// Same envelope, same parameter names
```

Enforce a maximum page size (100 items) server-side to prevent consumers from accidentally requesting unbounded results. See the `query-param-naming` check for consistent parameter naming across these endpoints.

## Detection

- **ID:** `pagination-contract`
- **Severity:** `high`
- **What to look for:** Examine all list endpoints. Check whether a consistent pagination pattern is defined and used across all of them. Look for: offset-based (`page`/`limit` with `total`), cursor-based (`cursor`/`limit` with `nextCursor`), or keyset pagination. Check that the pagination metadata shape is consistent: `{ data: [...], meta: { page, limit, total } }` or `{ data: [...], pageInfo: { hasNextPage, endCursor } }`. Compare at least 2 list endpoints to verify they use the same pattern.
- **Pass criteria:** List all endpoints that return collections. All list endpoints use the same pagination pattern (same parameter names, same response metadata shape). The pagination approach is appropriate for the data (cursor-based for real-time feeds, offset-based for static lists). A maximum page size is enforced with no more than 100 items per page default.
- **Fail criteria:** List endpoints use different pagination patterns (one uses offset/limit, another uses cursor-based, another has no pagination). Or pagination metadata shapes differ across endpoints.
- **Skip (N/A) when:** Fewer than 2 list endpoints exist, or all collections are known to be small (under 50 items by design).
- **Detail on fail:** Note the inconsistency (e.g., "`GET /api/users` uses cursor-based pagination with `nextCursor`, but `GET /api/orders` uses `page`/`limit` with total count, and `GET /api/comments` has no pagination at all."). Max 500 chars.
- **Cross-reference:** For query parameter naming consistency across these list endpoints, see the `query-param-naming` check in the Naming Conventions category above.
- **Remediation:** Define one pagination pattern and apply it to all list endpoints:

  ```ts
  // Standard pagination response shape:
  interface PaginatedResponse<T> {
    data: T[]
    meta: {
      page: number
      limit: number
      total: number
      totalPages: number
    }
  }

  // Use everywhere:
  // GET /api/users?page=1&limit=20
  // GET /api/orders?page=2&limit=10
  // Same shape, same parameter names
  ```

## External references

- iso-25010 compatibility.interoperability

Taxons: code-quality

HTML version: https://auditbuffet.com/patterns/ab-000363
