Skip to content

Pagination

Read long lists page by page with `limit` and an opaque `cursor`.

Lists that can grow large use keyset pagination: you ask for a page with limit, and the answer tells you where the next page starts.

Which operations page

OperationOrder
listBatteryModelsBy name, then id
listBatteriesBy serial number, then id
listEvidenceNewest first
listFacilitiesBy name, then id
listLifecycleEventsNewest first
listPassportVersionsNewest first

Other lists, such as values, value histories and model versions, return all items in one response.

Request

limitintegerquerydefault: 50

Number of items per page, from 1 to 200.

cursorstringquery

The next_cursor of the previous page. Leave it out for the first page.

Send the operation's filters (for example battery_model_id or passport_status of listBatteries) unchanged with every page.

Response

itemsobject[]required

The items of this page, at most limit.

next_cursorstring | nullrequired

The cursor for the next page, or null when this is the last page.

200
{
  "items": [
    { "id": "0192f0a3-1b7c-7e2a-8f45-6a9d0c3e5b12", "serial_number": "SV2027-000001" },
    { "id": "0192f0a3-1b7c-7e2a-8f45-6a9d0c3e5b13", "serial_number": "SV2027-000002" }
  ],
  "next_cursor": "eyJzIjoiU1YyMDI3LTAwMDAwMiJ9"
}

The sample shows only two members of each unit; real items carry the full schema of the operation.

Rules for cursors

  • A cursor is opaque. Do not parse it, build it or change it; its content may change at any time.
  • A cursor belongs to the query that produced it: the same operation, the same path parameters and the same filters. Use it right away for the next page; do not keep cursors as long-term bookmarks.
  • next_cursor: null means there are no more items.
  • A cursor that cannot be used is 400 validation_error on the field cursor; a limit outside 1 to 200 is a 400 on the field limit.
  • Pages are not a snapshot. Items created or changed while you walk through a list may or may not appear. For a consistent copy, page through and then reconcile by id (see Synchronise units).

Read every page

The loops below read all units with a published passport, 200 per page.

type Battery = { id: string; serial_number: string };
type Page<T> = { items: T[]; next_cursor: string | null };

const base = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` };

async function* listBatteries(filters: Record<string, string>): AsyncGenerator<Battery> {
  let cursor: string | null = null;
  do {
    const query = new URLSearchParams({ ...filters, limit: '200' });
    if (cursor) query.set('cursor', cursor);
    const res = await fetch(`${base}/batteries?${query}`, { headers });
    if (!res.ok) throw new Error(`listBatteries: ${res.status} ${await res.text()}`);
    const page: Page<Battery> = await res.json();
    yield* page.items;
    cursor = page.next_cursor;
  } while (cursor);
}

let count = 0;
for await (const unit of listBatteries({ passport_status: 'published' })) {
  console.log(unit.serial_number, unit.id);
  count++;
}
console.log(`${count} units`);

Each page is one request against your organisation's limit of 600 requests per minute. Use limit=200 for bulk reads and see Rate limits for long walks.

On this page