DocsIDarsa.technology

Docs / AI Box And Video Analytics Software / API Reference

AI Box Platform API

The part of the ARSA AI Box Series dashboard that is the same on every product: sites and their zones, cameras and what is drawn on them, live video and recordings, alerts, alert contacts and webhooks, sirens and relays, reports, and webhooks for integrators. Each product adds its own API beside this one, under /api/<product>/v1.

Calling the API

Send an API account's token in the Authorization header: Authorization: Bearer <API_KEY>. An admin makes the account on the dashboard (API accounts); the token is shown once. The token sees only the sites its account has a role on, and only what its scopes allow (read, write, export, personal_data). Each operation says who can call it and which scopes it needs. The dashboard serves HTTPS with the box's own certificate: an admin downloads a copy (box.pem) on the dashboard (System, then API accounts, then Download the box's certificate) with its SHA-256 fingerprint, and a token with read can fetch it with getBoxCertificate. Check the fingerprint before your client trusts the file.

Errors are RFC 9457 problem details (application/problem+json) with a stable code. Times are RFC 3339; hourly and daily figures follow the site's time zone. Long lists are paged: pass limit, then the answer's next_before as before. Calls are limited per API account (by default 600 a minute; then 429 with Retry-After).

Webhooks for Integrators

An API account can subscribe a receiver of its own (/webhook-subscriptions): detection events in batches, periodic summaries and alerts, signed the Standard Webhooks way. The webhooks guide explains the payloads, the signature and the retries.

1.0.0

Sites And Zones

Sites (a plant, a store, a corridor) and the zones inside them.

The Sites The API Account Has A Role On

GET/sites

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "name": "North Plant",
      "timezone": "Asia/Jakarta",
      "created": "2026-10-01T02:00:00Z",
      "version": 4,
      "my_role": "viewer",
      "location": {
        "lat": -6.2,
        "lon": 106.8
      },
      "area": null,
      "zones": 3
    },
    {
      "id": 2,
      "name": "Warehouse B",
      "timezone": "Asia/Jakarta",
      "created": "2026-10-03T02:00:00Z",
      "version": 1,
      "my_role": "admin",
      "location": null,
      "area": null,
      "zones": 0
    }
  ]
}

A Site

GET/sites/{id}

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "name": "North Plant",
  "timezone": "Asia/Jakarta",
  "created": "2026-10-01T02:00:00Z",
  "version": 4,
  "my_role": "viewer",
  "location": {
    "lat": -6.2,
    "lon": 106.8
  },
  "area": null,
  "zones": 3
}

Rename A Site, Change Its Timezone, Or Place It On The Map (Pinpoint, Boundary)

PATCH/sites/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namestring
timezonestring
locationone ofThe pinpoint; null removes it.
areaone ofThe boundary; null removes it.

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/sites/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","timezone":"Asia/Jakarta","location":{"lat":-6.2,"lon":106.8},"area":[[12,12],[12,12],[12,12]]}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/sites/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "timezone": "Asia/Jakarta",
      "location": {
        "lat": -6.2,
        "lon": 106.8
      },
      "area": [
        [
          12,
          12
        ],
        [
          12,
          12
        ],
        [
          12,
          12
        ]
      ]
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "timezone": "Asia/Jakarta",
    "location": {
      "lat": -6.2,
      "lon": 106.8
    },
    "area": [
      [
        12,
        12
      ],
      [
        12,
        12
      ],
      [
        12,
        12
      ]
    ]
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "name": "Main gate",
  "timezone": "Asia/Jakarta",
  "created": "2026-11-02T08:00:00Z",
  "version": 3,
  "my_role": "admin",
  "location": {
    "lat": -6.2,
    "lon": 106.8
  },
  "area": [
    [
      12,
      12
    ],
    [
      12,
      12
    ],
    [
      12,
      12
    ]
  ],
  "zones": 12
}

The Site's Zones

GET/sites/{id}/zones

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1/zones" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1/zones",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/zones", {
  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",
      "area": [
        [
          12,
          12
        ],
        [
          12,
          12
        ],
        [
          12,
          12
        ]
      ],
      "created": "2026-11-02T08:00:00Z",
      "version": 3
    }
  ]
}

Add A Zone To The Site

POST/sites/{id}/zones

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namerequiredstring
areaone of

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/sites/1/zones" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","area":[[12,12],[12,12],[12,12]]}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/sites/1/zones",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "area": [
        [
          12,
          12
        ],
        [
          12,
          12
        ],
        [
          12,
          12
        ]
      ]
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/zones", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "area": [
      [
        12,
        12
      ],
      [
        12,
        12
      ],
      [
        12,
        12
      ]
    ]
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "area": [
    [
      12,
      12
    ],
    [
      12,
      12
    ],
    [
      12,
      12
    ]
  ],
  "created": "2026-11-02T08:00:00Z",
  "version": 3
}

Rename A Zone Or Change Its Outline

PATCH/sites/{id}/zones/{zone}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger
zonerequired pathinteger

Body

FieldTypeDescription
namestring
areaone ofThe outline; null removes it.

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/sites/1/zones/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","area":[[12,12],[12,12],[12,12]]}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/sites/1/zones/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "area": [
        [
          12,
          12
        ],
        [
          12,
          12
        ],
        [
          12,
          12
        ]
      ]
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/zones/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "area": [
      [
        12,
        12
      ],
      [
        12,
        12
      ],
      [
        12,
        12
      ]
    ]
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "area": [
    [
      12,
      12
    ],
    [
      12,
      12
    ],
    [
      12,
      12
    ]
  ],
  "created": "2026-11-02T08:00:00Z",
  "version": 3
}

Delete A Zone (Its Cameras Stay On The Site, In No Zone)

DELETE/sites/{id}/zones/{zone}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger
zonerequired pathinteger

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/sites/1/zones/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/sites/1/zones/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/zones/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Cameras

Cameras, their streams, what is drawn on them (lines, zones, lanes) and snapshots.

The Cameras Of The API Account's Sites

GET/cameras

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/cameras" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/cameras",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 11,
      "name": "Gate 1",
      "site_id": 1,
      "zone_id": 2,
      "password_set": true,
      "over_licence": false,
      "status": {
        "running": true,
        "capture": "streaming"
      }
    },
    {
      "id": 12,
      "name": "Loading Bay",
      "site_id": 1,
      "zone_id": null,
      "password_set": true,
      "over_licence": false,
      "status": {
        "running": true,
        "capture": "streaming"
      }
    }
  ]
}

Add A Camera To A Site

POST/cameras

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
Idempotency-Key headerstring

