---
title: Connections and scope
description: A connection belongs permanently to one space, and binding the wrong one publishes fine and then never refreshes.
updated: 2026-09-20
---

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](/guides/upload-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.

:::danger{title="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.

:::note{title="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](/concepts/plans-and-gating).

## Next

[Schedules](/concepts/schedules), for what happens once a connection is bound.
