Connections and scope
A connection belongs permanently to one space, and binding the wrong one publishes fine and then never refreshes.
A connection is Dashies' access to a place your data lives. There are two kinds.
self is built in, always available, and needs no setup. It exposes a
curated, no-PII view of your own Dashies account activity. It always ships its
data inside the file and never offloads.
A warehouse connection is yours: a PostgreSQL database, a BigQuery project, a Snowflake account, an Amazon Redshift warehouse, a Databricks workspace, or a Microsoft SQL Server database. You create it in the Dashies web app under Data sources, and creating one requires a paid plan.
Credentials are entered through that form and stored encrypted. They never pass through your AI tool or the publish service, which is why your AI cannot connect a warehouse for you: if you have not added one, you add it yourself before authoring can begin.
The web app calls these data sources. The wire format, the error messages and the spec field call the same thing a connection. Two names, one object.
Scope is an immutable pair
Every connection carries a scope, and the scope is the pair (owner, workspace). Both halves are fixed at creation and neither can ever change. A database trigger rejects any attempt to move either one.
So there is no move path in either direction. A personal connection cannot be promoted into a workspace, and a workspace connection cannot be pulled out into your personal space. If you need a warehouse available inside a workspace, you create it from inside that workspace, with its own credentials, as a separate connection.
This is deliberate rather than unfinished. A connection is a live credential into someone's warehouse, and moving one between spaces silently changes who can use it. Making the scope immutable means the answer to "who can reach this warehouse through Dashies" is fixed at the moment the credential was entered, by the person who entered it.
The workspace trap
Here is the consequence, and it is the mistake authors make most often, because every single thing about it looks fine until it does not.
A workspace connection can only be bound by a dashboard in that same workspace. If you publish a spec that names a workspace connection but do not publish into that workspace, the publish does not stop. It succeeds.
- The SQL validates, because validation runs against the connection and you are a member, so you may author against it.
- The seed runs, against that very connection, and returns real numbers.
- The dashboard is written and goes live at a real URL, with correct data on it.
- Then the refresh manifest is refused, and only the refresh manifest.
You are left with a live dashboard that looks completely correct and can never refresh. It is frozen at its seeded numbers forever.
A dry run does not catch this
dry_run stops before the manifest is installed, so a clean dry run is not
evidence that the connection can be bound in the scope you are publishing into.
The only thing that exercises this path is the real publish, and by then the
dashboard is already live.
What you get back is this sentence, verbatim:
dashboard content was saved, but refresh installation failed - call get_dashboard_spec and republish with the returned spec_hash to complete.
followed by an explanation naming the connection and the workspace argument.
The fix is to publish into the connection's own space, or to point the spec at a
connection created in the space you are publishing into. Republishing unchanged
will fail identically.
The reason the early steps pass is worth internalizing, because it predicts the behaviour rather than just describing it: authoring is scoped to you, binding is scoped to the dashboard. You may author against any connection you can reach. A dashboard may only bind a connection in its own space. Those are different questions, asked at different moments, and only the second one is about the dashboard.
Note that the raw-body publish path behaves differently, and inconsistently enough to be worth knowing: a republish of an existing dashboard by raw body is refused before anything is written. A brand-new dashboard, on either path, goes live first.
The refusal is deliberately uninformative
The message you get is:
source_config.connection is not an active warehouse connection available to this dashboard
That exact sentence is returned for four different causes: the id is unknown, the connection is not a warehouse, it is not active, or it is the wrong scope. It is identical in all four cases on purpose. A message that distinguished "does not exist" from "exists but you may not use it" would be an oracle telling you which connection ids are real.
You can still diagnose it, by elimination rather than by reading. If
list_connections just showed you that id and reported it active, then
unknown, non-warehouse and inactive are all ruled out, and scope is the only
cause left. Re-fetching the id will return the same id; that is not the
problem.
If the message instead says the connection is not active, that is a genuinely different failure with a different fix. Test the connection in the web app. Changing scope will not help.
Two sibling messages come from the same place:
source_config.connection is not a valid connection
means the value is not a well-formed connection id at all.
a paid plan is required to use a warehouse connection
means exactly what it says.
What you can and cannot do with a connection
Two levels of permission, and they are not the same set of people.
Using it means authoring against it and binding it to a dashboard:
- a personal connection: its owner
- a workspace connection: any current member of that workspace, whoever created it
Current membership is required with no exemption for the creator. Removing someone from a workspace tears down no connections, so without that rule a departed member would keep authoring against the team's warehouse.
Managing it means deleting, rotating, renaming, resyncing, or re-testing it, all of which touch the stored credential:
- a personal connection: its owner
- a workspace connection: the creator, or a current workspace admin
The creator keeps this even after leaving the workspace. That exemption is load-bearing rather than generous: requiring membership produced a workspace that could never be deleted, because the cleanup ran as the connection's own owner and aborted.
One more asymmetry that surprises people: listing your personal connections does not include workspace connections you created. They belong to the workspace, so they are listed from inside it.
Status and health are two different facts
A data source carries two independent readings, and the product shows both:
statusis what an explicit Test concluded. That test may be days old.health_stateis what real refreshes and authoring calls have observed since.
So a connection reading active and failing at the same time is a normal, correct reading rather than a contradiction. Status answers "did a test pass". Health answers "is it working right now, in production". When they disagree, health is the newer news.
Health is deliberately narrow and deliberately non-gating. Only three failure kinds count as evidence about the connection itself: a rejected credential, a TLS failure, and a host that could not be reached. It takes two consecutive observations to flip, and any success on any dashboard using that connection clears it. A timeout is excluded on purpose, because a heavy query and a sick warehouse look identical from the outside, and a broken dashboard query is a problem with that dashboard rather than with the connection.
Nothing reads health to decide whether a refresh runs. It is there so that when you are about to build on a connection whose credential expired last Tuesday, you find out before you author rather than after.
Whose plan is checked
For a personal dashboard, yours.
For a workspace warehouse dashboard, the plan checked at refresh time is the plan of whoever created the data source, not the plan of whoever wrote the dashboard. A free member of a paid workspace can bind the team's warehouse and author against it, because the creator is paid.
The consequence to remember: if that creator's plan lapses, every scheduled run on that connection stops. A whole team's dashboards going quiet at once is the signature of this case. See Plans and what is gated.
Next
Schedules, for what happens once a connection is bound.