Body

FieldTypeDescription
site_idrequiredinteger
namerequiredstring
rtsp_urlrequiredstring
hoststring
usernamestring
passwordstring
widthinteger
heightinteger
fpsinteger

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/cameras" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Idempotency-Key: <IDEMPOTENCY_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"site_id":1,"name":"Main gate","rtsp_url":"https://<RECEIVER_HOST>/hooks/arsa","host":"<host>","username":"<username>","password":"<password>","width":12,"height":12,"fps":12}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/cameras",
    headers={"Authorization": "Bearer <API_KEY>", "Idempotency-Key": "<IDEMPOTENCY_KEY>"},
    json={
      "site_id": 1,
      "name": "Main gate",
      "rtsp_url": "https://<RECEIVER_HOST>/hooks/arsa",
      "host": "<host>",
      "username": "<username>",
      "password": "<password>",
      "width": 12,
      "height": 12,
      "fps": 12
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Idempotency-Key": "<IDEMPOTENCY_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "site_id": 1,
    "name": "Main gate",
    "rtsp_url": "https://<RECEIVER_HOST>/hooks/arsa",
    "host": "<host>",
    "username": "<username>",
    "password": "<password>",
    "width": 12,
    "height": 12,
    "fps": 12
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "id": 1,
  "name": "Main gate",
  "rtsp_url": "https://<RECEIVER_HOST>/hooks/arsa",
  "password_set": true,
  "site_id": 1,
  "status": {},
  "over_licence": true,
  "zone_id": 1
}

A Camera

GET/cameras/{id}

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/cameras/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/cameras/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "name": "Main gate",
  "rtsp_url": "https://<RECEIVER_HOST>/hooks/arsa",
  "password_set": true,
  "site_id": 1,
  "status": {},
  "over_licence": true,
  "zone_id": 1
}

Change A Camera (JSON Merge Patch, If-Match)

PATCH/cameras/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger
If-Match headerstringThe ETag read before; required for changes.

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
412The ETag in If-Match is stale
413Body too large
415Not JSON
422Invalid body
428If-Match is required
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/cameras/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "If-Match: <IF_MATCH>"
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/cameras/1",
    headers={"Authorization": "Bearer <API_KEY>", "If-Match": "<IF_MATCH>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "If-Match": "<IF_MATCH>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "name": "Main gate",
  "rtsp_url": "https://<RECEIVER_HOST>/hooks/arsa",
  "password_set": true,
  "site_id": 1,
  "status": {},
  "over_licence": true,
  "zone_id": 1
}

Delete A Camera

DELETE/cameras/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger
If-Match headerstringThe ETag read before; required for changes.

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
412The ETag in If-Match is stale
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/cameras/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "If-Match: <IF_MATCH>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/cameras/1",
    headers={"Authorization": "Bearer <API_KEY>", "If-Match": "<IF_MATCH>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>", "If-Match": "<IF_MATCH>" },
});
console.log(r.status, await r.text());

Put A Camera In A Zone Of Its Site, Or In None

PUT/cameras/{id}/zone

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
zone_idrequiredintegerA zone of the camera's own site, or null for none.

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PUT "https://<BOX_HOST>/api/v1/cameras/1/zone" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"zone_id":1}'
import requests

r = requests.put(
    "https://<BOX_HOST>/api/v1/cameras/1/zone",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "zone_id": 1
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1/zone", {
  method: "PUT",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "zone_id": 1
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "camera_id": 1,
  "site_id": 1,
  "zone_id": 1
}

The Camera's Geometry (Lines, Zones, Lanes)

GET/cameras/{id}/geometry

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/cameras/1/geometry" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/cameras/1/geometry",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1/geometry", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{}

Replace The Geometry (If-Match)

PUT/cameras/{id}/geometry

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger
If-Match headerstringThe ETag read before; required for changes.

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
412The ETag in If-Match is stale
413Body too large
415Not JSON
422Invalid body
428If-Match is required
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X PUT "https://<BOX_HOST>/api/v1/cameras/1/geometry" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "If-Match: <IF_MATCH>" \
  -H "Content-Type: application/json" \
  -d '{}'
import requests

r = requests.put(
    "https://<BOX_HOST>/api/v1/cameras/1/geometry",
    headers={"Authorization": "Bearer <API_KEY>", "If-Match": "<IF_MATCH>"},
    json={},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1/geometry", {
  method: "PUT",
  headers: { "Authorization": "Bearer <API_KEY>", "If-Match": "<IF_MATCH>", "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
console.log(r.status, await r.text());

Response 200

{}

The Camera's Current Frame

GET/cameras/{id}/snapshot

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read, personal_data.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200JPEG
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/cameras/1/snapshot" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/cameras/1/snapshot",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/cameras/1/snapshot", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Live Video And Recordings

Live view (WebRTC, HLS), recorded segments, MP4 exports and event clips.

WHEP: The Browser's SDP Offer For The Camera's Live Stream; Answers The SDP Answer (RFC 9725)

POST/live/{camera}/whep

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: write, personal_data.

Parameters

FieldTypeDescription
camerarequired pathinteger

Responses

StatusMeaning
201the SDP answer
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/live/1/whep" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/live/1/whep",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/live/1/whep", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

The Camera's HLS Playlist (The Fallback When WebRTC Cannot Connect)

GET/live/{camera}/hls.m3u8

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: read, personal_data.

Parameters

FieldTypeDescription
camerarequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/live/1/hls.m3u8" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/live/1/hls.m3u8",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/live/1/hls.m3u8", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

An HLS Segment

GET/live/{camera}/segments/{name}

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: read, personal_data.

Parameters

FieldTypeDescription
camerarequired pathinteger
namerequired pathstring

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/live/1/segments/{name}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/live/1/segments/{name}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/live/1/segments/{name}", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

The Camera's Recorded Segments (One Camera Per Query)

GET/recordings

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: read, personal_data.

Parameters

FieldTypeDescription
camerarequired queryinteger
from querystring
to querystring
cursor querystring
limit queryinteger

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/recordings?camera=1&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/v1/recordings?camera=1&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/v1/recordings?camera=1&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

{}

Export Up To 60 Minutes Of A Camera To MP4 (A Job; Recorded In The Audit Log)

POST/recordings/export

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: write, export, personal_data.

Body

FieldTypeDescription
camerarequiredinteger
fromrequireddate-time
torequireddate-time

Responses

StatusMeaning
202a job; poll its Location (/api/v1/recordings/exports/{job})
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/recordings/export" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"camera":12,"from":"2026-11-02T08:00:00Z","to":"2026-11-02T08:00:00Z"}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/recordings/export",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "camera": 12,
      "from": "2026-11-02T08:00:00Z",
      "to": "2026-11-02T08:00:00Z"
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/recordings/export", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "camera": 12,
    "from": "2026-11-02T08:00:00Z",
    "to": "2026-11-02T08:00:00Z"
  }),
});
console.log(r.status, await r.text());

