Pagination and batching

Follow continuations to finish a read, and batch independent reads in one request.

View Markdown
On this page

A query can return part of a result with a more continuation. Send that continuation back unchanged to get the next part, and repeat until more is null. Batching lets you send several independent reads together; each result can still need its own continuation.

Read every message page

This example uses the token and workspace ID from First API request. Start there if you have not set CABANE_TOKEN and CABANE_WORKSPACE_ID yet.

In the same Bash session, export those existing variables so Node can read them through process.env. Then enter the ID of the conversation you want to read:

bash
export CABANE_TOKEN CABANE_WORKSPACE_ID
read -r -p "Conversation ID: " CABANE_CONVERSATION_ID
export CABANE_CONVERSATION_ID

The export command passes the existing token to Node without putting its value in command history.

Save the following as read-messages.mjs. It requires Node.js 20 or later and no extra packages.

js
const required = [
  'CABANE_TOKEN',
  'CABANE_WORKSPACE_ID',
  'CABANE_CONVERSATION_ID',
];
for (const name of required) {
  if (!process.env[name]) {
    throw new Error(`Set and export ${name} before running this script.`);
  }
}

const token = process.env.CABANE_TOKEN;
const workspaceId = process.env.CABANE_WORKSPACE_ID;
const conversationId = process.env.CABANE_CONVERSATION_ID;
const endpoint =
  `https://app.cabane.ai/api/workspaces/${encodeURIComponent(workspaceId)}/ops/query`;

let query = {
  op: 'conversations.listMessages',
  args: { id: conversationId, order: 'asc', limit: 20 },
};

while (query !== null) {
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(query),
    signal: AbortSignal.timeout(30_000),
  });

  if (!response.ok) {
    throw new Error(`Query failed with HTTP ${response.status}.`);
  }
  const envelope = await response.json();
  if (envelope.ok !== true) {
    throw new Error(`Query failed: ${JSON.stringify(envelope.error)}`);
  }

  for (const message of envelope.result.messages) {
    console.log(JSON.stringify(message));
  }
  query = envelope.result.more;
}

Run it in the shell where you exported the variables:

bash
node read-messages.mjs

The script prints one message row per line in ascending order and stops when the page's more is null. It checks both the HTTP status and the response envelope's ok value. Each request has a 30-second timeout.

The next request is exactly envelope.result.more: an object containing op and args. Keep both unchanged, and send them to the same query endpoint. Do not build a cursor or copy the first request's arguments over the continuation.

Finish shortened rows too

The loop above finishes the message pages. It does not automatically retrieve shortened content inside a message row.

A final page can still contain rows with their own more. If you need a row's full content, send that row's continuation as a separate query and follow any further continuations it returns. A row with no continuation is complete.

A row continuation can name a different operation and carry offsets or version information. Treat it as the next request, unchanged, just like a page continuation. Finishing a page's continuation and finishing its rows are separate jobs.

Choose the response size

Put maxBytes inside an operation's args to set its output budget. The default is 16 KiB (16384 bytes); the maximum is 23 KiB (23552 bytes). Use limit only where the operation declares it, as conversations.listMessages does above. See the Operation reference for each operation's arguments and result.

Some bounded results, including files.list, report truncated instead of providing more. If a result is truncated, narrow the request. There is no continuation to follow for that result.

Batch independent reads

Send up to 16 independent read operations in an ops array to the same POST /api/workspaces/{id}/ops/query endpoint, with the same bearer token and JSON headers. For example, this request reads rooms and agents:

json
{
  "ops": [
    { "op": "rooms.list", "args": { "maxBytes": 4096 } },
    { "op": "agents.list", "args": { "maxBytes": 4096 } }
  ]
}

Check the HTTP status and outer envelope's ok, then inspect every entry in results. Entries appear in input order and identify their op. Each has either ok: true with a result, or ok: false with an error. A successful outer envelope does not mean every operation succeeded. Follow any continuations in successful results as you would for a single read.

The batch shares one output budget. An entry with error.kind: "output_too_large" can have either of these codes:

error.codeWhat happenedWhat to do
not_runThe operation did not execute.Resend the original read alone or in a smaller batch.
not_returnedThe operation executed, but its answer did not fit.Resend the original read alone or in a smaller batch.

Neither error supplies a more continuation. Keep the original reads so you can resend the affected entries.

Writes cannot be batched. If one read needs a value returned by another, run them sequentially: finish the first read, then build the next request from its result.