Skip to content

Core concepts

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 three 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, a Microsoft SQL Server database, or an Oracle 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 uploaded-file connection holds CSV and Excel files you sent to Dashies rather than a database Dashies reaches. It is the one kind you do not create in the web app: the first upload in a workspace creates it, labelled Uploaded files, and there is exactly one of them per workspace however many files it holds. Creating it needs a paid plan for the same reason a warehouse connection does, and it spends one of the workspace's connection slots. See Build a dashboard from a file.

It is also the one kind the web app does not show you. The Data sources page lists the connections you create there, and the uploaded-file row is hidden from it: the app has no upload surface, so a row it could neither create, edit nor test would only raise a question it cannot answer. Ask your AI tool for the workspace's connections to see it, with your uploads listed under it.

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. A connection cannot be pulled out of the workspace it was created in, and it cannot be handed to another one. If you need the same warehouse available inside a second workspace, you create it from inside that workspace, with its own credentials, as a separate connection.

Every data source belongs to a workspace. There is no personal scope: a data source cannot be created outside a workspace, and none exists.

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.

The raw-body publish path used to behave differently here, and that difference is gone with the path: every publish carrying raw bytes is now refused before anything is written, whether it is a brand-new dashboard or a republish of an existing one. A brand-new dashboard on the spec path goes live first, as it did.

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: any current member of the workspace the connection belongs to, 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: 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.

Connections are listed from inside the workspace they belong to, so a connection you created in one workspace does not appear while you are looking at another.

The uploaded-file connection has no creator, so only an admin can manage it

Nobody owns it. It is created by the workspace's first upload rather than by a person, which leaves the "creator" half of the rule above with nobody in it: a current workspace admin is the only one who can delete it. Deleting it tombstones the workspace's uploads with it.

Using it follows the ordinary rule above: any current member with a creator seat may author against an upload that is already there, and creating one needs the same seat. A view-only role can do neither.

Status and health are two different facts

A data source carries two independent readings, and the product shows both:

  • status is what an explicit Test concluded. That test may be days old.
  • health_state is 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

The workspace's. Neither the person who created the data source nor the person who wrote the dashboard is consulted at refresh time. A free member of a paid workspace can bind the team's warehouse and author against it, because the workspace is paid.

What follows from that is what to watch: the thing that stops a team's scheduled runs is the workspace's plan lapsing, not any one member's. An individual whose own subscription ends keeps every workspace dashboard running. See Plans and what is gated.

Next

Schedules, for what happens once a connection is bound.