Response 202

{}

An Export Or Clip Job (File Link To The Dashboard When Done)

GET/recordings/exports/{job}

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: read, export, personal_data.

Parameters

FieldTypeDescription
jobrequired pathstring

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/recordings/exports/{job}" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/recordings/exports/{job}",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/recordings/exports/{job}", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{}

The Exported MP4 (Streamed; Byte Ranges)

GET/recordings/exports/{job}/file

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: read, export, personal_data.

Parameters

FieldTypeDescription
jobrequired pathstring

Responses

StatusMeaning
200OK
206a byte range
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/recordings/exports/{job}/file" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/recordings/exports/{job}/file",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/recordings/exports/{job}/file", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

A Clip Around An Event (By Default 10 s Before To 20 s After; A Job; Recorded In The Audit Log)

POST/events/{id}/clip

Who can call it: An account with a role (viewer or admin) on the camera's site. Token scopes: write, export, personal_data.

Parameters

FieldTypeDescription
idrequired pathstring

Responses

StatusMeaning
202a job; poll its Location (/api/v1/recordings/exports/{job})
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body
502The analysis engine refused the request (engine_refused)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/events/{id}/clip" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/events/{id}/clip",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/events/{id}/clip", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({}),
});
console.log(r.status, await r.text());

Response 202

{}

Alerts

Alerts raised from detections; list them, read one, acknowledge it.

Alerts, Newest First

GET/alerts

Who can call it: Any API account; the answer covers the sites it has a role on. Token scopes: read. Without personal_data the answer is the anonymous form: no names, plates, pictures or footage.

Parameters

FieldTypeDescription
site queryintegeronly this site
state querystringopen or acknowledged
kind querystringonly this kind
before queryintegerthe next_before of the previous page
limit queryinteger1..500, default 50

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/alerts?site=1&before=2026-11-02T08%3A00%3A00Z&limit=50" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/alerts?site=1&before=2026-11-02T08%3A00%3A00Z&limit=50",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/alerts?site=1&before=2026-11-02T08%3A00%3A00Z&limit=50", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "site_id": 1,
      "camera_id": 1,
      "kind": "default",
      "severity": "info",
      "first": "2026-11-02T08:00:00Z",
      "last": "2026-11-02T08:00:00Z",
      "count": 12,
      "event_id": "<event_id>",
      "details": "<details>",
      "state": "open",
      "acked_by": "<acked_by>",
      "acked_by_name": "<acked_by_name>",
      "acked": "2026-11-02T08:00:00Z",
      "note": "<note>",
      "footage": "<footage>",
      "footage_state": "none"
    }
  ],
  "next_before": 12
}

Open Alerts Per Visible Site

GET/alerts/summary

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/alerts/summary" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/alerts/summary",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/alerts/summary", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "open": 12,
  "sites": [
    {
      "site_id": 1,
      "open": 12
    }
  ]
}

The Product's Alert Kinds

GET/alerts/kinds

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/alerts/kinds" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/alerts/kinds",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/alerts/kinds", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": "<id>",
      "severity": "info",
      "window_s": 60
    }
  ]
}

An Alert

GET/alerts/{id}

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read. Without personal_data the answer is the anonymous form: no names, plates, pictures or footage.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/alerts/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/alerts/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/alerts/1", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "camera_id": 1,
  "kind": "default",
  "severity": "info",
  "first": "2026-11-02T08:00:00Z",
  "last": "2026-11-02T08:00:00Z",
  "count": 12,
  "event_id": "<event_id>",
  "details": "<details>",
  "state": "open",
  "acked_by": "<acked_by>",
  "acked_by_name": "<acked_by_name>",
  "acked": "2026-11-02T08:00:00Z",
  "note": "<note>",
  "footage": "<footage>",
  "footage_state": "none"
}

Acknowledge An Alert, With An Optional Note (Recorded In The Audit Log)

POST/alerts/{id}/ack

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
notestring

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/alerts/1/ack" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"note":"<note>"}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/alerts/1/ack",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "note": "<note>"
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/alerts/1/ack", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "note": "<note>"
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "camera_id": 1,
  "kind": "default",
  "severity": "info",
  "first": "2026-11-02T08:00:00Z",
  "last": "2026-11-02T08:00:00Z",
  "count": 12,
  "event_id": "<event_id>",
  "details": "<details>",
  "state": "open",
  "acked_by": "<acked_by>",
  "acked_by_name": "<acked_by_name>",
  "acked": "2026-11-02T08:00:00Z",
  "note": "<note>",
  "footage": "<footage>",
  "footage_state": "none"
}

Contacts And Notifications

Who is told about alerts, and the log of the messages sent to them.

The Site's Alert Contacts

GET/sites/{id}/contacts

Who can call it: An account that is an admin of the site. Token scopes: read, personal_data.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1/contacts" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1/contacts",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/contacts", {
  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",
      "email": "ops@example.com",
      "whatsapp": "<whatsapp>",
      "telegram": "<telegram>",
      "language": "en",
      "channels": [
        "email"
      ],
      "kinds": [
        "default"
      ],
      "min_severity": "info",
      "quiet_start": "08:00",
      "quiet_end": "08:00",
      "active": true
    }
  ]
}

Add An Alert Contact

POST/sites/{id}/contacts

Who can call it: An account that is an admin of the site. Token scopes: write, personal_data.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namerequiredstring
emailstring
whatsappstringE.164, e.g. +6281234567890
telegramstringa Telegram chat id
languageenumen, id
channelsenum[]
kindsstring[]alert kinds; [] = every kind
min_severityenuminfo, warning, critical
quiet_startstring
quiet_endstring
activeboolean

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/sites/1/contacts" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","email":"ops@example.com","whatsapp":"<whatsapp>","telegram":"<telegram>","language":"en","channels":["email"],"kinds":["default"],"min_severity":"info","quiet_start":"08:00","quiet_end":"08:00","active":true}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/sites/1/contacts",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "email": "ops@example.com",
      "whatsapp": "<whatsapp>",
      "telegram": "<telegram>",
      "language": "en",
      "channels": [
        "email"
      ],
      "kinds": [
        "default"
      ],
      "min_severity": "info",
      "quiet_start": "08:00",
      "quiet_end": "08:00",
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/contacts", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "email": "ops@example.com",
    "whatsapp": "<whatsapp>",
    "telegram": "<telegram>",
    "language": "en",
    "channels": [
      "email"
    ],
    "kinds": [
      "default"
    ],
    "min_severity": "info",
    "quiet_start": "08:00",
    "quiet_end": "08:00",
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "email": "ops@example.com",
  "whatsapp": "<whatsapp>",
  "telegram": "<telegram>",
  "language": "en",
  "channels": [
    "email"
  ],
  "kinds": [
    "default"
  ],
  "min_severity": "info",
  "quiet_start": "08:00",
  "quiet_end": "08:00",
  "active": true
}

