Replace a dashboard's spec (URL stays the same). Every update is saved as a numbered version. With "draft": true it is saved without going live: viewers keep seeing the live version, members can preview the draft at the returned preview_url, and publish_dashboard_version makes it live. Use drafts for larger changes and publish when the user confirms. Dashboard spec: {"title": str, "description"?: str, "range"?: "1m"|"3m"|"6m"|"ytd"|"1y"|"2y"|"5y"|"10y"|"max", "theme"?: "auto"|"light"|"dark" (default mode; auto follows the viewer's system and viewers can switch), "layout"?: "auto"|"overview"|"detail" (only the default for panels without "display": overview makes them tiles, detail makes them charts, auto makes them tiles above 8 panels), "visibility"?: "public"|"private" (default public: anyone with the link; private: only members of this account signed in to tlly.dev, so embeds and shared links stop working for others), "panels": [{"title"?: str,
"series": "name" | ["a","b"], "type"?: "line"|"area"|"bar"|"stat"|"gauge"|"bargauge"|"pie"|"heatmap"|"table", "unit"?: str, "note"?: str, "stale_after_days"?: num,
"thresholds"?: {"red_above"?, "yellow_above"?, "yellow_below"?, "red_below"?}, "range"?: (as above, overrides the dashboard's),
"section"?: str (starts a new titled section at this panel), "views"?: bool (show the Level / Y/Y % / Index buttons; default: shown unless the unit is already a rate like %, y/y or pp), "display"?: "tile"|"chart" (tile: a compact number with a sparkline, click for the full chart; chart: a card with the full chart; mix them freely, e.g. tiles for the many supporting numbers and charts for the few that need their history), "headline"?: bool (show as a big number at the top of the dashboard, under a status strip; use for the 3-6 most important), "width"?: "half"|"full", "scale"?: "linear"|"log",
"id"?: str (stable panel id, e.g. pn_abc123XYZ012; keep existing ids when editing so alerts stay attached),
"right_axis"?: [series names] (line, area and bar charts: draw these series against a second y axis on the right, for series of different sizes or units; at least one stays on the left), "right_unit"?: str (unit of the right axis; "unit" is the left's), "left_label"?/"right_label"?: str (≤40 chars, a title written vertically along the left / right y axis, e.g. "Policy rate"; right_label needs right_axis),
"view"?: "level"|"yoy"|"index" (initial view; viewers can switch), "source"?: str (shown as "Source: …"; defaults to the job's host),
"labels"?: [display name per series], "status_series"?: name (a red/yellow/green text series that sets the panel colour),
"compare"?: "prev"|"3m"|"1y" (what the change is measured against), "period"?: "D"|"M"|"Q" (show dates as 2026-06 or 2026Q2), "value_names"?: {"green": "Hold", …} (display names for text values)}],
"badge"?: "worst" (default: the header badge shows the worst panel) | "off" | panel index (0-based; the badge follows that panel),
"shade"?: [{"from": date, "to": date, "label"?: str}] (up to 20 shaded periods on every chart)}.
"type": "table" shows one row per series (up to 20): label, latest value, date and a status dot; its "status_series" is a list with one entry per row ("" for none).
"area" is a line chart filled underneath; "gauge" is a dial of the latest value with the threshold zones, and "bargauge" a horizontal bar per series (up to 20, coloured by thresholds), both scaled by "min"?/"max"?: num (default 0 to a bit above the largest value or threshold); "pie" is a donut of the series' latest values (shares of a whole, up to 20); "heatmap" is a calendar of the first series' daily values (good for activity per day).
Status colour is computed from the latest value of the panel's first series; a series of red/yellow/green labels (e.g. a derived status) colours itself.
Notes may use {latest}, {date}, {period} and {change}, filled in from the panel's latest value. Annotations on a panel's series appear as markers.
Viewers can pick a time range (1M to Max, year to date, or a custom from-to period), zoom, switch views and download CSV; ?embed=1 hides the page chrome and &panel=N shows one panel.
Text limits: title ≤120, description ≤500, panel title ≤120, unit ≤20, note ≤280, section and source ≤80 characters, single line, plain text.