Dashboards API
Project dashboards let you build persistent, shareable views over normalized telemetry. Each dashboard is a collection of widgets that query the spans or findings view. Queries are executed by the telemetry backend and streamed to the Workbench as Server-Sent Events.
When To Use It#
Use the Dashboards API when you want to:
- create or update dashboards programmatically;
- embed dashboard definitions in project configuration;
- run ad-hoc widget queries outside the Workbench.
For interactive exploration, use the Workbench Dashboards view.
Authentication#
All routes are project-qualified under /v1/projects/:projectId.
| Action | API key capability | Workbench permission |
|---|---|---|
| List, overview, get a dashboard | workspace:read |
project:read |
| Create, update, delete, set default | workspace:write |
project:update |
Execute widget queries (POST .../dashboard-queries) |
telemetry:read |
project:data:read |
Dashboard Definition#
A dashboard body is { "widgets": WidgetDefinition[] }. Each widget follows this shape:
{
"id": "requests",
"title": "Gateway requests",
"layout": { "columnSpan": 6, "rowSpan": 2 },
"query": {
"view": "spans",
"shape": "timeseries",
"metric": "trace.count",
"dimensions": ["gen_ai.operation.name"],
"timeBucket": "hour",
"filters": [],
"limit": 100
},
"visual": { "kind": "line", "seriesLayout": "overlap" }
}query.viewis one ofspansorfindings.query.shapeis one ofsingle,timeseries, orbreakdown.visual.kindis one ofmetric,line,area,bar,horizontal_bar,donut,radial,table,summary, orlist.layout.columnSpanis one of2, 3, 4, 5, 6, 8, 12;layout.rowSpanis one of1, 2, 3.
A dashboard may contain at most 24 widgets (maxDashboardWidgets). Time range is not a per-widget
field; it is supplied per request (see below).
List Dashboards#
curl "$PROVON_API_URL/projects/$PROJECT_ID/dashboards" \
-H "Authorization: Bearer $PROVON_API_KEY"Dashboard Overview#
curl "$PROVON_API_URL/projects/$PROJECT_ID/dashboard-overview" \
-H "Authorization: Bearer $PROVON_API_KEY"Returns the project's dashboard list together with the default dashboard. Pass an optional
?dashboardId=<id> query parameter to narrow the overview to one dashboard.
Create A Dashboard#
curl -X POST "$PROVON_API_URL/projects/$PROJECT_ID/dashboards" \
-H "Authorization: Bearer $PROVON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Gateway health",
"description": "Request volume, errors, and latency",
"widgets": [
{
"id": "requests",
"title": "Gateway requests",
"layout": { "columnSpan": 6, "rowSpan": 2 },
"query": { "view": "spans", "shape": "timeseries", "metric": "trace.count" },
"visual": { "kind": "line" }
}
]
}'Validation failures return 400 INVALID_DASHBOARD (or INVALID_DASHBOARD_DEFINITION on the
streaming route).
Get, Update, And Delete#
# Get
curl "$PROVON_API_URL/projects/$PROJECT_ID/dashboards/$DASHBOARD_ID" \
-H "Authorization: Bearer $PROVON_API_KEY"
# Update
curl -X PATCH "$PROVON_API_URL/projects/$PROJECT_ID/dashboards/$DASHBOARD_ID" \
-H "Authorization: Bearer $PROVON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Updated name", "widgets": [ ... ]}'
# Delete
curl -X DELETE "$PROVON_API_URL/projects/$PROJECT_ID/dashboards/$DASHBOARD_ID" \
-H "Authorization: Bearer $PROVON_API_KEY"A missing dashboard returns 404 DASHBOARD_NOT_FOUND.
Set The Default Dashboard#
curl -X PUT "$PROVON_API_URL/projects/$PROJECT_ID/default-dashboard" \
-H "Authorization: Bearer $PROVON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dashboardId": "<dashboard-id>"}'dashboardId must be a non-empty string (400 INVALID_DASHBOARD_ID otherwise). The default
dashboard is shown first when a project opens the Dashboards view. The response is the updated
dashboard list.
Execute A Widget Query#
Run a set of widget queries and stream results. The body is
{ "range": "<time-range>", "widgets": WidgetDefinition[] }; range uses the same relative,
preset, or <startMs>-<endMs> forms documented in Trace Read API.
curl -X POST "$PROVON_API_URL/projects/$PROJECT_ID/dashboard-queries" \
-H "Authorization: Bearer $PROVON_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"range": "24h",
"widgets": [
{
"id": "requests",
"title": "Gateway requests",
"layout": { "columnSpan": 6, "rowSpan": 2 },
"query": { "view": "spans", "shape": "timeseries", "metric": "trace.count" },
"visual": { "kind": "line" }
}
]
}'The response is a Server-Sent Events stream (text/event-stream, Cache-Control: no-cache,
Connection: keep-alive). At most 24 widgets can be requested; the query service executes them
with bounded concurrency (default maxConcurrency = 3 when the service does not declare its own
capacity). More than 24 widgets returns 400 DASHBOARD_WIDGET_LIMIT_EXCEEDED.
Stream Events#
| Event | Data fields |
|---|---|
init |
totalWidgets, maxConcurrency |
progress |
completedWidgets, totalWidgets, index, widgetId, percent, durationMs |
widget_data |
widgetId, status (success or error), rows (or [] on error), optional trend (success only), error (error only), durationMs |
complete |
success, durationMs, completedWidgets |
error |
message |
Example stream:
event: init
data: {"totalWidgets":2,"maxConcurrency":3}
event: progress
data: {"completedWidgets":1,"totalWidgets":2,"index":0,"widgetId":"requests","percent":50,"durationMs":120}
event: widget_data
data: {"widgetId":"requests","status":"success","rows":[...],"trend":null,"durationMs":120}
event: progress
data: {"completedWidgets":2,"totalWidgets":2,"index":1,"widgetId":"errors","percent":100,"durationMs":165}
event: widget_data
data: {"widgetId":"errors","status":"error","error":"...","rows":[],"durationMs":45}
event: complete
data: {"success":true,"durationMs":165,"completedWidgets":2}The client should read events until the stream closes and handle error events as failures.
Cancelling the HTTP request aborts in-flight widget queries.
Limitations#
- Dashboard definitions are project-scoped; sharing across projects requires copying the definition.
- Widget query syntax is validated by the telemetry backend and may differ slightly between DuckDB and Cloudflare R2 SQL deployments.
- The API does not render charts; it returns the query result
rowsandtrendthat the Workbench visualizes.