Change An Alert Contact

PATCH/contacts/{id}

Who can call it: An account that is an admin of the site. Token scopes: write, personal_data.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namestring
emailstring
whatsappstringE.164, e.g. +6281234567890
telegramstringa Telegram chat id
languageenumen, id
channelsenum[]
kindsstring[]alert kinds; [] = every kind
min_severityenuminfo, warning, critical
quiet_startstring
quiet_endstring
activeboolean

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/contacts/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","email":"ops@example.com","whatsapp":"<whatsapp>","telegram":"<telegram>","language":"en","channels":["email"],"kinds":["default"],"min_severity":"info","quiet_start":"08:00","quiet_end":"08:00","active":true}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/contacts/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "email": "ops@example.com",
      "whatsapp": "<whatsapp>",
      "telegram": "<telegram>",
      "language": "en",
      "channels": [
        "email"
      ],
      "kinds": [
        "default"
      ],
      "min_severity": "info",
      "quiet_start": "08:00",
      "quiet_end": "08:00",
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/contacts/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "email": "ops@example.com",
    "whatsapp": "<whatsapp>",
    "telegram": "<telegram>",
    "language": "en",
    "channels": [
      "email"
    ],
    "kinds": [
      "default"
    ],
    "min_severity": "info",
    "quiet_start": "08:00",
    "quiet_end": "08:00",
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "email": "ops@example.com",
  "whatsapp": "<whatsapp>",
  "telegram": "<telegram>",
  "language": "en",
  "channels": [
    "email"
  ],
  "kinds": [
    "default"
  ],
  "min_severity": "info",
  "quiet_start": "08:00",
  "quiet_end": "08:00",
  "active": true
}

Remove An Alert Contact

DELETE/contacts/{id}

Who can call it: An account that is an admin of the site. Token scopes: write, personal_data.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/contacts/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/contacts/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/contacts/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Deliveries, Newest First

GET/notify/deliveries

Who can call it: An account that is an admin of a site; the answer covers those sites. Token scopes: read, personal_data.

Parameters

FieldTypeDescription
site queryintegeronly this site
ref querystringe.g. alert:12
state querystringqueued, sending, sent or failed
before queryintegerthe next_before of the previous page
limit queryinteger1..500, default 50

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/notify/deliveries?site=1&before=2026-11-02T08%3A00%3A00Z&limit=50" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/notify/deliveries?site=1&before=2026-11-02T08%3A00%3A00Z&limit=50",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/notify/deliveries?site=1&before=2026-11-02T08%3A00%3A00Z&limit=50", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "site_id": 1,
      "ref": "<ref>",
      "channel": "email",
      "recipient": "<recipient>",
      "state": "queued",
      "attempts": 12,
      "created": "2026-11-02T08:00:00Z",
      "next": "2026-11-02T08:00:00Z",
      "sent": "2026-11-02T08:00:00Z",
      "last_error": "<last_error>"
    }
  ],
  "next_before": 12
}

Queue A Failed Delivery Again (A New Retry Period)

POST/notify/deliveries/{id}/retry

Who can call it: An account that is an admin of the site. Token scopes: write, personal_data.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/notify/deliveries/1/retry" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/notify/deliveries/1/retry",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/notify/deliveries/1/retry", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "ref": "<ref>",
  "channel": "email",
  "recipient": "<recipient>",
  "state": "queued",
  "attempts": 12,
  "created": "2026-11-02T08:00:00Z",
  "next": "2026-11-02T08:00:00Z",
  "sent": "2026-11-02T08:00:00Z",
  "last_error": "<last_error>"
}

Webhooks

Signed HTTP posts of alerts to other systems (Standard Webhooks).

The Site's Webhooks

GET/sites/{id}/webhooks

Who can call it: An account that is an admin of the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1/webhooks" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1/webhooks",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/webhooks", {
  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",
      "url": "https://<RECEIVER_HOST>/hooks/arsa",
      "kinds": [
        "default"
      ],
      "active": true,
      "created": "2026-11-02T08:00:00Z"
    }
  ]
}

Add A Webhook (Its Secret Is In This Answer Only)

POST/sites/{id}/webhooks

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namerequiredstring
urlrequiredstringhttp(s); internal addresses refused
kindsstring[]alert kinds; [] = every kind
activeboolean

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/sites/1/webhooks" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","url":"https://<RECEIVER_HOST>/hooks/arsa","kinds":["default"],"active":true}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/sites/1/webhooks",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "url": "https://<RECEIVER_HOST>/hooks/arsa",
      "kinds": [
        "default"
      ],
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/webhooks", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "url": "https://<RECEIVER_HOST>/hooks/arsa",
    "kinds": [
      "default"
    ],
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "webhook": {
    "id": 1,
    "site_id": 1,
    "name": "Main gate",
    "url": "https://<RECEIVER_HOST>/hooks/arsa",
    "kinds": [
      "default"
    ],
    "active": true,
    "created": "2026-11-02T08:00:00Z"
  },
  "secret": "<WEBHOOK_SECRET>"
}

Change A Webhook

PATCH/webhooks/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namestring
urlstringhttp(s); internal addresses refused
kindsstring[]alert kinds; [] = every kind
activeboolean

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/webhooks/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","url":"https://<RECEIVER_HOST>/hooks/arsa","kinds":["default"],"active":true}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/webhooks/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "url": "https://<RECEIVER_HOST>/hooks/arsa",
      "kinds": [
        "default"
      ],
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhooks/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "url": "https://<RECEIVER_HOST>/hooks/arsa",
    "kinds": [
      "default"
    ],
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "url": "https://<RECEIVER_HOST>/hooks/arsa",
  "kinds": [
    "default"
  ],
  "active": true,
  "created": "2026-11-02T08:00:00Z"
}

Remove A Webhook

DELETE/webhooks/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
204Done
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/webhooks/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/webhooks/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhooks/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

