Integration · Warehouse

Key-pair auth, no password anywhere.

Mapped rows are written through Snowflake’s SQL Statement API in batches of 500, authenticated with a key-pair JWT. Passwords are not accepted, and neither is an encrypted key.

POST /v1/gatewaykind: snowflake

How it works

What actually happens on a sync run

Snowflake is reached through its SQL Statement API — POST https://<account>.snowflakecomputing.com/api/v2/statements — authenticated with a key-pair JWT. Passwords are refused at save time, and so is an encrypted private key: a Worker has nowhere safe to hold a passphrase, and accepting one would be pretending otherwise. Rows go in batches of 500 with every identifier validated before a statement is built.

  1. Step 1

    Sign a JWT

    The unencrypted PEM private key signs a short-lived JWT bound to the account and user.
  2. Step 2

    Resolve the target

    Database, schema, warehouse and role are sent alongside the statement; the table may be dotted.
  3. Step 3

    Send batched statements

    500 rows per statement, each batch independent.
  4. Step 4

    Report per batch

    Failed batch indices are named; every batch failing is a loud error, not a quiet success.

What you get

Built for files that keep arriving

Auth

Key-pair only, and unencrypted by necessity

Snowflake key-pair auth is the supported mechanism. A password is refused; an encrypted key is refused with an explicit reason rather than accepted and then quietly unusable.

How · Checked at create: the value must contain PRIVATE KEY and must not be ENCRYPTED PRIVATE KEY. A Worker has nowhere safe to hold a passphrase, and saying so is better than implying it does.

Write

Bounded batches, named failures

500 rows per statement so one bad batch cannot take a whole delivery with it, and the report says which indices failed.

How · written_rows, batches, failed_batches. Every batch failing returns 502 destination_write_failed with the warehouse’s own message.

Schema

The table’s own columns, read not restated

A gateway call maps onto the real columns. Restating a schema in the request would create a second source of truth that drifts the moment someone adds a column.

How · GET /v1/connectors/{id}/schema returns the column list and a template. Naming a column that does not exist fails 422 schema_destination_mismatch before any write.

Secrets

The credential never travels in a request body

You reference a connector by id. Keys, tokens and service-account JSON are encrypted at rest and read only by the code that makes the call.

How · normalizeSecretField() folds every provider spelling — private_key, token, secret_access_key, account_key, service_account_json — into one auth_value field, which is KEK-envelope-encrypted before the row is written. A GET masks it to a 4-character hint. If encryption fails the field is dropped rather than stored in plaintext.

Configuration

The connector record, field by field

A password is not an accepted credential here and an encrypted key is refused with the reason. Both checks run at save time, so the connector that exists is one that can actually authenticate.

KeyRequiredWhat it is
accountRequiredSnowflake account identifier. Required, checked at save time.
userRequiredThe user the key pair belongs to.
auth_valuesecretRequiredAn UNENCRYPTED PEM private key. A value without PRIVATE KEY is refused; so is ENCRYPTED PRIVATE KEY.
public_key_fingerprintOptionalFingerprint for the JWT’s issuer claim, when your account requires it.
tableOptionalTarget table, optionally dotted. Overridable per call with destination.table.
databaseOptionalDatabase context for the statement.
schemaOptionalSchema context for the statement.
warehouseOptionalWarehouse to run on.
roleOptionalRole to assume — the place to scope what the credential can do.
hostOptionalOverride the derived <account>.snowflakecomputing.com host.

In code

A messy export in, a clean load out.

The connector holds the key pair. The call carries a bearer token, an input and a destination id.

  • POST/v1/connectorsSave the connector. The secret is encrypted before it reaches Postgres.session
  • GET/v1/connectors/{id}/schemaRead the target’s own columns — metadata only, never row data.bearer
  • POST/v1/gatewayAny input in, this destination populated, a delivery report out.bearer
  • POST/v1/connectors/{id}/rotate-secretReplace the credential in place; the old one becomes unrecoverable.session
  • Scope the role narrowly. AdaptivMapr holds a credential that writes into your warehouse; it should be able to do that and nothing more.
  • Identifiers are validated before a statement is built, so a table name that would have to be rewritten is refused instead of reshaped.
  • dry_run reports the batch count without executing anything.
