Connect Snowflake
Set up key-pair authentication for a read-only Snowflake user, add the data source, and keep the per-refresh credit cost predictable.
Snowflake, via key-pair authentication. Dashies signs a JWT with your private key and calls the SQL API. There is no password and no host to enter.
Before you start
Two things about Snowflake catch people out more than anything else on this page, so read them first.
Use the hyphenated account identifier
The identifier must be the hyphen form, myorg-myaccount. The dotted org form
myorg.myaccount is rejected outright with:
account_identifier is invalid. Use the hyphenated form (for example myorg-myaccount), not the dotted org form.A dotted identifier is accepted only in the legacy locator-with-region shape,
such as xy12345.ap-southeast-2.aws.
An account network policy will look like an auth failure
If your account has a NETWORK POLICY that allowlists office or VPN addresses,
it blocks Dashies, and Snowflake reports that as a rejected sign-in rather than a
blocked address. Check for one before you go hunting for a key problem. The
Dashies error text names this possibility for the same reason.
1. Create the key pair
Run these on your own machine. The private key stays with you until you paste it into the Dashies form; the public key goes on the Snowflake user.
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out dashies_key.p8 -nocrypt
openssl rsa -in dashies_key.p8 -pubout -out dashies_key.pub
dashies_key.p8 is the PKCS#8 private key you paste into Dashies.
dashies_key.pub holds the public key you register on the Snowflake user in the
next step. When you register it, paste only the base64 body, without the
-----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- lines.
2. Create the warehouse, role, and user
Run this in a Snowflake worksheet as ACCOUNTADMIN. It creates a dedicated
X-Small warehouse, a read-only role, and a service user carrying the public key.
create warehouse if not exists DASHIES_WH
warehouse_size = XSMALL
auto_suspend = 60
auto_resume = true
initially_suspended = true;
create role if not exists DASHIES_RO;
grant usage on warehouse DASHIES_WH to role DASHIES_RO;
grant usage on database ANALYTICS to role DASHIES_RO;
grant usage on schema ANALYTICS.PUBLIC to role DASHIES_RO;
grant select on all tables in schema ANALYTICS.PUBLIC to role DASHIES_RO;
grant select on future tables in schema ANALYTICS.PUBLIC to role DASHIES_RO;
create user if not exists DASHIES_SVC
type = SERVICE
login_name = 'DASHIES_SVC'
default_role = DASHIES_RO
default_warehouse = DASHIES_WH;
grant role DASHIES_RO to user DASHIES_SVC;
alter user DASHIES_SVC set rsa_public_key = 'PASTE_THE_BASE64_BODY_OF_dashies_key.pub';
Replace ANALYTICS and ANALYTICS.PUBLIC with your own database and schema, and
repeat the two grant select lines for every schema you want readable.
LOGIN_NAME must match what you type into Dashies
The JWT identity Dashies presents is derived from the user name you enter. If the
user's LOGIN_NAME differs from it, Snowflake rejects the sign-in. Keeping them
identical, as above, avoids the whole class of problem.
The grant select on future tables line matters for the same reason
alter default privileges does on Postgres: without it, a table created later is
invisible and the refresh that needed it breaks with no warning.
3. Add the data source
Open dashies.xyz/app/connections, click to add a data source, and pick Snowflake.
| Field | Required | Example | Notes |
|---|---|---|---|
| Account identifier | yes | myorg-myaccount | Hyphen form. See above. |
| User | yes | DASHIES_SVC | The login name of the read-only user. |
| Warehouse | yes | DASHIES_WH | The warehouse refresh queries run on. |
| Role | no | DASHIES_RO | |
| Database | no | ANALYTICS | |
| Private key | yes | a PKCS#8 PEM block | The contents of dashies_key.p8. |
| Display name | no | Analytics warehouse | Up to 120 characters. |
Warehouse, role, and database are upper-cased on the way in, because an unquoted Snowflake identifier resolves upper case.
The host is derived from the account identifier, so there is nothing to enter and no address to allowlist on our side.
4. It verifies in one step
Snowflake is a single-step connect. Creating the data source stores the key, mints the key-pair JWT, and runs two metadata probes, then comes back active with the databases it found, or error with a class. Up to 200 databases are stored as the allowlist. There is no Resync button on Snowflake.
If it fails:
| Message | Meaning |
|---|---|
Snowflake denied access. Check that the key pair is registered on the user (RSA_PUBLIC_KEY) and that the account identifier is correct. An account-level network policy can also block access. | Any authentication failure. |
The warehouse was not found or the user's role cannot use it. Check the warehouse name and its grants. | The warehouse name is wrong, or the role has no usage on it. |
We couldn't read that account. Check the account identifier and try again. | The account identifier resolves but the probes did not succeed. |
Cost
A cold refresh costs at least 60 seconds of credits
Snowflake bills a 60-second minimum every time a warehouse auto-resumes. A scheduled refresh that finds the warehouse suspended therefore costs at least one minute of that warehouse's credits, however short the query is.
Two things keep this predictable:
- Use a dedicated X-Small warehouse with
auto_suspend = 60, as the script above creates. Do not point Dashies at a large shared warehouse. - Match the refresh cadence to how fast the data actually moves. Hourly refresh of a month-grained cube buys nothing and pays the 60-second floor 24 times a day. See Set a refresh schedule.
Connecting itself is free. The verification runs SHOW DATABASES and
SHOW WAREHOUSES LIKE, which are metadata-only and burn zero warehouse
credits.
Caps
| Limit | Value | What happens past it |
|---|---|---|
| Rows per dataset query | 100,000 | Refused before any partition is fetched. |
| Inline result partitions | 32 | Query errors. |
| Statement wait | 30 seconds, then polling | Query errors if it never completes. |
| Data island, whole dashboard | 8,388,608 bytes | Publish is refused. |
| Compiled dashboard body | 5,242,880 bytes | Publish is refused. Usually binds first. |
There is no execution-time byte cap the way there is on Postgres. The adapter reads Snowflake's own row total up front and refuses before fetching. A wide cube is still bounded, one layer later, by the island and body limits.
Snowflake supports every materialization, including Parquet offload for a row-level dataset.
How these ceilings relate to each other, and which one binds first, is in sizes and ceilings.
Dialect notes
Cube SQL is Snowflake SQL.
-
Table references are database-qualified:
from ANALYTICS.PUBLIC.ORDERS. -
Bucket dates in your business time zone, in the SQL:
select date_trunc('MONTH', convert_timezone('UTC', 'America/Los_Angeles', ordered_at)) as month, sum(amount) as revenue from ANALYTICS.PUBLIC.ORDERS group by 1 order by 1 -
Relative window:
dateadd('month', -12, current_timestamp()). Conditional count:count_if(c). Exact median:percentile_cont(0.5) within group (order by x). -
Snowflake folds an unquoted output alias to upper case. That is handled for you: a case-only difference between the alias and your declared measure key is canonicalized at publish and at refresh, so
sum(amount) as revenueagainst a measure namedrevenueis correct. You do not need upper-case manifest keys. What is refused, loudly and naming both, is two output columns that differ only by case landing on one declared key. -
A
VARIANT,OBJECT, orARRAYcolumn has to be addressed into before it is usable, andlateral flattenmultiplies rows. Nothing rejects that, so the cube runs and the numbers are simply wrong. Aggregate back to the grain you meant. See Verify your numbers.
Rotating the key
Edit the data source and paste a new PKCS#8 PEM under Private key, then register the matching public key on the Snowflake user.
Check it worked
-
The data source reads active on dashies.xyz/app/connections, and lists the databases it discovered.
-
Ask your AI tool to introspect it and run one query:
Introspect my Snowflake data source, then validate this cube SQL against it:
select 1 as ok