A New Secret (The Old One Stops At Once)

POST/webhooks/{id}/secret

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/webhooks/1/secret" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/webhooks/1/secret",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhooks/1/secret", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "secret": "<WEBHOOK_SECRET>"
}

Post A Test Event Now And Report The Outcome

POST/webhooks/{id}/test

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/webhooks/1/test" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/webhooks/1/test",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhooks/1/test", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "ok": true,
  "error": "Text",
  "retryable": true
}

Webhooks For Integrators

An API account's subscriptions: detection events in batches, periodic summaries and alerts, signed, with a delivery log.

The Product's Event Types For Webhook Subscriptions, And The Limits

GET/webhook-event-types

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/webhook-event-types" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/webhook-event-types",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-event-types", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "product": "traffic",
  "items": [
    {
      "id": "traffic.passage",
      "label": {
        "en": "Vehicle passages",
        "id": "Lintasan kendaraan"
      },
      "personal": [
        "plate",
        "photo"
      ],
      "personal_admin": false,
      "personal_only": false
    },
    {
      "id": "traffic.speeding",
      "label": {
        "en": "Speeding",
        "id": "Melaju terlalu cepat"
      },
      "personal": [
        "plate"
      ],
      "personal_admin": false,
      "personal_only": false
    }
  ],
  "summaries": true,
  "periods": [
    "15m",
    "1h",
    "1d"
  ],
  "batch_seconds": {
    "min": 1,
    "max": 3600,
    "default": 10
  },
  "batch_max": {
    "min": 1,
    "max": 1000,
    "default": 100
  },
  "max_per_account": 10
}

The Webhook Subscriptions Of The Token's Own API Account

GET/webhook-subscriptions

Who can call it: The API account that owns the subscription (its own token). Token scopes: read.

Parameters

FieldTypeDescription
account queryintegeronly this API account's

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/webhook-subscriptions?account=1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions?account=1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions?account=1", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "account_id": 1,
      "account": "erp-sync",
      "name": "Main gate",
      "url": "https://<RECEIVER_HOST>/hooks/arsa",
      "event_types": [
        "<event_type>"
      ],
      "sites": [
        12
      ],
      "batch_seconds": 60,
      "batch_max": 12,
      "summary": "15m",
      "alerts": true,
      "active": true,
      "created": "2026-11-02T08:00:00Z",
      "created_by": "<created_by>",
      "health": {
        "state": "ok",
        "last_success": "2026-11-02T08:00:00Z",
        "last_failure": "2026-11-02T08:00:00Z",
        "last_error": "<last_error>",
        "dropped": 12,
        "queued": 12
      },
      "version": 3
    }
  ]
}

Subscribe The API Account (The Secret Is In This Answer Only)

POST/webhook-subscriptions

Who can call it: The API account that owns the subscription (its own token). Token scopes: write.

Body

FieldTypeDescription
account_idintegerthe API account; a token: its own (may be left out)
namerequiredstring
urlrequiredstringhttp(s); private networks only when the box allows them
event_typesstring[]the product's event types that are on (GET /webhook-event-types)
sitesinteger[]site ids within the account's; [] = every site of the account
batch_secondsintegerdefault 10 on creation
batch_maxintegerdefault 100 on creation
summaryenumperiodic summaries of the overview's figures per site, in the site's time zone; null: none 15m, 1h, 1d, null
alertsbooleandefault false on creation
activebooleandefault true on creation

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
409Conflict
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/webhook-subscriptions" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"ERP receiver","url":"https://<RECEIVER_HOST>/hooks/arsa","event_types":["traffic.passage","traffic.speeding"],"sites":[1],"summary":"1h","alerts":true}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "ERP receiver",
      "url": "https://<RECEIVER_HOST>/hooks/arsa",
      "event_types": [
        "traffic.passage",
        "traffic.speeding"
      ],
      "sites": [
        1
      ],
      "summary": "1h",
      "alerts": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "ERP receiver",
    "url": "https://<RECEIVER_HOST>/hooks/arsa",
    "event_types": [
      "traffic.passage",
      "traffic.speeding"
    ],
    "sites": [
      1
    ],
    "summary": "1h",
    "alerts": true
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "subscription": {
    "id": 5,
    "account_id": 2,
    "account": "erp-sync",
    "name": "ERP receiver",
    "url": "https://<RECEIVER_HOST>/hooks/arsa",
    "event_types": [
      "traffic.passage",
      "traffic.speeding"
    ],
    "sites": [
      1
    ],
    "batch_seconds": 10,
    "batch_max": 100,
    "summary": "1h",
    "alerts": true,
    "active": true,
    "created": "2026-11-06T03:00:00Z",
    "created_by": "api:erp-sync",
    "health": {
      "state": "ok",
      "last_success": null,
      "last_failure": null,
      "last_error": null,
      "dropped": 0,
      "queued": 0
    },
    "version": 1
  },
  "secret": "<WEBHOOK_SECRET>"
}

A Webhook Subscription And Its Health

GET/webhook-subscriptions/{id}

Who can call it: The API account that owns the subscription (its own token). Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/webhook-subscriptions/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions/1", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "account_id": 1,
  "account": "erp-sync",
  "name": "Main gate",
  "url": "https://<RECEIVER_HOST>/hooks/arsa",
  "event_types": [
    "<event_type>"
  ],
  "sites": [
    12
  ],
  "batch_seconds": 60,
  "batch_max": 12,
  "summary": "15m",
  "alerts": true,
  "active": true,
  "created": "2026-11-02T08:00:00Z",
  "created_by": "<created_by>",
  "health": {
    "state": "ok",
    "last_success": "2026-11-02T08:00:00Z",
    "last_failure": "2026-11-02T08:00:00Z",
    "last_error": "<last_error>",
    "dropped": 12,
    "queued": 12
  },
  "version": 3
}

Change A Webhook Subscription (Pause It, Or Resume It From Now On)

PATCH/webhook-subscriptions/{id}

