Kubesense

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

ConcernHow it's passed
Content typeContent-Type: application/json
BodyJSON object (see each endpoint)
ResponseThe standard envelope — { "data": …, "error": false, "message": "…" }

Endpoints

MethodPathDescription
POST/api/dashboardCreate a dashboard.
PUT/api/dashboardUpdate an existing dashboard.
POST/api/dashboard/validateValidate a preset without saving.
GET/api/dashboard-listsList every dashboard list.
POST/api/dashboard-listsCreate 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}/dashboardsAdd dashboards to a list.
DELETE/api/dashboard-lists/{id}/dashboardsRemove dashboards from a list.
PUT/api/dashboard-lists/dashboard/{id}Replace one dashboard's list membership.
GET/api/dashboard/{id}/accessWho can reach a dashboard.
PUT/api/dashboard/{id}/accessSet the restriction and the whole grant list.
PUT/api/dashboard/{id}/ownerTransfer 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\":[]}"
  }'
FieldTypeDescription
namestringDashboard name.
descriptionstringFree text.
statusstringactive.
presetstringThe dashboard document — panels, layout, variables and optional tabs — as a JSON-encoded string.
idstringOptional 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.

  • id matches ^[a-z0-9][a-z0-9-]{0,39}$ and is unique in the dashboard.
  • title is 1 to 50 characters after trimming, and unique ignoring case.
  • When tabs is non-empty, the top-level panels, gridLayout, subGrids and subGridLayout must 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.json

note: 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"
  }'
FieldTypeDescription
namestringRequired. Max 255 characters. Must be unique among live lists, compared case-insensitively.
descriptionstringOptional 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

strategyEffect
detach (default)Deletes the list and unfiles its dashboards. The dashboards survive and keep their other lists.
deleteDeletes 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" }
    ]
  }'
FieldValues
restriction_modeopen or restricted
default_accessWhat an ungranted person gets when restricted: none, viewer (recommended) or editor. Omitted means none
grants[].principal_typeuser today; team and role are planned
grants[].emailMust match an existing, active user
grants[].access_levelviewer 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

StatusMeaning
400Invalid body — malformed JSON, a preset that isn't a string, or a document that isn't a preset at all.
401Missing, invalid, or expired API key.
403Either the key isn't scoped to dashboard, or the role behind it has read-only access.
404The dashboard list does not exist, or was already deleted.
409A dashboard list with that name already exists (create or rename).
500Server error while persisting.