Skip to content
adapters.io

QuickBooks to Snowflake: how to load financial data your team can actually query

9 min read Data engineering The Adapters team

Last updated July 2026

Field mapping auto-plugged · tap a port to rewire

5 sample records ready

To load QuickBooks Online into Snowflake, pull entities from the Intuit v3 REST Accounting API with OAuth2, land the raw JSON in a staging table, then MERGE it into modeled target tables keyed on the entity Id. Pull only changed records with the MetaData.LastUpdatedTime watermark or the ChangeDataCapture endpoint, store that watermark, and overlap the window so retries stay idempotent. Map amounts to NUMBER(38, 2), dates to DATE, timestamps to TIMESTAMP_NTZ in UTC, ids to VARCHAR, and keep transaction currency plus home currency so the general ledger ties out.

Key takeaways

  • QuickBooks has no native Snowflake connector. A managed connector, a scheduled script, or the CDC endpoint has to sit between the Intuit API and the warehouse.
  • Watermark, do not full-scan. Per-realm throttles and the ~1000-row page cap make naive full pulls slow, so filter on LastUpdatedTime or use CDC to move only changed rows.
  • Amounts are NUMBER, never FLOAT. Float drift on money breaks reconciliation, and finance spots it before analytics does. Use NUMBER(38, 2) and keep the raw string.
  • MERGE on the natural key for idempotency. Stage the batch, collapse to one row per Id, then upsert. Re-running a batch lands the same state instead of duplicates.

How do you load QuickBooks Online into Snowflake?

You load QuickBooks Online into Snowflake by authenticating to the Intuit v3 REST Accounting API with OAuth2, reading each entity for a company (its "realm"), staging the raw records in Snowflake, and MERGE-ing them into modeled tables keyed on the entity Id. QuickBooks ships no native Snowflake driver, so a managed connector, a scheduled script, or the CDC endpoint has to move the data between them.

The job has three moving parts. First, auth: an OAuth2 app with a client id and secret, a refresh token per company, and the realm id that scopes every request. Second, the read side: the v3 API exposes entities like Invoice, Bill, Payment, JournalEntry, Account, Customer, Vendor, and Item, queried with a SQL-like syntax, plus the general ledger through the reports endpoints. Third, the load: a Snowflake warehouse, a staging table (often a VARIANT column for the raw JSON), and a MERGE into the typed target.

A practical pattern lands the raw payload first and types it inside the warehouse. Write each page of API results into a staging table, flatten the JSON with Snowflake's LATERAL FLATTEN, cast the fields to their target types, then MERGE. The managed path is the QuickBooks to Snowflake integration, which handles the OAuth refresh, pagination, the watermark, the staging table, the upsert, and the retries. The same shape works for other sources, like Stripe to Snowflake, with a different API in front.

How do you map QuickBooks fields to Snowflake types?

Amounts map to Snowflake NUMBER(38, 2) (never FLOAT for money), TxnDate maps to DATE, timestamps such as MetaData.LastUpdatedTime map to TIMESTAMP_NTZ stored in UTC, and the entity Id stays a VARCHAR. Currency codes are short strings and exchange rates are NUMBER. Get these right at load time or reconciliation gets painful later.

QuickBooks returns amounts as JSON strings or decimals, so cast them explicitly rather than trusting an inferred type. Nested line items (an invoice carries a Line array) should flatten into a separate line-level fact table, one row per line, so you can sum at the grain finance actually reports on. Here is the field-level mapping.

