Docs / AI Box And Video Analytics Software / API Reference
Smart Retail Counter API
The Smart Retail Counter API: store traffic from the door and floor cameras. It gives entries, passersby and the capture rate, visits and dwell per zone, the dwell heatmaps (per zone and hour, and over each camera's floor), queue length and measured waits, the visitor log, and the store's point-of-sale (POS) transactions for the conversion rate. It also sets the cameras' door and staff lines.
Personal data. Counting is anonymous: the API gives no identity and no image.
Calling the API
The same as the platform API: an API account's token as Authorization: Bearer <API_KEY>. Site and camera ids come from the platform API (GET /api/v1/sites, GET /api/v1/cameras). Each operation says who can call it and which scopes it needs. Errors, times, paging and the rate limit follow the platform API.
A Feed With Its Own Credential
The store's POS can send its transactions itself, signed with the site's POS key (Standard Webhooks signatures); an admin of the site makes the key on the dashboard.
1.0.0
Store Traffic
Headline figures, trend, zones, the zone and floor heatmaps, and queues.
Headline Numbers Of A Period
GET/sites/{id}/overview
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/overview?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/overview?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/overview?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"entries": 1284,
"exits": 1241,
"passersby": 5310,
"capture_rate": 19.5,
"visits": 1190,
"engaged_rate": 41.3,
"checkout_rate": 28.7,
"avg_dwell_min": 11.4,
"conversion": {
"value": 24.1,
"source": "pos",
"transactions": 309
},
"in_store_now": 37,
"queues_now": [
{
"zone": "Checkout",
"length": 4,
"est_wait_s": 95,
"at": "2026-11-02T10:58:00Z",
"long_queue_length": 6
}
],
"repeat_share": null,
"vip_visits": 0,
"demographics": {
"cells": [
{
"age_band": "25-34",
"gender": "female",
"visits": 214,
"share": 22.3
}
],
"unknown_visits": 231
},
"occupancy_limit": 120,
"long_queue_length": 6,
"store_hours": {
"open": "10:00",
"close": "22:00"
}
}Entries, Passersby And Visits Per Hour Or Day
GET/sites/{id}/trend
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time | |
bucket query | enum | hour, day |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/trend?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&bucket=hour" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/trend?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&bucket=hour",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/trend?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&bucket=hour", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"bucket": "hour",
"buckets": [
{
"start": "2026-11-02T08:00:00Z",
"entries": 12,
"passersby": 12,
"visits": 12,
"engaged": 12,
"checkout": 12,
"capture_rate": 12
}
]
}Visits And Dwell Per Zone
GET/sites/{id}/zones
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/zones?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/zones?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/zones?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"zones": [
{
"zone": "Loading bay",
"kind": "default",
"visits": 12,
"avg_dwell_s": 60,
"total_dwell_min": 12
}
]
}Dwell Minutes Per Zone And Local Hour
GET/sites/{id}/heatmap
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/heatmap?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/heatmap?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/heatmap?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"zones": [
"Loading bay"
],
"hours": 12,
"dwell_min": [
[
12
]
]
}One Camera's Floor Heatmap: Seconds Per Cell Of The Grid Over Its Floor Area
GET/sites/{id}/floor-heatmap
The site's cameras with a floor area (still on the site) and, for camera (else the first), its floor area as last reported: the grid size, the polygon (normalised frame coordinates) and the seconds shoppers stood in each cell, added up over the hours that overlap the period (seconds[row][col], 0 for none; cells of the grid's box outside the polygon stay 0). Anonymous: no track, no image. floor is null when no camera of the site has a floor area. 404 floor_not_found: the camera has none on this site.
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time | |
camera query | integer | The camera (one of cameras); default: the first |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/floor-heatmap?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&camera=1" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/floor-heatmap?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&camera=1",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/floor-heatmap?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&camera=1", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"cameras": [
{
"camera": 12,
"zone": "Loading bay",
"name": "Main gate"
}
],
"floor": {
"camera": 12,
"zone": "Loading bay",
"name": "Main gate",
"cols": 12,
"rows": 12,
"polygon": [
[
12,
12
]
],
"seconds": [
[
12
]
],
"total_s": 60,
"max_s": 60,
"last_report": "2026-11-02T08:00:00Z"
}
}Queue Length And Measured Wait Per Hour Or Day
GET/sites/{id}/queues
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time | |
bucket query | enum | hour, day |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/queues?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&bucket=hour" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/queues?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&bucket=hour",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/queues?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&bucket=hour", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"bucket": "hour",
"samples": [
{
"start": "2026-11-02T08:00:00Z",
"zone": "Loading bay",
"avg_length": 12,
"max_length": 12,
"avg_wait_s": 60
}
]
}How Often Visits Are Followed Across Cameras, Per Hour And In Total
GET/sites/{id}/journey-stats
A visit is followed across the store's cameras by the shopper's appearance: the door camera opens it, a track on a camera without a door joins it when it looks like the same shopper, and the door closes it (or it ends 20 minutes after the last sighting). These counts show how often that joining happens: joined_share is the share of the tracks on cameras without a door that joined a visit; the others became visits of their own, seen only inside. Counted per UTC hour. Anonymous: counts only.
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/journey-stats?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/journey-stats?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/journey-stats?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"from": "2026-11-02T08:00:00Z",
"to": "2026-11-02T08:00:00Z",
"totals": {
"opened": 12,
"joined": 12,
"unjoined": 12,
"closed_door": 12,
"timed_out": 12
},
"joined_share": 12,
"hours": [
{
"start": "2026-11-02T08:00:00Z",
"opened": 12,
"joined": 12,
"unjoined": 12,
"closed_door": 12,
"timed_out": 12,
"joined_share": 12
}
]
}Visitor Log
Every visit, newest first, and its export.
The Visitor Log, Newest First, Paged
GET/sites/{id}/logs
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time | |
limit query | integer | |
before query | string |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/logs?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&limit=50&before=2026-11-02T08%3A00%3A00Z" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/logs?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&limit=50&before=2026-11-02T08%3A00%3A00Z",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/logs?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&limit=50&before=2026-11-02T08%3A00%3A00Z", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"items": [
{
"id": "31:7712",
"entered": "2026-11-02T10:31:02Z",
"exited": "2026-11-02T10:44:40Z",
"dwell_s": 818,
"engaged": true,
"reached_checkout": true,
"age_band": "25-34",
"gender": "female",
"repeat_visitor": null,
"entry_line": "Main door",
"cameras": [
31,
32
],
"zones": [
{
"zone": "Aisle 2",
"kind": "display",
"dwell_s": 240
}
],
"path": [
{
"camera": 31,
"camera_name": "Main door",
"entered": "2026-11-02T10:31:02Z",
"dwell_s": 95,
"zones": []
},
{
"camera": 32,
"camera_name": "Aisles",
"entered": "2026-11-02T10:32:37Z",
"dwell_s": 723,
"zones": [
{
"zone": "Aisle 2",
"kind": "display",
"entered": "2026-11-02T10:35:10Z",
"dwell_s": 240
}
]
}
]
}
],
"next_before": "2026-11-02T10:31:02Z"
}The Visitor Log Of A Period As CSV Or XLSX (Times In The Site's Time Zone, At Most 20 000 Rows; The Path Column Lists Each Visit's Cameras In Order, With Their Zones)
GET/sites/{id}/logs/export
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read, export.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time | |
format query | enum | csv, xlsx |
language query | enum | en, id |
Responses
| Status | Meaning |
|---|---|
200 | the file |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/logs/export?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&format=csv&language=en" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/logs/export?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&format=csv&language=en",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/logs/export?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&format=csv&language=en", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Point Of Sale
Transactions from the store's POS (signed feed or CSV) for the conversion rate.
Upload POS Transactions As CSV (start,end,transactions)
POST/sites/{id}/pos/upload
Who can call it: An account that is an admin of the site. Token scopes: write.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X POST "https://<BOX_HOST>/api/retail/v1/sites/1/pos/upload" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.post(
"https://<BOX_HOST>/api/retail/v1/sites/1/pos/upload",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/pos/upload", {
method: "POST",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"stored": 12
}The Site's POS Intervals Of A Period, And Whether It Has A Key
GET/sites/{id}/pos
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
fromrequired query | date-time | |
torequired query | date-time |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/pos?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/pos?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/pos?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"key": true,
"intervals": [
{
"start": "2026-11-02T08:00:00Z",
"end": "2026-11-02T08:00:00Z",
"transactions": 12,
"source": "pos"
}
]
}POS Transactions Sent By The Store's POS, Signed With The Site's Key
POST/pos/{site}
The POS signs the body with the site's POS key the Standard Webhooks way: webhook-id, webhook-timestamp (within 5 minutes) and webhook-signature. A timestamp more than 5 minutes off, a repeated message id or a bad signature is refused. An interval is stored once per site and start: a later report of the same interval replaces it.
Authentication: The site's point-of-sale key (made by an admin of the site on the dashboard); no API token.
Parameters
| Field | Type | Description |
|---|---|---|
siterequired path | integer | |
webhook-idrequired header | string | A unique id for this message, at most 128 characters. A repeated id is refused. |
webhook-timestamprequired header | string | The time of signing in Unix seconds; it must be within 5 minutes of the box's clock. |
webhook-signaturerequired header | string | v1, and the base64 HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body> with the site's key (the base64 part after whsec_). Several signatures may be given, separated by spaces. |
Body
| Field | Type | Description |
|---|---|---|
intervalsrequired | object[] | |
intervals[].startrequired | date-time | |
intervals[].endrequired | date-time | |
intervals[].transactionsrequired | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X POST "https://<BOX_HOST>/api/retail/v1/pos/1" \
-H "webhook-id: <WEBHOOK_ID>" \
-H "webhook-timestamp: <WEBHOOK_TIMESTAMP>" \
-H "webhook-signature: <WEBHOOK_SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{"intervals":[{"start":"2026-11-02T10:00:00+07:00","end":"2026-11-02T11:00:00+07:00","transactions":42}]}'import requests
r = requests.post(
"https://<BOX_HOST>/api/retail/v1/pos/1",
headers={"webhook-id": "<WEBHOOK_ID>", "webhook-timestamp": "<WEBHOOK_TIMESTAMP>", "webhook-signature": "<WEBHOOK_SIGNATURE>"},
json={
"intervals": [
{
"start": "2026-11-02T10:00:00+07:00",
"end": "2026-11-02T11:00:00+07:00",
"transactions": 42
}
]
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/pos/1", {
method: "POST",
headers: { "webhook-id": "<WEBHOOK_ID>", "webhook-timestamp": "<WEBHOOK_TIMESTAMP>", "webhook-signature": "<WEBHOOK_SIGNATURE>", "Content-Type": "application/json" },
body: JSON.stringify({
"intervals": [
{
"start": "2026-11-02T10:00:00+07:00",
"end": "2026-11-02T11:00:00+07:00",
"transactions": 42
}
]
}),
});
console.log(r.status, await r.text());Response 200
{
"stored": 12
}Settings
Opening hours, the occupancy limit, queue and visit rules, retention, each camera's door and staff lines, and its latest door crossings (the door-line test).
The Site's Smart Retail Counter Settings
GET/sites/{id}/settings
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/sites/1/settings" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/sites/1/settings",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/settings", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"engaged_min_s": 60,
"long_queue_length": 12,
"occupancy_limit": 12,
"open": "08:00",
"close": "08:00",
"stats_days": 30,
"identity_days": 12,
"version": 3,
"visit_timeout_s": 60
}Change The Site's Smart Retail Counter Settings (Applied To Every Camera Of The Site)
PUT/sites/{id}/settings
Who can call it: An account that is an admin of the site. Token scopes: write.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer |
Body
| Field | Type | Description |
|---|---|---|
engaged_min_srequired | number | |
long_queue_lengthrequired | integer | People in a queue zone that raise retail.long_queue; a queue zone may set its own (long_queue_length on the zone in the camera's geometry), this is the default |
occupancy_limit | integer | |
openrequired | string | |
closerequired | string | |
stats_daysrequired | integer | |
identity_daysrequired | integer | |
version | integer | |
visit_timeout_s | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X PUT "https://<BOX_HOST>/api/retail/v1/sites/1/settings" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"engaged_min_s":60,"long_queue_length":12,"occupancy_limit":12,"open":"08:00","close":"08:00","stats_days":30,"identity_days":12,"version":3,"visit_timeout_s":60}'import requests
r = requests.put(
"https://<BOX_HOST>/api/retail/v1/sites/1/settings",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"engaged_min_s": 60,
"long_queue_length": 12,
"occupancy_limit": 12,
"open": "08:00",
"close": "08:00",
"stats_days": 30,
"identity_days": 12,
"version": 3,
"visit_timeout_s": 60
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/sites/1/settings", {
method: "PUT",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"engaged_min_s": 60,
"long_queue_length": 12,
"occupancy_limit": 12,
"open": "08:00",
"close": "08:00",
"stats_days": 30,
"identity_days": 12,
"version": 3,
"visit_timeout_s": 60
}),
});
console.log(r.status, await r.text());Response 200
{
"engaged_min_s": 60,
"long_queue_length": 12,
"occupancy_limit": 12,
"open": "08:00",
"close": "08:00",
"stats_days": 30,
"identity_days": 12,
"version": 3,
"visit_timeout_s": 60
}The Camera's Door And Staff Lines
GET/cameras/{id}/lines
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/cameras/1/lines" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/cameras/1/lines",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/cameras/1/lines", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"camera": 12,
"door_lines": [
"<door_line>"
],
"staff_lines": [
"<staff_line>"
],
"default": "<default>"
}Set The Camera's Door And Staff Lines (IDs Of The Geometry's Lines)
PUT/cameras/{id}/lines
Who can call it: An account that is an admin of the site. Token scopes: write.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer |
Body
| Field | Type | Description |
|---|---|---|
door_lines | string[] | |
staff_lines | string[] |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
422 | RFC 9457 problem |
502 | RFC 9457 problem |
Request
curl -s -X PUT "https://<BOX_HOST>/api/retail/v1/cameras/1/lines" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"door_lines":["<door_line>"],"staff_lines":["<staff_line>"]}'import requests
r = requests.put(
"https://<BOX_HOST>/api/retail/v1/cameras/1/lines",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"door_lines": [
"<door_line>"
],
"staff_lines": [
"<staff_line>"
]
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/cameras/1/lines", {
method: "PUT",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"door_lines": [
"<door_line>"
],
"staff_lines": [
"<staff_line>"
]
}),
});
console.log(r.status, await r.text());Response 200
{
"camera": 12,
"door_lines": [
"<door_line>"
],
"staff_lines": [
"<staff_line>"
]
}The Camera's Latest Door Crossings (The Door-Line Test)
GET/cameras/{id}/crossings
The door crossings reported for the camera after after, oldest first, at most 500 per call; a door line can be checked live with it. Without after: no crossings, only the cursor to start from. Anonymous: no track or identity.
Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
after query | integer | The cursor of the previous call |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem |
403 | RFC 9457 problem |
404 | RFC 9457 problem |
409 | RFC 9457 problem |
Request
curl -s -X GET "https://<BOX_HOST>/api/retail/v1/cameras/1/crossings?after=2026-11-02T08%3A00%3A00Z" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/retail/v1/cameras/1/crossings?after=2026-11-02T08%3A00%3A00Z",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/retail/v1/cameras/1/crossings?after=2026-11-02T08%3A00%3A00Z", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"camera": 12,
"cursor": 12,
"crossings": [
{
"line": "<line>",
"direction": "in",
"staff": true,
"time": "2026-11-02T08:00:00Z"
}
]
}