LIVE · AUDIT-CHAINED · EU-RESIDENT
SYSTEM · 99.99% UPTIME
v 1.0 ↗ MADE IN EU

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

  1. Issue the first request without a cursor param.
  2. If has_more is true, pass next_cursor verbatim as the cursor query param on the next request.
  3. Repeat until has_more is false.
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

ParamDefaultMax
limit25100

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