QBO field QBO type Snowflake type Note
TotalAmt, Line.Amount String / decimal NUMBER(38, 2) Never FLOAT for money. Scale 2 covers most currencies to the cent; widen scale only if you hold rates or units
TxnDate String (date) DATE Calendar date of the transaction, no time component. Good clustering key for fact tables
MetaData.LastUpdatedTime String (ISO 8601) TIMESTAMP_NTZ Convert to UTC before storing. This is the incremental watermark, so treat it as an absolute point in time
MetaData.CreateTime String (ISO 8601) TIMESTAMP_NTZ Also UTC. Useful for auditing when a record first appeared in the ledger
Id, CustomerRef.value String VARCHAR Intuit ids are opaque strings. Keep them as VARCHAR and MERGE on them; do not cast to a number
CurrencyRef.value String VARCHAR Three-letter ISO code such as USD or EUR. Store it next to every amount
ExchangeRate Decimal NUMBER(38, 6) Converts transaction currency to home currency. Extra scale keeps home-currency totals reproducible
Line (nested array) Array of objects Flatten to rows Use LATERAL FLATTEN into a line-level fact table, one row per line, linked back to the header Id

Cluster the large fact tables (invoice lines, journal lines) on transaction date so pruning skips partitions a report does not need. Snowflake handles micro-partitions automatically, but a good clustering key on high-cardinality date columns is what keeps a quarter-to-date query from scanning every invoice you have ever loaded.

How do you load QuickBooks incrementally?

You load QuickBooks incrementally by filtering each entity on MetaData.LastUpdatedTime as a watermark, or by calling the ChangeDataCapture (CDC) endpoint, which returns every entity changed since a timestamp including deleted ones. Store the last watermark, overlap the window slightly on each run, and MERGE into Snowflake keyed on the entity Id so retries stay idempotent.

The watermark query looks like WHERE MetaData.LastUpdatedTime > :last_watermark, ordered by that timestamp and paged through with STARTPOSITION and MAXRESULTS (the API caps each page near 1000 rows). After a clean run you advance the stored watermark. Two habits keep it honest: overlap the window by a few minutes so records committed slightly out of order are not skipped, and keep the MERGE idempotent so re-reading a handful of rows costs nothing.

The load itself: write the batch to a staging table, collapse it to one row per Id keeping the latest LastUpdatedTime, then MERGE ... ON target.Id = staging.Id WHEN MATCHED THEN UPDATE WHEN NOT MATCHED THEN INSERT. Re-running the same batch after a timeout lands the same state rather than duplicates. This upsert-on-natural-key shape is the whole reason a retry is safe. The table below weighs the three extraction methods so you can pick per entity.

Extraction method Pros Cons
Full reload Simplest to reason about; always self-correcting; fine for small, slow dimensions like Account or Item Re-reads the whole entity every run; hits throttles and the 1000-row page cap on large companies; wasteful for high-volume transactions
LastUpdatedTime watermark Moves only changed rows; cheap and fast; easy to checkpoint and resume; the default for invoices, bills, and payments Does not surface hard deletes on its own; needs an overlap window so out-of-order commits are not missed
ChangeDataCapture (CDC) Returns inserts, updates, and deletes since a timestamp in one call; keeps the warehouse free of ghost rows More API surface to handle; still requires MERGE plus delete handling on the load side; timestamp bookkeeping matters

A common split is a watermark load for high-volume transactions, CDC when deletes have to be reflected (a voided invoice should disappear from the warehouse, not linger), and a periodic full reload for tiny dimensions where correctness beats efficiency. Handle CDC deletes by reading the returned entity status and either soft-deleting the row or removing it in the same MERGE.

How do you handle multi-currency and tie out to the general ledger?

Carry both amounts and the rate. QuickBooks stores a home currency and, for multi-currency companies, a transaction CurrencyRef plus an ExchangeRate. Load the transaction-currency amount, the home-currency amount, and the rate into Snowflake so general ledger totals tie back to QuickBooks exactly. Drop any one of the three and reconciliation breaks somewhere down the line.

With a single-currency company this is a non-issue: every amount is already in the home currency. The moment multi-currency is on, a euro invoice posts in EUR but rolls up to the home currency (say USD) at the transaction's exchange rate. Store only the EUR amount and your USD ledger will not match; store only the converted figure and you lose the number the customer actually saw. Keep both plus the rate, and every derived column stays auditable.

