Real-world examples of best practices for pagination in APIs

If you build or maintain APIs, you’ve probably learned the hard way that pagination is where performance, usability, and correctness all collide. Instead of yet another abstract theory piece, this guide walks through real-world examples of best practices for pagination in APIs, showing what actually works in production. We’ll look at how teams at companies like GitHub, Stripe, and Twitter design their pagination, why cursor-based approaches are beating old-school page/limit patterns, and how to avoid the classic footguns that break clients. You’ll see examples of offset pagination, cursor pagination, keyset pagination, and hybrid designs, along with concrete request and response patterns you can adapt. We’ll also cover how to document pagination clearly, how to keep it stable across versions, and how to test it under load. By the end, you’ll have a set of practical, battle-tested patterns and examples of best practices for pagination in APIs that you can apply to REST, GraphQL, or even gRPC-backed systems.
Written by
Jamie
Published
Updated

Examples of best practices for pagination in APIs in the real world

When people ask for examples of best practices for pagination in APIs, I point them to real production systems. You learn more from a single well-designed API than from ten abstract blog posts.

Let’s start with a few concrete patterns you can borrow immediately.

Example of simple offset pagination (good for admin tools)

The classic pattern: ?page= and ?per_page=. It’s not glamorous, but it’s still a solid option for internal tools and low-volume data.

Example request

GET /v1/users?page=3&per_page=50

Example response

{
  "data": [ /* 50 users */ ],
  "pagination": {
    "page": 3,
    "per_page": 50,
    "total_pages": 20,
    "total_items": 1000
  }
}

This is one of the simplest examples of best practices for pagination in APIs when you don’t expect massive datasets or constantly changing results. The key: always return enough metadata for clients to build UI controls without guessing. Admin dashboards, back-office tools, and analytics pages are classic use cases where this style shines.

Cursor-based pagination: the modern default

Offset pagination falls apart when datasets get big or change quickly. That’s where cursor-based pagination comes in.

GitHub’s API is one of the best examples of cursor-based pagination done right. Instead of page=3, you pass a cursor that represents a specific position in the dataset.

Example request (GraphQL-style cursor)

GET /v1/issues?first=50&after=Y3Vyc29yOnYyOpHOAAABb2k

Example response

{
  "data": [ /* 50 issues */ ],
  "pageInfo": {
    "hasNextPage": true,
    "hasPreviousPage": false,
    "startCursor": "Y3Vyc29yOnYyOpHOAAABb2k",
    "endCursor": "Y3Vyc29yOnYyOpHOAAABb2w"
  }
}

Clients don’t care what the cursor means; they just pass it back. This is one of the best examples of best practices for pagination in APIs when you need:

  • Stable results while new data is being inserted
  • High performance on large tables
  • Predictable ordering based on a key (like created_at or id)

Keyset pagination: the high-performance workhorse

Under the hood, many cursor-based systems are doing keyset pagination: instead of OFFSET 1000, the query says “give me items where created_at < last_seen_created_at ordered by created_at DESC.”

Example request

GET /v1/transactions?limit=100&before=2025-11-01T00:00:00Z

Example response

{
  "data": [ /* 100 transactions */ ],
  "next": {
    "before": "2025-10-31T12:34:56Z"
  }
}

This pattern is a strong example of best practices for pagination in APIs that deal with financial records, logs, or timelines, where you typically page backward in time. It scales well because the database can use an index on created_at instead of scanning and skipping rows.

Twitter-style timeline pagination

Twitter’s older REST API used max_id and since_id parameters to page through tweets. This is a classic example of keyset-style pagination exposed directly in the query string.

Example request

GET /1.1/statuses/user_timeline.json?count=50&max_id=1777777777777777777

The server returns tweets with IDs less than or equal to max_id, and the client uses the smallest ID from the response as the next max_id.

This is a practical example of best practices for pagination in APIs where:

  • Records have a monotonic ID
  • You care about “load older items” behavior
  • You want to avoid the cost of large offsets

Stripe-style pagination with opaque cursors

Stripe’s API is a favorite reference for many teams. Its pagination is cursor-based, but the cursor is just an opaque starting_after or ending_before value.

Example request

GET /v1/customers?limit=10&starting_after=cus_O6JqYfZ5rW0zAb

Example response

{
  "object": "list",
  "data": [ /* 10 customers */ ],
  "has_more": true
}

Clients pass the ID of the last seen object as starting_after. Stripe keeps the mental model simple while still using efficient queries internally. If you’re looking for real examples of best practices for pagination in APIs that balance simplicity and performance, Stripe is near the top of the list.

Combining pagination with filtering and sorting

A lot of pagination bugs come from the interaction between filters, sorting, and cursors. Here’s an example of a safer pattern:

GET /v1/orders?status=shipped&sort=-shipped_at&limit=50&cursor=eyJzaGlwcGVkX2F0IjoiMjAyNS0xMS0xMFQxMDozMDowMFoifQ

The server encodes the current filter and sort criteria into the cursor, so if the client changes filters, the cursor becomes invalid and is ignored. The response can include a flag like "cursor_valid": false when that happens.

This is one of the best examples of best practices for pagination in APIs that need advanced querying:

  • Always tie the cursor to the exact query conditions
  • Fail predictably when clients change filters mid-pagination
  • Document that cursors are not reusable across different queries

