Skip to content
  • There are no suggestions because the search field is empty.

Counting devices and people for a dashboard

If you are building an inventory dashboard, you do not need to download every device just to display a number. The API can count for you and return the totals already grouped:

Counting devices for a dashboard

If you are building an inventory dashboard, you do not need to download every device just to display a number. The API can count for you and return the totals already grouped:

GET /api/public/v1/assets/count?group_by=asset_class&site=38&checked_out=false 
{
  "total": 15,
  "results": [
    { "asset_class": { "id": 3, "name": "Chromebook" }, "count": 15 }
  ]
}

This is a read-only call. It needs a key with the assets:read permission, the same one that lets you list devices.

Choosing what to group by

group_by is the one required parameter. It takes asset_class, site, or both:

group_by=asset_class          one row per device class
group_by=site                 one row per site
group_by=site,asset_class     one row per class at each site

You can also group by health, antivirus, hardware, and activity — group_by=site,health_state gives you fleet health per building in one call. Those dimensions need their own permissions and report slightly differently.

Each dimension you group by appears as a member on every row, so group_by=site,asset_class returns rows carrying both a site and an asset_class.

Narrowing what is counted

Parameter What it does
site Count only these sites. Ids or names: site=15,16 or site=Library.
asset_class Count only these classes: asset_class=3,4 or asset_class=Chromebook,Laptop.
checked_out false = still in inventory, true = out with a person. Omit for both.
state Which device states to include. Defaults to devices in inventory.
include_descendants Roll subclasses (or sub-sites) up into the one you asked for.

Any of these can take several values, separated by commas, and — as everywhere else in the API — you can use a name instead of an id.

The zero-fill rule (the important one)

If you name the values you want, you get all of them back — including the empty ones.

A library that stocks Chromebooks and iPads but has run out of laptops still returns a laptop row, with a count of zero:

GET /api/public/v1/assets/count?group_by=asset_class&site=15&asset_class=Chromebook,Laptop,iPad&checked_out=false 
{
  "total": 7,
  "results": [
    { "asset_class": { "id": 3, "name": "Chromebook" }, "count": 5 },
    { "asset_class": { "id": 4, "name": "Laptop" },     "count": 0 },
    { "asset_class": { "id": 5, "name": "iPad" },       "count": 2 }
  ]
}

That "count": 0 is deliberate. It means your dashboard can display "0 Available Laptops" as an alert state without having to handle a missing entry, and it means a class you asked about never silently disappears from the response.

If you leave a filter off, it behaves as a wildcard, and you get back only the values actually present. Omit asset_class entirely and you will see every class that exists at that site — and nothing for the ones that do not.

So the rule in one line: ask for something explicitly and it always comes back, even at zero; leave it out and you only see what is there.

One call for an entire dashboard

Because the values you name are always returned, filtering several sites and several classes at once gives you every combination — a complete grid, zeros filled in:

GET /api/public/v1/assets/count?group_by=site,asset_class&site=15,16,17,18&asset_class=Chromebook,Laptop,iPad&checked_out=false 

That is one request for a four-building, three-class panel: 12 rows, every one of them present whether or not the building stocks that class.

Which devices are counted

By default, counts include devices that are in inventory — active and inactive ones. Archived and trashed devices are left out, so a repair-bench tile is not quietly inflated by devices that were thrown away months ago.

To count differently, name the states you want, or ask for everything:

state=active                  only active devices
state=active,archived         active plus archived
state=any                     every device, whatever its state

The states available to you are listed at GET /api/public/v1/meta.

Counting a class and its subclasses together

Device classes can be nested — "Laptop" might have "MacBook" and "ThinkPad" beneath it. By default a count is for the exact class you asked for, so a district with only MacBooks would see Laptop: 0.

Add include_descendants=true and each class you asked for absorbs everything beneath it:

GET /api/public/v1/assets/count?group_by=asset_class&asset_class=Laptop&include_descendants=true 

Now the MacBooks are counted under Laptop. This applies only to classes and sites you name explicitly; a wildcard always counts by exact class.

Reading total

total is the number of distinct devices matching your filters. It is usually the sum of the rows, but not always: if you ask for a class and one of its subclasses while include_descendants=true is on, a device legitimately counts in both rows. total never double-counts, so trust it as the true figure.

Devices with no class

A device that has never been assigned a class still exists, and it appears as a row with "asset_class": null. This is intentional — it makes unclassified devices visible rather than quietly dropping them and leaving your rows short of the total. If you don't want them on a dashboard, filter to the classes you care about.

Counting people

People count the same way devices do, at GET /api/public/v1/people/count. It needs a key with the people:read permission, and it follows every rule above — the same group_by, the same zero-fill, the same total.

You can group by:

group_by=org_unit             one row per org unit
group_by=group                one row per person group
group_by=has_assets           one row for "has a device", one for "has none"
group_by=org_unit,has_assets  both, per org unit

And narrow with:

Parameter What it does
org_unit Only these org units. Ids or names: org_unit=87919 or org_unit=2028.
group Only these person groups: group=Band,Chess Club or group=4,7.
has_assets false = has no device checked out, true = has at least one.
include_descendants Roll an org unit's sub-units up into it.

The same filters work on GET /api/public/v1/people, so you can list the people behind any number you see.

Counting a whole branch of your directory

Org units nest, and by default a count is for the exact unit you named. If your students sit in Students/HighSchool/2027, …/2028 and so on, ask for the parent and roll the branch up:

GET /api/public/v1/people/count?group_by=org_unit&org_unit=HighSchool&include_descendants=true&has_assets=false 
{
  "total": 41,
  "results": [
    { "org_unit": { "id": 87919, "name": "HighSchool" }, "count": 41 }
  ]
}

That is "high schoolers who do not have a device checked out", in one call.

Org units named after a graduating year work as you would expect: org_unit=2028 finds the unit named "2028" as long as no org unit has that id. If you want to be certain which one you are asking for, use the id.

Working out what is left over

A common planning question — how many devices will still be on the shelf once everyone who needs one has one — is two counts and a subtraction:

GET /api/public/v1/people/count?group_by=has_assets&org_unit=HighSchool&include_descendants=true
GET /api/public/v1/assets/count?group_by=asset_class&asset_class=Chromebook&checked_out=false

The first tells you how many students still need a device, the second how many are available. Most dashboard tools can subtract one query from the other for you; Grafana does it with a maths expression across the two panels.

People in no group

Grouping by group behaves like grouping by device tag: a person can be in several groups and is counted under each of them, so the rows can add up to more than total. total is always the number of distinct people. People in no group at all appear as a row with "group": null, rather than dropping out of the response.