Docs / AI Box And Video Analytics Software / API Reference
DOOH Audience Meter API
The DOOH Audience Meter API: the audience of digital screens, measured by a camera beside each screen. It gives opportunities to see (OTS), impressions, attention and engagement, the audience's age bands, gender and mood, the view log, play logs from the screens' CMS or players, a basic campaign manager, the audience per campaign and creative with its cost per thousand (CPM), and an audience feed for ad platforms.
Personal data. Views are anonymous: no identity and no image is kept.
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.
Feeds With Their Own Credentials
Two operations are for other systems and use a credential of their own, made by an admin of the site on the dashboard: the signed play-log push from a CMS or player (the site's play-log key, Standard Webhooks signatures) and the audience feed for ad servers (a feed token).
1.0.0
Audience
Headline figures, trend and audience insights.
OTS, Impressions, Attention And Engagement, The Audience Now, And The Impressions' Demographics And Moods
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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/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/dooh/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/dooh/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
{
"ots": 9620,
"impressions": 3105,
"engaged": 684,
"attention_rate": 32.3,
"engagement_rate": 7.1,
"avg_attention_s": 3.1,
"avg_dwell_s": 6.4,
"audience_now": {
"in_view": 7,
"watching": 2
},
"demographics": {
"cells": [
{
"age_band": "18-24",
"gender": "male",
"impressions": 512,
"share": 19.8
}
],
"unknown": 514
},
"emotions": {
"neutral": 2210,
"happy": 604,
"sad": 41,
"surprised": 88,
"angry": 12,
"unknown": 150
}
}OTS And Impressions 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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
bucket query | string | hour or day |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/trend?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/dooh/v1/sites/1/trend?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/dooh/v1/sites/1/trend?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
{
"bucket": "hour",
"buckets": [
{
"start": "<start>",
"ots": 12,
"impressions": 12
}
]
}Audience Insights: The Peak Hour, Dayparts By Age Band, The Attention Histogram, The Status Per Day, Each Screen
GET/sites/{id}/analytics
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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/analytics?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/dooh/v1/sites/1/analytics?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/dooh/v1/sites/1/analytics?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
{
"summary": {
"ots": 12,
"impressions": 12,
"engaged": 12,
"attention_rate": 12,
"engagement_rate": 12,
"avg_attention_s": 60,
"avg_dwell_s": 60,
"peak_hour": {
"start": "<start>",
"impressions": 12
}
},
"dayparts": {
"dayparts": [
"<daypart>"
],
"age_bands": [
"<age_band>"
],
"impressions": [
[
12
]
]
},
"attention_histogram": {
"edges_s": [
12
],
"counts": [
12
]
},
"status_per_day": [
{
"day": "<day>",
"walk_by": 12,
"noticed": 12,
"engaged": 12
}
],
"screens": [
{
"ots": 12,
"impressions": 12,
"engaged": 12,
"attention_rate": 12,
"engagement_rate": 12,
"avg_attention_s": 60,
"avg_dwell_s": 60,
"camera_id": 1,
"screen": "Screen 1"
}
]
}The Campaign Manager's Records Of The Site (Campaigns With Their Creatives), By Name
GET/sites/{id}/campaign-records
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 details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"items": [
{
"id": 1,
"site_id": 1,
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "<flight_start>",
"flight_end": "<flight_end>",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"id": 1,
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3,
"updated": "<updated>"
}
]
}Add A Campaign Record With Its Creatives (Recorded In The Audit Log)
POST/sites/{id}/campaign-records
Names (trimmed, any case) and CMS ids are unique on the site, creatives' within the campaign.
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 |
|---|---|---|
namerequired | string | unique on the site (trimmed, any case): plays are matched by it |
advertiser | string | |
flight_startrequired | date | YYYY-MM-DD, the site's local day, inclusive |
flight_endrequired | date | on or after flight_start |
screensrequired | integer[] | camera ids of the site's screens |
external_id | string | the CMS's campaign id, unique on the site; null or empty: none |
creatives | object[] | |
creatives[].namerequired | string | |
creatives[].length_srequired | integer | |
creatives[].external_id | string | the CMS's creative id; null or empty: none |
version | integer | updates: the version read |
Responses
| Status | Meaning |
|---|---|
201 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X POST "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"name":"Main gate","advertiser":"Advertiser A","flight_start":"2026-11-02","flight_end":"2026-11-02","screens":[12],"external_id":"<external_id>","creatives":[{"name":"Main gate","length_s":60,"external_id":"<external_id>"}],"version":3}'import requests
r = requests.post(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "2026-11-02",
"flight_end": "2026-11-02",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records", {
method: "POST",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "2026-11-02",
"flight_end": "2026-11-02",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3
}),
});
console.log(r.status, await r.text());Response 201
{
"id": 1,
"site_id": 1,
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "<flight_start>",
"flight_end": "<flight_end>",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"id": 1,
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3,
"updated": "<updated>"
}One Campaign Record
GET/sites/{id}/campaign-records/{record}
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 | |
recordrequired path | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"id": 1,
"site_id": 1,
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "<flight_start>",
"flight_end": "<flight_end>",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"id": 1,
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3,
"updated": "<updated>"
}Replace A Campaign Record And Its Creatives (Pass The Version Read: A Stale One Is Refused With 409); Audited
PUT/sites/{id}/campaign-records/{record}
Who can call it: An account that is an admin of the site. Token scopes: write.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
recordrequired path | integer |
Body
| Field | Type | Description |
|---|---|---|
namerequired | string | unique on the site (trimmed, any case): plays are matched by it |
advertiser | string | |
flight_startrequired | date | YYYY-MM-DD, the site's local day, inclusive |
flight_endrequired | date | on or after flight_start |
screensrequired | integer[] | camera ids of the site's screens |
external_id | string | the CMS's campaign id, unique on the site; null or empty: none |
creatives | object[] | |
creatives[].namerequired | string | |
creatives[].length_srequired | integer | |
creatives[].external_id | string | the CMS's creative id; null or empty: none |
version | integer | updates: the version read |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X PUT "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"name":"Main gate","advertiser":"Advertiser A","flight_start":"2026-11-02","flight_end":"2026-11-02","screens":[12],"external_id":"<external_id>","creatives":[{"name":"Main gate","length_s":60,"external_id":"<external_id>"}],"version":3}'import requests
r = requests.put(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "2026-11-02",
"flight_end": "2026-11-02",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1", {
method: "PUT",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "2026-11-02",
"flight_end": "2026-11-02",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3
}),
});
console.log(r.status, await r.text());Response 200
{
"id": 1,
"site_id": 1,
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "<flight_start>",
"flight_end": "<flight_end>",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"id": 1,
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3,
"updated": "<updated>"
}Delete A Campaign Record (Its Plays Stay, Reported Under Their Names Again); Audited
DELETE/sites/{id}/campaign-records/{record}
Who can call it: An account that is an admin of the site. Token scopes: write.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer | |
recordrequired path | integer |
Responses
| Status | Meaning |
|---|---|
204 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X DELETE "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.delete(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/1", {
method: "DELETE",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());The Campaigns Named In The Play Logs That Match No Record
GET/sites/{id}/campaign-records/unmatched
The most played first, with what their plays say: the first and last play, the screens, the CMS id seen most, and the creatives with their mean length.
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 details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/unmatched" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/unmatched",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/unmatched", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"items": [
{
"name": "Main gate",
"external_id": "<external_id>",
"plays": 12,
"first": "2026-11-02T08:00:00Z",
"last": "2026-11-02T08:00:00Z",
"screens": [
12
],
"creatives": [
{
"name": "Main gate",
"external_id": "<external_id>",
"plays": 12,
"length_s": 60
}
]
}
]
}Add An Unmatched Campaign To The Manager, Prefilled From Its Plays
POST/sites/{id}/campaign-records/adopt
The flight runs from the first to the last play's local day, with its screens, CMS ids and creatives with their mean length; no advertiser. Recorded in the audit log.
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 |
|---|---|---|
namerequired | string | an unmatched campaign's name (any case) |
Responses
| Status | Meaning |
|---|---|
201 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X POST "https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/adopt" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"name":"Main gate"}'import requests
r = requests.post(
"https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/adopt",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"name": "Main gate"
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/campaign-records/adopt", {
method: "POST",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"name": "Main gate"
}),
});
console.log(r.status, await r.text());Response 201
{
"id": 1,
"site_id": 1,
"name": "Main gate",
"advertiser": "Advertiser A",
"flight_start": "<flight_start>",
"flight_end": "<flight_end>",
"screens": [
12
],
"external_id": "<external_id>",
"creatives": [
{
"id": 1,
"name": "Main gate",
"length_s": 60,
"external_id": "<external_id>"
}
],
"version": 3,
"updated": "<updated>"
}View Log
Every viewer, newest first, and its export.
The View 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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
camera query | integer | |
status query | string | walk_by, noticed or engaged |
age_band query | string | |
gender query | string | |
limit query | integer | |
before query | string | next_before of the previous page |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/logs?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&camera=1&limit=50&before=2026-11-02T08%3A00%3A00Z" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/logs?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&camera=1&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/dooh/v1/sites/1/logs?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&camera=1&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": "<id>",
"camera_id": 1,
"screen": "Screen 1",
"time": "2026-11-02T08:00:00Z",
"dwell_s": 60,
"attention_s": 60,
"first_glance_s": 60,
"status": "walk_by",
"age_band": "<age_band>",
"gender": "female",
"emotion": "<emotion>"
}
],
"next_before": "<next_before>"
}The View Log As CSV Or XLSX (EN/ID, Local Times, At Most 20 000 Rows; Audited)
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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
format query | string | csv or xlsx |
language query | string | en or id |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/logs/export?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/dooh/v1/sites/1/logs/export?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/dooh/v1/sites/1/logs/export?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());Campaigns
The campaign manager (advertiser, flight dates, screens, creatives; plays matched by the CMS's id, else by name), the names in the play logs not in it, and the audience per campaign and creative with its export.
The Audience Per Campaign And Creative, With The Estimated Cost And CPM
GET/sites/{id}/campaigns
A view counts for every creative on its screen while the person was in view. Plays are matched to the campaign manager's records by the CMS's id when the play carries one a record has, else by name (trimmed, any case). A record's campaign carries its advertiser and flight dates and counts the plays outside them; records in flight without a play are listed with zeros.
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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/campaigns?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/dooh/v1/sites/1/campaigns?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/dooh/v1/sites/1/campaigns?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
{
"price_per_slot": null,
"currency": "IDR",
"campaigns": [
{
"campaign_id": "Morning Coffee",
"plays": 320,
"play_s": 4800,
"ots": 2410,
"impressions": 806,
"engaged": 170,
"attention_rate": 33.4,
"engagement_rate": 7.1,
"avg_attention_s": 2.9,
"est_cost": null,
"est_cpm": null,
"out_of_flight": 0,
"record": {
"id": 3,
"advertiser": "Advertiser A",
"flight_start": "2026-11-01",
"flight_end": "2026-11-30",
"external_id": "CMP-1001",
"screens": [
41
]
},
"creatives": [
{
"creative_id": "Coffee 15s",
"record_id": 4,
"length_s": 15,
"plays": 320,
"play_s": 4800,
"ots": 2410,
"impressions": 806,
"engaged": 170,
"attention_rate": 33.4,
"engagement_rate": 7.1,
"avg_attention_s": 2.9,
"est_cost": null,
"est_cpm": null,
"out_of_flight": 0
}
]
}
]
}The Audience Per Campaign And Creative As CSV Or XLSX (EN/ID; Audited)
GET/sites/{id}/campaigns/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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
format query | string | csv or xlsx |
language query | string | en or id |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/campaigns/export?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/dooh/v1/sites/1/campaigns/export?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/dooh/v1/sites/1/campaigns/export?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());Play Logs
What each screen played: the signed feed from a CMS or player, or a CSV.
Import Play Logs As CSV
POST/sites/{id}/plays/upload
Columns screen,creative,campaign,start,end, optionally followed by the CMS's campaign_id,creative_id. A play is stored once per screen, creative and start. Recorded in the audit log.
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 details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X POST "https://<BOX_HOST>/api/dooh/v1/sites/1/plays/upload" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.post(
"https://<BOX_HOST>/api/dooh/v1/sites/1/plays/upload",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/plays/upload", {
method: "POST",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"stored": 12
}The Latest Plays Of A Period And Whether A Key Is Set
GET/sites/{id}/plays
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 | string | RFC 3339 |
torequired query | string | RFC 3339 |
limit query | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/plays?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&limit=50" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/plays?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&limit=50",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/plays?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00&limit=50", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"key": true,
"items": [
{
"camera_id": 1,
"screen": "Screen 1",
"creative_id": "<creative_id>",
"campaign_id": "<campaign_id>",
"start": "<start>",
"end": "<end>",
"source": "api",
"cms_campaign_id": "<cms_campaign_id>",
"cms_creative_id": "<cms_creative_id>"
}
]
}Signed Play Log From A CMS Or Player
POST/feeds/plays/{site}
Signed with the site's play-log key the Standard Webhooks way: webhook-id, webhook-timestamp (within 5 minutes) and webhook-signature: v1,<base64 HMAC-SHA256 of id.timestamp.body>; a repeated id is refused. Screens are named as on the dashboard; campaign_id and creative_id (optional) are the CMS's ids, which the campaign manager matches first.
Authentication: The site's play-log 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 |
|---|---|---|
playsrequired | object[] | |
plays[].screenrequired | string | |
plays[].creativerequired | string | |
plays[].campaignrequired | string | |
plays[].startrequired | string | |
plays[].endrequired | string | |
plays[].campaign_id | string | the CMS's campaign id (optional) |
plays[].creative_id | string | the CMS's creative id (optional) |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X POST "https://<BOX_HOST>/api/dooh/v1/feeds/plays/1" \
-H "webhook-id: <WEBHOOK_ID>" \
-H "webhook-timestamp: <WEBHOOK_TIMESTAMP>" \
-H "webhook-signature: <WEBHOOK_SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{"plays":[{"screen":"Lobby Screen","creative":"Coffee 15s","campaign":"Morning Coffee","start":"2026-11-02T07:00:00+07:00","end":"2026-11-02T07:00:15+07:00","campaign_id":"CMP-1001","creative_id":"CRV-2001"}]}'import requests
r = requests.post(
"https://<BOX_HOST>/api/dooh/v1/feeds/plays/1",
headers={"webhook-id": "<WEBHOOK_ID>", "webhook-timestamp": "<WEBHOOK_TIMESTAMP>", "webhook-signature": "<WEBHOOK_SIGNATURE>"},
json={
"plays": [
{
"screen": "Lobby Screen",
"creative": "Coffee 15s",
"campaign": "Morning Coffee",
"start": "2026-11-02T07:00:00+07:00",
"end": "2026-11-02T07:00:15+07:00",
"campaign_id": "CMP-1001",
"creative_id": "CRV-2001"
}
]
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/feeds/plays/1", {
method: "POST",
headers: { "webhook-id": "<WEBHOOK_ID>", "webhook-timestamp": "<WEBHOOK_TIMESTAMP>", "webhook-signature": "<WEBHOOK_SIGNATURE>", "Content-Type": "application/json" },
body: JSON.stringify({
"plays": [
{
"screen": "Lobby Screen",
"creative": "Coffee 15s",
"campaign": "Morning Coffee",
"start": "2026-11-02T07:00:00+07:00",
"end": "2026-11-02T07:00:15+07:00",
"campaign_id": "CMP-1001",
"creative_id": "CRV-2001"
}
]
}),
});
console.log(r.status, await r.text());Response 200
{
"stored": 12
}Audience Feed
The token-protected feed of the audience per screen and interval.
Whether The Site's Audience Feed Token Is Set, And The Feed's URL
GET/sites/{id}/audience-feed
Who can call it: An account that is an admin of the site. Token scopes: read.
Parameters
| Field | Type | Description |
|---|---|---|
idrequired path | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/audience-feed" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/audience-feed",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/audience-feed", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"site": 12,
"token": true,
"url": "https://<RECEIVER_HOST>/hooks/arsa"
}The Audience Per Screen And Interval, For Ad Servers
GET/feeds/audience/{site}
Intervals of 15 minutes, an hour or a day in the site's time zone, at most 31 days: OTS, impressions, engaged viewers and the impressions' attention. Send the site's audience feed token as Authorization: Bearer <FEED_TOKEN>.
Authentication: The site's audience feed token (made by an admin of the site on the dashboard); no API token.
Parameters
| Field | Type | Description |
|---|---|---|
siterequired path | integer | |
fromrequired query | string | RFC 3339 |
torequired query | string | RFC 3339 |
bucket query | string | 15m, hour (default) or day |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/feeds/audience/1?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00" \
-H "Authorization: Bearer <FEED_TOKEN>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/feeds/audience/1?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00",
headers={"Authorization": "Bearer <FEED_TOKEN>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/feeds/audience/1?from=2026-11-01T00%3A00%3A00%2B07%3A00&to=2026-11-02T00%3A00%3A00%2B07%3A00", {
method: "GET",
headers: { "Authorization": "Bearer <FEED_TOKEN>" },
});
console.log(r.status, await r.text());Response 200
{
"site": 12,
"timezone": "Asia/Jakarta",
"from": "2026-11-02T08:00:00Z",
"to": "2026-11-02T08:00:00Z",
"bucket": "15m",
"definitions": {
"ots": "<ots>",
"impressions": "<impressions>",
"engaged": "<engaged>",
"attention_s": "<attention_s>"
},
"screens": [
{
"camera_id": 1,
"screen": "Screen 1",
"intervals": [
{
"start": "<start>",
"ots": 12,
"impressions": 12,
"engaged": 12,
"attention_s": 60
}
]
}
]
}Settings
When a viewer has noticed or engaged, the crowd threshold, the price per slot, retention, and each camera's screen.
The Site's DOOH Audience Meter 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 details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/sites/1/settings" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/sites/1/settings",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/settings", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"noticed_s": 60,
"engaged_s": 60,
"crowd_threshold": 12,
"stats_days": 30,
"price_per_slot": 12,
"version": 3
}Change The Site's DOOH Audience Meter Settings (Applied To Every Camera Of The Site; Recorded In The Audit Log)
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 |
|---|---|---|
noticed_srequired | number | attention that makes an impression |
engaged_srequired | number | attention that makes an engaged viewer; at least noticed_s |
crowd_threshold | integer | null: no crowd alert |
stats_daysrequired | integer | |
price_per_slot | number | IDR per play, for the estimated cost and CPM; null: no estimate |
version | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X PUT "https://<BOX_HOST>/api/dooh/v1/sites/1/settings" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"noticed_s":60,"engaged_s":60,"crowd_threshold":12,"stats_days":30,"price_per_slot":12,"version":3}'import requests
r = requests.put(
"https://<BOX_HOST>/api/dooh/v1/sites/1/settings",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"noticed_s": 60,
"engaged_s": 60,
"crowd_threshold": 12,
"stats_days": 30,
"price_per_slot": 12,
"version": 3
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/sites/1/settings", {
method: "PUT",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"noticed_s": 60,
"engaged_s": 60,
"crowd_threshold": 12,
"stats_days": 30,
"price_per_slot": 12,
"version": 3
}),
});
console.log(r.status, await r.text());Response 200
{
"noticed_s": 60,
"engaged_s": 60,
"crowd_threshold": 12,
"stats_days": 30,
"price_per_slot": 12,
"version": 3
}The Camera's Screen: Its Name And Where It Is As Seen From The Camera
GET/cameras/{id}/screen
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 details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X GET "https://<BOX_HOST>/api/dooh/v1/cameras/1/screen" \
-H "Authorization: Bearer <API_KEY>"import requests
r = requests.get(
"https://<BOX_HOST>/api/dooh/v1/cameras/1/screen",
headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/cameras/1/screen", {
method: "GET",
headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());Response 200
{
"camera": 12,
"name": "Main gate",
"yaw_offset_deg": 90,
"pitch_offset_deg": 90,
"cone_yaw_deg": 90,
"cone_pitch_deg": 90,
"version": 3
}Set The Camera's Screen (Recorded In The Audit Log)
PUT/cameras/{id}/screen
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 |
|---|---|---|
namerequired | string | |
yaw_offset_deg | number | the head yaw of someone looking at the screen |
pitch_offset_deg | number | |
cone_yaw_deg | number | |
cone_pitch_deg | number | |
version | integer |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | RFC 9457 problem details |
403 | RFC 9457 problem details |
404 | RFC 9457 problem details |
409 | RFC 9457 problem details |
422 | RFC 9457 problem details |
502 | RFC 9457 problem details |
Request
curl -s -X PUT "https://<BOX_HOST>/api/dooh/v1/cameras/1/screen" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"name":"Main gate","yaw_offset_deg":90,"pitch_offset_deg":90,"cone_yaw_deg":90,"cone_pitch_deg":90,"version":3}'import requests
r = requests.put(
"https://<BOX_HOST>/api/dooh/v1/cameras/1/screen",
headers={"Authorization": "Bearer <API_KEY>"},
json={
"name": "Main gate",
"yaw_offset_deg": 90,
"pitch_offset_deg": 90,
"cone_yaw_deg": 90,
"cone_pitch_deg": 90,
"version": 3
},
)
print(r.status_code, r.text)const r = await fetch("https://<BOX_HOST>/api/dooh/v1/cameras/1/screen", {
method: "PUT",
headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
body: JSON.stringify({
"name": "Main gate",
"yaw_offset_deg": 90,
"pitch_offset_deg": 90,
"cone_yaw_deg": 90,
"cone_pitch_deg": 90,
"version": 3
}),
});
console.log(r.status, await r.text());Response 200
{
"camera": 12,
"name": "Main gate",
"yaw_offset_deg": 90,
"pitch_offset_deg": 90,
"cone_yaw_deg": 90,
"cone_pitch_deg": 90,
"version": 3
}