Including pagination hints in HTTP headers

Some teams prefer keeping the body focused on data and putting pagination hints in headers. GitHub’s REST API is a well-known example.

Example response headers

Link: <https://api.example.com/v1/repos?page=2&per_page=50>; rel="next",
      <https://api.example.com/v1/repos?page=20&per_page=50>; rel="last"
X-Total-Count: 1000

The body just contains the array of items, while headers handle navigation. For APIs consumed mostly by servers or SDKs, this is a clean example of best practices for pagination in APIs that keeps the payload tidy and aligns with HTTP standards.

Authoritative HTTP guidance on this pattern can be found in the RFCs linked from the Internet Engineering Task Force (IETF) at ietf.org, which is a good reference when you want pagination to play nicely with standard HTTP semantics.

Designing stable, predictable pagination contracts

The biggest pagination mistakes happen when API contracts change silently. A few patterns consistently show up in the best examples of pagination design:

  • Never change default sort order without a version bump
  • Always document the default limit and maximum limit
  • Treat cursors as opaque; don’t promise a specific internal format

For public APIs, publishing clear versioning and deprecation policies is as important as the pagination mechanism itself. While not pagination-specific, the general API design guidance from organizations like the U.S. Digital Service at digital.gov offers helpful patterns for stable contracts.

Handling empty pages, race conditions, and edge cases

Real examples of best practices for pagination in APIs always talk about edge cases, because that’s where clients break.

Some patterns that work well in production:

  • If a client requests beyond the last page, return an empty data array and has_next_page: false instead of an error.
  • When underlying data changes (items inserted or deleted), cursor-based pagination should still move forward consistently, even if clients see small overlaps or gaps.
  • For offset-based pagination, warn in your docs that results are not stable when data is changing quickly.

You can validate your behavior with automated tests that simulate concurrent inserts and deletes while a client paginates. This is an unglamorous but real example of best practices for pagination in APIs that separates sturdy systems from fragile ones.

A few trends have become very clear in modern API design:

  • Cursor-based and keyset pagination are increasingly the default for public APIs with large datasets.
  • Mobile and SPA frontends expect infinite scroll behavior, which works best with cursors and has_next_page style flags.
  • GraphQL schemas tend to standardize around edges/node and pageInfo structures, inspired by the Relay spec.

If you’re designing a new API in 2024 or 2025, the best examples of pagination in modern stacks almost always:

  • Use a cursor or keyset approach for user-facing data
  • Reserve offset pagination for internal admin use cases
  • Expose clear, discoverable pagination metadata

For broader context on data access patterns and performance, university database courses and materials (for example, those published by institutions like MIT OpenCourseWare) offer a solid grounding in why keyset queries scale better than offsets.

Documentation patterns that actually help clients

A beautifully designed pagination mechanism is useless if clients can’t figure out how to use it. Strong documentation often includes:

  • A short narrative example of listing items and following the next cursor
  • Copy-pastable code snippets in at least two languages
  • A clear table of query parameters (limit, cursor, sort, etc.)
  • A description of maximum limits and rate limit interactions

For instance, your docs might include a Python example like:

cursor = None
while True:
    params = {"limit": 100}
    if cursor:
        params["cursor"] = cursor

    resp = requests.get("https://api.example.com/v1/events", params=params)
    body = resp.json()

    for event in body["data"]:
        process(event)

    cursor = body.get("next", {}).get("cursor")
    if not cursor:
        break

This kind of snippet is a concrete example of best practices for pagination in APIs documentation: it shows the happy path and the termination condition in a way that developers can adapt immediately.

FAQ: examples of pagination patterns teams actually use

What are some real examples of best practices for pagination in APIs?

Real examples include:

  • GitHub’s cursor-based pagination with pageInfo and startCursor / endCursor
  • Stripe’s starting_after / ending_before cursors with a simple has_more flag
  • Twitter’s timeline-style max_id and since_id parameters
  • Offset-based pagination with page and per_page plus total_items metadata in many admin UIs

All of these are examples of best practices for pagination in APIs because they combine predictable ordering, clear metadata, and stable contracts.

Can you give an example of when offset pagination is still a good choice?

Offset pagination is still a good choice for low-volume, mostly static data: think configuration lists, small reference tables, or internal admin dashboards. A typical example of this is an internal CRM’s “All users” page that rarely exceeds a few thousand records and doesn’t change rapidly.

How many items should I return per page?

There is no single best number, but many APIs use defaults between 20 and 100 items per page, with a documented maximum (often 100 or 250). The best examples of pagination design document both the default and maximum, so clients can tune behavior without trial and error.

How do I keep pagination fast on very large datasets?

Use keyset or cursor-based pagination tied to indexed columns, avoid large offsets, and cache common queries where possible. Many database courses and performance guides from universities and research groups (for example, resources linked from nsf.gov) discuss why index-friendly queries matter for scalability.

Should cursors be readable or opaque?

Most mature APIs treat cursors as opaque tokens. That gives you freedom to change internal implementation without breaking clients. This approach shows up repeatedly in the best examples of best practices for pagination in APIs, because it preserves flexibility while keeping the client contract simple.

Explore More Best Practices for API Design

Discover more examples and insights in this category.

View All Best Practices for API Design