Analytics & AI
Face Recognition & People Counting
Enroll and manage known people, query face-match events, tune the matching threshold, and pull camera people-counting analytics through the Rhombus API.
On this page
- Overview
- Prerequisites
- Enroll and manage known people
- Create a person
- Upload a face image
- Poll the upload transaction
- Label a person
- List people and matchmakers
- Update or remove people and faces
- Query face-match events
- Search filter
- Page request
- Interpreting a face event
- Tune matching and correct events
- Read and update the matching threshold
- Correct a misidentified event
- People-counting analytics
- Count time series
- Occupancy counts over time
- Reference: report enums
- Use cases
- Troubleshooting
- Next steps
Overview
This guide covers two distinct camera analytics capabilities that are easy to confuse:
- Face Recognition (identity) — enroll known people from face images, then receive face-match events when a camera detects a matching face. Each event carries a confidence score and the best-matching person. Use this to recognize who appears on camera.
- People counting (headcount) — aggregate, non-identifying camera analytics that count how many people cross a camera's field of view over time. These come from the Report Webservice as count time series and occupancy counts. No identity is involved.
Through the API you can:
- Enroll and manage people by creating person records and uploading face images
- Query face-match events with rich filters and pagination
- Tune the matching threshold and correct misidentified events
- Pull people-count analytics as time series or the most recent counts per camera
Not the same as occupancy sensors. Rhombus also offers physical occupancy sensors (Bluetooth/motion hardware) that report room presence. Those are a separate product served by a different API — see the IoT Sensors guide. This page's "people counting" is camera analytics.
Biometric privacy. Face recognition processes biometric data. Many jurisdictions regulate the collection, storage, and use of biometric identifiers, and some require notice or consent. Review the biometric-privacy laws that apply to your locations before enrolling faces, and confirm your organization's policies. This guide describes only the API surface — it is not legal advice.
Prerequisites
Before you begin, make sure you have:
- A Rhombus API key. Enrolling or managing faces requires face-management permissions; reading people-count reports requires report read access. Generate and scope keys in the Rhombus Console under Settings → Integrations & Developer Resources → API Tokens.
- At least one camera with the relevant analytics enabled (face recognition for identity workflows, people counting for headcount analytics).
- Face Recognition is a licensed, gated feature. If the face endpoints are unavailable for your organization, contact your Rhombus account team to confirm entitlement.
All requests are POST to https://api2.rhombussystems.com and authenticate with the x-auth-scheme and x-auth-apikey headers. Timestamps are epoch milliseconds unless noted, and match confidence is a float between 0 and 1.
Enroll and manage known people
A person is a named identity record. A matchmaker is an enrolled face template attached to a person; the recognition engine compares detected faces against your matchmakers.
Create a person
Create a named person record to enroll against.
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/person/createPerson",
headers=headers,
json={"name": "Jordan Rivera"}
)
person = response.json().get("person", {})
print(f"Created person {person.get('name')} -> {person.get('uuid')}")const response = await fetch('https://api2.rhombussystems.com/api/faceRecognition/person/createPerson', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({ name: 'Jordan Rivera' })
});
const { person } = await response.json();
console.log(`Created person ${person.name} -> ${person.uuid}`);curl -X POST https://api2.rhombussystems.com/api/faceRecognition/person/createPerson \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{"name": "Jordan Rivera"}'Display name for the person.
The response returns a person object:
Upload a face image
Enroll a face by uploading a .jpg or .png image (maximum 5 MB) as multipart/form-data. The file must be sent under the form field name file. Two optional query parameters control the transaction:
transaction— a transaction ID you supply to track and later poll the upload. If omitted, the server generates one and returns it.createPersonIfNotFound— set totrueto have the system create a new person automatically when the uploaded face does not match an existing one.
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY"
# Do NOT set Content-Type manually; requests sets the multipart boundary
}
with open("jordan_rivera.jpg", "rb") as f:
files = {"file": ("jordan_rivera.jpg", f, "image/jpeg")}
response = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/matchmaker/uploadFaceMatchmakers",
headers=headers,
params={"transaction": "enroll-jordan-001", "createPersonIfNotFound": "true"},
files=files
)
data = response.json()
print(f"Transaction: {data.get('transactionId')}")
for result in data.get("fileUploadResults", []):
print(f" {result.get('fileName')}: success={result.get('success')} {result.get('message', '')}")import fs from 'fs';
const form = new FormData();
const buffer = fs.readFileSync('jordan_rivera.jpg');
form.append('file', new Blob([buffer], { type: 'image/jpeg' }), 'jordan_rivera.jpg');
const url = new URL('https://api2.rhombussystems.com/api/faceRecognition/matchmaker/uploadFaceMatchmakers');
url.searchParams.set('transaction', 'enroll-jordan-001');
url.searchParams.set('createPersonIfNotFound', 'true');
const response = await fetch(url, {
method: 'POST',
headers: {
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: form
});
const data = await response.json();
console.log(`Transaction: ${data.transactionId}`);
for (const result of data.fileUploadResults || []) {
console.log(` ${result.fileName}: success=${result.success} ${result.message || ''}`);
}curl -X POST "https://api2.rhombussystems.com/api/faceRecognition/matchmaker/uploadFaceMatchmakers?transaction=enroll-jordan-001&createPersonIfNotFound=true" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-F "file=@jordan_rivera.jpg;type=image/jpeg"Upload one image per request. Send exactly one file part, keep it under 5 MB, and use .jpg or .png. Let your HTTP client set the Content-Type: multipart/form-data boundary — do not set it by hand.
The response returns:
fileName, success (boolean), and message.Poll the upload transaction
Face images process asynchronously. Poll the transaction to learn the resulting faceId and the personUuid the face was enrolled or linked to.
import requests
import time
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
for _ in range(10):
response = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/matchmaker/findFaceUploadMetadataByTransaction",
headers=headers,
json={"transactionId": "enroll-jordan-001"}
)
metadata = response.json().get("faceUploadMetadata", [])
if metadata and all(m.get("success") is not None for m in metadata):
for m in metadata:
print(f"faceId={m.get('faceId')} personUuid={m.get('personUuid')} "
f"success={m.get('success')} {m.get('errorMsg', '')}")
break
time.sleep(2)const headers = {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
};
for (let i = 0; i < 10; i++) {
const response = await fetch('https://api2.rhombussystems.com/api/faceRecognition/matchmaker/findFaceUploadMetadataByTransaction', {
method: 'POST',
headers,
body: JSON.stringify({ transactionId: 'enroll-jordan-001' })
});
const { faceUploadMetadata = [] } = await response.json();
if (faceUploadMetadata.length && faceUploadMetadata.every(m => m.success != null)) {
for (const m of faceUploadMetadata) {
console.log(`faceId=${m.faceId} personUuid=${m.personUuid} success=${m.success} ${m.errorMsg || ''}`);
}
break;
}
await new Promise(r => setTimeout(r, 2000));
}curl -X POST https://api2.rhombussystems.com/api/faceRecognition/matchmaker/findFaceUploadMetadataByTransaction \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{"transactionId": "enroll-jordan-001"}'Each entry in faceUploadMetadata includes:
| Field | Type | Description |
|---|---|---|
transactionId | string | The transaction this upload belongs to |
faceId | string | Identifier of the enrolled face matchmaker (once processed) |
personUuid | string | Person the face was enrolled or linked to |
success | boolean | Whether processing succeeded |
errorMsg | string | Error detail when success is false |
createdAtMillis | integer | Upload timestamp in epoch milliseconds |
origS3Key | string | Storage key for the original uploaded image |
Prefer to enroll a person from a face the camera already captured? Use createFaceMatchmakerFromSighting with the faceEventUuid of an existing face event and the target personUuid to turn that sighting into an enrolled template — no upload required.
Label a person
Attach labels (for example, employee or visitor) to organize people and filter face events later.
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/person/addPersonLabel",
headers=headers,
json={"personUuid": "YOUR_PERSON_UUID", "label": "employee"}
)
print(response.json())const response = await fetch('https://api2.rhombussystems.com/api/faceRecognition/person/addPersonLabel', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({ personUuid: 'YOUR_PERSON_UUID', label: 'employee' })
});
console.log(await response.json());curl -X POST https://api2.rhombussystems.com/api/faceRecognition/person/addPersonLabel \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{"personUuid": "YOUR_PERSON_UUID", "label": "employee"}'Use removePersonLabel with the same body to detach a label, and findPersonLabelsByOrg (empty body {}) to retrieve a labelsByPerson map of every person's labels.
List people and matchmakers
List every enrolled person with findPeopleByOrg, and list enrolled face templates with findFaceMatchmakersByOrg (or findFaceMatchmakersByPerson for one person).
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
people = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/person/findPeopleByOrg",
headers=headers, json={}
).json().get("people", [])
for person in people:
print(f"{person.get('name')} ({person.get('uuid')})")
matchmakers = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/matchmaker/findFaceMatchmakersByOrg",
headers=headers, json={}
).json().get("faceMatchmakers", [])
for mm in matchmakers:
print(f"faceId={mm.get('id')} personUuid={mm.get('personUuid')} uploaded={mm.get('uploaded')}")const headers = {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
};
const people = await (await fetch('https://api2.rhombussystems.com/api/faceRecognition/person/findPeopleByOrg', {
method: 'POST', headers, body: '{}'
})).json();
for (const person of people.people || []) {
console.log(`${person.name} (${person.uuid})`);
}
const matchmakers = await (await fetch('https://api2.rhombussystems.com/api/faceRecognition/matchmaker/findFaceMatchmakersByOrg', {
method: 'POST', headers, body: '{}'
})).json();
for (const mm of matchmakers.faceMatchmakers || []) {
console.log(`faceId=${mm.id} personUuid=${mm.personUuid} uploaded=${mm.uploaded}`);
}curl -X POST https://api2.rhombussystems.com/api/faceRecognition/person/findPeopleByOrg \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{}'
curl -X POST https://api2.rhombussystems.com/api/faceRecognition/matchmaker/findFaceMatchmakersByOrg \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{}'Each face matchmaker includes id (the face ID), personUuid, orgUuid, uploaded (boolean), and createdOn.
Update or remove people and faces
| Endpoint | Body | Purpose |
|---|---|---|
/api/ | { | Fetch one person |
/api/ | { | Update name/email (selective) |
/api/ | { | Delete a person |
/api/ | { | Fetch one enrolled face |
/api/ | { | Remove an enrolled face template |
To manage retention and deletion of biometric data, use deletePerson (removes the person and their enrollment) and deleteFaceMatchmaker (removes a single enrolled face). Face events can be removed individually with deleteFaceEvent (below).
Query face-match events
When a camera detects a face, it generates a face event. Retrieve events with findFaceEventsByOrg, which takes a searchFilter and a pageRequest.
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/faceRecognition/faceEvent/findFaceEventsByOrg",
headers=headers,
json={
"searchFilter": {
"hasName": True,
"timestampFilter": {
"rangeStart": str(one_day_ago_ms),
"rangeEnd": str(now_ms)
}
},
"pageRequest": {"maxPageSize": 50}
}
)
data = response.json()
for event in data.get("faceEvents", []):
match = event.get("selectedPersonMatch") or {}
print(f"{event.get('faceName')} on {event.get('deviceUuid')} "
f"confidence={match.get('confidence')} at {event.get('eventTimestamp')}")
# Fetch the next page if lastEvaluatedKey is present
next_key = data.get("lastEvaluatedKey")const headers = {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
};
const now = Date.now();
const oneDayAgo = now - (24 * 60 * 60 * 1000);
const response = await fetch('https://api2.rhombussystems.com/api/faceRecognition/faceEvent/findFaceEventsByOrg', {
method: 'POST',
headers,
body: JSON.stringify({
searchFilter: {
hasName: true,
timestampFilter: {
rangeStart: String(oneDayAgo),
rangeEnd: String(now)
}
},
pageRequest: { maxPageSize: 50 }
})
});
const data = await response.json();
for (const event of data.faceEvents || []) {
const match = event.selectedPersonMatch || {};
console.log(`${event.faceName} on ${event.deviceUuid} confidence=${match.confidence} at ${event.eventTimestamp}`);
}
const nextKey = data.lastEvaluatedKey;curl -X POST https://api2.rhombussystems.com/api/faceRecognition/faceEvent/findFaceEventsByOrg \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"searchFilter": {
"hasName": true,
"timestampFilter": {
"rangeStart": "1712620800000",
"rangeEnd": "1712707200000"
}
},
"pageRequest": { "maxPageSize": 50 }
}'Search filter
All searchFilter fields are optional. When timestampFilter is omitted, the search defaults to the last 7 days.
faceNames if both are set.true) or absence (false) of a matched person name.rangeStart and rangeEnd, each a string containing an epoch-millisecond value (inclusive). Defaults to the last 7 days.Page request
lastEvaluatedKey from the previous response to fetch the next page. Absent when there are no more results.Interpreting a face event
Each event in faceEvents is an object with these fields:
name of selectedPersonMatch when a match was selected.uuid, name, faceId, and confidence (0–1). Null when no match was selected.uuid, name, faceId, and confidence (0–1). Useful for reviewing near-matches.Fetch a single event by its UUID with getFaceEvent (body { "eventUuid": "..." }).
Tune matching and correct events
Read and update the matching threshold
The matching threshold controls how strict face matching is. It is a confidence value between 0 and 1 — a higher value requires a closer match (fewer false positives, more misses); a lower value is more permissive.
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
# Read current threshold
current = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/matchmaker/getFaceMatchingConfig",
headers=headers, json={}
).json()
print(f"Current threshold: {current.get('faceMatchConfidenceThreshold')}")
# Raise the threshold to reduce false positives
updated = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/matchmaker/updateFaceMatchingConfig",
headers=headers,
json={"faceMatchConfidenceThreshold": 0.8}
).json()
print(f"New threshold: {updated.get('faceMatchConfidenceThreshold')}")const headers = {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
};
const current = await (await fetch('https://api2.rhombussystems.com/api/faceRecognition/matchmaker/getFaceMatchingConfig', {
method: 'POST', headers, body: '{}'
})).json();
console.log(`Current threshold: ${current.faceMatchConfidenceThreshold}`);
const updated = await (await fetch('https://api2.rhombussystems.com/api/faceRecognition/matchmaker/updateFaceMatchingConfig', {
method: 'POST', headers, body: JSON.stringify({ faceMatchConfidenceThreshold: 0.8 })
})).json();
console.log(`New threshold: ${updated.faceMatchConfidenceThreshold}`);curl -X POST https://api2.rhombussystems.com/api/faceRecognition/matchmaker/getFaceMatchingConfig \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{}'
curl -X POST https://api2.rhombussystems.com/api/faceRecognition/matchmaker/updateFaceMatchingConfig \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{"faceMatchConfidenceThreshold": 0.8}'Correct a misidentified event
Reassign a face event to the correct person (or set a name directly) with updateFaceEvent. This is how you fix false matches.
import requests
headers = {
"x-auth-scheme": "api-token",
"x-auth-apikey": "YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(
"https://api2.rhombussystems.com/api/faceRecognition/faceEvent/updateFaceEvent",
headers=headers,
json={
"eventUuid": "YOUR_FACE_EVENT_UUID",
"personUuid": "CORRECT_PERSON_UUID"
}
)
print(response.json())const response = await fetch('https://api2.rhombussystems.com/api/faceRecognition/faceEvent/updateFaceEvent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-scheme': 'api-token',
'x-auth-apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
eventUuid: 'YOUR_FACE_EVENT_UUID',
personUuid: 'CORRECT_PERSON_UUID'
})
});
console.log(await response.json());curl -X POST https://api2.rhombussystems.com/api/faceRecognition/faceEvent/updateFaceEvent \
-H "Content-Type: application/json" \
-H "x-auth-scheme: api-token" \
-H "x-auth-apikey: YOUR_API_KEY" \
-d '{
"eventUuid": "YOUR_FACE_EVENT_UUID",
"personUuid": "CORRECT_PERSON_UUID"
}'personUuid matches one of the event's top person matches.To remove an event entirely, call deleteFaceEvent with { "eventUuid": "..." }.
People-counting analytics
People counting is aggregate camera analytics — it counts people without identifying them, served by the Report Webservice. Use it for footfall, occupancy, and trend dashboards.
Count time series
getCountReportV2 returns a time series of counts bucketed by interval. For people counting, set types to ["PEOPLE"].
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)
week_ago_ms = now_ms - (7 * 24 * 60 * 60 * 1000)
response = requests.post(
"https://api2.rhombussystems.com/api/report/getCountReportV2",
headers=headers,
json={
"startTimeMs": week_ago_ms,
"endTimeMs": now_ms,
"interval": "DAILY",
"scope": "DEVICE",
"types": ["PEOPLE"],
"uuid": "YOUR_CAMERA_UUID",
"timeZone": "America/Los_Angeles"
}
)
data = response.json()
for point in data.get("timeSeriesDataPoints", []):
counts = point.get("eventCountMap", {})
print(f"{point.get('dateLocal')}: {counts.get('PEOPLE', 0)} people")const now = Date.now();
const weekAgo = now - (7 * 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({
startTimeMs: weekAgo,
endTimeMs: now,
interval: 'DAILY',
scope: 'DEVICE',
types: ['PEOPLE'],
uuid: 'YOUR_CAMERA_UUID',
timeZone: 'America/Los_Angeles'
})
});
const data = await response.json();
for (const point of data.timeSeriesDataPoints || []) {
const counts = point.eventCountMap || {};
console.log(`${point.dateLocal}: ${counts.PEOPLE || 0} people`);
}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 '{
"startTimeMs": 1712016000000,
"endTimeMs": 1712620800000,
"interval": "DAILY",
"scope": "DEVICE",
"types": ["PEOPLE"],
"uuid": "YOUR_CAMERA_UUID",
"timeZone": "America/Los_Angeles"
}'MINUTELY, QUARTERHOURLY, HOURLY, DAILY, WEEKLY, MONTHLY.REGION, DEVICE, LOCATION, ORG.["PEOPLE"] for people counting.scope is DEVICE). Omit for ORG scope.America/Los_Angeles).Each entry in timeSeriesDataPoints includes:
{ "PEOPLE": 42 }).getCountReportV2 supports many types beyond PEOPLE — see the Reports & Analytics guide for the full report model, additional analytics endpoints, and CSV export.
Occupancy counts over time
getOccupancyCountsV2 returns a time series of camera-derived occupancy counts for a single camera.
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)
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": day_ago_ms,
"endTimeMs": now_ms,
"interval": "HOURLY"
}
)
for point in response.json().get("timeSeriesDataPoints", []):
print(f"{point.get('dateLocal')}: {point.get('eventCountMap')}")const now = Date.now();
const dayAgo = 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: dayAgo,
endTimeMs: now,
interval: 'HOURLY'
})
});
const data = await response.json();
for (const point of data.timeSeriesDataPoints || []) {
console.log(`${point.dateLocal}:`, 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": 1712534400000,
"endTimeMs": 1712620800000,
"interval": "HOURLY"
}'MINUTELY, QUARTERHOURLY, HOURLY, DAILY, WEEKLY, MONTHLY).Each data point adds timestampMs and approximateTimestampMsMap to the standard dateUtc, dateLocal, eventCountMap, and reportingDevicesMap fields.
This occupancy report is camera-derived. For room presence measured by physical Bluetooth/motion occupancy sensors, see the IoT Sensors guide — that is a separate hardware product with its own API.
Reference: report enums
| Enum | Values |
|---|---|
| Report type | CROWD, PEOPLE, FACES, MOTION, BANDWIDTH, VEHICLES, LICENSEPLATES, ALERTS, AM_VERIFICATION, DWELL |
| Interval | MINUTELY, QUARTERHOURLY, HOURLY, DAILY, WEEKLY, MONTHLY |
| Scope | REGION, DEVICE, LOCATION, ORG |
Use cases
- Watchlist alerting — enroll people of interest, then poll
findFaceEventsByOrgfiltered bypersonUuidsorlabelsto flag sightings. - Access reconciliation — cross-reference face events with access-control activity to confirm the person who badged in matches who the camera saw.
- Model quality loop — review
topPersonMatcheson low-confidence events, correct them withupdateFaceEvent, and tunefaceMatchConfidenceThresholdto balance false positives against misses. - Footfall dashboards — chart
getCountReportV2withtypes: ["PEOPLE"]for daily or hourly traffic trends per camera or location. - Occupancy tiles — chart
getOccupancyCountsV2for a camera to show occupancy through the day.
Troubleshooting
Face endpoints return an error or are unavailable
Face Recognition is a licensed, gated feature. Confirm your organization is entitled and that your API key has face-management permissions. Contact your Rhombus account team if the endpoints are not available.
uploadFaceMatchmakers rejects my file
Send exactly one file per request under the form field name file, keep it under 5 MB, and use .jpg or .png. Let your HTTP client set the multipart Content-Type boundary — do not set Content-Type manually.
findFaceUploadMetadataByTransaction returns nothing yet
Uploads process asynchronously. Poll the transaction until each entry reports a success value. When success is false, read errorMsg for the reason.
No face events come back
If you omit timestampFilter, the search only covers the last 7 days. Widen the window with rangeStart/rangeEnd (epoch-millisecond strings), and loosen filters such as hasName or personUuids. Remember to page with lastEvaluatedKey.
Too many false matches (or too few matches)
Adjust faceMatchConfidenceThreshold with updateFaceMatchingConfig. Raise it (closer to 1) to reduce false positives; lower it to catch more potential matches. Correct individual mistakes with updateFaceEvent.
Next steps
Full count-report model, threshold-crossing counts, and CSV export
Detect and search license plates and vehicles across your cameras
Physical occupancy, climate, and environmental sensors
Browse and test every Rhombus API endpoint