Skip to content

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

limit
integeroptionaldefault 25
Maximum number of items to return, from 1 to 100.
cursor
stringoptional
The nextCursor from the previous page. Send it unchanged, together with the same filters. See Pagination.
includeTotal
booleanoptionaldefault false
Set to true to also return totalCount. 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

object
string
Always list.
data
array
The items on this page, for example device objects. Can hold fewer than limit items, or none, even when hasMore is true.
hasMore
boolean
true when there may be more items after this page. Keep paging until it is false.
nextCursor
stringnullable
Pass it as cursor to get the next page. null on the last page.
totalCount
integer
Total number of matching items. Present only when you sent includeTotal=true.
GET /v1/devices?limit=1&includeTotal=true (device abridged)
{
  "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.

EndpointOrderlimit
GET/v1/devicesBy id (creation order)1 to 100, default 25
GET/v1/devices/{deviceId}/locationsNewest first1 to 1000, default 100
GET/v1/devices/{deviceId}/app-usageNewest day first, then by app identifier1 to 1000, default 100
GET/v1/policiesBy id (creation order)1 to 100, default 25
GET/v1/policies/{policyId}/devicesBy id (creation order)1 to 100, default 25
GET/v1/usersBy id (creation order)1 to 100, default 25
GET/v1/audit-logsNewest first1 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);
}

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, or since and until. You can change limit and includeTotal.
  • On event lists, if you leave out until, the first page fixes “now” as the end of the window and every nextCursor carries that value. All pages of one listing then cover the same window. Keep leaving until out 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.
400 Bad Request
{
  "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.

We use cookies

We use cookies to ensure you get the best experience on our website. For more information on how we use cookies, please see our cookie policy.

By clicking "Accept", you agree to our use of cookies.

Learn more