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

# Snowflake

> Connect a Snowflake warehouse so the agent can explore its schemas and run SQL

BOW connects to Snowflake with the standard Snowflake driver, reads the tables, views, and semantic views in the schemas you point it at, and runs SQL against a warehouse you choose. You can authenticate with a username and password, with **key pair (RSA) authentication** — the option most Snowflake accounts require once MFA is enforced on human users — or per-user with **Sign in with Snowflake** (OAuth), where each person signs in with their own Snowflake account and queries run under their own role and permissions.

## How it works

BOW opens a connection scoped to one `database` and one or more `schema` values, using the `warehouse` you specify for compute (and the `role` you specify, if any). During indexing it reads:

* **Tables and views** from `INFORMATION_SCHEMA`, including table and column comments where they exist.
* **Semantic views** — discovered with `SHOW SEMANTIC VIEWS` and described with `DESC SEMANTIC VIEW`. These are exposed to the agent alongside regular tables, and the agent knows to query them through the `SEMANTIC_VIEW()` function rather than as ordinary tables.

Schema names are normalized to uppercase, matching Snowflake's `INFORMATION_SCHEMA` behavior. Queries are read-only.

## Before you start

* A Snowflake **account identifier**, in the organization-account form (for example `ABCDEF-GHIJKL`). This is the identifier from your account URL, not the full hostname.
* A **warehouse** the connecting user can use, and a **database** to query.
* The names of the **schemas** you want indexed. One schema, or several as a comma-separated list.
* A user for BOW to connect as. A dedicated service user with a read-only role is the usual pattern.
* Network access from the BOW host to your Snowflake account. If your account restricts access by network policy, allow the BOW egress IP.

> [!TIP]
>
> Create a dedicated role with `USAGE` on the warehouse, database, and schemas, plus `SELECT` on the objects you want available, and grant it to the service user. Then set that role in the **Role** field so the connection can never see more than intended.

## Step 1 — Generate a key pair (key-pair auth only)

Skip this step if you are using a username and password.

Snowflake key-pair authentication uses a 2048-bit RSA key. You generate the pair yourself, register the **public** key on the Snowflake user, and paste the **private** key into BOW.

**1. Generate the private key**

For an encrypted key (recommended — `openssl` prompts you for a passphrase):

```bash theme={null}
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -v2 des3 -out rsa_key.p8
```

For an unencrypted key:

```bash theme={null}
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -nocrypt -out rsa_key.p8
```

Either form works. BOW accepts a PKCS#8 PEM private key and decrypts it with the passphrase you provide.

**2. Derive the public key**

```bash theme={null}
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
```

If the private key is encrypted, `openssl` asks for the passphrase here.

**3. Register the public key on the Snowflake user**

Open `rsa_key.pub`, and copy the body **without** the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines and without line breaks. Then, as a user with `SECURITYADMIN` (or ownership of the target user):

```sql theme={null}
ALTER USER BOW_SVC SET RSA_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...';
```

Verify it registered:

```sql theme={null}
DESC USER BOW_SVC;
-- the RSA_PUBLIC_KEY_FP property should now hold a fingerprint
```

**4. Keep the private key safe**

> [!WARNING]
>
> `rsa_key.p8` is a credential. Store it in your secret manager and delete any working copies. Never register the private key with `ALTER USER` — only the public key goes to Snowflake.

## Step 2 — Create an OAuth security integration (per-user sign-in only)

Skip this step unless you want **Sign in with Snowflake** — per-user OAuth where each person queries as themselves.

Snowflake acts as its own OAuth authorization server: you register BOW as a client with a **security integration**, then save the integration's client ID and secret on the connection.

**1. Create the security integration**

As `ACCOUNTADMIN` (or a role with `CREATE INTEGRATION`):

