Skip to main content

Analytics polling

Use analytics polling when you need aggregated utilisation metrics instead of raw occupancy changes.

Requires the Analytics API feature

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 Explorer

Example 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.

Open in API Explorer

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 Explorer

Example 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"
}
}
}
]
}
}