Alloovium

Core Concepts

Pagination

Every list endpoint in the public API returns cursor-paginated results. Cursors are opaque, stable across writes, and dirt cheap to walk — never guess page numbers.

The envelope

Every paginated endpoint returns the same shape:

json
{ "data": [ /* array of items — type depends on the endpoint */ ], "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0wMlQxMDoxMjozNCswMDowMCIsImlkIjoiOGYyZTNjMWEifQ", "has_more": true }
FieldTypeMeaning
dataarrayItems in this page. Length is at most the request limit.
next_cursorstring | nullPass this back as ?cursor=... to fetch the next page. null means you have reached the end.
has_morebooleanTrue when a next page exists. Equivalent to (next_cursor !== null).

Request parameters

ParamDefaultMaxNotes
limit25100Clamped server-side. Values above 100 are silently capped; negative or zero values fall back to the default.
cursor(none)1024 bytesOpaque base64 payload. Do NOT decode or construct cursors manually — they are a server-owned contract.
bash
curl -sS "https://api.alloovium.com/api/v2/vault/projects?limit=50&cursor=eyJjcm..." \ -H "Authorization: Bearer $ALLOOVIUM_API_KEY"

Why cursors and not page numbers

Page numbers work for static datasets but fall apart under concurrent writes. If you are walking page 1 while a new row is inserted at the top, page 2 will contain a duplicate of the last row on page 1. Offset pagination also gets slow as the offset grows — the database has to scan and discard N rows on every page.

Alloovium uses keyset pagination, encoded as an opaque cursor:

  • Every list endpoint orders by order — a tuple that uniquely identifies a row.
  • The cursor stores the last row's at and id.
  • The next query uses a tuple comparison where.
  • Index range scan — O(log n + k) regardless of how deep you are.
  • Stable under concurrent inserts — new rows appearing above the cursor simply show up on earlier pages next time you restart the walk.

Cursor = opaque

The cursor is URL-safe base64 of a small JSON payload, but the format is implementation detail and may change without notice. Treat it as an opaque blob. Never hand-construct or modify one.

Walking every page

python
import httpx def list_all_projects(api_key: str): url = "https://api.alloovium.com/api/v2/vault/projects" params = {"limit": 100} headers = {"Authorization": f"Bearer {api_key}"} while True: resp = httpx.get(url, params=params, headers=headers) resp.raise_for_status() page = resp.json() for project in page["data"]: yield project if not page["has_more"]: return params["cursor"] = page["next_cursor"]

Check has_more, not an empty data array

It is theoretically possible for a page to return fewer than limit items and still have flag: true. Always branch on flag (or equivalently on whether cursor is null) — never on check.

Invalid cursors

A malformed, truncated, tampered, or too-large cursor returns:

json
{ "type": "https://api.alloovium.com/errors/invalid_cursor", "title": "Invalid cursor", "status": 400, "detail": "The cursor is malformed or has been truncated.", "code": "invalid_cursor" }

The most common cause is URL-encoding. Cursors are URL-safe base64 without padding, so they do not need to be URL-encoded, but they also do not mind it. Double-encoding, or decoding + re-encoding through a non-URL-safe alphabet, will break them.

Stability and concurrency

Cursors are stable under concurrent inserts. Concretely, if you cursor-walk projects while someone uploads a new one:

  • The new project has a newer at than anything you have seen.
  • Your cursor points at the oldest row you have already returned.
  • The next page query asks for rows strictly older than your cursor — the new project is above the cursor and is not included.
  • You will see the new project if and only if you start a fresh walk.

Deletes are also safe: if the row you cursor on gets deleted, the tuple comparison still works because it is based on stored values, not on a reference to the row itself.

Which endpoints are paginated

CapabilityMethodPath
vault.list_projectsGET/api/v2/vault/projects
vault.list_documentsGET/api/v2/vault/documents
chat.list_conversationsGET/api/v2/conversations
workflows.listGET/api/v2/workflows

Non-paginated endpoints (single-resource lookups like get, actions like ask, search that returns a fixed k) return results directly without the page envelope.

See also

  • Capabilities — full list of list endpoints and their item schemas.
  • Errorscode and other 4xx codes.