Skip to content

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. body plus content_type. Optionally attach a source_config refresh 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

ParameterTypeRequiredNotes
pathstringyes<slug> or <slug>/<filename>. A bare slug means <slug>/index.html. The handle is added server-side.
bodystringnoBody mode. Raw text for text content types, base64 otherwise. Mutually exclusive with the three spec arguments.
content_typestringnoRequired in body mode. Forbidden in spec mode.
encodingstringnoraw or base64. Inferred from content_type when omitted: raw for text/*, application/json, application/xml, *+json and *+xml.
specstringnoSpec mode, document sent inline. Capped at 5242880 bytes.
spec_hashstringnoSpec mode, republish a document the server already holds. Cached for about an hour after any send.
spec_editsarraynoSpec mode, exact-string edits against the stored spec. Requires base_spec_hash.
dry_runbooleannoSpec-only. Runs compile, validation and a read-only seed and writes nothing. Default false.
base_spec_hashstringnoSpec-only lost-update guard. Required with spec_edits, where it also names the document.
namestringnoDisplay name. Defaults to a title-cased slug on insert; preserved on republish.
tagsarraynoPreserved when omitted. [] clears.
chartstringnoPreserved when omitted.
visibilitystringnopublic or private. New dashboards default to public personally, private in a workspace. Preserved on republish.
workspacestringnoWorkspace slug. Works in both modes.
source_configobjectnoRefresh 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:

ConditionText
Reserved slugslug "<slug>" is reserved - it would be shadowed by the app at https://<handle>.dashies.xyz/<slug>; choose a different slug
Missing bodybody is required
Missing content typecontent_type is required
Bad base64400 invalid base64 body: <err>
Bad encoding400 invalid encoding "<enc>"
Over the cap413 payload too large: encoded body is <n> bytes, maximum is 5242880
Storage write failedr2 put failed: <err>
Database write failed after the bytes landeddb 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.

Last updated 2026-08-04