> For the complete documentation index, see [llms.txt](https://learn.moselle.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://learn.moselle.io/getting-started/onboarding/setup-integrations/snowflake.md).

# Snowflake

{% hint style="info" %}
**Quick Answer:** Moselle connects to your Snowflake data warehouse using **read-only key-pair authentication**. You'll create a dedicated role, a small warehouse, and a service user that can only read the schema you choose — then send Moselle a few connection details. No passwords are exchanged, and Moselle can never write to your warehouse. Setup takes about 15 minutes and needs someone with admin access to Snowflake.
{% endhint %}

## What this connection does

Moselle reads sales, inventory, and order tables directly from your Snowflake warehouse so your forecasts and replenishment plans stay current — no exports. The connection is **read-only**: Moselle is granted `SELECT` on only the schema you specify, runs on its own isolated warehouse, and authenticates with a key pair (no shared password).

## What you'll need

* [ ] A Snowflake user with **ACCOUNTADMIN** (or delegated `SECURITYADMIN` + `USERADMIN`) to run the setup SQL
* [ ] The **public key** Moselle provides you during onboarding (a single text block beginning `MII...`)
* [ ] The **database and schema** you want Moselle to read

{% hint style="warning" %}
Moselle generates the key pair and gives you **only the public key**. The matching private key stays in Moselle's secure key store and is never shared. You do not need to generate any keys yourself.
{% endhint %}

## Set it up

{% stepper %}
{% step %}

### Open a Snowflake worksheet

Sign in to Snowflake, click **Worksheets** in the sidebar, then **+ Worksheet** in the top right.
{% endstep %}

{% step %}

### Run the setup script

Paste the script below, replace the three placeholders (`<YOUR_DATABASE>`, `<YOUR_SCHEMA>`, and the public key), then click **Run**.

{% code overflow="wrap" %}

```sql
-- 1) Dedicated, cost-isolated warehouse (auto-suspends when idle)
USE ROLE SYSADMIN;
CREATE WAREHOUSE IF NOT EXISTS MOSELLE_WH
  WAREHOUSE_SIZE = 'XSMALL'
  AUTO_SUSPEND = 60
  AUTO_RESUME = TRUE
  INITIALLY_SUSPENDED = TRUE
  COMMENT = 'Read-only warehouse for Moselle';

-- 2) Read-only role scoped to one schema
USE ROLE SECURITYADMIN;
CREATE ROLE IF NOT EXISTS MOSELLE_RO_ROLE;
GRANT USAGE ON WAREHOUSE MOSELLE_WH               TO ROLE MOSELLE_RO_ROLE;
GRANT USAGE ON DATABASE <YOUR_DATABASE>           TO ROLE MOSELLE_RO_ROLE;
GRANT USAGE ON SCHEMA <YOUR_DATABASE>.<YOUR_SCHEMA> TO ROLE MOSELLE_RO_ROLE;
GRANT SELECT ON ALL TABLES    IN SCHEMA <YOUR_DATABASE>.<YOUR_SCHEMA> TO ROLE MOSELLE_RO_ROLE;
GRANT SELECT ON ALL VIEWS     IN SCHEMA <YOUR_DATABASE>.<YOUR_SCHEMA> TO ROLE MOSELLE_RO_ROLE;
-- cover tables/views created later, automatically:
GRANT SELECT ON FUTURE TABLES IN SCHEMA <YOUR_DATABASE>.<YOUR_SCHEMA> TO ROLE MOSELLE_RO_ROLE;
GRANT SELECT ON FUTURE VIEWS  IN SCHEMA <YOUR_DATABASE>.<YOUR_SCHEMA> TO ROLE MOSELLE_RO_ROLE;

-- 3) Service user that authenticates with Moselle's public key
USE ROLE USERADMIN;
CREATE USER IF NOT EXISTS MOSELLE_SVC
  TYPE = SERVICE
  DEFAULT_ROLE = MOSELLE_RO_ROLE
  DEFAULT_WAREHOUSE = MOSELLE_WH
  DEFAULT_SECONDARY_ROLES = ()
  RSA_PUBLIC_KEY = '<PASTE_MOSELLE_PUBLIC_KEY>'
  COMMENT = 'Moselle read-only service account';
GRANT ROLE MOSELLE_RO_ROLE TO USER MOSELLE_SVC;
```

{% endcode %}
{% endstep %}

{% step %}

### Find your account identifier

Your account identifier is the first part of your Snowflake URL: `<account_identifier>.snowflakecomputing.com`. If your URL includes a region, include it (e.g. `xy12345.us-east-1`).
{% endstep %}

{% step %}

### Send Moselle your connection details

Reply to your onboarding contact (or email <support@moselle.io>) with:

| Field              | Value                    |
| ------------------ | ------------------------ |
| Account identifier | e.g. `xy12345.us-east-1` |
| User               | `MOSELLE_SVC`            |
| Role               | `MOSELLE_RO_ROLE`        |
| Warehouse          | `MOSELLE_WH`             |
| Database           | `<YOUR_DATABASE>`        |
| Schema             | `<YOUR_SCHEMA>`          |

{% hint style="success" %}
**Done.** Moselle will validate the connection and begin the initial sync. None of these values are secret on their own — the connection only works with the private key Moselle holds.
{% endhint %}
{% endstep %}
{% endstepper %}

## Control your sync schedule

Once your connection is live, you control how often Moselle refreshes data from Snowflake — right from **Settings → Integrations → Snowflake**. No need to email support to change it.

{% hint style="info" %}
**Quick Answer:** Open **Settings → Integrations → Snowflake**, choose a sync **frequency** (Off, Hourly, Every 6 hours, Daily, or Weekly), and Moselle keeps your data refreshed on that cadence. Need fresh data right now? Click **Sync now** to trigger a one-off sync without waiting for the next scheduled run.
{% endhint %}

### Choose how often Moselle syncs

| Frequency         | Best for                                                 |
| ----------------- | -------------------------------------------------------- |
| **Off**           | Pausing automatic syncs (you can still use **Sync now**) |
| **Hourly**        | Fast-moving catalogs that need near-real-time data       |
| **Every 6 hours** | A balance of freshness and Snowflake compute cost        |
| **Daily**         | Most brands — the recommended default                    |
| **Weekly**        | Slow-moving data or a tighter compute budget             |

* For **Daily** and **Weekly**, pick the **hour** the sync should run.
* For **Weekly**, also pick the **day of the week**.

A less frequent schedule (or **Off**) lowers your Snowflake compute cost — see [What this costs you](#what-this-costs-you) below.

### Sync on demand

Click **Sync now** any time to pull the latest data immediately — useful right after you've loaded new tables in Snowflake or want to confirm a change end-to-end. The page shows the **last synced** time as a relative timestamp (e.g. "2 hours ago") so you always know how current your data is.

## What this costs you

{% hint style="info" %}
**Short version:** Expect roughly **$10–40/month** in Snowflake compute on a typical daily sync. Moselle adds **no storage cost** (we only read your existing data), and the warehouse only bills while it's actively running.
{% endhint %}

Moselle's connection runs on its own **X-Small** warehouse (`MOSELLE_WH`) — the smallest size, 1 credit/hour — set to suspend after 60 seconds of inactivity. It only consumes credits during the seconds it's actively pulling data; there's no charge while it sits idle.

* **Compute** — billed per second (60-second minimum per run) at your account's credit rate. On a normal daily forecast/inventory sync, that's about **$10–40/month** depending on your Snowflake edition and region. A more frequent schedule (e.g. hourly) increases compute roughly in proportion to the number of runs, so pick the least frequent cadence that keeps your data fresh enough. (Credit rates run roughly $2/credit on Standard, \~$3 on Enterprise, \~$4 on Business Critical for US AWS on-demand; non-US regions carry a premium.)
* **Storage** — **$0 added.** Moselle reads your existing tables and stores nothing new in your warehouse.
* **Data transfer** — usually none. If Moselle's infrastructure is in a different cloud region than your Snowflake account, standard per-GB egress may apply to the data we read; for incremental daily syncs this is typically minimal. Ask us if you'd like to confirm region alignment.

### Cap the cost (optional)

For a guaranteed ceiling, attach a resource monitor to `MOSELLE_WH`. This caps monthly credits and can notify or auto-suspend if the limit is reached:

{% code overflow="wrap" %}

```sql
USE ROLE ACCOUNTADMIN;
CREATE RESOURCE MONITOR moselle_monitor
  WITH CREDIT_QUOTA = 50            -- generous monthly ceiling; well above typical use
  FREQUENCY = MONTHLY
  START_TIMESTAMP = IMMEDIATELY
  TRIGGERS
    ON 80 PERCENT DO NOTIFY
    ON 100 PERCENT DO SUSPEND;
ALTER WAREHOUSE MOSELLE_WH SET RESOURCE_MONITOR = moselle_monitor;
```

{% endcode %}

## Optional hardening (recommended for security teams)

Force key-pair-only authentication for the service user, so the account can never be used with a password:

{% code overflow="wrap" %}

```sql
USE ROLE SECURITYADMIN;
CREATE AUTHENTICATION POLICY moselle_keypair_only
  AUTHENTICATION_METHODS = ('KEYPAIR')
  CLIENT_TYPES = ('DRIVERS');
ALTER USER MOSELLE_SVC SET AUTHENTICATION POLICY moselle_keypair_only;
```

{% endcode %}

## Notes & FAQ

### How often does Moselle sync from Snowflake?

You choose. Set the frequency to **Hourly**, **Every 6 hours**, **Daily**, or **Weekly** under **Settings → Integrations → Snowflake**, or turn automatic syncing **Off** and refresh manually with **Sync now**. Daily is the recommended default for most brands.

### Is it really read-only?

Yes. `MOSELLE_RO_ROLE` is granted only `USAGE` and `SELECT`. Moselle cannot insert, update, or delete.

### What does Moselle see?

Only the database/schema you grant. To add more later, re-run the grant statements for the new schema.

### Will this increase my Snowflake bill much?

No. See [What this costs you](#what-this-costs-you) above — typically $10–40/month in compute, no added storage, and you can cap it with a resource monitor.

### How does key rotation work?

Snowflake supports two active keys (`RSA_PUBLIC_KEY` and `RSA_PUBLIC_KEY_2`) so Moselle can rotate keys with zero downtime. Moselle drives this; no action needed from you.

### How do I revoke access?

Run `ALTER USER MOSELLE_SVC SET DISABLED = TRUE;` to pause immediately, or `DROP USER MOSELLE_SVC;` to remove access entirely.

***

## Related Guides

{% content-ref url="/pages/29eF8AdlBEKOD8ebbN0F" %}
[NetSuite](/getting-started/onboarding/setup-integrations/netsuite.md)
{% endcontent-ref %}

{% content-ref url="/pages/iSRNCSGVWQanaAuYnC4i" %}
[Setup Integrations](/getting-started/onboarding/setup-integrations.md)
{% endcontent-ref %}
