Digital Systems7 min read

Ecommerce API pagination: cursors, stable ordering and consistent results

A practical approach to opaque cursors, deterministic ordering and read sessions that reduce duplicates, skipped records and deep-page costs.

Ordered flow of ecommerce parcels crossed by luminous cursors and consistent data branches

Pagination is a consistency contract, not a response detail

Catalogs, orders, customers and subscriptions keep changing while apps, connectors and operations tools traverse them. An API that returns everything in one response wastes memory, increases latency and makes integrations fragile. Splitting results into pages controls size, but raises a harder question: what does next mean while prices, states and records change? Unless the contract answers it, a client can receive the same order twice, skip another one or finish a synchronization with an inconsistent set.

The answer is not to replace offset with an arbitrary string called cursor. The service needs a total order, a token bound to the request, explicit rules for concurrent changes and metrics that separate client misuse from dataset instability. Define the journey before choosing syntax. Interactive browsing, full export, incremental synchronization and deep search have different guarantees. This scope prevents a team from promising snapshot consistency when its storage layer can provide only monotonic progress.

Define the contract before implementation

Separate page size, position and filters

A clear contract exposes a requested size, a token obtained from the previous response and an optional next token. Google AIP-158 describes fields such as page_size, page_token and next_page_token: the client repeats the request and changes only the token. Set a server maximum and define behavior for missing or excessive values. Requested size should not guarantee the number returned because authorization, filters and availability may legitimately produce a shorter page.

The token must represent position together with the parameters that define the result set: filters, sorting, tenant and contract version. If the client changes one of them, reject the token with a stable error rather than continuing through a different sequence. Do not trust mutable fields sent separately from the cursor. For synchronization endpoints, document token expiry, whether replay is allowed and which operation starts a fresh read.

Keep cursors opaque and versionable

A cursor is not a public primary key. Encode the ordering key, tie-breaker, format version and, when required, a session reference in an internal structure. Protect it from tampering with a signature or a server-side identifier. Encoding is not encryption, so never include personal data, secrets or implementation details that should remain private. Opacity allows the format to evolve without clients parsing it, provided the service maintains a declared compatibility window.

Build a total and repeatable order

Always add a unique tie-breaker

Sorting only by creation time is insufficient because multiple records can share one timestamp. Use a total sequence such as created_at DESC, id DESC and carry both values in the cursor. The next page applies a lexicographic predicate that matches the direction instead of recounting earlier rows. If users can choose an order, every allowed option needs a stable tie-breaker and a supporting index. Arbitrary sort expressions without storage support create unpredictable performance.

PostgreSQL warns that LIMIT and OFFSET need an ORDER BY producing a unique order if a predictable subset is required. Rows skipped by an offset still have to be computed. Offset remains reasonable for small administration screens or shallow numbered pages, but it is a weak foundation for large exports. Keyset pagination starts after the last observed key and can keep work closer to page size when the query and index align.

Align query and index

Inspect plans for real filter shapes and directions. An index should support tenant columns, selective predicates and the ordering tuple without multiplying structures for every theoretical variant. Measure duration, rows examined and memory from the first page to a deep page. Fetching one item beyond the requested limit can establish whether another page exists, or use an equivalent signal, rather than calculating an expensive total count the product does not need.

Learn from public contracts without copying them blindly

Stripe lists subscriptions with parameters including starting_after and ending_before, a page-size limit and the has_more signal. It illustrates how an object already received can mark the point of progress. Shopify GraphQL connections expose pageInfo, hasNextPage and endCursor; the caller passes the cursor back through after. In both models, the consumer follows a server-issued token instead of deriving a page number independently.

Those examples do not define your consistency level. A catalog with changing relevance, an order list with stable identifiers and an event history require different policies. Document direction, default order, maximum size, cursor lifetime, and the treatment of inserted, updated and deleted records. If bidirectional navigation is necessary, test forward and backward movement as two complete contracts rather than assuming one is a reversed string.

Handle concurrent writes and deep search

Choose a monotonic boundary or a snapshot

With immutable ordering keys, a cursor can ensure that each page advances beyond the last observed position. New records placed before the boundary will not appear in the current traversal, while updates to fields outside the order do not change position. If an ordering key itself changes, the record can move. Avoid mutable sort fields for synchronization or introduce an immutable watermark. For audit requirements, use a snapshot or an event log instead of promising strong consistency over a moving table.

Search adds ranking and shard behavior. Elastic documents search_after with matching query and sort values and recommends a point in time to preserve index state during a traversal; PIT adds an internal tie-breaker. An application token can protect the PIT reference and sort values. Manage expiry and session closure because a long-lived snapshot consumes resources. If it expires, return an error that requires a restart rather than pretending the sequence can continue unchanged.

Design errors, security and observability

Distinguish malformed tokens, invalid signatures, unsupported versions, expired sessions and incompatible parameters. Each condition needs a documented response that does not expose cursor internals. Limit page size, request rate and active sessions per identity. A valid cursor never grants access to data the caller is no longer allowed to see. Reapply authorization and scope on every request and bind tokens to the correct tenant.

Measure pages per traversal, latency by depth, rows examined, rejected-token rate, expired sessions, restarts and empty pages that still issue a next token. Tests should create records sharing the same timestamp, insert and delete items between pages, and modify sortable fields. Check for no duplicates, no gaps relative to the declared guarantee and certain termination. Logs retain cursor version and a hash, not the complete token or customer data.

Release pagination without breaking integrations

Introduce the new contract in parallel or behind a compatible API version. First observe cost and correctness in shadow mode, then migrate one controlled client and compare final cardinality and duration with the old path. Keep a rollback and an expiry date for the legacy mode. Publish a complete traversal example and state that the token is opaque. Quality appears on deep pages and during writes, not only in the first response.

Reliable pagination combines API design, database access, search, security and telemetry. If catalogs or connectors show duplicates, timeouts or incomplete syncs, AE Digital Agency's ecommerce and systems services can turn the flow into a measurable protocol with versioned cursors, aligned indexes and a verifiable rollout.

api ecommercepaginazionecursor paginationkeyset paginationordine stabilecoerenza datiosservabilità

Frequently asked questions

What is the difference between offset and cursor-based pagination?

Offset counts and skips rows, while a cursor resumes from an ordered position already observed. Cursor-based pagination suits large, changing sequences when it uses a total order.

Does a cursor automatically guarantee duplicate-free results?

No. Deterministic ordering, a tie-breaker, concurrent-change rules and consistent queries are still required. An opaque token alone changes only the syntax.

When does an ecommerce API need snapshot pagination?

Use it when the complete traversal must reflect one coherent view, such as an audit export. A documented monotonic boundary may be enough for ordinary feeds.

How should an expired page token be handled?

Return a specific error and require the client to restart the traversal. Do not silently reconstruct a different position or accept incompatible parameters.

Related articles

Got a similar project?

Tell us the problem. We'll build the solution.

Let's talk

Have a project in mind?

Tell us the problem. We'll build the solution.

Let's talk