api · guide

Migrate from offset to cursor pagination

How to move a list integration from offset pagination to cursors without dropping or repeating rows.

Sunset: 2026-07-31 23:59:59 UTC Affected endpoints: GET /api/v1/orders, /products, /conversations Last updated: 2026-05-25

Why we're switching

Offset pagination scans the full preceding window on every page, so performance falls off a cliff on tenants with >100k rows. Cursor pagination is constant-time per page and stable across concurrent inserts.

What changes

  • New query params: ?cursor=<opaque> + ?limit=<1..100> (default 50; conversations 20).

  • New response field: next_cursor. Null on the last page.

  • offset= keeps working until 2026-07-31 — but responses now emit:

    Deprecation: true
    Sunset: Sat, 31 Jul 2026 23:59:59 GMT
    Link: <https://docs.vendu.app/api/pagination#cursor>; rel="successor-version"
    Warning: 299 - "Offset pagination is deprecated; migrate to cursor pagination before the Sunset date."
    

Before

GET /api/v1/orders?offset=100&limit=50
{
  "object": "list",
  "url": "/api/v1/orders",
  "has_more": true,
  "data": [ ... ]
}

After

First page:

GET /api/v1/orders?limit=50
{
  "object": "list",
  "url": "/api/v1/orders",
  "data": [ ... ],
  "next_cursor": "eyJ2IjoxLCJzb3J0IjoiY3JlYXRlZF9hdCIsImRpciI6ImRlc2MiLCJsYXN0IjoiMjAyNi0wNS0yNVQwMDowMDowMFoiLCJpZCI6ImFiYy0xMjMifQ",
  "has_more": true
}

Next page — pass back the next_cursor:

GET /api/v1/orders?cursor=eyJ2IjoxLCJ…&limit=50

When has_more = false (or next_cursor = null), you're done.

Sort stability

Cursors include both the sort column (created_at by default, updated_at for conversations) and the row id as a tie-breaker. Even if two rows share the same timestamp, you'll never see duplicates or skips between pages.

What if I really need random access?

You probably don't — but if you're paginating an export, the safer path is to record the first created_at you saw, request ?fields=id to slim the response, and walk forward with cursors. Random offset into a >100k window is the failure mode this migration is fixing.

Reference

  • Cursor payload: { v: 1, sort, dir, last, id }, base64url-encoded
  • Implementation: src/lib/pagination/cursor.ts
  • Deprecation policy: Versioning and deprecation
Last updated 2026-07-29