rebase.energy
Dashboards

Overview

A dashboard is one declarative JSON document the platform stores, resolves into cards and lines, and renders.

A dashboard is one JSON document the platform stores and renders. It names a window (the time range the page opens with), a grid layout, and a list of components. The API is the primary authoring surface: the web app lists and draws dashboards, and its editor saves through the same routes.

There are two component types:

  • chart draws one card. On its own it must name every record it draws.
  • group draws a section of cards: one chart per item of its for_each, which iterates every record of an object type or a list of records. The group owns what its cards share: the iterator, the heading, and the chart template. Inside the template, placeholders such as {{ item.name }} fill in per item, and a series without a record draws the item's own.

The document is declarative. Nobody enumerates sites; a dashboard over "all sites" keeps following the data as sites are added. Ten charts from a dozen lines:

{
  "name": "Power",
  "window": {"last": "P2D", "ahead": "P2D"},
  "components": [{
    "type": "group",
    "title": "Sites",
    "for_each": {"object": "sites"},
    "component": {
      "type": "chart",
      "title": "{{ item.name }}",
      "chart": {
        "series": [
          {"timeseries": "power", "label": "Actual"},
          {"timeseries": "power-forecast", "column": "power", "label": "Forecast"}
        ]
      }
    }
  }]
}

GET /dashboards/{id}/resolve turns the document into concrete cards and lines, which is what the web app and any other renderer draws. POST /dashboards/resolve does the same for an unsaved document: the dry run an author or an agent uses before saving.

Vocabulary

WordMeaning
documentthe JSON a dashboard is written as (POST/PUT body, GET response)
componentan entry of components: a chart or a group
groupa component that repeats a chart template per item
cardone drawn chart, as the resolved form lists it
series (document)one entry of chart.series: a timeseries type on a record, with drawing options
lineone drawn series in the resolved form
series typethe platform's timeseries slug on a record (power, power-forecast), as in Upload / read timeseries

Routes and permissions

MethodPathDoesPermission
GET/dashboardslist the workspace's dashboards (summaries, no components)objects:read
POST/dashboardscreate from a document; 201 with the stored documentobjects:write
GET/dashboards/schemathe document's JSON Schemaobjects:read
POST/dashboards/resolveresolve an unsaved document, saving nothingobjects:read
GET/dashboards/{id}the stored document with its envelopeobjects:read
PUT/dashboards/{id}replace the whole documentobjects:write
DELETE/dashboards/{id}delete; 204objects:delete
GET/dashboards/{id}/resolvethe concrete cards and lines to drawobjects:read

Rendering a resolved dashboard reads values through POST /timeseries/read, which needs timeseries:read.

Status codes: 404 for an unknown id or another workspace's dashboard; 409 when a name is already used in the workspace; 422 for anything the schema rejects, with a detail list whose loc names the field, or a plain detail string for the checks the schema cannot express (an unknown object type, too many lines).

The envelope

GET /dashboards returns {"items": [...]} of summaries, sorted by name. GET /dashboards/{id}, POST, and PUT return the summary plus the document:

{
  "id": "c1719097-71d9-468b-a5c1-30fe7ee1ba42",
  "name": "Power",
  "description": "Forecast against actual production, one chart per site.",
  "component_count": 2,
  "created_by": {"id": "...", "name": "Henrik", "picture": "https://..."},
  "created_at": "2026-10-02T13:10:04Z",
  "updated_at": "2026-10-02T13:55:41Z",
  "layout": {"columns": 12},
  "window": {"start": null, "end": null, "last": "P2D", "ahead": "P2D"},
  "components": []
}
  • component_count counts top-level components (a group counts as one).
  • created_by is the creating user's name and picture, or null when the creator is not a user of the organization (an API key's service account) or is gone.
  • Documents are stored as written: fields the author did not set are not stored, so a later change of a default or the removal of an optional field never breaks a stored dashboard. The response fills defaults in, so a GET shows the effective values (width: 6, height: 1, kind: "line", and so on).
  • PUT replaces the whole document. There is no partial patch; the pattern is GET, edit, PUT back.

Rejected on write, or only a warning

Authors otherwise assume a 201 means everything resolves. Three things are rejected on POST and PUT with a 422:

  • the standalone checks on a chart outside a group: every series must name a record, and no template may refer to item;
  • the axis rules: an undeclared axis, two axes on the same side of one pane, a panes list that does not cover the panes in use, zero_at or align_zero combined with min;
  • an unknown object type in a for_each.

Two things only warn, at resolve time, so the dashboard still saves: records in an iterator that do not exist, and items or cards dropped by a limit. A record that simply lacks a series is reported per line, not as a warning. Resolving and rendering shows where each lands.

The authoring loop

  1. Fetch GET /dashboards/schema once, so an editor or an agent knows the document.
  2. Write the document. Prefer a group over listing record ids, so the dashboard follows the data. Give every group a title and every chart one; {{ item.name }} is the usual chart title inside a group.
  3. POST /dashboards/resolve and read warnings, every card's errors, and every line's error. Fix and repeat.
  4. POST /dashboards.
  5. To change it later, GET, edit, PUT back.

Build a dashboard walks through the loop end to end; The document lists every field.

On this page