What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose pagination when you design a collection endpoint, not after it ships. Adding it later can change behavior incompatibly, even when the new fields are technically additive. For most changing or large datasets, use an opaque continuation cursor with a stable sort; use offset or skip when clients genuinely need positional jumps and the backend can support deep offsets; use response links when discoverability and a uniform client experience matter most.

What API pagination solves

A collection response must have a bounded size. Pagination divides that collection into pages so clients can control transfer size, latency, memory use, and retry scope. It also gives a service room to evolve its storage without exposing every record at once.

Google’s AIP-158 says collection-returning RPCs should provide pagination from the outset because adding it later is a backwards-incompatible behavioral change. Apply the same principle to REST and other HTTP APIs: define pagination fields, ordering, limits, and end-of-results semantics in the first public contract.

Define the contract before choosing a pattern

Page-size rules

  • Make page_size or limit optional. A missing or zero value selects a documented default.
  • Clamp values above the service maximum to that maximum rather than failing, if that is your documented behavior.
  • Reject negative values.
  • Document that a response may contain fewer records than requested without proving that the collection has ended; filtering, server limits, or concurrent changes can all produce a short page.

Record the default and maximum beside the endpoint definition. For example: “limit defaults to 50, is capped at 200, and negative values return 400.” Do not silently invent a universal limit across APIs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Continuation and termination

Choose one unambiguous terminal signal. Under AIP-158, an empty next_page_token means there are no more results. In SCIM cursor pagination, RFC 9865 requires nextCursor to be omitted only on the final page. Document the exact rule in your schema and examples.

Ordering and consistency

Every page needs a deterministic order. Prefer a unique, indexed key as a tie-breaker (for example, created_at plus id). State whether the traversal is a snapshot, a best-effort view of changing data, or a cursor that encodes a read position. Without this, inserts and deletes can cause duplicates or omissions.

Opaque, context-bound tokens

AIP-158 requires page tokens to be opaque, URL-safe, and not user-parseable. A token should identify where to continue, not grant authorization. Recheck authorization on every request. Bind the token to the original filter, sort, tenant, and other relevant query context; reject or safely invalidate it if those inputs change. RFC 9865 likewise requires subsequent SCIM requests to preserve the original query parameters other than the cursor.

Token storage and expiry are API-specific. AIP-158 offers three days as a rule of thumb for internally stored tokens, not a universal lifetime. Return a documented error when a token expires and tell clients whether they should restart from the first page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Compare the three common patterns

Pattern Request example Strengths Trade-offs
Offset/skip ?limit=50&offset=100 Simple; supports jumping to an approximate position and familiar page-number interfaces. Deep offsets may require more backend work, and inserts or deletes before the offset can shift records between pages. Actual cost depends on the storage engine and query plan.
Cursor/keyset ?limit=50&cursor=opaque-token Good for sequential traversal of changing or large collections; continuation state can encode a stable key and snapshot. Usually no random page-number jumps; requires a stable ordering, token lifecycle, and context validation.
Link-based Link: <https://api.example/items?page=3>; rel="next" The server supplies complete continuation URLs, making clients discoverable and reducing parameter assumptions. Links are endpoint-specific and must be parsed correctly; clients still need clear terminal behavior.

Zalando’s REST pagination guideline recommends preferring cursors over offsets in many designs, but neither that guidance nor AIP-158 establishes a performance result for every database or workload. Choose from your access requirements and measured query plans, not slogans.

Offset pagination: implementation details

Request and response

GET /v1/orders?limit=25&offset=50
{
  "items": [/* 25 orders */],
  "limit": 25,
  "offset": 50,
  "has_more": true
}

Use offset when a user must jump to a position, when the collection is small or relatively static, or when the data store has an efficient strategy for the expected depth. Put a hard maximum on offset or offer an alternative for unbounded histories.

Handling changing rows

If records can be inserted or deleted while a client walks pages, offset is vulnerable to duplicates and gaps. A stable snapshot, a consistent transaction, or a documented “results may shift” policy is necessary. If correctness across a long traversal matters, keyset or cursor pagination is usually a better fit.

Cursor and keyset pagination

Design the cursor

A cursor can reference the last sort key, a server-side snapshot, or both. Serialize and authenticate it on the server, then encode it as an opaque URL-safe value. Do not expose a base64-encoded object that clients are expected to edit. Include enough context to detect a changed filter, tenant, direction, or sort.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
GET /v1/events?limit=100&cursor=eyJ...opaque...
{
  "items": [/* up to 100 events */],
  "next_cursor": "eyJ...opaque..."
}

Return an empty token or omit the field according to the convention you publish. The client must use the server-provided value exactly; it should never calculate a cursor from an undocumented ID.

Forward and backward traversal

For forward keyset pagination, query rows greater than the cursor’s last key, using the same ordering and tie-breaker. For reverse traversal, define whether the API supports a separate previous cursor, an ending_before-style parameter, or only forward iteration. Stripe list methods are a vendor-specific example: they use object IDs with starting_after or ending_before and provide auto-pagination helpers in client libraries. Its documented list default is 10; its search API documents a limit from 1 through 100 with default 10, values that should be checked against the current Stripe reference.

