Guides
Pagination
List endpoints return results in pages. Follow the cursor in each page to get the next one.
How it works
WeGuard uses cursor (keyset) pagination. Each page tells you whether more items follow and gives you an opaque nextCursor that points past the last item. Unlike page numbers, cursors stay correct while data changes: if devices enroll during your sync, you don’t skip or repeat items. Cursors also keep the hundredth page as fast as the first.
Parameters
limitintegeroptionaldefault 25- Maximum number of items to return, from
1to100. cursorstringoptional- The
nextCursorfrom the previous page. Send it unchanged, together with the same filters. See Pagination. includeTotalbooleanoptionaldefault false- Set to
trueto also returntotalCount. It runs an extra count, so request it only when you need it.
Some endpoints accept a larger limit. See the table under Ordering.
The list object
objectstring- Always
list. dataarray- The items on this page, for example device objects. Can hold fewer than
limititems, or none, even whenhasMoreistrue. hasMorebooleantruewhen there may be more items after this page. Keep paging until it isfalse.nextCursorstringnullable- Pass it as
cursorto get the next page.nullon the last page. totalCountinteger- Total number of matching items. Present only when you sent
includeTotal=true.
{
"object": "list",
"data": [
{
"id": "6523f1c2a7b9d40012345678",
"object": "device",
"name": "Warehouse Scanner 12"
}
],
"hasMore": true,
"nextCursor": "YzE6NjUyM2YxYzJhN2I5ZDQwMDEyMzQ1Njc4",
"totalCount": 1342
}Ordering
Inventory lists (devices, policies and users) are ordered by id ascending, which is creation order. Event lists (location history and the audit log) are ordered newest first by the time of the event, then by id, within a time window. App usage, a per-day list, is ordered by date, newest first, then by app.identifier, ignoring case, within a date range.
| Endpoint | Order | limit |
|---|---|---|
GET/ | By id (creation order) | 1 to 100, default 25 |
GET/ | Newest first | 1 to 1000, default 100 |
GET/ | Newest day first, then by app identifier | 1 to 1000, default 100 |
GET/ | By id (creation order) | 1 to 100, default 25 |
GET/ | By id (creation order) | 1 to 100, default 25 |
GET/ | By id (creation order) | 1 to 100, default 25 |
GET/ | Newest first | 1 to 100, default 25 |
Iterating through all pages
Request the first page, process data, and repeat with cursor=<nextCursor> until hasMore is false. Use the largest limit you can handle to make fewer requests.
Use hasMore to decide whether to continue, not the size of data. The API skips records it can’t represent, such as a location without valid coordinates, so a page can hold fewer than limit items while hasMore is still true.
const BASE_URL = "https://api-gw.weguard.ai/core";
const headers = { Authorization: `Bearer ${process.env.WEGUARD_API_KEY}` };
/** Yields every item of a list endpoint, following nextCursor. */
async function* listAll(path, filters = {}) {
let cursor = null;
do {
const params = new URLSearchParams({ ...filters, limit: "100" });
if (cursor) params.set("cursor", cursor);
const response = await fetch(`${BASE_URL}${path}?${params}`, { headers });
const page = await response.json();
if (!response.ok) throw new Error(`${page.code}: ${page.detail}`);
yield* page.data;
cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);
}
for await (const device of listAll("/v1/devices", { platform: "android,ios" })) {
console.log(device.id, device.name);
}import os
import requests
BASE_URL = "https://api-gw.weguard.ai/core"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['WEGUARD_API_KEY']}"
def list_all(path, **filters):
"""Yields every item of a list endpoint, following nextCursor."""
params = {**filters, "limit": 100}
while True:
response = session.get(f"{BASE_URL}{path}", params=params, timeout=30)
response.raise_for_status()
page = response.json()
yield from page["data"]
if not page["hasMore"]:
return
params["cursor"] = page["nextCursor"]
for device in list_all("/v1/devices", platform="android,ios"):
print(device["id"], device["name"])Total counts
Add includeTotal=true to get totalCount, the number of items that match your filters across all pages. Counting costs an extra query. Ask for it only when you need it, typically on the first page, and leave it off while paging.
The count includes records that are skipped because they can’t be represented, such as location points without valid coordinates. On location history, totalCount can therefore be higher than the number of items you receive across all pages.
Cursor rules
- Treat cursors as opaque strings. Don’t build, decode or modify them. Their format can change.
- Send the cursor with the same filters as the request that returned it, for example the same
platform,policyId, orsinceanduntil. You can changelimitandincludeTotal. - On event lists, if you leave out
until, the first page fixes “now” as the end of the window and everynextCursorcarries that value. All pages of one listing then cover the same window. Keep leavinguntilout on the following pages. Per-day lists do the same with today’s date. - A cursor that is malformed or was issued for different filters fails with 400 invalid_cursor. Start again without a cursor.
{
"type": "https://weguard.ai/docs/errors#invalid_cursor",
"title": "Invalid pagination cursor",
"status": 400,
"code": "invalid_cursor",
"detail": "The cursor was issued for different filters. Repeat the original query parameters or start again without a cursor.",
"instance": "/core/v1/devices",
"requestId": "req_k3j2h1g0f9e8d7c6b5a4z3y2",
"errors": [
{
"field": "cursor",
"code": "invalid_cursor",
"message": "The cursor was issued for different filters. Repeat the original query parameters or start again without a cursor."
}
]
}Lists that are not paginated
A few lists are small enough to return in full. They still use the list object, with hasMore always false and nextCursor always null, so code that reads data works unchanged. Don’t send limit or cursor to them. Those parameters are rejected with unknown_parameter. Today this applies to a device’s installed apps and its daily screen time, which covers at most 31 days.
To keep a copy of your inventory in sync, run a full paginated pass on a schedule instead of polling individual devices. It uses far less of your rate limit.