> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quadrillion.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Snowflake Integration

> Connect Qualia to your Snowflake account with per-user OAuth, including accounts that only sign in through an identity provider.

Connect Qualia to your Snowflake account so notebooks can query it as **you**, not as a shared service account. Snowflake enforces your own grants, and every query runs under a single role your admin chooses.

Both the desktop app and Qualia Cloud connect the same way, from the same settings page. Where they differ — who configures the account, which security integration it needs, and where the browser opens — is called out below.

<Note>
  It works for accounts that have **no password to give**. If your Snowflake account authenticates through Microsoft Entra, Okta, or another identity provider, signing in here starts at Snowflake, which hands you off to that provider and back. Nobody types a Snowflake username or password anywhere in Qualia.
</Note>

<Note>
  The agent is instructed to use this integration for reads only. That instruction is not enforcement: the connector sends the statement it is given, as you, so a connecting user who can write can write. Point `ALLOWED_ROLES_LIST` at a read-only role if you want writes to be impossible rather than merely unasked for.
</Note>

## Prerequisites

* **A Snowflake account** you can create a security integration in, which needs `ACCOUNTADMIN`. On a trial account you have it; in an enterprise account this is usually a request to whoever owns it.
* **A role** for Qualia sessions to run under. One role covers every sign-in, and it is baked into each token — Snowflake's OAuth binds the role at authorization time rather than letting a session switch later.
* **A warehouse** that role can use.

<Note>
  Cloud and desktop need **separate security integrations**, and cannot share one. Cloud is a *confidential* client — it keeps a secret on the server and its callback is an HTTPS URL on the Qualia API. Desktop is a *public* client — it has nowhere to keep a secret, so it uses PKCE and a loopback callback. Snowflake's `PRE_AUTHORIZED_ROLES_LIST`, which suppresses the consent screen, is confidential-clients-only, so a desktop integration cannot carry it. `ALLOWED_ROLES_LIST` is not restricted that way, so desktop still gets the same server-side role bound and only loses consent-suppression.
</Note>

## Required privileges

Creating the security integration needs `ACCOUNTADMIN` **once**. In Qualia Cloud, saving the connection also needs an **organization admin in Qualia**; on desktop there is one install and one user, so you configure it yourself.

Each person who then signs in needs, in Snowflake:

| Privilege                        | On                               | Why                                       |
| -------------------------------- | -------------------------------- | ----------------------------------------- |
| The role named in the connection | Granted to their user            | The token is issued for exactly that role |
| `USAGE`                          | The warehouse                    | Run queries                               |
| `USAGE`                          | Each database and schema to read | Resolve names                             |
| `SELECT`                         | Each table to read               | Read data                                 |

## Creating the security integration (Qualia Cloud)

Snowflake has no automatic client registration, so this statement is run once, by hand, in the account Qualia will connect to.

First, copy the **redirect URL** from **Settings → Integrations → Snowflake** in Qualia. Snowflake matches it character for character, and amending it later means another change in your Snowflake account, so paste it rather than retyping it.

```sql theme={null}
CREATE SECURITY INTEGRATION QUALIA_OAUTH
  TYPE = OAUTH
  OAUTH_CLIENT = CUSTOM
  OAUTH_CLIENT_TYPE = 'CONFIDENTIAL'
  OAUTH_REDIRECT_URI = '<the redirect URL Qualia shows>'
  OAUTH_ENFORCE_PKCE = TRUE
  OAUTH_ISSUE_REFRESH_TOKENS = TRUE
  OAUTH_REFRESH_TOKEN_VALIDITY = 7776000
  ALLOWED_ROLES_LIST = ('<your read-only role>')
  PRE_AUTHORIZED_ROLES_LIST = ('<your read-only role>')
  IS_AGENTIC = TRUE
  ENABLED = TRUE
  COMMENT = 'Qualia (Quadrillion) - per-user read access';

SELECT SYSTEM$SHOW_OAUTH_CLIENT_SECRETS('QUALIA_OAUTH');
```

The parameters that matter, and why:

