Normalizing API Pagination for AI Agents
When an AI agent reads records from an API, the API tells the client how to request the next page. One API…
Before choosing endpoints, the URLs and methods callers use to reach API operations, identify who will call the API, what each caller needs to do, and what limits the interface must meet. An API is the contract between a caller and a service: it defines available operations, accepted inputs, returned results, and access rules. Those answers shape the contract and keep the design focused on stated requirements.
Ask who the callers are, such as a mobile client, a partner service, or an internal batch job. Then ask what each caller must accomplish: read a record, create or update it, trigger an action, or follow progress on work that takes time. Clarify how many requests callers expect to send and how much data each response should contain. Ask how quickly callers need answers, what they expect to see when records change, and which users or roles can access each record. When the prompt leaves a constraint open, ask for it or state an assumption and explain what design choice depends on it.
If callers need to browse a large collection, limit the number of results in each response and define how callers request the next page; Google’s pagination guidance recommends designing this behavior from the outset. If a mobile client and a partner service need different data or interaction patterns, consider whether they need separate interfaces. Microsoft’s API design guidance recommends modeling the domain and accounting for client needs rather than exposing an internal database schema. If callers access records belonging to different users, define who may access each record and which fields they may read or change; these rules address risks identified in the OWASP API Security Top 10.
For an order service, suppose the stated use case is that customers create orders and check their status. That supports a create operation and a way to retrieve an order, with access tied to the customer. It does not, by itself, justify adding refunds, bulk exports, or administrative endpoints. Ask whether customers must cancel orders before designing that action, then define which order states allow cancellation if the requirement is confirmed.
Keep API requirements separate from broader system-design concerns. The API requirements describe observable behavior, such as operations, request and response fields, errors, access rules, and pagination. Storage choices, service boundaries, queues, and sharding describe how the backend delivers that behavior. Those architecture choices matter when they affect the contract, such as when a request starts long-running work, but they do not automatically create new API features. State confirmed needs first, make assumptions visible, and add conditional design details only when a requirement supports them.
After identifying what callers need to do, organize the API around the things they recognize and need to work with. A URL-shortening service, for example, creates a short link, redirects visitors who open it, and lets its owner retrieve the link’s details:
POST /v1/links
Content-Type: application/json
{"destination_url": "https://example.com/page"}
GET /v1/links/{linkId}
GET /{code}
The create request returns the new link’s public URL and details. The first GET retrieves those details; the second redirects a visitor to the destination. These paths reflect distinct client tasks while treating the short link as a resource, a thing the API creates and manages. For another domain, such as reservations, use the same reasoning: identify the resource and the actions callers need, then choose paths and methods that express those tasks.
Keep the contract consistent. Name related resources in the same style and use the same HTTP methods for similar actions. Keep response and error formats predictable. Choose a stable public identifier, such as linkId. Keep database table names out of paths and responses. Do the same for keys used to divide data across storage shards and for storage layout. Those details belong to the service implementation; exposing them ties clients to storage choices and makes internal changes harder. Microsoft’s API design guidance likewise recommends modeling the domain rather than an internal schema.
HTTP methods set expectations for what a request does and whether a client can repeat it safely. A safe method, such as GET, is intended to read data without changing the resource. An idempotent method produces the same intended state when repeated: PUT and DELETE are idempotent, though a repeated request can return a different status or response. POST is not inherently idempotent, so a client that times out after creating a resource cannot know from the timeout alone whether the server completed the request. Define retry behavior explicitly, and use an idempotency key for writes that must tolerate duplicate submissions. These method semantics follow HTTP standards.
Validate a request before applying its changes. Check that required fields are present and have the right types. Confirm that values are allowed and satisfy business rules, then return a clear client error if validation fails. For example, a malformed body or invalid field can receive 400 Bad Request; a request that conflicts with the resource’s current state can receive 409 Conflict. Explain which fields the client controls and what happens when a field is missing, null, or invalid. That contract lets clients correct requests without guessing or accidentally repeating a partial write.
Use 4xx responses for problems the client must address. Examples include invalid input, missing authentication, insufficient permission, and a missing resource. Use 5xx responses when the server fails while handling the request. Give errors a stable code that client software can check and a short explanation, so clients can choose to correct the request, escalate the problem, or retry. The Google API error guidance describes a structured approach; for rate limiting, 429 Too Many Requests can include Retry-After, which tells the client when to try again.
For collection endpoints, return bounded pages rather than an unbounded result set. Offset pagination lets clients request a starting position and makes jumping between pages straightforward. When records are inserted or deleted between requests, however, later offsets can point to different records, causing skips or duplicates. Cursor pagination returns a continuation token tied to a position in the ordered results. It handles traversal more predictably as data changes, though clients generally follow pages in sequence rather than jump to an arbitrary page. Keep cursor tokens unreadable to clients, and require clients to keep using the same filters and sort order as they request more pages; Google’s pagination guidance also says tokens do not grant access, so the server must check authorization on every request.
State the default and maximum page size, the ordering rule, and what happens when the collection changes during traversal. Those choices tell clients whether they can expect stable sequential results or must tolerate records shifting between pages.
HTTP method semantics also set expectations for retries. GET reads a resource and is safe: the request does not ask the server to change its state. PUT replaces a resource at a known URL, and DELETE removes one; both are idempotent, meaning repeating the same request has the same intended effect as sending it once. The HTTP standard defines these properties so clients can make informed decisions after timeouts.
POST commonly creates a resource or triggers an action, so repeating it can create duplicates or repeat the action. If clients need to retry safely after a timeout, define an idempotency key: a client-supplied identifier that lets the server recognize repeated attempts as the same operation. Document how long the server honors the key and what response it returns for a retry.
Define who may perform each action and how clients recover from failures. Then specify how the interface changes over time. In a payment or booking API, these rules determine whether a caller can access another customer’s record, whether a timeout creates a duplicate charge, and whether existing clients keep working after a release.
Authentication identifies the caller; authorization checks what that caller may do. Check whether the caller may access each payment or booking named by an ID, and restrict which fields the caller may read or change. Set limits on request size and page size. Also cap expensive operations to reduce resource abuse. The OWASP API Security Top 10 highlights risks such as callers accessing records or fields they should not, and callers using too many resources.
A POST that creates a payment or booking is not automatically safe to retry. If the service completes the request but the response times out, the client cannot tell whether the write succeeded. Retrying without protection can charge a customer twice or create duplicate reservations. One option is an idempotency key: the client sends a unique key with the request, and the service records the result so a repeated request with that key returns the same outcome. Define how long the service keeps keys, how it handles concurrent requests with the same key, and what happens when a client reuses a key with different data. The HTTP semantics standard warns against automatically retrying non-idempotent requests unless the client knows the operation is safe; AWS guidance describes idempotency as a way to make retries safer.
Keep retries bounded. Clients should set timeouts and limit the number of attempts. Between attempts, they should vary the wait time so retries spread out instead of arriving all at once. The AWS Well-Architected guidance recommends limiting retries and using backoff and jitter. Each mechanism adds work: idempotency requires storing and checking keys, while retry policies add configuration and operational behavior to test. Add them when the operation’s impact and failure conditions justify that cost; avoid building elaborate retry infrastructure for low-impact operations without a stated need.
Plan how clients will receive contract changes. Additions such as optional response fields are less disruptive than changing existing field meanings or removing fields, though client behavior still matters. For changes that break compatibility, state how the service introduces versions, announces when it will stop supporting an older version, and continues to support older clients. Versioning conventions vary: Google’s guidance uses major versions in REST paths, while other organizations choose different policies. The interview answer should name a policy and explain how it protects clients that have not upgraded.
A hotel booking API shows how endpoint choices expose important behavior. A client first checks availability, creates a reservation, then retrieves its status:
GET /v1/rooms?check_in=2026-07-10&check_out=2026-07-12
{
"rooms": [
{ "room_type_id": "room_42", "available": true }
]
}
POST /v1/reservations
Idempotency-Key: request-abc123
Content-Type: application/json
{
"room_type_id": "room_42",
"check_in": "2026-07-10",
"check_out": "2026-07-12"
}
{
"reservation_id": "res_789",
"status": "confirmed"
}
GET /v1/reservations/res_789
The availability response helps the client choose a room, but it does not guarantee that the room remains available. Another caller could book it before the reservation request arrives. The reservation endpoint must check availability again when it creates the booking and return a conflict if the room has already been taken. This gives the client a clear outcome while requiring it to handle that conflict.
A timed-out POST leaves the client unsure whether the server created the reservation. The idempotency key lets the server recognize a retry and return the recorded result rather than create a second booking. This adds storage and rules for key retention and reuse; HTTP does not make POST idempotent by default, so the API must define this behavior explicitly (RFC 9110, AWS guidance on idempotent APIs).
The same reasoning applies across common interview prompts: a feed needs bounded, paginated reads; a booking flow needs safe creation and conflict handling; a URL shortener needs link creation, link lookup, and redirection. Pick one prompt, show the requests and outcomes clients depend on, then explain the tradeoff those choices create.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com