Dashboards API
Create and validate dashboards programmatically with an API key.
Dashboards can be created from your own tooling — provisioning a standard dashboard set per service, generating one per tenant, or keeping dashboards in Git and applying them from CI.
This page covers the write (POST / PUT) endpoints for dashboards. For reading
logs and traces see the Logs & Traces API; for metrics see the
Metrics API; for alert rules see the
Alerts API.
Authentication
An API key in the X-API-Key header:
X-API-Key: <your-api-key>Create one under Settings → API Key Management. See Authentication for details.
API keys are scoped per key: you choose which modules a key may reach when you
create it. The endpoints on this page need the dashboard scope — a key without it is
rejected for this module regardless of who created it.
warning: Two things must both hold for a create or update to succeed: The key is scoped to dashboard — otherwise the request is refused as out of scope. A key can only be given scopes its creator's own role can access, so a key never grants more than the person who made it. The role behind the key has write access to the module. Scopes select which modules a key can touch, not what it may do inside them — read vs write still comes from the role. A read-only role yields 403 on POST / PUT, while GET still works. (/validate writes nothing and is not write-gated — see below.)
Request conventions
| Concern | How it's passed |
|---|---|
| Content type | Content-Type: application/json |
| Body | JSON object (see each endpoint) |
| Response | The standard envelope — { "data": …, "error": false, "message": "…" } |
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/dashboard | Create a dashboard. |
PUT | /api/dashboard | Update an existing dashboard. |
POST | /api/dashboard/validate | Validate a preset without saving. |
GET | /api/dashboard-lists | List every dashboard list. |
POST | /api/dashboard-lists | Create a list. |
GET | /api/dashboard-lists/{id} | Fetch one list. |
PUT | /api/dashboard-lists/{id} | Rename a list or change its description. |
DELETE | /api/dashboard-lists/{id} | Delete a list. |
POST | /api/dashboard-lists/{id}/dashboards | Add dashboards to a list. |
DELETE | /api/dashboard-lists/{id}/dashboards | Remove dashboards from a list. |
PUT | /api/dashboard-lists/dashboard/{id} | Replace one dashboard's list membership. |
GET | /api/dashboard/{id}/access | Who can reach a dashboard. |
PUT | /api/dashboard/{id}/access | Set the restriction and the whole grant list. |
PUT | /api/dashboard/{id}/owner | Transfer ownership. |
Create a dashboard
POST /api/dashboard
curl -s -X POST "https://<your-kubesense-host>/api/dashboard" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Payments overview",
"description": "Golden signals for the payments service",
"status": "active",
"preset": "{\"panels\":[],\"gridLayout\":[]}"
}'| Field | Type | Description |
|---|---|---|
name | string | Dashboard name. |
description | string | Free text. |
status | string | active. |
preset | string | The dashboard document — panels, layout, variables and optional tabs — as a JSON-encoded string. |
id | string | Optional UUID; omit to have one generated. |
The response returns the new dashboard's id.
warning: preset is a string, not an object — the whole dashboard document is JSON-encoded and passed as one field. Sending a nested object instead of a string is the most common cause of a 400 invalid request body here.
Building the preset
The preset holds the panels, grid layout and template variables. The practical way to get a valid one is to build the dashboard once in the UI and export it, then template the parts you want to vary (cluster name, namespace, service) and re-post it. Hand-writing a preset from scratch is possible but fiddly, and the schema evolves.
Presets are migrated on the way in, so a document exported from an older KubeSense version is upgraded automatically rather than rejected.
Tabs
A preset can carry an optional tabs array. Each tab is
{"id", "title", "panels", "gridLayout", "subGrids", "subGridLayout"}, so it holds its own
panels, layout and rows. variables and eventOverlays stay at the top level and apply
to every tab.
idmatches^[a-z0-9][a-z0-9-]{0,39}$and is unique in the dashboard.titleis 1 to 50 characters after trimming, and unique ignoring case.- When
tabsis non-empty, the top-levelpanels,gridLayout,subGridsandsubGridLayoutmust be empty. - Sub-grid ids are unique across all tabs.
An untabbed dashboard omits tabs or sends [].
warning: When you update a dashboard that has tabs, send the tabs key. A PUT whose preset has no tabs key over a tabbed dashboard returns 409 with This dashboard has tabs that this client does not support. Reload the page and try again. Read the dashboard, edit the preset, and send it back whole.
Update a dashboard
PUT /api/dashboard takes the same body with id set to the dashboard you're
updating.
Validate a preset
POST /api/dashboard/validate checks a preset without writing anything — useful in CI
before committing a generated dashboard.
It is deliberately not gated behind write permission, so a read-only key can lint.
It accepts the preset in any of three forms, so you can pipe a document straight through without reshaping it:
- the preset object itself (
{"panels":[…],"gridLayout":[…]}) - the preset as a JSON string
- the import envelope — an object carrying the stringified preset under
preset
curl -s -X POST "https://<your-kubesense-host>/api/dashboard/validate" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @dashboard.jsonnote: Validation runs the same migrate-then-check path the save uses, so a dry run and the save that follows it reach the same verdict — a preset that validates will store.
Request bodies are capped at 8 MB.
Dashboard lists
Lists group dashboards for navigation. Membership is many-to-many: a dashboard belongs to as many lists as you file it under, and adding it to one never removes it from another. See Dashboard Lists for the concept.
These endpoints reuse the dashboard scope and the dashboard module's
permissions — there is no separate list scope to grant. Reads need read access; every
other verb needs write access.
Create a list
POST /api/dashboard-lists
curl -s -X POST "https://<your-kubesense-host>/api/dashboard-lists" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production",
"description": "Dashboards on call refers to"
}'| Field | Type | Description |
|---|---|---|
name | string | Required. Max 255 characters. Must be unique among live lists, compared case-insensitively. |
description | string | Optional free text. |
The response returns the new list's id. A duplicate name returns 409.
List and fetch
GET /api/dashboard-lists returns every list with a dashboard_count of the live
dashboards in it. It is not paginated. GET /api/dashboard-lists/{id} returns one.
Rename a list
PUT /api/dashboard-lists/{id} takes the same body as create. Renaming to a name
another live list already holds returns 409.
Add and remove dashboards
# Add
curl -s -X POST "https://<your-kubesense-host>/api/dashboard-lists/$LIST_ID/dashboards" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dashboard_ids": ["<uuid>", "<uuid>"]}'
# Remove
curl -s -X DELETE "https://<your-kubesense-host>/api/dashboard-lists/$LIST_ID/dashboards" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dashboard_ids": ["<uuid>"]}'Adding a dashboard already in the list is a no-op, not an error, so a batch
containing some existing members succeeds rather than failing partway. Adding a
dashboard that does not exist returns 400.
Removing only unfiles the dashboard — the dashboard itself is untouched and keeps every other list it belongs to.
Replace a dashboard's membership
PUT /api/dashboard-lists/dashboard/{id} sets the complete set of lists one
dashboard belongs to:
curl -s -X PUT "https://<your-kubesense-host>/api/dashboard-lists/dashboard/$DASHBOARD_ID" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"list_ids": ["<uuid>", "<uuid>"]}'Lists omitted from list_ids are removed, so an empty array unfiles the dashboard
entirely. Use this when you know the desired end state; use the add/remove endpoints
above when you only want to adjust one membership without reading the current set
first.
Delete a list
DELETE /api/dashboard-lists/{id}?strategy=detach
strategy | Effect |
|---|---|
detach (default) | Deletes the list and unfiles its dashboards. The dashboards survive and keep their other lists. |
delete | Deletes the list and soft-deletes every dashboard in it, including dashboards that also belong to other lists. |
warning: strategy=delete destroys dashboards, not just the grouping. It must be requested explicitly and cannot be undone from the UI.An absent strategy defaults to detach. An unrecognised one is refused with 400 rather than quietly detaching, so a typo such as strategy=delet cannot be reported as a success that did something other than what was asked.
Both strategies run as a single transaction, so a list is never left half-deleted.
Dashboard access
Restricting a dashboard narrows who may reach it. See Dashboard Access for the concept and what each choice means.
The read needs viewer access to the dashboard; both writes are owner only. Module write is necessary but not sufficient — an editor cannot change who else reaches a dashboard.
Read the access state
GET /api/dashboard/{id}/access returns the mode, the owner, and every grant
with the addresses resolved:
{
"restriction_mode": "restricted",
"default_access": "viewer",
"owner_id": "…",
"owner_email": "admin@kubesense.ai",
"owner_name": "KubeSense Admin",
"grants": [
{
"principal_type": "user",
"principal_id": "…",
"email": "karthik@kubesense.ai",
"name": "Karthik K",
"access_level": "editor"
}
]
}Set the access state
PUT /api/dashboard/{id}/access takes the whole grant list, not a delta —
the last writer wins cleanly rather than diffing against a list someone else
may have changed since:
curl -s -X PUT "https://<your-kubesense-host>/api/dashboard/$DASHBOARD_ID/access" \
-H "X-API-Key: $KUBESENSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"restriction_mode": "restricted",
"default_access": "viewer",
"grants": [
{ "principal_type": "user", "email": "karthik@kubesense.ai", "access_level": "editor" }
]
}'| Field | Values |
|---|---|
restriction_mode | open or restricted |
default_access | What an ungranted person gets when restricted: none, viewer (recommended) or editor. Omitted means none |
grants[].principal_type | user today; team and role are planned |
grants[].email | Must match an existing, active user |
grants[].access_level | viewer or editor |
Grants are stored against the user id, not the address, so a grant survives someone changing their email.
warning: Addresses are validated before anything is written: a request naming a user who does not exist is refused in full, with the unmatched addresses listed, and nothing is saved. Setting restriction_mode back to open discards the grants rather than parking them — restricting again starts from an empty list.
Transfer ownership
PUT /api/dashboard/{id}/owner takes {"email": "…"}. Owner only. There is no
administrator override anywhere in this API, so a dashboard whose owner has
left cannot have its access changed by anyone else — transfer it before that
happens.
Errors
| Status | Meaning |
|---|---|
400 | Invalid body — malformed JSON, a preset that isn't a string, or a document that isn't a preset at all. |
401 | Missing, invalid, or expired API key. |
403 | Either the key isn't scoped to dashboard, or the role behind it has read-only access. |
404 | The dashboard list does not exist, or was already deleted. |
409 | A dashboard list with that name already exists (create or rename). |
500 | Server error while persisting. |