Who can call it: The API account that owns the subscription (its own token). Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
account_idintegercannot change
namestring
urlstringhttp(s); private networks only when the box allows them
event_typesstring[]the product's event types that are on (GET /webhook-event-types)
sitesinteger[]site ids within the account's; [] = every site of the account
batch_secondsintegerdefault 10 on creation
batch_maxintegerdefault 100 on creation
summaryenumperiodic summaries of the overview's figures per site, in the site's time zone; null: none 15m, 1h, 1d, null
alertsbooleandefault false on creation
activebooleandefault true on creation

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/webhook-subscriptions/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"account_id":1,"name":"Main gate","url":"https://<RECEIVER_HOST>/hooks/arsa","event_types":["<event_type>"],"sites":[12],"batch_seconds":60,"batch_max":12,"summary":"15m","alerts":true,"active":true}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "account_id": 1,
      "name": "Main gate",
      "url": "https://<RECEIVER_HOST>/hooks/arsa",
      "event_types": [
        "<event_type>"
      ],
      "sites": [
        12
      ],
      "batch_seconds": 60,
      "batch_max": 12,
      "summary": "15m",
      "alerts": True,
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "account_id": 1,
    "name": "Main gate",
    "url": "https://<RECEIVER_HOST>/hooks/arsa",
    "event_types": [
      "<event_type>"
    ],
    "sites": [
      12
    ],
    "batch_seconds": 60,
    "batch_max": 12,
    "summary": "15m",
    "alerts": true,
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "account_id": 1,
  "account": "erp-sync",
  "name": "Main gate",
  "url": "https://<RECEIVER_HOST>/hooks/arsa",
  "event_types": [
    "<event_type>"
  ],
  "sites": [
    12
  ],
  "batch_seconds": 60,
  "batch_max": 12,
  "summary": "15m",
  "alerts": true,
  "active": true,
  "created": "2026-11-02T08:00:00Z",
  "created_by": "<created_by>",
  "health": {
    "state": "ok",
    "last_success": "2026-11-02T08:00:00Z",
    "last_failure": "2026-11-02T08:00:00Z",
    "last_error": "<last_error>",
    "dropped": 12,
    "queued": 12
  },
  "version": 3
}

Delete A Webhook Subscription And Its Delivery Log

DELETE/webhook-subscriptions/{id}

Who can call it: The API account that owns the subscription (its own token). Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/webhook-subscriptions/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

A New Signing Secret (The Old One Stops At Once); In This Answer Only

POST/webhook-subscriptions/{id}/secret

Who can call it: The API account that owns the subscription (its own token). Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/webhook-subscriptions/1/secret" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions/1/secret",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions/1/secret", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "secret": "<WEBHOOK_SECRET>"
}

Post A Signed Test Message Now And Report The Outcome (Kept In The Delivery Log As A Test)

POST/webhook-subscriptions/{id}/test

Who can call it: The API account that owns the subscription (its own token). Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/webhook-subscriptions/1/test" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions/1/test",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions/1/test", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "ok": true,
  "error": "Text",
  "retryable": true
}

The Subscription's Delivery Log, Newest First

GET/webhook-subscriptions/{id}/deliveries

Who can call it: The API account that owns the subscription (its own token). Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger
state queryenumqueued, sending, sent, failed, dropped
before queryintegerthe next_before of the previous page
limit queryinteger1..500, default 50

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/webhook-subscriptions/1/deliveries?state=queued&before=2026-11-02T08%3A00%3A00Z&limit=50" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/webhook-subscriptions/1/deliveries?state=queued&before=2026-11-02T08%3A00%3A00Z&limit=50",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-subscriptions/1/deliveries?state=queued&before=2026-11-02T08%3A00%3A00Z&limit=50", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "subscription_id": 1,
      "kind": "events",
      "site_id": 1,
      "count": 12,
      "bytes": 12,
      "webhook_id": "<webhook_id>",
      "state": "queued",
      "attempts": 12,
      "status": 12,
      "created": "2026-11-02T08:00:00Z",
      "next": "2026-11-02T08:00:00Z",
      "expires": "2026-11-02T08:00:00Z",
      "sent": "2026-11-02T08:00:00Z",
      "last_error": "<last_error>",
      "retryable": true
    }
  ],
  "next_before": 12
}

Send A Failed Or Waiting Delivery Again Now, With A New 24 h

POST/webhook-deliveries/{id}/retry

Who can call it: The API account that owns the subscription (its own token). Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/webhook-deliveries/1/retry" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/webhook-deliveries/1/retry",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/webhook-deliveries/1/retry", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "subscription_id": 1,
  "kind": "events",
  "site_id": 1,
  "count": 12,
  "bytes": 12,
  "webhook_id": "<webhook_id>",
  "state": "queued",
  "attempts": 12,
  "status": 12,
  "created": "2026-11-02T08:00:00Z",
  "next": "2026-11-02T08:00:00Z",
  "expires": "2026-11-02T08:00:00Z",
  "sent": "2026-11-02T08:00:00Z",
  "last_error": "<last_error>",
  "retryable": true
}

Sirens And Relays

Outputs switched by alerts: Modbus TCP boards, HTTP relays and sirens, ONVIF alarm outputs.

The Site's Siren And Relay Outputs, With Their State

GET/sites/{id}/relays

Who can call it: An account that is an admin of the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1/relays" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1/relays",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/relays", {
  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",
      "kind": "modbus",
      "settings": {},
      "kinds": [
        "default"
      ],
      "cameras": [
        12
      ],
      "min_severity": "info",
      "mode": "pulse",
      "pulse_s": 60,
      "active": true,
      "password_set": true,
      "state": {
        "on": true,
        "since": "2026-11-02T08:00:00Z",
        "until": "2026-11-02T08:00:00Z",
        "last_error": "<last_error>"
      }
    }
  ]
}

Add An Output (Modbus TCP Board, HTTP Relay Or Siren, ONVIF Camera Alarm Output)

POST/sites/{id}/relays

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namerequiredstring
kindrequiredenummodbus, http, onvif
settingsrequiredobjectper kind. modbus: host, port (502), unit (1), coil, invert. http: on_url, off_url, method (GET|POST|PUT), username. onvif: device_url, relay_token, username.
kindsstring[]
camerasinteger[]
min_severityenuminfo, warning, critical
modeenumpulse, until_ack
pulse_sinteger
activeboolean
passwordstring

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/sites/1/relays" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","kind":"modbus","settings":{},"kinds":["default"],"cameras":[12],"min_severity":"info","mode":"pulse","pulse_s":60,"active":true,"password":"<password>"}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/sites/1/relays",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "kind": "modbus",
      "settings": {},
      "kinds": [
        "default"
      ],
      "cameras": [
        12
      ],
      "min_severity": "info",
      "mode": "pulse",
      "pulse_s": 60,
      "active": True,
      "password": "<password>"
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/relays", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "kind": "modbus",
    "settings": {},
    "kinds": [
      "default"
    ],
    "cameras": [
      12
    ],
    "min_severity": "info",
    "mode": "pulse",
    "pulse_s": 60,
    "active": true,
    "password": "<password>"
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "kind": "modbus",
  "settings": {},
  "kinds": [
    "default"
  ],
  "cameras": [
    12
  ],
  "min_severity": "info",
  "mode": "pulse",
  "pulse_s": 60,
  "active": true,
  "password_set": true,
  "state": {
    "on": true,
    "since": "2026-11-02T08:00:00Z",
    "until": "2026-11-02T08:00:00Z",
    "last_error": "<last_error>"
  }
}

