Agent infrastructure

API strategy

API pagination for AI agents: why cursor beats offset when the caller isn't human

API pagination for AI agents is where a lot of working tools quietly fail. Why cursor beats offset, how to shape tool responses, and where the loop breaks.

8 minute read
Decorative imagery showcasing Pontil's brand

Pagination looks like a solved problem. Pick a scheme, document it, ship it. Human developers read the docs, wire up a loop, move on.

Agents don't read the docs. They read tool descriptions, they read one response at a time, and they have a context window that fills up faster than most teams expect. Pagination is where a lot of otherwise-working tools quietly fall over — the first call succeeds, the model tries to fetch page two, and something goes wrong that never showed up in the eval.

Our view: for agent-facing tools, cursor pagination is the default and offset pagination is a legacy compromise. But the scheme is only half the answer. The rest is how the tool response is shaped, how the model is told to continue, and how the runtime handles the loop. This piece walks through where each layer breaks, what agent-friendly pagination actually looks like, and how to design past the failure modes we see most often.

Why the pagination scheme matters more for agents than for humans

A human developer paginates once. They write the loop, test it, and it stays written. If the API returns a stale cursor or an off-by-one offset on page seventeen, they debug it, patch it, and move on.

An agent paginates every time. Each tool call is a fresh decision by the model — whether to continue, what argument to pass, whether the result so far is enough to answer the user. Every quirk in the pagination scheme becomes a place the model can get confused. And unlike a human, the agent can't step through with a debugger. It sees the last tool response, and it decides.

That changes the maths. A pagination scheme with a 1% edge-case failure rate is fine for a human developer who hits it once a quarter. It's a production incident for an agent that runs the loop a thousand times a day across a thousand users. The failure modes that show up:

  • Silent truncation. The tool returns 100 items when there are 10,000, no indication that more exist, and the model confidently answers based on the first page.
  • Infinite loops. The model keeps calling with the same cursor because the response doesn't clearly signal termination.
  • Context exhaustion. The model paginates through everything when it only needed the first ten results, and burns the context window on data it doesn't use.
  • Stale cursor drift. The underlying data changes between calls, and offset-based schemes start returning duplicates or skipping records.

Each one is a design problem, not a model problem. And each one has a fix at the tool layer.

Offset vs cursor pagination: the honest comparison

The two dominant schemes are offset (?page=2&per_page=50 or ?offset=100&limit=50) and cursor (?cursor=eyJpZCI6MTIzfQ&limit=50). There are hybrids — keyset pagination is the same underlying technique exposed as plain column values rather than an opaque token, and page-token schemes like Google's AIP-158 are cursor pagination with different naming. For this piece, the split that matters is offset vs cursor.

Offset pagination
Cursor pagination

How it works

Client asks for page N or offset M

Server returns an opaque token; client sends it back

Correctness under writes

Duplicates and skips when data changes mid-loop

Stable — cursor anchors to a specific position

Deep-page performance

Slow (server scans and discards)

Fast (server seeks by key)

Model-friendliness

High — pages are numbered, easy to describe

High if the cursor is treated as opaque; low if the model tries to interpret it

Termination signal

Empty page or page count

Explicit `next_cursor: null` or missing field

Failure mode

Silent duplication when data mutates

Stale cursor errors when the underlying position vanishes


The honest answer: offset pagination is fine for small, static, human-facing datasets. It breaks in the two situations agents actually operate in — large datasets and mutating data. Cursor pagination is what production agent tools should default to.

But the choice of scheme is upstream of the harder question: how do you shape the tool so the model uses pagination correctly? That's where most teams stop thinking, and it's where most of the real failures live.

What agent-friendly pagination actually looks like at the tool layer

A tool response isn't an API response. It's what the model sees. If your API returns a REST envelope with data, links.next, links.prev, meta.total_count, and meta.per_page, that's fine for a human developer. For an agent, it's noise — five fields where the model needs one clear signal.

Agent-friendly pagination boils down to four properties:

1. One termination signal, unambiguous. Return next_cursor as a string when there's more, and null (or omit it) when there isn't. Don't make the model compute total_count - offset > per_page. Don't return has_more: false alongside a non-null cursor. One field, one meaning.

2. The cursor is opaque. The tool description should tell the model to pass the cursor back verbatim and never interpret it. If the cursor is a base64-encoded JSON blob, the model will occasionally try to decode and edit it — we've seen this happen in production. Make it opaque enough that it's obviously a token, not data.

3. Page size defaults are agent-sized, not API-sized. REST APIs often default to 20 or 50 items per page because that's what fits on a UI screen. For agent tool responses, the right default is usually smaller — 10 to 25 — because each item consumes context. If a user asks "how many open tickets do I have?" and the tool returns 50 full ticket objects to answer with a count, you've wasted 90% of the response.

