Pagination
OneAnalytics paginates with opaque cursors on every list endpoint. OFFSET / page=N pagination is deliberately not supported — it produces duplicate and missing rows under any concurrent write, and it scales quadratically.
Request parameters
limit— integer, 1 to 500, default 50cursor— opaque base64url string from the previous response'snext_cursor, or omit for the first page
Response envelope
{
"data": [ /* items */ ],
"page": {
"limit": 50,
"next_cursor": "eyJpZCI6ImFiYyIsImNyZWF0ZWRfYXQiOiIyMDI2Li4ufQ",
"has_more": true
}
}
When there are no more pages, next_cursor is null and has_more is false.
Example — list workspaces
# first page
curl "https://api.analytics.rstglobal.in/v1/workspaces?limit=100" \
-H "Authorization: Bearer $OA_TOKEN"
# next page — paste next_cursor verbatim
curl "https://api.analytics.rstglobal.in/v1/workspaces?limit=100&cursor=eyJp..." \
-H "Authorization: Bearer $OA_TOKEN"
What's in the cursor
The cursor is opaque — treat it as a black box. Internally, it's a base64url-encoded JSON blob with the last row's primary key plus the sort-order columns (usually created_at, id). This gives us deterministic, consistent pagination: inserts after you started a pagination are simply not seen until you restart; deletes don't cause skips.
Stability guarantees
- A cursor is valid for 24 hours from issue. Past that, we return
400 pagination/cursor-expired. - A cursor is bound to its endpoint and its query string. Reusing a
/workspacescursor on/datasetsreturns400 pagination/cursor-mismatch. - A cursor survives schema migrations unless the primary key changes (rare).
Client pattern (language-agnostic)
cursor = None
while True:
resp = client.get("/v1/workspaces", params={"limit": 100, "cursor": cursor}).json()
for item in resp["data"]:
handle(item)
if not resp["page"]["has_more"]:
break
cursor = resp["page"]["next_cursor"]
Sorting
List endpoints have a default sort (usually created_at desc, id asc). Alternative sorts are exposed via sort=<field> where supported; the cursor encodes the active sort so you can't accidentally mix two sorts in one pagination.
Why no OFFSET?
- Correctness —
OFFSET 1000over a table that's being inserted into produces duplicates and misses. Cursors read at a stable point. - Performance —
OFFSETisO(N)per page; cursors areO(page_size). - Security — cursor scope-checking prevents a user from skipping to pages that mix tenants.