Change An Output (Paused While On: Switched Off)

PATCH/relays/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
namestring
kindenummodbus, http, onvif
settingsobjectper kind. modbus: host, port (502), unit (1), coil, invert. http: on_url, off_url, method (GET|POST|PUT), username. onvif: device_url, relay_token, username.
kindsstring[]
camerasinteger[]
min_severityenuminfo, warning, critical
modeenumpulse, until_ack
pulse_sinteger
activeboolean
passwordstringabsent: keep; empty: remove

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/relays/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","kind":"modbus","settings":{},"kinds":["default"],"cameras":[12],"min_severity":"info","mode":"pulse","pulse_s":60,"active":true,"password":"<password>"}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/relays/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "name": "Main gate",
      "kind": "modbus",
      "settings": {},
      "kinds": [
        "default"
      ],
      "cameras": [
        12
      ],
      "min_severity": "info",
      "mode": "pulse",
      "pulse_s": 60,
      "active": True,
      "password": "<password>"
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/relays/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "name": "Main gate",
    "kind": "modbus",
    "settings": {},
    "kinds": [
      "default"
    ],
    "cameras": [
      12
    ],
    "min_severity": "info",
    "mode": "pulse",
    "pulse_s": 60,
    "active": true,
    "password": "<password>"
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "kind": "modbus",
  "settings": {},
  "kinds": [
    "default"
  ],
  "cameras": [
    12
  ],
  "min_severity": "info",
  "mode": "pulse",
  "pulse_s": 60,
  "active": true,
  "password_set": true,
  "state": {
    "on": true,
    "since": "2026-11-02T08:00:00Z",
    "until": "2026-11-02T08:00:00Z",
    "last_error": "<last_error>"
  }
}

Remove An Output (Switched Off First)

DELETE/relays/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/relays/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/relays/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/relays/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

A 2 s Test Pulse Now; Reports Whether The Device Switched On

POST/relays/{id}/test

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/relays/1/test" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/relays/1/test",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/relays/1/test", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "ok": true,
  "error": "Text"
}

Switch An Output Off Now (Silence A Siren)

POST/relays/{id}/off

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
409Conflict

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/relays/1/off" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/relays/1/off",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/relays/1/off", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "name": "Main gate",
  "kind": "modbus",
  "settings": {},
  "kinds": [
    "default"
  ],
  "cameras": [
    12
  ],
  "min_severity": "info",
  "mode": "pulse",
  "pulse_s": 60,
  "active": true,
  "password_set": true,
  "state": {
    "on": true,
    "since": "2026-11-02T08:00:00Z",
    "until": "2026-11-02T08:00:00Z",
    "last_error": "<last_error>"
  }
}

The Last 50 Switches, Newest First

GET/relays/{id}/events

Who can call it: An account that is an admin of the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/relays/1/events" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/relays/1/events",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/relays/1/events", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "at": "2026-11-02T08:00:00Z",
      "action": "on",
      "cause": "alert",
      "alert_id": 1,
      "ok": true,
      "error": "Text"
    }
  ]
}

Reports

PDF and XLSX reports made now or on a schedule.

The Reports This Dashboard Offers

GET/reports/kinds

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/reports/kinds" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/reports/kinds",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/reports/kinds", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": "<id>",
      "title": {
        "en": "<en>",
        "id": "<id>"
      }
    }
  ]
}

The Site's Report Files, Newest First (Kept 90 Days)

GET/sites/{id}/reports

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger
limit queryinteger1..500, default 100

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1/reports?limit=50" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1/reports?limit=50",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/reports?limit=50", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "site_id": 1,
      "report": "<report>",
      "title": "Weekly summary",
      "period": "1h",
      "language": "en",
      "format": "pdf",
      "bytes": 12,
      "created": "2026-11-02T08:00:00Z",
      "created_by": "<created_by>",
      "schedule_id": 1,
      "download": "<download>"
    }
  ]
}

Generate A Report Now For Local Dates In The Site's Time Zone (At Most 366 Days)

POST/sites/{id}/reports

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
reportrequiredstring
fromrequireddate
torequireddate
languageenumen, id
formatsenum[]

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/sites/1/reports" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"report":"<report>","from":"2026-11-02","to":"2026-11-02","language":"en","formats":["pdf"]}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/sites/1/reports",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "report": "<report>",
      "from": "2026-11-02",
      "to": "2026-11-02",
      "language": "en",
      "formats": [
        "pdf"
      ]
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/reports", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "report": "<report>",
    "from": "2026-11-02",
    "to": "2026-11-02",
    "language": "en",
    "formats": [
      "pdf"
    ]
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "items": [
    {
      "id": 1,
      "site_id": 1,
      "report": "<report>",
      "title": "Weekly summary",
      "period": "1h",
      "language": "en",
      "format": "pdf",
      "bytes": 12,
      "created": "2026-11-02T08:00:00Z",
      "created_by": "<created_by>",
      "schedule_id": 1,
      "download": "<download>"
    }
  ]
}

Download A Report File

GET/reports/files/{id}

Who can call it: An account with a role (viewer or admin) on the site. Token scopes: read, export.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200The file
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/reports/files/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/reports/files/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/reports/files/1", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Delete A Report File

DELETE/reports/files/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/reports/files/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/reports/files/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/reports/files/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

The Site's Scheduled Reports

GET/sites/{id}/report-schedules

Who can call it: An account that is an admin of the site. Token scopes: read.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/sites/1/report-schedules" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/sites/1/report-schedules",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/report-schedules", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "items": [
    {
      "id": 1,
      "site_id": 1,
      "report": "<report>",
      "period": "daily",
      "hour": 7,
      "recipients": [
        12
      ],
      "formats": [
        "pdf"
      ],
      "active": true,
      "next_run": "2026-11-02T08:00:00Z",
      "last_run": "2026-11-02T08:00:00Z",
      "last_error": "<last_error>"
    }
  ]
}

Schedule A Report (E-Mailed To The Chosen Contacts In Their Language)

