Getting Started
Pagination and Time Ranges
How Rhombus list operations return results in pages: lastEvaluatedKey cursors, page tokens, timestamp cursors and time windows, with loops in Python and JavaScript and the pitfalls that cause missed or duplicated results.
Operations that can return many results page them in one of a few styles. Each operation's API Reference page shows which fields it takes; this page explains how each style works.
| Style | Request fields | Response field | Last page when |
|---|---|---|---|
| Cursor | maxPageSize, lastEvaluatedKey (sometimes inside pageRequest) | lastEvaluatedKey | lastEvaluatedKey is absent |
| Page token | pageSize, pageToken | pageToken | pageToken is absent |
| Timestamp cursor | maxPageSize, pageTimestampMs | lastEvaluatedTimestampMs | lastEvaluatedTimestampMs is absent |
| Time window | createdAfterMs, createdBeforeMs, limit | none | A page has fewer than limit events |
| Last item | maxResults, lastTimestampMs, lastUuid | none | A page comes back empty |
All timestamps are Unix epoch milliseconds.
Cursor pages
Most list operations take maxPageSize and lastEvaluatedKey, and return lastEvaluatedKey while more results remain. Send it back unchanged to get the next page; when the response has no lastEvaluatedKey, you have everything. For example, findAccessControlledDoors:
import os
import requests
URL = "https://api2.rhombussystems.com/api/component/findAccessControlledDoors"
HEADERS = {"x-auth-scheme": "api-token", "x-auth-apikey": os.environ["RHOMBUS_API_KEY"]}
doors, key = [], None
while True:
body = {"maxPageSize": 100}
if key:
body["lastEvaluatedKey"] = key
page = requests.post(URL, headers=HEADERS, json=body).json()
if page.get("error"):
raise RuntimeError(page.get("errorMsg"))
doors += page.get("accessControlledDoors", [])
key = page.get("lastEvaluatedKey")
if not key:
breakconst url = "https://api2.rhombussystems.com/api/component/findAccessControlledDoors";
const headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": process.env.RHOMBUS_API_KEY,
"Content-Type": "application/json",
};
const doors = [];
let key;
do {
const body = { maxPageSize: 100, ...(key && { lastEvaluatedKey: key }) };
const page = await (await fetch(url, { method: "POST", headers, body: JSON.stringify(body) })).json();
if (page.error) throw new Error(page.errorMsg);
doors.push(...(page.accessControlledDoors ?? []));
key = page.lastEvaluatedKey;
} while (key);Some operations nest the same two fields in a pageRequest object ({"pageRequest": {"maxPageSize": 100, "lastEvaluatedKey": "…"}}) and return lastEvaluatedKey at the top level of the response.
- Keep going until
lastEvaluatedKeyis absent. A page can hold fewer results thanmaxPageSize, or none at all, while more remain: results you aren't allowed to see are removed after the page is read. - Always set
maxPageSize. Without it, some operations return every result in one response, and others apply their own default. Each operation has its own maximum, and a larger value is rejected with400. - Treat
lastEvaluatedKeyas opaque. Don't build, edit or store it long-term. A value the server can't read isn't rejected: the operation starts again from the first page. - Keep the other fields the same on every page of one listing.
Page tokens
A few operations, such as getClipsWithProgress, use pageSize and pageToken instead. Send the pageToken from each response with the next request, and stop when the response has none. For getClipsWithProgress, pageSize is 1 to 200 and defaults to 100.
Timestamp cursors
getSavedClipsV2 returns lastEvaluatedTimestampMs. Send it as pageTimestampMs in the next request, and stop when the response doesn't include it. Set maxPageSize; it defaults to 100. As with cursors, a page can be short while more clips remain.
Time windows
Event history for doors and sensors, such as findComponentEventsByAccessControlledDoor, takes a window and a count instead of a cursor:
createdAfterMsandcreatedBeforeMsbound the window.createdBeforeMsis exclusive.limitcaps the number of events, the most recent first. Set it: withoutlimit, every event in the window comes back in one response.
To read further back, repeat the request with createdBeforeMs set to one millisecond after the oldest event you received, and drop events you already have by their UUID. Moving the boundary to exactly the oldest timestamp can skip events that share that millisecond.
Policy alerts
getPolicyAlerts returns alerts newest first, up to maxResults, with no cursor in the response. For the next page, send the timestampMs and uuid of the last alert you received as lastTimestampMs and lastUuid, keeping the same filters and window (afterTimestampMs, beforeTimestampMs). Stop when a page comes back empty or you reach the start of your window.
Choosing a page size
Larger pages mean fewer requests against your rate limit, but slower responses. For a full sync of a large organization, use the largest size the operation accepts, and keep the loop going until the last-page condition in the table above.