POST /v1/connectors
{
  "kind": "snowflake",
  "name": "Finance warehouse",
  "config": {
    "account": "acme-eu_central",
    "user": "MAPR_LOADER",
    "database": "FINANCE",
    "schema": "RAW",
    "warehouse": "LOAD_WH",
    "role": "MAPR_WRITER",
    "table": "INVOICES",
    "private_key": "-----BEGIN PRIVATE KEY-----\n…"
  }
}
curl
curl https://api.adaptivmapr.com/v1/gateway \
  -H "Authorization: Bearer $MAPR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": { "connector_id": "con_4a91…" },
    "destination": { "connector_id": "con_77de…", "table": "FINANCE.RAW.INVOICES" }
  }'
response
{
  "schema_id": "invoices_v1",
  "source": "destination",
  "row_count": 2400,
  "destination": {
    "connector_id": "con_77de…",
    "kind": "snowflake",
    "table": "FINANCE.RAW.INVOICES",
    "schema_source": "destination",
    "protocol": "snowflake",
    "written_rows": 2400,
    "batches": 5,
    "failed_batches": []
  }
}
→ 2 400 rows in 5 statements · 0 failed batches · key-pair JWT, no password in play

Limits & failure modes

What it refuses, and what it tells you

CodeWhenWhat to do
400 config_invalidA password was supplied, or the key is encrypted, or account/user is missing.All three are checked at save time with an explicit message naming which one.
400 config_invalid (identifier)The table is not an identifier, optionally dotted.Refused rather than silently rewritten at write time.
502 destination_write_failedEvery batch was rejected.Surfaced with Snowflake’s own message — a permission error and a type error need different fixes.
422 schema_destination_mismatchA supplied schema names a column the table does not have.Pre-flight catches it before the first statement runs.

Incremental sync

Not applicable: Snowflake is a destination here. To pull FROM a warehouse on a cadence, expose the query behind an HTTPS endpoint and use the SQL-over-HTTP source, which supports a {{since}} watermark.

PHI & residency

A warehouse delivery runs under the workspace’s routing policy, not around it. PHI routing is a separate axis from the data mode: it sends X-PHI and X-Region to phi-cloud so a regulated run lands on an in-region, BAA-eligible model, it costs +20% on the whole charge, and it is locked until the workspace accepts the BAA in Settings → Security & Data. An explicit PHI ask without an acceptance is 403 agreement_required, never a silent downgrade. A standard run keeps the workspace’s region pin — the region decides where compute may run, and the sandbox refuses a region-less run.

What it costs

Billed on the same prepaid wallet

Moving bytes is not a line item. A sync that pulls a file and a destination write that lands the rows are both part of one map, and the map is what the wallet sees. There is no free tier and no subscription — top up from $10, a balance shared across the phi-cloud suite.

ChargeRateNotes
Every map$0.001A flat per-map fee — a few tokens — charged even when the run was fully deterministic or hit the layout cache and used no AI at all.
AI, only when it ranat cost × 2Layer-5 cleanup, any-to-any convert and structural reshape bill the phi-cloud tokens actually consumed. Bring your own model key and it is × 0.5.
PHI / enterprise routing+20%Multiplies the whole charge, flat fee included — and only when the run genuinely got that routing. Locked until the workspace accepts the BAA in-app.

Questions

Before you wire it up

Key-pair auth is the mechanism this integration implements, and it is the stronger one. A password is refused at save time rather than stored and then found not to work. An encrypted private key is also refused, with the reason: a Worker has nowhere safe to hold the passphrase, and accepting the key while being unable to use it would be a worse experience than saying so.
With a Snowflake role. Set config.role to a role that has INSERT on the target table and nothing else, and set the database, schema and warehouse explicitly. AdaptivMapr sends those alongside every statement. The private key itself is envelope-encrypted at rest, masked on read, and rotatable in place.
It is reported. Batches are independent — 500 rows each — and the response lists failed_batches by index alongside written_rows, so a partial write reads as a partial write. If every batch fails, that is a configuration or permission problem and the call returns 502 destination_write_failed with Snowflake’s own error text rather than a success that wrote nothing.
Yes. Pass destination.dry_run and the request validates the mapping, runs the validators and reports the batch count that would be sent — without executing a statement.

Verified against lib/warehouseSql.ts · lib/destinations.ts · lib/introspect.ts · app/api/v1/connectors/route.ts

Snowflake is a trademark of Snowflake Inc. Named here to describe interoperability only — no affiliation, endorsement or partnership is claimed.

Ready when you are

Point it at Snowflake. Get your schema back.

Start with a $10 prepaid wallet. Every map draws a few tokens; in schema-only mode only headers and a few sample rows ever leave you.

$10 minimum to start · pay only for what you map · PHI under BAA coverage