POST/sites/{id}/report-schedules

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
reportrequiredstring
periodrequiredenumdaily: the previous day; weekly (Mondays): the previous Monday to Sunday; monthly (the 1st): the previous month daily, weekly, monthly
hourintegerlocal hour of sending
recipientsinteger[]the site's contact ids (those with an e-mail address receive it, in their language)
formatsenum[]
activeboolean

Responses

StatusMeaning
201Created
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/sites/1/report-schedules" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"report":"<report>","period":"daily","hour":7,"recipients":[12],"formats":["pdf"],"active":true}'
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/sites/1/report-schedules",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "report": "<report>",
      "period": "daily",
      "hour": 7,
      "recipients": [
        12
      ],
      "formats": [
        "pdf"
      ],
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/sites/1/report-schedules", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "report": "<report>",
    "period": "daily",
    "hour": 7,
    "recipients": [
      12
    ],
    "formats": [
      "pdf"
    ],
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 201

{
  "id": 1,
  "site_id": 1,
  "report": "<report>",
  "period": "daily",
  "hour": 7,
  "recipients": [
    12
  ],
  "formats": [
    "pdf"
  ],
  "active": true,
  "next_run": "2026-11-02T08:00:00Z",
  "last_run": "2026-11-02T08:00:00Z",
  "last_error": "<last_error>"
}

Change A Schedule (The Next Due Time Follows)

PATCH/report-schedules/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Body

FieldTypeDescription
reportstring
periodenumdaily: the previous day; weekly (Mondays): the previous Monday to Sunday; monthly (the 1st): the previous month daily, weekly, monthly
hourintegerlocal hour of sending
recipientsinteger[]the site's contact ids (those with an e-mail address receive it, in their language)
formatsenum[]
activeboolean

Responses

StatusMeaning
200OK
400Bad request
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
413Body too large
415Not JSON
422Invalid body

Request

curl -s -X PATCH "https://<BOX_HOST>/api/v1/report-schedules/1" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"report":"<report>","period":"daily","hour":7,"recipients":[12],"formats":["pdf"],"active":true}'
import requests

r = requests.patch(
    "https://<BOX_HOST>/api/v1/report-schedules/1",
    headers={"Authorization": "Bearer <API_KEY>"},
    json={
      "report": "<report>",
      "period": "daily",
      "hour": 7,
      "recipients": [
        12
      ],
      "formats": [
        "pdf"
      ],
      "active": True
    },
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/report-schedules/1", {
  method: "PATCH",
  headers: { "Authorization": "Bearer <API_KEY>", "Content-Type": "application/json" },
  body: JSON.stringify({
    "report": "<report>",
    "period": "daily",
    "hour": 7,
    "recipients": [
      12
    ],
    "formats": [
      "pdf"
    ],
    "active": true
  }),
});
console.log(r.status, await r.text());

Response 200

{
  "id": 1,
  "site_id": 1,
  "report": "<report>",
  "period": "daily",
  "hour": 7,
  "recipients": [
    12
  ],
  "formats": [
    "pdf"
  ],
  "active": true,
  "next_run": "2026-11-02T08:00:00Z",
  "last_run": "2026-11-02T08:00:00Z",
  "last_error": "<last_error>"
}

Remove A Schedule

DELETE/report-schedules/{id}

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
204Deleted
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X DELETE "https://<BOX_HOST>/api/v1/report-schedules/1" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.delete(
    "https://<BOX_HOST>/api/v1/report-schedules/1",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/report-schedules/1", {
  method: "DELETE",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Send It Now For The Latest Completed Period

POST/report-schedules/{id}/run

Who can call it: An account that is an admin of the site. Token scopes: write.

Parameters

FieldTypeDescription
idrequired pathinteger

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)
503The analysis engine is not answering (engine_unavailable); retry after Retry-After

Request

curl -s -X POST "https://<BOX_HOST>/api/v1/report-schedules/1/run" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.post(
    "https://<BOX_HOST>/api/v1/report-schedules/1/run",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/report-schedules/1/run", {
  method: "POST",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "files": [
    12
  ],
  "queued": 12
}

System

Whether the dashboard is up.

Whether The Dashboard Is Up

GET/health

Who can call it: No authentication.

Responses

StatusMeaning
200OK

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/health"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/health",
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/health", {
  method: "GET",
});
console.log(r.status, await r.text());

Response 200

{
  "status": "ok"
}

The Dashboard's TLS Certificate As PEM (box.pem), For A Client To Verify The Connection

GET/system/certificate

The box's certificate as a PEM file (box.pem), the certificates only, never the key; the X-Certificate-SHA256 header carries its fingerprint. The certificate is public, so any API account's token with read can fetch it. Compare the fingerprint with the one an admin reads on the dashboard before your client trusts the file. Answers 404 with the code certificate_not_available when the dashboard is reached through a proxy that holds the certificate.

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200The certificate chain, certificates only (never the key); the fingerprint is also in X-Certificate-SHA256
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)
404Not found (also when the account has no role on the site)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/system/certificate" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/system/certificate",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/system/certificate", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Whether The Dashboard Has A Certificate To Download, Its SHA-256 Fingerprint And The Names It Covers

GET/system/certificate/info

Whether the box has a certificate to hand out, where it comes from, its SHA-256 fingerprint (the figure to compare with what your client sees) and the names it covers: the host names and addresses your client may connect by.

Who can call it: Any API account. Token scopes: read.

Responses

StatusMeaning
200OK
401Not authenticated: the token is missing, wrong, revoked or expired (token_invalid)
403Forbidden: the account has no role for this, lacks a scope (scope_required), or the call is refused to API tokens (api_account_refused)

Request

curl -s -X GET "https://<BOX_HOST>/api/v1/system/certificate/info" \
  -H "Authorization: Bearer <API_KEY>"
import requests

r = requests.get(
    "https://<BOX_HOST>/api/v1/system/certificate/info",
    headers={"Authorization": "Bearer <API_KEY>"},
)
print(r.status_code, r.text)
const r = await fetch("https://<BOX_HOST>/api/v1/system/certificate/info", {
  method: "GET",
  headers: { "Authorization": "Bearer <API_KEY>" },
});
console.log(r.status, await r.text());

Response 200

{
  "available": true,
  "filename": "box.pem",
  "source": "self-signed",
  "sha256": "3A:1F:00:7C:AA:42:9B:10:5E:61:D3:0C:88:24:F7:93:2B:6E:C1:58:04:9A:7D:E2:13:B5:66:0F:4C:D8:A1:39",
  "certificates": 1,
  "names": [
    "<BOX_HOST>",
    "<BOX_HOST>.local",
    "localhost",
    "::1",
    "<BOX_LAN_ADDRESS>"
  ]
}
Was this page helpful?