Analytics & AI
Reports & Analytics
Pull count and time-series analytics from the Rhombus API — people and vehicle counts, occupancy trends, line-crossing (threshold) counts, and CSV export.
On this page
- Overview
- Prerequisites
- Build a count time-series
- Request fields
- Response fields
- Legacy date-string reports
- Build a people-count and occupancy dashboard
- Occupancy trend over time
- Count line crossings (threshold counts)
- Request fields
- Response fields
- Export report data as CSV
- Enum reference
- ReportType
- ReportInterval
- ReportScope
- CrossingObject
- Use cases
- Advanced: AI Scene Query analytics
- Troubleshooting
- Next steps
Overview
The Rhombus Report Webservice turns detections from your cameras into aggregated, time-bucketed analytics. Instead of iterating over individual events, you request a count report for a time range and interval, and Rhombus returns one data point per bucket with a per-type count.
Through the API, you can:
- Build count time-series for people, vehicles, motion, faces, and other detection types across an org, location, or single device
- Track people counts and occupancy with the latest readings and bucketed occupancy trends
- Measure line-crossing (threshold) counts for humans or vehicles that cross a configured line
- Export report data as CSV for spreadsheets, BI tools, and compliance reporting
A few conventions apply across this API:
- Timestamps are epoch milliseconds on the V2, occupancy, and threshold endpoints (
startTimeMs/endTimeMs). The legacy v1getCountReportuses"YYYY-MM-DD"date strings instead. - Reports are described by three enums — a report type (what to count), an interval (bucket size), and a scope (org, location, or device). See the reference tables below.
- There is no "list report types" endpoint. The available types are fixed and documented in the
ReportTypetable.
Prerequisites
Before you begin, make sure you have:
- A Rhombus API key with report permissions (generated in the Rhombus Console under Settings → Integrations & Developer Resources → API Tokens)
- At least one camera deployed and reporting the analytics you want to query (for example, a camera running people counting or LPR)
- The UUID of the org, location, or device you want to scope the report to
- For CSV export and audit/diagnostic feeds: the
REPORT_ADMINISTRATIONpermission on the API key
All requests are POST, send a JSON body, and use the base URL https://api2.rhombussystems.com with these headers:
x-auth-scheme: api-token
x-auth-apikey: YOUR_API_KEY
Content-Type: application/jsonBuild a count time-series
getCountReportV2 is the primary reporting endpoint. Supply a time range in epoch milliseconds, an interval, a scope, and one or more report types. Rhombus returns one data point per interval bucket, each carrying an eventCountMap keyed by report type.
This example pulls hourly people and vehicle counts for a single location over the last 24 hours.
import requests
import time
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
now_ms = int(time.time() * 1000)
one_day_ago_ms = now_ms - (24 * 60 * 60 * 1000)
response = requests.post(
"https://api2.rhombussystems.com/api/report/getCountReportV2",
headers=headers,
json={
"scope": "LOCATION",
"uuid": "YOUR_LOCATION_UUID",
"types": ["PEOPLE", "VEHICLES"],
"interval": "HOURLY",
"startTimeMs": one_day_ago_ms,
"endTimeMs": now_ms,
"timeZone": "America/Los_Angeles"
}
)
data = response.json()
for point in data.get("timeSeriesDataPoints", []):
counts = point.get("eventCountMap", {})
print(f"{point.get('dateLocal')}: "
f"people={counts.get('PEOPLE', 0)}, "
f"vehicles={counts.get('VEHICLES', 0)}")const now = Date.now();
const oneDayAgo = now - (24 * 60 * 60 * 1000);
const response = await fetch('https://api2.rhombussystems.com/api/report/getCountReportV2', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
scope: 'LOCATION',
uuid: 'YOUR_LOCATION_UUID',
types: ['PEOPLE', 'VEHICLES'],
interval: 'HOURLY',
startTimeMs: oneDayAgo,
endTimeMs: now,
timeZone: 'America/Los_Angeles'
})
});
const data = await response.json();
for (const point of data.timeSeriesDataPoints || []) {
const counts = point.eventCountMap || {};
console.log(`${point.dateLocal}: people=${counts.PEOPLE || 0}, vehicles=${counts.VEHICLES || 0}`);
}curl -X POST https://api2.rhombussystems.com/api/report/getCountReportV2 \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"scope": "LOCATION",
"uuid": "YOUR_LOCATION_UUID",
"types": ["PEOPLE", "VEHICLES"],
"interval": "HOURLY",
"startTimeMs": 1712620800000,
"endTimeMs": 1712707200000,
"timeZone": "America/Los_Angeles"
}'Request fields
Aggregation level. One of ORG, LOCATION, DEVICE, or REGION. See the ReportScope table.
The location or device UUID that matches your scope. Omit for scope: "ORG" to report across the whole organization.
One or more report types to count. See the ReportType table.
Bucket size for each data point. One of MINUTELY, QUARTERHOURLY, HOURLY, DAILY, WEEKLY, MONTHLY. See the ReportInterval table.
Start of the range, in epoch milliseconds.
End of the range, in epoch milliseconds.
Optional IANA time zone (for example, America/New_York). Controls where interval bucket boundaries fall and populates dateLocal. Defaults to UTC when omitted.
The V2 endpoint accepts legacy startDate / endDate string fields, but these are deprecated. Use the epoch-millisecond startTimeMs / endTimeMs fields instead.
Response fields
One entry per interval bucket.
Need a single total instead of a bucketed series? Call /api/report/getSummaryCountReport with the same startTimeMs, endTimeMs, interval, scope, and a single type.
Legacy date-string reports
The v1 /api/report/getCountReport endpoint predates epoch-millisecond timestamps. It takes startDate and endDate as "YYYY-MM-DD" strings and a single type rather than a types array. Prefer getCountReportV2 for new integrations; use v1 only if you already depend on its date-string behavior.
curl -X POST https://api2.rhombussystems.com/api/report/getCountReport \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"scope": "LOCATION",
"uuid": "YOUR_LOCATION_UUID",
"type": "PEOPLE",
"interval": "DAILY",
"startDate": "2024-01-01",
"endDate": "2024-01-31"
}'Build a people-count and occupancy dashboard
For an occupancy dashboard, use getOccupancyCountsV2 for a bucketed occupancy trend over time, and chart getCountReportV2 with types: ["PEOPLE"] alongside it for foot traffic (see Build a count time-series).
Occupancy trend over time
getOccupancyCountsV2 returns bucketed occupancy for a device across a time range, using the same interval model as count reports.
import requests
import time
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
now_ms = int(time.time() * 1000)
one_day_ago_ms = now_ms - (24 * 60 * 60 * 1000)
response = requests.post(
"https://api2.rhombussystems.com/api/report/getOccupancyCountsV2",
headers=headers,
json={
"deviceUuid": "YOUR_CAMERA_UUID",
"startTimeMs": one_day_ago_ms,
"endTimeMs": now_ms,
"interval": "HOURLY"
}
)
data = response.json()
for point in data.get("timeSeriesDataPoints", []):
print(f"{point.get('dateLocal')}: {point.get('eventCountMap')}")const now = Date.now();
const oneDayAgo = now - (24 * 60 * 60 * 1000);
const response = await fetch('https://api2.rhombussystems.com/api/report/getOccupancyCountsV2', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
deviceUuid: 'YOUR_CAMERA_UUID',
startTimeMs: oneDayAgo,
endTimeMs: now,
interval: 'HOURLY'
})
});
const data = await response.json();
for (const point of data.timeSeriesDataPoints || []) {
console.log(`${point.dateLocal}: ${JSON.stringify(point.eventCountMap)}`);
}curl -X POST https://api2.rhombussystems.com/api/report/getOccupancyCountsV2 \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"deviceUuid": "YOUR_CAMERA_UUID",
"startTimeMs": 1712620800000,
"endTimeMs": 1712707200000,
"interval": "HOURLY"
}'The response timeSeriesDataPoints carry the same dateUtc, dateLocal, eventCountMap, and reportingDevicesMap fields as count reports, plus a timestampMs (bucket start in epoch milliseconds) and an approximateTimestampMsMap.
This occupancy report is derived from camera analytics via the Report Webservice. It is distinct from the Occupancy Webservice (/api/occupancy/*), which reads physical BLE and motion sensors — a separate product.
Count line crossings (threshold counts)
When a camera has a crossing line configured, Rhombus counts objects that cross it. getThresholdCrossingCounts returns those counts bucketed over time for one or more devices, filtered to a single object class.
The dailyResetTimeMinute field sets the minute of the day (0–1439, in the device's local time) at which the running daily count resets — useful for aligning counts to a business day rather than midnight.
import requests
import time
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
now_ms = int(time.time() * 1000)
one_week_ago_ms = now_ms - (7 * 24 * 60 * 60 * 1000)
response = requests.post(
"https://api2.rhombussystems.com/api/report/getThresholdCrossingCounts",
headers=headers,
json={
"devices": ["YOUR_CAMERA_UUID"],
"startTimeMs": one_week_ago_ms,
"endTimeMs": now_ms,
"crossingObject": "HUMAN",
"dailyResetTimeMinute": 0
}
)
data = response.json()
for entry in data.get("counts", []):
print(f"[{entry.get('timestampMs')}] crossings={entry.get('count')}")const now = Date.now();
const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
const response = await fetch('https://api2.rhombussystems.com/api/report/getThresholdCrossingCounts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
devices: ['YOUR_CAMERA_UUID'],
startTimeMs: oneWeekAgo,
endTimeMs: now,
crossingObject: 'HUMAN',
dailyResetTimeMinute: 0
})
});
const data = await response.json();
for (const entry of data.counts || []) {
console.log(`[${entry.timestampMs}] crossings=${entry.count}`);
}curl -X POST https://api2.rhombussystems.com/api/report/getThresholdCrossingCounts \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"devices": ["YOUR_CAMERA_UUID"],
"startTimeMs": 1712016000000,
"endTimeMs": 1712620800000,
"crossingObject": "HUMAN",
"dailyResetTimeMinute": 0
}'Request fields
One or more camera UUIDs to include in the counts.
Start of the range, in epoch milliseconds.
End of the range, in epoch milliseconds.
The object class to count. Common values are HUMAN and VEHICLE. See the CrossingObject table.
Minute of the day (0–1439) at which the daily count resets, in the device's local time.
Response fields
Export report data as CSV
The Export Webservice streams report data as CSV (text/csv) rather than JSON. There is no PDF export — CSV is the only export format.
Export endpoints (and the audit/diagnostic feeds) require the REPORT_ADMINISTRATION permission on your API key. Without it, these calls are rejected.
/api/export/countReports mirrors the count report parameters but returns a CSV stream. Note that export takes a single type field, not a types array, and both startTimeMs and endTimeMs are required.
import requests
import time
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
now_ms = int(time.time() * 1000)
thirty_days_ago_ms = now_ms - (30 * 24 * 60 * 60 * 1000)
response = requests.post(
"https://api2.rhombussystems.com/api/export/countReports",
headers=headers,
json={
"scope": "LOCATION",
"uuidList": ["YOUR_LOCATION_UUID"],
"type": "PEOPLE",
"interval": "DAILY",
"startTimeMs": thirty_days_ago_ms,
"endTimeMs": now_ms,
"timeZone": "America/Los_Angeles"
}
)
# Response is CSV text, not JSON
with open("people_count_report.csv", "w") as f:
f.write(response.text)
print("Exported report to people_count_report.csv")const now = Date.now();
const thirtyDaysAgo = now - (30 * 24 * 60 * 60 * 1000);
const response = await fetch('https://api2.rhombussystems.com/api/export/countReports', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
scope: 'LOCATION',
uuidList: ['YOUR_LOCATION_UUID'],
type: 'PEOPLE',
interval: 'DAILY',
startTimeMs: thirtyDaysAgo,
endTimeMs: now,
timeZone: 'America/Los_Angeles'
})
});
// Response is CSV text, not JSON
const csv = await response.text();
console.log(csv);curl -X POST https://api2.rhombussystems.com/api/export/countReports \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"scope": "LOCATION",
"uuidList": ["YOUR_LOCATION_UUID"],
"type": "PEOPLE",
"interval": "DAILY",
"startTimeMs": 1710028800000,
"endTimeMs": 1712620800000,
"timeZone": "America/Los_Angeles"
}' \
-o people_count_report.csvBecause the response body is CSV, read it as text (response.text / .text()) or write it straight to a file with the -o flag in cURL. Do not attempt to parse it as JSON.
To export raw people-count readings instead of aggregated reports, use /api/export/peopleCountEvents with a startInterval and endInterval (epoch milliseconds). It returns CSV the same way.
Enum reference
Reports are described by a small set of fixed enums. There is no endpoint that lists these at runtime — the values below are the complete set.
ReportType
Values accepted by the types array (V2) and single type field (v1 and export).
| Value | Counts |
|---|---|
CROWD | Crowd-density detections |
PEOPLE | People detections / people counting |
FACES | Face detections |
MOTION | Motion events |
BANDWIDTH | Device bandwidth usage |
VEHICLES | Vehicle detections |
LICENSEPLATES | License-plate reads |
ALERTS | Alert / policy events |
AM_VERIFICATION | Alarm-monitoring verifications |
DWELL | Dwell-time detections |
ReportInterval
Bucket size for each data point.
| Value | Bucket |
|---|---|
MINUTELY | Per minute |
QUARTERHOURLY | Per 15 minutes |
HOURLY | Per hour |
DAILY | Per day |
WEEKLY | Per week |
MONTHLY | Per month |
ReportScope
Aggregation level for a count report. Provide a matching uuid for LOCATION, DEVICE, or REGION; omit it for ORG.
| Value | Aggregates over |
|---|---|
ORG | The entire organization |
LOCATION | A single location |
DEVICE | A single device |
REGION | A configured region |
CrossingObject
Object class for getThresholdCrossingCounts.
| Value | Object |
|---|---|
HUMAN | People |
VEHICLE | Vehicles |
FACE | Faces |
LPR | License plates |
POSE | Pose detections |
CLIP_EMBED | Visual-embedding matches |
UNKNOWN | Unclassified |
Use cases
- Foot-traffic dashboards — chart hourly or daily
PEOPLEcounts per location withgetCountReportV2to compare stores or track trends over time. - Occupancy monitoring — chart
getOccupancyCountsV2buckets to show occupancy through the day and compare it with earlier periods. - Entrance and lane counting — use
getThresholdCrossingCountswithcrossingObject: "HUMAN"or"VEHICLE"to count people through a doorway or cars through a lane. - Scheduled reporting — run
/api/export/countReportson a cron and drop the CSV into a BI tool or data warehouse for weekly and monthly reporting.
Advanced: AI Scene Query analytics
Rhombus also offers Scene Query, an AI feature that counts custom, natural-language-defined events (for example, "forklifts without a spotter"). Scene Query is configured through the /api/scenequery/* endpoints and reported via /api/report/getCustomEventsReport. It is a distinct, licensed capability rather than part of the core reporting workflow above — treat it as an optional extension when standard report types don't capture what you need to measure.
Troubleshooting
Empty timeSeriesDataPoints or all-zero counts
Confirm that the device actually produces the analytic you requested — a camera without people counting enabled returns no PEOPLE data. Also verify your time range is in epoch milliseconds (not seconds) and that the scope/uuid pair is correct.
Buckets are offset from local time
Set the timeZone field to the relevant IANA zone (for example, America/Chicago). Without it, bucket boundaries and dateLocal fall on UTC.
Export request is rejected
CSV export requires the REPORT_ADMINISTRATION permission. Check the API key's permissions in the Rhombus Console, and remember the export body uses a single type field rather than the types array used by getCountReportV2.
Export response looks like garbage JSON
The export endpoints return text/csv, not JSON. Read the response as text or write it to a file rather than calling .json().
Next steps
Enroll people, query face events, and dig deeper into people-counting analytics
Query license-plate reads and vehicle detections that feed the vehicle report types
Explore complete request and response schemas for every report and export endpoint