Ralti can return workspace metadata and bounded record pages so a client does not need every row before rendering. Send X-Ralti-Workspace-Mode:paged on session and supported mutation/history responses. Sparse data is still authoritative for the rows it contains; it is not a complete in-memory copy of every sheet.
Bootstrap a selected sheet#
GET /api/session?initialSheet=sheet_projects&view=view_open HTTP/1.1
Host: app.your-domain.example
Authorization: Bearer <current-session-jwt>
X-Atlas-Workspace: workspace_example
X-Ralti-Workspace-Mode: pagedFor a paged GET /api/session, initialSheet selects a bounded initial sheet read. Optional view, query (or q), and archived=true shape that selection. Continue with /api/sheets/{id}/items. That route accepts view, query, archived, cursor, limit, item, items (a JSON-encoded ID array), and exact=true for exact label matching.
| Response | Fields |
|---|---|
| SheetPage | workspaceId, spaceId, revision, items, total, nextCursor, labels |
| WorkspaceRecordPage | workspaceId, revision, records:[{spaceId,item}], total, nextCursor, labels, counts:{all,assigned,due} |
GET /api/records provides workspace-wide paging with scope all, assigned, or due; optional query and today; and cursor/limit. Record page sizes are integers from 1 to 100, defaulting to 100. labels maps related IDs to display labels. Keep IDs as identifiers even when their visible names change.
Treat the cursor as opaque#
Record cursors bind an offset to the workspace revision and the query identity. Pass the returned nextCursor unchanged with the same scope and filters. A changed workspace returns 409 page_changed; an incompatible or malformed cursor returns 400. Restart at the first page after a revision change rather than merging pages from different snapshots.
async function readSheetPage(baseUrl, sheetId, token, workspaceId, cursor) {
const url = new URL(`/api/sheets/${encodeURIComponent(sheetId)}/items`, baseUrl);
url.searchParams.set("limit", "50");
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, { headers: {
Authorization: `Bearer ${token}`,
"X-Atlas-Workspace": workspaceId
}});
const body = await response.json();
if (!response.ok) throw Object.assign(new Error(body.error), { status: response.status, code: body.code });
return body; // Next call reuses body.nextCursor only with the same revision/query.
}nextCursor is null when record paging is complete. Before merging a page into the UI, verify its workspaceId and revision match the rendered workspace. A late response from a workspace or view you have left should be ignored. Do not infer a sheet’s total size from one page’s array length or present a partial page as a full export.
Email pages use a different contract#
GET /api/email/threads clamps requested limit to 1–50, defaulting to 30. Its cursor is an offset string validated from 0 to 100,000. Email responses use an optional nextCursor rather than the record API’s null sentinel. Thread detail pages also use an offset cursor and at most 50 messages per page. These are not revision-bound record cursors and should not be interchanged with them.
Email filters include connectionId, folder, category, linked spaceId/itemId, and query. Category results are private to the mailbox owner. The dashboard can include first pages and counts for convenient navigation; it does not expose other users’ private inboxes. A shared conversation grants only the explicitly shared linked conversation, not general mailbox paging.