Articles in this section

Querying Zone Occupancy Data — Best Practices

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 RangeMax Zones (Theoretical)Recommended Batch Size
Up to 1 hour160+30
Up to 2 hours~15030
Up to 4 hours~7520
Up to 8 hours~3720
Up to 24 hours~1010

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

ScenarioMax Zones per Request
Default / general use30
Time range > 24 hours10

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>
Was this article helpful?
0 out of 0 found this helpful

Comments

0 comments

Article is closed for comments.