Link-based pagination

GitHub’s REST API places navigation URLs in the HTTP Link response header. A client should parse relations such as rel="next" and rel="last" rather than reconstructing undocumented query parameters. The official GitHub guidance shows this approach, including endpoints where a response page is 30 items while many more issues remain.

Link: <https://api.example.com/items?page=2>; rel="next", <https://api.example.com/items?page=10>; rel="last"

Link headers work well when the server can express the complete continuation URL. Also include a machine-readable body field if your client ecosystem does not reliably expose headers, and state which source wins if they disagree.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Client traversal that does not lose data

  1. Send the first request with an explicit page size that is within the documented maximum.
  2. Process items idempotently. Store a durable checkpoint if a traversal may be interrupted.
  3. Read the server’s next token, cursor, or link. Preserve all original filters, sorting, authorization, and tenant context.
  4. Stop only on the documented terminal signal: an empty AIP-158 token, an omitted RFC 9865 nextCursor, or no rel="next" link.
  5. Retry transient failures with bounded exponential backoff. Reuse the same continuation value; do not advance it until the page has been committed successfully.
  6. Handle token expiry or invalidation by restarting according to the API’s documented recovery policy, and deduplicate by a stable resource ID if the collection can change.

Python example

import requests

url = "https://api.example.com/v1/items"
params = {"limit": 100}
while True:
    response = requests.get(url, params=params, timeout=30)
    response.raise_for_status()
    page = response.json()
    for item in page["items"]:
        consume(item)
    token = page.get("next_page_token")
    if not token:
        break
    params["page_token"] = token

Node.js example

let pageToken;
do {
  const query = new URLSearchParams({ limit: "100" });
  if (pageToken) query.set("page_token", pageToken);
  const res = await fetch(`https://api.example.com/v1/items?${query}`);
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const page = await res.json();
  for (const item of page.items) consume(item);
  pageToken = page.next_page_token || "";
} while (pageToken);

cURL inspection

curl -i -G "https://api.example.com/v1/items" 
  -d limit=100 
  -H "Authorization: Bearer $TOKEN"

Performance, reliability, and cost decisions

  • Index the order: a cursor on created_at,id needs an index that matches the filter and order.
  • Bound page sizes: larger pages reduce request overhead but increase latency, memory, and retry cost.
  • Measure deep access: offset behavior varies by database, indexes, and query shape; benchmark your workload instead of claiming one pattern is universally faster.
  • Protect the service: apply per-client rate limits, maximum traversal duration, and cancellation support.
  • Make retries safe: GET traversal is normally idempotent, but downstream processing may not be. Use resource IDs or an idempotency store.
  • Monitor: track page size, depth, token-invalid errors, empty-page frequency, latency percentiles, and abandoned traversals.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

Repeated or missing records

Cause: offset against a changing collection, or an unstable sort with ties. Fix: use a snapshot or cursor tied to a unique deterministic order, and deduplicate by ID when the contract permits changes.

400 for a page request

Cause: negative size, malformed token, changed filter, or unsupported parameter. Fix: validate against the published schema, preserve the original query, and restart only under the documented invalid-token policy.

Short page mistaken for completion

Cause: the client assumes fewer records than requested means “last page.” Fix: follow the explicit token, cursor, or link termination rule.

401 or 403 on a later page

Cause: authorization changed or a client treated a continuation token as permission. Fix: refresh credentials if appropriate and enforce authorization independently on every request.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Expired cursor

Cause: server-side state or a signed token passed its lifetime. Fix: surface the API’s expiry error, restart from the beginning or a supported checkpoint, and avoid promising a lifetime the service does not guarantee.

Pagination is not search-engine page navigation

API continuation tokens are for clients traversing data. Web-page SEO has a separate requirement: Google Search Central explains that crawlers generally discover pages through URLs in anchor href attributes and generally do not click buttons or trigger user actions that load more content. For crawlable HTML, provide sequential, valid links and correct URL handling as described in Google’s pagination guidance. Do not assume an API cursor alone makes a “load more” interface indexable.

Or skip the browser setup

For developers who need screenshots of paginated pages rather than API records, ScreenshotNeo returns PNG, JPEG, WebP, or PDF from one request. It accepts the cookie or consent banner as a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Example (see the ScreenshotNeo API documentation):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Sign up for the free ScreenshotNeo plan.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Should an API return total page counts?

Only when the count is useful and affordable to compute. A count can become stale while pages are being read; a continuation signal is the reliable control for traversal.

Can I let clients decode a cursor for debugging?

No. Keep production cursors opaque as required by AIP-158. Log server-side correlation data or provide a separate diagnostic facility instead.

Is a 204 response the right last page?

Usually not for a paginated collection. Keep the collection response shape stable and use the documented empty-token, omitted-cursor, or missing-link signal.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.