publish_dashboard
Upload a dashboard file or compile a Dashies spec, and get a stable URL back. Parameters, return shape, errors and scope rules.
Publishes a dashboard and returns its URL. It has two modes that share one tool, and the mode is decided by which argument names the content.
- Body mode: you upload bytes.
bodypluscontent_type. Optionally attach asource_configrefresh manifest to make it refreshable. - Spec mode: you send a Dashies spec and the server compiles it to HTML, a data island and a refresh manifest, then seeds every dataset from the connection and validates the result. A structurally broken or silently wrong dashboard cannot be published this way.
Spec mode is the supported authoring path. Body mode is how a hand-written file gets published.
Behaviour hints for clients: not read-only, not idempotent, not open-world.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
path | string | yes | <slug> or <slug>/<filename>. A bare slug means <slug>/index.html. The handle is added server-side. |
body | string | no | Body mode. Raw text for text content types, base64 otherwise. Mutually exclusive with the three spec arguments. |
content_type | string | no | Required in body mode. Forbidden in spec mode. |
encoding | string | no | raw or base64. Inferred from content_type when omitted: raw for text/*, application/json, application/xml, *+json and *+xml. |
spec | string | no | Spec mode, document sent inline. Capped at 5242880 bytes. |
spec_hash | string | no | Spec mode, republish a document the server already holds. Cached for about an hour after any send. |
spec_edits | array | no | Spec mode, exact-string edits against the stored spec. Requires base_spec_hash. |
dry_run | boolean | no | Spec-only. Runs compile, validation and a read-only seed and writes nothing. Default false. |
base_spec_hash | string | no | Spec-only lost-update guard. Required with spec_edits, where it also names the document. |
name | string | no | Display name. Defaults to a title-cased slug on insert; preserved on republish. |
tags | array | no | Preserved when omitted. [] clears. |
chart | string | no | Preserved when omitted. |
visibility | string | no | public or private. New dashboards default to public personally, private in a workspace. Preserved on republish. |
workspace | string | no | Workspace slug. Works in both modes. |
source_config | object | no | Refresh manifest, manifest_version 1, 2, 3 or 4. Body mode only. |
Exactly one of spec, spec_hash and spec_edits may name the document. All
three compile, validate and seed identically; the saving is transmission, never
checking.
Returns
Body mode returns the URLs, the byte count and the resolved metadata:
Published as mickey.
Dashboard URL: https://mickey.dashies.xyz/revenue-overview
File URL: https://mickey.dashies.xyz/revenue-overview/index.html
Bytes: 48213
Name: Revenue Overview
Visibility: public (new dashboard)
Refresh: daily (manifest stored; the cube SQL re-runs on this schedule).
On republish the visibility line reads
Visibility: public (preserved from prior publish; pass visibility to change).
A manual cadence reads
Refresh: manual (manifest stored; no automatic schedule - refresh on demand).
Auto-promote notes, seed notes and Warning: blocks are appended, separated by
blank lines.
Body mode also rides structuredContent:
{ slug, filename, url, fileUrl, bytes, name, visibility, wasInsert }, plus
optional refresh: { frequency } and warnings: string[].
Spec mode returns human lines then a JSON block:
Published revenue-overview as mickey.
Dashboard URL: https://mickey.dashies.xyz/revenue-overview
Spec hash: 6f1c...
Datasets: revenue (lattice), pipeline (rows)
Bytes: body 51204, island 18422
BEGIN_JSON
{"ok":true,"published":true,"url":"https://mickey.dashies.xyz/revenue-overview","slug":"revenue-overview","spec_hash":"6f1c...","mode_choices":[],"warnings":[],"obligations":[],"bytes":{"body":51204,"island":18422,"per_dataset":{}}}
END_JSON
A dry run opens with
Dry run: <slug> compiled cleanly - nothing was published. and reports
published:false, url:null.
Read the rows-seeded line in full
The receipt reports rows in and rows shipped per dataset. Equal counts render as
mix 9 (total unconfirmed), not a bare mix 9, and the suffix is load-bearing:
where an engine reports no server-side total the server falls back to the length
of what it received, which always manufactures agreement. Only a genuine
shortfall gets the bare 3/6 form. Quote the line whole or not at all.
Errors
Argument matrix, returned before any work happens:
provide exactly one of `spec`, `spec_hash`, or `spec_edits` (got `spec` + `spec_hash`): `spec` sends the document inline, `spec_hash` republishes a document the server already validated, and `spec_edits` patches the stored spec.
provide either `body` or `spec`, not both: `spec` publishes the compiled file format, `body` publishes raw bytes.
The remaining matrix refusals, each verbatim:
dry_run is only valid with `spec` (the file-format publish); a body publish always writes.
base_spec_hash is only valid with `spec` (the file-format publish's lost-update guard).
dry_run must be a boolean.
base_spec_hash must be a non-empty string (a spec_hash returned by get_dashboard_spec).
spec must be a non-empty string (the Dashies file-format YAML).
spec_hash must be a non-empty string (a spec_hash returned by a dry run, a previous publish, or get_dashboard_spec).
content_type is not allowed with `spec`: a compiled spec is always text/html.
source_config is not allowed with `spec`: the refresh manifest is compiled from the spec's datasets and source.
spec_edits requires base_spec_hash: the edits are applied to the spec with that hash, and it is also the lost-update guard. Read the current spec with get_dashboard_spec and pass the spec_hash it returns.
In the first two, <noun> is whichever of the three spec arguments you sent, so
spec_edits produces the same sentence naming spec_edits.
An edit whose old_string is not found, or is found more than once without
replace_all, refuses the whole publish and writes nothing. Include more
surrounding lines rather than retrying blind.
Body mode, beyond the shared path and metadata rules:
| Condition | Text |
|---|---|
| Reserved slug | slug "<slug>" is reserved - it would be shadowed by the app at https://<handle>.dashies.xyz/<slug>; choose a different slug |
| Missing body | body is required |
| Missing content type | content_type is required |
| Bad base64 | 400 invalid base64 body: <err> |
| Bad encoding | 400 invalid encoding "<enc>" |
| Over the cap | 413 payload too large: encoded body is <n> bytes, maximum is 5242880 |
| Storage write failed | r2 put failed: <err> |
| Database write failed after the bytes landed | db write failed after R2 put: <err> |
Manifest shape errors come from the same validator the refresh cron mirrors, and
include source_config must be a JSON object,
source_config.manifest_version must be 1, 2, 3, or 4,
source_config.connection must be a non-empty string,
source_config.cube_sql must be a non-empty string,
source_config.schedule must be one of manual/hourly/daily/weekly/monthly, and
source_config.timezone "<tz>" is not a valid IANA time zone (e.g. "America/Los_Angeles", "Europe/London", "UTC"). Omit it to default to UTC.
A manifest that stores but fails to attach is a partial success:
Published, but failed to attach the refresh manifest: <err>. The dashboard is live as a static page; retry the same publish to attach the manifest and enable refresh.
Spec mode failures print a count, then one line per finding in the shape
- [<layer>] <path or (spec)>: <message> (<hint>):
Publish failed - 1 error (nothing was published):
- [seed] datasets[0]: <the engine's own error> (the seed runs under the same time budget a scheduled refresh gets, so this cube did not finish in the time the cron would give it - publishing it would produce a dashboard that fails every refresh. If the warehouse was cold or paused, retry (it should be warm now); otherwise aggregate to a coarser grain or narrow the window. Nothing was published.)
The angle-bracketed part is the engine's message, which varies. The hint after it
is ours and is fixed per failure class; the one above is the connection_timeout
hint, and it is worth reading closely because a spec publish is a dry run of
the refresh's clock. The seed gets the cron's budget, not the more generous
authoring budget validate_cube_sql runs under, so a cube that validates in 90
seconds can still be refused here. That is the point: it would otherwise publish
cleanly and then time out on every scheduled refresh, forever.
Layers are arg, compile, seed, conflict, internal and write. Going
over the byte cap reports the heaviest dataset by name, so you know which one to
coarsen:
the compiled dashboard is <n> bytes, over the 5242880-byte publish limit. The heaviest dataset is `<name>` (<n> bytes) - coarsen its grain or reduce its rows.
A base_spec_hash mismatch arrives under code: "spec_conflict".
One spec failure keeps published: true, because the content did land:
dashboard content was saved, but refresh installation failed - call get_dashboard_spec and republish with the returned spec_hash to complete.
Rate limited at 60 calls per user per minute, in both modes and both scopes. See Rate limits.
Scope
Workspace-capable in both modes. Pass workspace, or use a
workspace-authorized connection. See
Scope.
Check it worked
Open the Dashboard URL the receipt printed. If you attached a manifest, confirm
the Refresh: line names the cadence you asked for rather than manual, then
read the state back with
get_refresh_status.