```sql theme={null}
CREATE SECURITY INTEGRATION BOW_OAUTH
  TYPE = OAUTH
  ENABLED = TRUE
  OAUTH_CLIENT = CUSTOM
  OAUTH_CLIENT_TYPE = 'CONFIDENTIAL'
  OAUTH_REDIRECT_URI = 'https://<your-bow-host>/api/connections/oauth/callback'
  OAUTH_ISSUE_REFRESH_TOKENS = TRUE
  OAUTH_REFRESH_TOKEN_VALIDITY = 7776000;  -- 90 days, the maximum
```

The redirect URI must match your BOW host exactly and must use HTTPS — for a local or non-TLS deployment, add `OAUTH_ALLOW_NON_TLS_REDIRECT_URI = TRUE`.

**2. Read the client ID and secret**

```sql theme={null}
SELECT SYSTEM$SHOW_OAUTH_CLIENT_SECRETS('BOW_OAUTH');
```

Copy `OAUTH_CLIENT_ID` and one of the `OAUTH_CLIENT_SECRET` values — they go into the connection's **OAuth Client ID** and **OAuth Client Secret** fields in Step 3.

**3. Check the roles users will sign in with**

A Snowflake OAuth session cannot use `ACCOUNTADMIN` or `SECURITYADMIN` (they are on the integration's blocked-roles list by default). With the connection's **Role** field blank, each token is bound to the user's **default role**, so that role needs `USAGE` on the warehouse, database, and schemas. If you set a **Role** on the connection, BOW requests the `session:role:<ROLE>` scope so the token authorizes exactly that role — users still need the role granted to them.

> [!NOTE]
>
> `OAUTH_ISSUE_REFRESH_TOKENS = TRUE` is what lets BOW renew the short-lived access token (about 10 minutes) without asking the user to sign in again. When the refresh token itself expires — 90 days at most — the user reconnects.

## Step 3 — Add the connection in BOW

Go to **Data Sources → Add data source → Snowflake** and fill in the form.

| Field | Required | Default | Notes |
| :- | :- | :- | :- |
| `account` | Yes | — | Account identifier, for example `ABCDEF-GHIJKL`. Not the full `.snowflakecomputing.com` hostname. |
| `warehouse` | Yes | — | Warehouse used for compute on every query. |
| `database` | Yes | — | Database to connect to. |
| `schema` | Yes | — | Schema to index. Accepts a **comma-separated list** (`SALES, MARKETING, FINANCE`); the first entry is used as the connection's default schema. |
| `role` | No | — | Optional Snowflake role to activate for the connection. Leave blank to use the user's default role. |

Then choose an authentication method (below), click **Test Connection**, and select the tables the agent may use.

## Authentication modes

Snowflake supports three modes. Username/password and key pair can be used as a **shared** credential for the whole workspace (system scope) or as a **per-user** credential where each person supplies their own (user scope). **Sign in with Snowflake** is per-user only: each person authorizes through Snowflake's own sign-in and consent screens, and their token is stored and refreshed for them.

> [tab] Username / Password
>
> | Field | Required | Notes |
> | :- | :- | :- |
> | `user` | Yes | Snowflake login name. |
> | `password` | Yes | The user's password. |
>
> Straightforward, but not usable for a user on which your account enforces MFA. For service accounts, prefer key pair.

> [tab] Key Pair (Private Key)
>
> | Field | Required | Notes |
> | :- | :- | :- |
> | `user` | Yes | Snowflake login name the public key is registered on. |
> | `private_key_pem` | Yes | The **full** PEM private key, including the `-----BEGIN ... PRIVATE KEY-----` and `-----END ... PRIVATE KEY-----` lines and the newlines between them. Paste the contents of `rsa_key.p8`. |
> | `private_key_passphrase` | No | Passphrase that decrypts the key. Leave blank when the key was generated with `-nocrypt`. |
>
> BOW parses the PEM, decrypts it with the passphrase if one is given, and hands the key to the Snowflake driver. No passphrase is sent to Snowflake.

> [tab] Sign in with Snowflake (OAuth)
>
> Per-user only, and requires the security integration from Step 2. Users fill in **no fields** — clicking **Connect** on the data source sends them to Snowflake's sign-in (password, MFA, and the consent screen), and BOW stores and auto-refreshes their token.
>
> The integration's client belongs on the connection's **system credentials**, next to the shared username/password or key pair:
>
> | Field | Required | Notes |
> | :- | :- | :- |
> | `oauth_client_id` | Yes | `OAUTH_CLIENT_ID` from `SYSTEM$SHOW_OAUTH_CLIENT_SECRETS`. |
> | `oauth_client_secret` | Yes | An `OAUTH_CLIENT_SECRET` value from the same call. |
>
> Queries then run as the signed-in person: their Snowflake user, their role (the default role, or the connection's **Role** via the `session:role` scope), their permissions. The shared credential is still used for admin-side indexing and background catalog refresh.

To force everyone to use their own account, enable **Require user authentication** on the connection. With OAuth configured as the only user auth mode, each person's **Connect** click goes straight to Snowflake's sign-in page and back.

> [!NOTE]
>
> Using per-user credentials on a database connector — every person authenticating as themselves rather than through one shared login — requires an Enterprise license. See [Authentication](https://docs.bow.ai/data-sources/authentication.md).

## Troubleshooting

> [accordion] Invalid Snowflake private key
>
> The PEM did not parse. Check that you pasted the whole file including both `-----BEGIN`/`-----END` lines, that no lines were reflowed or truncated, and that the passphrase field matches how the key was generated. An encrypted key with a blank passphrase, or an unencrypted key with a passphrase set, both fail here.

> [accordion] JWT token is invalid / user authentication failed
>
> The public key registered on the Snowflake user does not match the private key you supplied. Re-derive the public key with `openssl rsa -in rsa_key.p8 -pubout`, re-run `ALTER USER ... SET RSA_PUBLIC_KEY='...'`, and confirm `DESC USER` shows a new `RSA_PUBLIC_KEY_FP`. Also confirm the `user` field is the Snowflake **login name**, not an email address, unless they are the same.

> [accordion] No tables appear after indexing
>
> The role in use has no `USAGE` on the database or schema, or no `SELECT` on the objects. Check the `role` field — with it blank, the user's default role applies, which may be more restrictive than you expect. Also confirm the `schema` names are spelled as they appear in Snowflake; they are matched in uppercase.

> [accordion] Object does not exist or not authorized on the warehouse
>
> The connecting role lacks `USAGE` on the warehouse named in the `warehouse` field, or the warehouse is suspended and the role cannot resume it. Grant `USAGE` (and `OPERATE` if auto-resume is off).

> [accordion] OAuth sign-in is not enabled for this connection
>
> The connection is missing `oauth_client_id`/`oauth_client_secret`, or **Require user authentication** is off. Save the security integration's client ID and secret on the connection credentials and enable the toggle — OAuth is then added to the allowed sign-in methods automatically.

> [accordion] Snowflake rejects the redirect after sign-in
>
> The `OAUTH_REDIRECT_URI` on the security integration must exactly match `https://<your-bow-host>/api/connections/oauth/callback` — scheme, host, and path. Snowflake also refuses non-HTTPS redirect URIs unless the integration sets `OAUTH_ALLOW_NON_TLS_REDIRECT_URI = TRUE`.

> [accordion] Role-related errors on an OAuth session
>
> `ACCOUNTADMIN` and `SECURITYADMIN` are blocked for OAuth sessions by default, so a user whose default role is one of those cannot query until a working role is set. And if the connection pins a **Role** the user does not hold, the `session:role` scope fails — grant the role, or clear the field so each user's default role applies. Users must reconnect after their default role or grants change, since the token is bound to the role it was issued for.

## Related

* [Connecting data sources](https://docs.bow.ai/data-sources/connecting.md)
* [Authentication](https://docs.bow.ai/data-sources/authentication.md)
* [SQL databases](https://docs.bow.ai/data-sources/connectors/databases.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.