Tie-out is the real test. Pull the general ledger through the reports endpoint, sum your modeled journal_lines by account in Snowflake, and confirm the totals match the GL report for the same period. If they drift, the usual culprits are a FLOAT amount column, a missed CDC delete, or a posting period that differs from the TxnDate (a bill dated at month end can post into the next period). Keep both the transaction date and the posting period so reports group either way. Getting clean source documents into QuickBooks in the first place helps here too; if you are still keying in bank activity by hand, a tool that can convert those statements straight to a QuickBooks-ready file keeps the ledger you are replicating accurate at the source.

Should you build this or use a managed integration?

Build it if you have engineers who want to own OAuth refresh, pagination, throttling, the watermark, CDC deletes, the staging flatten, and the MERGE, and keep owning them as Intuit changes the API. Use a managed integration if you would rather define the field mapping once and let the pipeline run. Both land the same modeled tables in Snowflake; the difference is who maintains the plumbing.

The hidden cost of building is not the first sync, it is the second year. The Intuit API evolves, refresh tokens expire, throttles shift, and a schema change can silently flip an incremental job back to a full scan. Someone has to notice and fix that. A managed data integration platform absorbs that maintenance and gives you per-record logs, scheduled incremental sync, and automatic retries out of the box, which is the same reasoning behind the sibling QuickBooks to BigQuery pipeline.

On price, our plans are flat by tier (Starter $49, Growth $149, Scale $399, Enterprise custom) rather than metered per row, so a busy month-end close does not produce a surprise line item. You map QuickBooks to Snowflake in the browser with no-code field mapping, set the schedule, and watch the per-record logs. See pricing for the full breakdown, or start a demo to see the entity mapping against your own company file.

Questions

How do you load QuickBooks Online into Snowflake?

Authenticate to the Intuit v3 REST Accounting API with OAuth2, read each entity for a company realm, stage the raw records in Snowflake, then MERGE them into modeled tables keyed on the entity Id. QuickBooks ships no native Snowflake driver, so a managed connector, a scheduled script, or the CDC endpoint sits in the middle and moves the data.

How do you map QuickBooks fields to Snowflake types?

Amounts map to NUMBER(38, 2) (never FLOAT for money), TxnDate maps to DATE, timestamps such as LastUpdatedTime map to TIMESTAMP_NTZ in UTC, and the entity Id stays VARCHAR. Currency codes are short VARCHARs and exchange rates are NUMBER. Flatten nested Line arrays into a line-level fact table with LATERAL FLATTEN, one row per line.

How do you load QuickBooks into Snowflake incrementally?

Filter each entity on MetaData.LastUpdatedTime as a watermark, or call the ChangeDataCapture endpoint which returns every entity changed since a timestamp including deletes. Store the last watermark, overlap the window slightly, and MERGE into Snowflake keyed on the entity Id so a retried batch lands the same state instead of duplicates.

How do you handle QuickBooks pagination and rate limits?

The v3 API uses SQL-like queries with STARTPOSITION and MAXRESULTS and caps each page near 1000 rows, and Intuit enforces per-realm throttles. A production job pages in order, backs off on throttle responses, and checkpoints its position so a mid-run failure resumes rather than restarting the whole pull from zero.

How do you handle multi-currency when loading QuickBooks into Snowflake?

Carry both amounts and the rate. QuickBooks stores a home currency plus, for multi-currency companies, a transaction CurrencyRef and an ExchangeRate. Load the transaction-currency amount, the home-currency amount, and the rate so general ledger totals tie back to QuickBooks. Keep both TxnDate and posting period since they can differ.

Should you build a QuickBooks to Snowflake pipeline or use a managed tool?

Build it if engineers want to own OAuth refresh, pagination, throttling, the watermark, CDC deletes, and the MERGE, and keep owning them as Intuit changes the API. Use a managed integration to define the mapping once and let it run with scheduled incremental sync, per-record logs, and retries. Both land the same modeled tables in Snowflake.

Load QuickBooks Online into Snowflake incrementally, without writing the merge logic

OAuth refresh, pagination, LastUpdatedTime watermark, CDC deletes, staging, MERGE, and retries. Map the pair in the browser and pay a flat price from $49 a month.

Try the live demo

No credit card required.