VideoBI / Developers / Dashboard API

Dashboard API

Two read-only endpoints: list your published dashboards, and fetch one as JSON with each widget's layout, metric and data.

Authentication

Send your organization API key as a bearer token on every request. Keys are created in VideoBI under Settings → API keys by an owner or admin, and look like vai_key_….

Authorization: Bearer vai_key_…
  • Base URL: https://app.videobi.ai
  • Keys are read-only. A key limited to some locations sees only those locations' data, and dashboards scoped to them or to every location.
  • Call the API from your server. It sends no CORS headers, so browser pages can't call it directly.
  • Revoking a key in Settings stops it at once.

List dashboards

GET /v1/api/dashboards

Every published dashboard the key can see.

{
  "items": [
    { "id": "43009756-579f-45ea-b0dd-9ed0a10f0bc8", "name": "Area manager weekly",
      "description": "Every store against the chain, by district: …",
      "audience": "manager", "cadence": "weekly", "version": 1, "published_at": "2026-10-01T18:29:07.406Z" }
  ]
}

audience is executive, manager or frontline. version increases each time the dashboard is republished.

Get a dashboard with its data

GET /v1/api/dashboards/{id}?range=last_7_days
ParameterWhat it does
rangeA time range, relative to now in the location's time zone. One of today, yesterday, last_24_hours, last_7_days, this_week, last_week, last_4_weeks, last_30_days, this_month, last_month, last_90_days, this_quarter, last_quarter, last_13_weeks, last_12_months. Leave it out to use the dashboard's own default.
from, toA custom window instead of range: ISO 8601 with a time-zone offset, like 2026-09-01T00:00:00-07:00. Send both, or neither.
locationOne location's id: the dashboard for that store or site only. Without it, every location the key can see.
daypartA daypart name, like lunch_peak, to limit hours where the widgets support it.

The response

{
  "api_version": 1,
  "dashboard": { "id": "…", "name": "Area manager weekly", "description": "…", "audience": "manager",
                 "cadence": "weekly", "version": 1, "published_at": "…", "columns": 12 },
  "time_range": { "preset": "last_7_days" },
  "timezone": "America/Los_Angeles",
  "filters": { "location_ids": null, "daypart": null },
  "rendered_at": "2026-10-06T22:07:34.448Z",
  "widgets": [
    {
      "id": "waste", "title": "Waste", "viz": "stat",
      "layout": { "x": 0, "y": 0, "w": 3, "h": 3 },
      "metric": { "id": "waste_value", "name": "Waste", "description": "What the discarded items would have sold for.",
                  "unit": "currency", "format": "currency", "direction": "lower_is_better" },
      "query": { "metric": "waste_value", "grain": "period", "group_by": [], "compare_to": "prior_period", … },
      "display": { "format": "currency", "aggregate": "sum" },
      "data": {
        "columns": [ { "name": "bucket", "type": "timestamp" }, { "name": "value", "type": "currency" },
                     { "name": "coverage_pct", "type": "ratio" }, { "name": "compare_value", "type": "currency" }, … ],
        "rows": [ ["2026-09-29T07:00:00.000Z", 1840.5, 0.97, 2010.25, -169.75, "…"] ],
        "coverage": { "overall": 0.97, "byRow": [0.97] },
        "meta": { "from": "…", "to": "…", "grain": "period", "timezone": "…", "warnings": [] }
      }
    }
  ]
}
Widget fieldMeaning
vizHow the app draws it: stat, line, bar, area, table, gauge, heatmap and more. Rendering maps each to Apache ECharts.
layoutPosition on a grid of dashboard.columns (12) columns: x, y in cells from the top left, w, h in cells.
metricWhat is measured, its unit and format, and whether higher or lower is better. null for widgets that aren't a metric (images, alerts).
queryWhat was asked: the metric, the time grain (hour, day, week, month, quarter, or period for one total), the dimensions it is split by (group_by) and any comparison (compare_to).
displayPresentation hints: number format, alert thresholds, sort order, reference lines.
dataThe result. For metric widgets: columns (name and type), rows as arrays in column order, coverage (how much of the time the cameras could see, 0–1) and meta; assets for image and clip widgets, with links valid for 10 minutes; or {"error": {"code", "message"}} when that one widget couldn't be computed. One failed widget never fails the request.

Examples

Node.js

const res = await fetch("https://app.videobi.ai/v1/api/dashboards/DASHBOARD_ID?range=last_30_days", {
  headers: { Authorization: `Bearer ${process.env.VIDEOBI_KEY}` },
});
if (!res.ok) throw new Error(`VideoBI ${res.status}: ${(await res.json()).detail}`);
const dashboard = await res.json();
for (const w of dashboard.widgets) console.log(w.title, w.viz, w.data.rows?.length ?? 0, "rows");

Python

import os, requests

r = requests.get(
    "https://app.videobi.ai/v1/api/dashboards/DASHBOARD_ID",
    params={"range": "last_30_days"},
    headers={"Authorization": f"Bearer {os.environ['VIDEOBI_KEY']}"},
    timeout=30,
)
r.raise_for_status()
for w in r.json()["widgets"]:
    print(w["title"], w["viz"], len(w["data"].get("rows", [])), "rows")

Errors

Errors use problem details (application/problem+json): {"type", "title", "status", "detail"}, with detail written for a person.

StatusWhen
400A parameter isn't valid: an unknown range, from without to, both range and from.
401No key, or a key that is revoked, expired or mistyped.
403The plan doesn't include the API, or the key isn't allowed that location.
404No published dashboard with that id that this key can see.
429More than 60 requests in a minute with one key. Wait a minute and retry.