4. The tool description tells the model when to stop. This is the piece most teams miss. The tool description should include continuation guidance: "Return the first page. Only paginate if the user's question requires data beyond what the first page contains." Without that instruction, models default to either paginating everything or stopping after page one — neither is right.

We've written more about the shape of good tool descriptions in how to write tool descriptions for LLM agents, and the pagination guidance there is one of the higher-leverage places to spend effort.

A concrete before-and-after

Here's a typical REST response, unchanged, exposed as a tool:

{
 "data": [ /* 50 tickets */ ],
 "links": {
   "self": "/tickets?page=1",
   "next": "/tickets?page=2",
   "prev": null
 },
 "meta": { "total": 4823, "per_page": 50, "current_page": 1 }
}

The model has to figure out: is there more? (Yes, links.next is not null.) How do I get it? (Extract the URL, or re-derive the page number.) How many total? (meta.total.) That's three fields, two of which are redundant, and a URL the model has to parse.

Here's the same data, shaped for an agent tool:

{
 "items": [ /* 15 tickets, trimmed to essential fields */ ],
 "next_cursor": "eyJpZCI6NDgyM30",
 "approximate_total": 4823
}

One termination signal. One opaque cursor. A total count that's flagged as approximate so the model doesn't over-index on it. Fewer items per page, each with only the fields the tool's purpose actually needs.

That's not a REST redesign — it's a tool-layer transform. The API stays as it is; the tool wraps it.

Where cursor pagination still breaks for agents

Switching from offset to cursor doesn't fix everything. Three failure modes survive the change, and they're worth naming.

Stale cursors on mutating data. If a cursor encodes a position that's been deleted or moved, most APIs return a 400 or an empty result. The model doesn't know if the cursor is invalid, the data is gone, or the loop is done. Fix: the tool layer should catch stale-cursor errors and return a structured message the model can act on — "the previous cursor is no longer valid, please restart pagination" rather than a raw 400.

Cursor incompatibility across filter changes. Cursors are usually bound to the query that produced them. If the model paginates through tickets filtered by status=open and then changes the filter mid-loop, the cursor won't apply. Fix: tool descriptions should be explicit that cursors are per-query and can't be mixed. The runtime can also validate this — reject calls that combine a cursor with mismatched filter arguments before the API sees them.

Runaway loops. The model decides to paginate through 4,823 tickets to answer "how many are urgent?" and burns 50,000 tokens doing it. Fix: this is a runtime concern, not a pagination-scheme concern. The tool runtime should cap total pages per turn (three to five is usually right), refuse further pagination past a threshold, and return a summary hint: "partial results returned; consider a filtered query instead." This is the same principle as agent context window management — the tool layer holds the boundary the model won't.

These are all fixable, but not at the API level. They're fixable at the tools layer, where the tool description, the response shape, and the runtime behaviour combine into something the model can actually use.

How Pontil fits

Pagination is a good example of why we say the tools layer isn't the same as the API layer. The API is the source of truth for what's paginable and how. But the tool the agent invokes needs a different shape — smaller pages, opaque cursors, one termination signal, description-level guidance on when to continue. And it needs a runtime that catches stale cursors, caps runaway loops, and enforces per-user auth on every page fetch, not just the first one.

Pontil is a Tools-as-a-Service platform. We generate tools from the APIs a SaaS product already has, and we run them at a layer that handles the things agents need but APIs don't provide: response shaping, pagination discipline, per-user identity on every call, and observability across the whole tool loop. Teams that want to see what that looks like on their own product can book a walkthrough.

What changes when the caller stops being human?

Pagination is one of maybe a dozen API design decisions that made sense for human developers and quietly stop working when the caller is an agent. Auth is another. Error contracts are another. Rate limits, versioning, response shape — the same pattern shows up everywhere. The API was designed for someone who reads docs, writes a loop once, and moves on. The agent doesn't do any of those things.

The teams that get this right treat the tool layer as a separate design surface, not a thin wrapper on the API. That means smaller pages, opaque cursors, explicit continuation guidance, runtime-enforced loop caps, and structured error messages the model can act on. None of it requires an API rewrite. All of it requires taking the agent seriously as a caller with different needs.

The question worth asking on your own product: if you looked at your paginated endpoints today and tried to expose them as agent tools tomorrow, how many would work correctly on the second page?

Join our weekly newsletter

Stay up to date on the ever changing agentic landscape.

POSTS

Related content

Agents in production

Agent infrastructure

How to write tool descriptions for LLM agents

7 minute read

API strategy

Agent infrastructure

REST API design best practices for the agent era

10 minute read

Agents in production

Agent infrastructure

Agent context window management: a practical guide for production

7 minute read