This guide explains how to query zone occupancy data from the Butlr Reporting API and avoid empty responses caused by request size limits.
The Problem
When requesting zone_occupancy data for too many zones in a single API call, the API may return an empty response:
{
"data": {},
"page_info": {
"page": 1,
"page_item_count": 0,
"total_item_count": 0,
"total_pages": 1
}
}
This happens because the API has an internal processing time limit. Queries with many zones and/or wide time ranges require more processing, and when this limit is exceeded, the request returns no data.
The API still returns HTTP 200, but the data field will be an empty object {}. Always check whether data contains results.
Recommended Batch Size
Limit each request to 30 zones or fewer.
This batch size has been validated across common query time ranges:
| Time Range | Max Zones (Theoretical) | Recommended Batch Size |
|---|---|---|
| Up to 1 hour | 160+ | 30 |
| Up to 2 hours | ~150 | 30 |
| Up to 4 hours | ~75 | 20 |
| Up to 8 hours | ~37 | 20 |
| Up to 24 hours | ~10 | 10 |
For queries spanning 24 hours or longer, reduce the batch size to 10 zones per request.
How to Batch Requests
If you need data for more than 30 zones, split the zone list into batches and make multiple API calls.
Step 1 — Split the zone list into batches of 30
For example, if you have 100 zones:
- Batch 1: zones 1–30
- Batch 2: zones 31–60
- Batch 3: zones 61–90
- Batch 4: zones 91–100
Step 2 — Send each batch as a separate request
POST /v3/reporting
{
"group_by": {
"order": \\["zone_id"\\],
"raw": true
},
"window": {
"every": "1m",
"function": "max"
},
"filter": {
"start": "2026-04-06T05:00:00.000Z",
"stop": "2026-04-06T09:00:00.000Z",
"measurements": \\["zone_occupancy"\\],
"zones": {
"eq": \\[
"zone_abc123",
"zone_def456"
\\]
},
"value": { "gte": 0 }
}
}
Step 3 — Merge the results
Combine the data objects from each response on the client side. Each batch returns a data map keyed by zone ID, so merging is straightforward.
Code Examples
Python
import requests
ZONES = ["zone_1", "zone_2", ..., "zone_100"]
BATCH_SIZE = 30
API_URL = "<https://api.butlr.io/api/v3/reporting>"
HEADERS = {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
}
all_data = {}
for i in range(0, len(ZONES), BATCH_SIZE):
batch = ZONES[i : i + BATCH_SIZE]
body = {
"group_by": {"order": ["zone_id"], "raw": True},
"window": {"every": "1m", "function": "max"},
"filter": {
"start": "2026-04-06T05:00:00.000Z",
"stop": "2026-04-06T09:00:00.000Z",
"measurements": ["zone_occupancy"],
"zones": {"eq": batch},
"value": {"gte": 0}
}
}
resp = requests.post(API_URL, json=body, headers=HEADERS)
all_data.update(resp.json().get("data", {}))
print(f"Retrieved data for {len(all_data)} zones")
JavaScript
const ZONES = ["zone_1", "zone_2", /* ... */ "zone_100"];
const BATCH_SIZE = 30;
const API_URL = "<https://api.butlr.io/api/v3/reporting>";
async function fetchZoneOccupancy(zones, start, stop) {
const allData = {};
for (let i = 0; i < zones.length; i += BATCH_SIZE) {
const batch = zones.slice(i, i + BATCH_SIZE);
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
group_by: { order: ["zone_id"], raw: true },
window: { every: "1m", function: "max" },
filter: {
start, stop,
measurements: ["zone_occupancy"],
zones: { eq: batch },
value: { gte: 0 }
}
})
});
const result = await response.json();
Object.assign(allData, result.data || {});
}
return allData;
}
Quick Reference
| Scenario | Max Zones per Request |
|---|---|
| Default / general use | 30 |
| Time range > 24 hours | 10 |
Troubleshooting
Symptom: API returns 200 OK but data is an empty object {}.
Cause: Too many zones for the given time range.
Solution:
- Reduce the number of zones per request to 30 or fewer
- If still seeing empty responses, reduce to 10 zones
- For very long time ranges (multiple days), also consider narrowing the time window and making multiple time-based requests </aside>
Comments
Article is closed for comments.