Analytics polling
Use analytics polling when you need aggregated utilisation metrics instead of raw occupancy changes.
The Analytics API feature is enabled by XY Sense per organisation and can vary per building and floor. If your queries return an access-disabled error, your XY Sense representative can enable it. See Feature enablement.
1. Find your buildings and how much data is ready
Open in API ExplorerExample query
query AnalyticsBuildings {
buildings {
id
name
analyticsAvailableTo
floors(isLive: true) {
id
name
scale
floorPlanUrl
}
}
}
Example response
{
"data": {
"buildings": [
{
"id": "0b26f8f3-5d1c-4c6a-9d3e-1a2b3c4d5e6f",
"name": "1 Example Street",
"analyticsAvailableTo": "2026-06-02T00:00:00Z",
"floors": [
{
"id": "a2961d46-971f-4ce1-a66e-7ee7bdb2a204",
"name": "Level 12",
"scale": 1.8,
"floorPlanUrl": "https://cdn.xysense.io/floorplans/level-12.png"
}
]
}
]
}
}
2. Pull the aggregates at the resolution you need
Pick the interval that matches your reporting need: occupancyPerMinute, occupancyPerFiveMinutes, occupancyPerHour, or occupancyPerDay. This example uses hourly aggregation. The window each interval allows per request, and when a day's data becomes available, are in Limits and retention.
Example query
query OccupancyPerHour($floorId: ID!, $from: Date!, $to: Date!) {
occupancyPerHour(floorId: $floorId, from: $from, to: $to, take: 1000) {
items {
floorSpaceId
periodStartUtc
periodStartLocal
minOccupants
maxOccupants
avgOccupants
occupiedMinutes
occupiedHours
}
hasNextPage
totalItems
}
}
Example variables
{
"floorId": "a2961d46-971f-4ce1-a66e-7ee7bdb2a204",
"from": "2026-06-01T00:00:00Z",
"to": "2026-06-02T00:00:00Z"
}
Example response
{
"data": {
"occupancyPerHour": {
"items": [
{
"floorSpaceId": "5c8a1d92-4b6e-4f03-9a71-2d4e6f8a0b13",
"periodStartUtc": "2026-06-01T01:00:00Z",
"periodStartLocal": "2026-06-01T11:00:00+10:00",
"minOccupants": 0,
"maxOccupants": 1,
"avgOccupants": 0.63,
"occupiedMinutes": 38,
"occupiedHours": 0.63
}
],
"hasNextPage": false,
"totalItems": 1
}
}
}
Paging through large results
Analytics queries return offset pages (skip/take, up to the maximum page size). A year of hourly data for a large portfolio spans multiple pages, so loop until hasNextPage is false:
query HourlyPage($buildingId: ID, $from: Date!, $to: Date!, $skip: Int, $take: Int) {
occupancyPerHour(buildingId: $buildingId, from: $from, to: $to, skip: $skip, take: $take) {
totalItems
hasNextPage
items {
floorSpaceId
periodStartLocal
avgOccupants
maxOccupants
occupiedSeconds
}
}
}
First page: { "skip": 0, "take": 50000 }. While the response has "hasNextPage": true, request the next page with { "skip": 50000, "take": 50000 }, and so on. totalItems lets you size the loop up front.
Intervals with no occupancy are omitted
Empty intervals are left out of the results rather than returned as zero rows, so treat an absent interval for a known space as zero occupancy rather than missing data. Averaging only the rows you received will overstate utilisation. Why that is.
3. Join the rows back to named spaces
Pull floor-space metadata so the analytics rows can be joined back to named spaces, types, and capacities.
Open in API ExplorerExample query
query AnalyticsFloorSpaces($floorId: ID!) {
floorSpaces(floorId: $floorId) {
id
name
capacity
spaceType {
id
name
globalSpaceCategory {
id
name
}
}
}
}
Example variables
{
"floorId": "a2961d46-971f-4ce1-a66e-7ee7bdb2a204"
}
Example response
{
"data": {
"floorSpaces": [
{
"id": "5c8a1d92-4b6e-4f03-9a71-2d4e6f8a0b13",
"name": "Desk NE-101",
"capacity": 1,
"spaceType": {
"id": "7d2e4f60-8a91-4c23-b5d6-0e1f2a3b4c56",
"name": "Desk",
"globalSpaceCategory": {
"id": "3b9c7e14-2d58-4a6f-8b70-9c1d2e3f4a5b",
"name": "Workstation"
}
}
}
]
}
}