* **`ALLOWED_ROLES_LIST` is the access bound.** When set, only the listed roles can be used through this integration and every other role is blocked. Without it, "bounded to one read-only role" would rest entirely on Qualia asking for the right one, which is an application-level promise rather than something Snowflake enforces.
* **`PRE_AUTHORIZED_ROLES_LIST` is not a bound.** It only suppresses the consent screen for the roles listed. Both parameters are wanted; they do different jobs.
* **`OAUTH_ENFORCE_PKCE` defaults to `FALSE`** and has to be asked for. Qualia always sends a PKCE challenge; this makes Snowflake require it.
* **`OAUTH_ISSUE_REFRESH_TOKENS` only permits refresh tokens.** Qualia requests the `refresh_token` scope to actually get one — that is what keeps you signed in for the validity window instead of returning to the browser every few minutes.
* **`OAUTH_REFRESH_TOKEN_VALIDITY`** caps at 7,776,000 seconds (90 days), which is also the default. After it lapses, everyone signs in again.
* **`IS_AGENTIC`** makes `IS_AGENT_ACTIVATED()` return true for these sessions, so agent access is distinguishable in your audit history.

Leave `OAUTH_USE_SECONDARY_ROLES` unset. Its default, `NONE`, is what makes `ALLOWED_ROLES_LIST` meaningful; `IMPLICIT` would activate the user's default secondary roles and bypass it.

Keep the client ID and secret from `SYSTEM$SHOW_OAUTH_CLIENT_SECRETS` — you need both in the next step, and the secret is not shown again.

<Warning>
  If your Snowflake account has a **network policy**, Qualia's requests come from a cloud sandbox whose addresses are neither stable nor ours, so they will be refused by IP before any of the above matters. Check for one before starting.
</Warning>

## Creating the security integration (desktop)

Desktop is a public client, so the statement differs in three ways: no secret, a loopback redirect, and no `PRE_AUTHORIZED_ROLES_LIST`. Copy the **redirect URL** from Qualia's Snowflake settings first — the port is fixed, because Snowflake byte-matches the URI and the arbitrary loopback port [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3) allows cannot be used.

```sql theme={null}
CREATE SECURITY INTEGRATION QUALIA_OAUTH_DESKTOP
  TYPE = OAUTH
  OAUTH_CLIENT = CUSTOM
  OAUTH_CLIENT_TYPE = 'PUBLIC'
  OAUTH_REDIRECT_URI = 'http://127.0.0.1:8021/'
  OAUTH_ALLOW_NON_TLS_REDIRECT_URI = TRUE
  OAUTH_ENFORCE_PKCE = TRUE
  OAUTH_ISSUE_REFRESH_TOKENS = TRUE
  OAUTH_REFRESH_TOKEN_VALIDITY = 7776000
  ALLOWED_ROLES_LIST = ('<your read-only role>')
  IS_AGENTIC = TRUE
  ENABLED = TRUE
  COMMENT = 'Qualia desktop (Quadrillion) - per-user read access';

SELECT SYSTEM$SHOW_OAUTH_CLIENT_SECRETS('QUALIA_OAUTH_DESKTOP');
```

`OAUTH_ALLOW_NON_TLS_REDIRECT_URI` is required even for a loopback address: Snowflake refuses any `http` redirect URI without it.

You still need the **client ID** from `SYSTEM$SHOW_OAUTH_CLIENT_SECRETS`. Ignore the secret — a public client does not use one, and Qualia's desktop settings has no field for it.

If something else on the machine already holds port 8021, set `SNOWFLAKE_CALLBACK_PORT` and register the URL Qualia then shows instead.

## Connecting

Setup is two steps, and the settings page labels them as such. **Step 1, Account** points Qualia at a Snowflake account and its security integration. **Step 2, Your access** is your personal sign-in, and stays disabled until step 1 is saved.

1. Go to **Settings → Integrations → Snowflake**.
2. Enter your **account identifier** — the part of your Snowsight URL before `.snowflakecomputing.com`. Both `myorg-myaccount` and the legacy `xy12345.us-east-1` form work.
3. Paste the **OAuth client ID**. In Qualia Cloud, paste the **client secret** too; desktop has no secret field, because a public client has none.
4. Enter the **role** you listed in `ALLOWED_ROLES_LIST`, and the **warehouse** queries should use. A default database and schema are optional.
5. Click **Save connection**.

Where you sign in differs:

