Pagination + idempotency
Cursor pagination on every list endpoint; Idempotency-Key
on every state-mutating endpoint. Two patterns to internalise once;
every endpoint follows them.
Cursor pagination
All list endpoints (GET /api/v1/public/sessions,
/evidence, /whiteboards, etc.) return an
envelope:
{
"data": [
{ /* resource */ },
{ /* resource */ }
],
"has_more": true,
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0yMVQxNDoyMSswMDowMCJ9"
} How to page
- Issue the first request without a
cursorparam. - If
has_moreistrue, passnext_cursorverbatim as thecursorquery param on the next request. - Repeat until
has_moreisfalse.
curl "https://app.nexbasira.com/api/v1/public/sessions?limit=25" \
-H "Authorization: Bearer nb_sec_..."
# response includes next_cursor: "eyJjcmVhdGVkX2F0Ijo..."
curl "https://app.nexbasira.com/api/v1/public/sessions?limit=25&cursor=eyJjcmVhdGVkX2F0Ijo..." \
-H "Authorization: Bearer nb_sec_..." Limits
| Param | Default | Max |
|---|---|---|
limit | 25 | 100 |
Higher limit values reduce round-trip count but increase
per-response payload size + serialisation time. Default 25 is
suitable for UI use; nightly batch jobs typically pass 100.
Cursor shape
The cursor is opaque to clients — it's a base64-encoded JSON blob encoding the position in the underlying queryset. Don't parse or construct cursors; just pass them verbatim. The shape is not stable across API versions.
Ordering
Default ordering is created_at DESC (newest first) for
every list endpoint. This is also the order the cursor advances
in — you'll walk from most-recent to oldest as you page.
SDK helpers
Both SDKs ship a transparent async-iterator that pages for you:
// @nexbasira/node
for await (const session of nb.sessions.list({ limit: 100 })) {
// ...
}
// nexbasira (Python)
for session in nb.sessions.iter(limit=100):
... Idempotency
Every state-mutating endpoint accepts an Idempotency-Key
header. Pass a unique key (typically a UUID) per logical operation;
a retry with the same key returns the cached response without
re-creating the resource.
curl -X POST https://app.nexbasira.com/api/v1/public/sessions \
-H "Authorization: Bearer nb_sec_..." \
-H "Idempotency-Key: 01HGAB7T8X3PVT3HKEXAMPLE" \
-H "Content-Type: application/json" \
-d '{"notes": "Vehicle damage CL-2026-0042"}' Cache window
Cached responses live for 24 hours. Replay after 24h with the same key creates a new resource — treat the key as valid only for the duration of your retry loop.
Scoping
Keys are scoped to (credential, endpoint, method):
- A retry from the same credential to the same endpoint with the same key returns the cached response.
- A different credential using the same key creates a new resource (treats it as a fresh call).
- The same key on a different endpoint creates a new resource (separate cache slot).
What gets cached
Only successful responses (2xx). A failed request doesn't poison the cache — your next retry with the same key gets a fresh attempt.
Header echo
Successful idempotent responses echo the key back in the
Idempotency-Key response header — useful for logging /
correlation.
Generating keys
Use anything that's globally unique per logical operation:
crypto.randomUUID()in Node 19+uuid.uuid4()in Python- Your business-process id (e.g. claim number + timestamp) if you want human-readable traces in logs
SDK helpers
// @nexbasira/node — pass via second arg
await nb.sessions.create(
{ notes: "..." },
{ idempotencyKey: crypto.randomUUID() },
);
// nexbasira (Python)
nb.sessions.create(notes="...", idempotency_key=str(uuid.uuid4())) What's next
- Errors + rate limits — what happens when paging or retry-loops go wrong
- Sessions API — first endpoint to apply both patterns