Skip to main content
DRIFTSTACK

Admin API pagination

Driftstack admin list endpoints use endpoint-specific pagination. High-volume lists use opaque cursors; status subscribers use limit plus offset. Check the current endpoint matrix below instead of assuming one universal envelope.

Why crypto orders use cursors, not offsets

Offset pagination (?page=2) is convenient but breaks when rows are inserted or removed between page requests: callers can skip rows or see duplicates. The crypto-order list orders rows by created_at DESC with order_id as the tiebreaker; new orders arrive at the front of the list constantly. A cursor anchored to the last seen (created_at, order_id) pair gives a stable walk across the list even while new rows are being added.

The crypto-order contract

GET /v1/admin/crypto-orders returns:

{
  "orders": [ ...up to `limit` rows... ],
  "next_cursor": "eyJ0cyI6MTcwMDAwMDAwMCwiaWQiOiJvcmRfYWJjIn0"
}

The walk loop

let cursor = null;
const all = [];
do {{
  const params = new URLSearchParams({{ limit: '50' }});
  if (cursor) params.set('cursor', cursor);
  const res = await fetch(`/v1/admin/crypto-orders?${params}`, {{
    headers: {{ authorization: `Bearer $ADMIN_KEY` }}
  }});
  const body = await res.json();
  all.push(...body.orders);
  cursor = body.next_cursor;
}} while (cursor !== null);

Stop when next_cursor is null. Do not try to parse the cursor — its internal shape is not part of the contract and may change between releases. Treat it as opaque bytes.

Combining with filters

Pagination composes with the existing filter parameters (status, search, account_id). Send the same filter values on every page request; the server applies them before walking past the cursor anchor. Changing a filter mid-walk is undefined — always start a fresh walk (drop the cursor) when filters change.

Cursor lifetime

Cursors are not signed and do not expire on a timer; the server treats them as the literal (created_at, order_id) pair to seek past. A cursor remains useful as long as the row it anchors to is still in the scan window (default 1000 rows on filtered queries, 51 rows on the unfiltered first page — the limit + 1 overflow probe).

If you walk slowly enough that the anchor row falls out of the window, the server returns an empty page with next_cursor: null. The caller should treat that as "end of walk" and restart from the front if a full scan is still needed.

Validation errors

Current endpoint matrix

These list routes currently accept cursor and return next_cursor:

GET /v1/admin/status-subscribers is the current exception: it accepts limit and offset and returns the rows in data, without a next_cursor field.

Cursor encoding, ordering, limits, filters, and collection-field names are route-specific. The (created_at, order_id), orders, default limit=50, and max 200 details above apply specifically to crypto orders.

Related