* **Qualia Cloud** — under **Your access**, click **Sign in with Snowflake** and authorize. Every other member of your organization then signs in individually from the same page: the account configuration is shared, the authorization is not.
* **Desktop** — there is no button. Your **first query** opens the browser, because the Snowflake connector runs the flow itself on the machine the query runs on. It remembers you afterwards, so later queries and new kernels do not ask again.

Either way, if your account uses single sign-on, Snowflake hands you to your identity provider at that point.

<Note>
  Changing the **role** on the connection signs everyone out. Snowflake binds the role into each token, so an existing authorization cannot be reinterpreted under a new one — everyone re-authorizes. The same is true of changing the account or the OAuth client.
</Note>

## Querying from a notebook

Ask the agent, or write it yourself:

```python theme={null}
import qualia_snowflake

df = qualia_snowflake.query("SELECT * FROM analytics.public.orders LIMIT 100")
```

The result is a real pandas DataFrame in the kernel, which is what lets Qualia trace a claim in a writeup back to the data behind it.

In Qualia Cloud the connector is already in the kernel image, so the first query runs straight away. On desktop and other kernels the first query installs `snowflake-connector-python[pandas,secure-local-storage]`; the agent handles that, and it takes a few seconds once.

Do not call `snowflake.connector.connect` directly. `qualia_snowflake` fetches a short-lived token for the signed-in user and renews it, including rebuilding the connection after Snowflake expires the session — which the Snowflake connector does not do for a token-supplied login.

## What is stored, and where

In Qualia Cloud:

| Value                         | Where it lives                       | Who can read it                            |
| ----------------------------- | ------------------------------------ | ------------------------------------------ |
| OAuth client ID               | The connection row                   | Organization admins                        |
| OAuth client secret           | Encrypted in the connection row      | Nobody — it never leaves the backend       |
| Refresh token (up to 90 days) | Encrypted in your grant row          | Nobody — it never leaves the backend       |
| Access token (minutes)        | Issued per connection to your kernel | Your own kernel, for the life of the token |

Refresh tokens and the client secret never reach a notebook, a kernel environment, a log line, or anything the agent can read. Only short-lived access tokens leave the backend.

On desktop there is less to protect, because there is no secret and Qualia holds no tokens at all:

| Value                               | Where it lives                                                                                                              |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Account, client ID, role, warehouse | `settings.json`, in the clear — none of it is secret                                                                        |
| Refresh and access tokens           | The Snowflake connector's own cache: the OS keyring on macOS and Windows, a `0600` file under `~/.cache/snowflake` on Linux |

That cache belongs to the connector, not to Qualia, which is why **removing the connection** on desktop clears the configuration and leaves the cached token alone — without an account and client ID nothing can use it, and it expires on its own. To revoke it outright, drop the grant in Snowflake.

**Disconnecting** from **Your access** (Qualia Cloud) discards your tokens immediately. **Removing the connection** discards the account configuration, and in cloud every member's authorization with it.

## Troubleshooting

**"Redirect URI must point at this application"** — the URL you entered is not the one Qualia serves. Use the copy button next to the Redirect URL field rather than retyping it.

**Snowflake shows an invalid redirect URI** — the URL registered in `OAUTH_REDIRECT_URI` does not byte-match the one Qualia sent. A trailing slash counts as a difference. Fix it with `ALTER SECURITY INTEGRATION QUALIA_OAUTH SET OAUTH_REDIRECT_URI = '...'`.

**"Your Snowflake authorization expired"** — either the 90-day refresh token validity lapsed or the grant was revoked in Snowflake. Sign in again.

**Sign-in succeeds but queries fail on the role** — the role in the connection is not granted to that user, or is not in `ALLOWED_ROLES_LIST`. Both have to be true.

**The connection is refused entirely** — check for an account-level network policy. In Qualia Cloud the egress addresses are not stable, so an IP allowlist cannot admit them. On desktop the traffic comes from your own machine, so a policy that allows your office or VPN range works.

**A browser opens on every query (desktop)** — the connector could not cache the sign-in. Almost always a missing `keyring`: reinstall with `snowflake-connector-python[pandas,secure-local-storage]>=4.1.0`. macOS and Windows need it; Linux uses a file cache and does not.
