Estuary

NetSuite to Snowflake: SuiteAnalytics Connect, SuiteQL & Incremental Sync Guide

Compare NetSuite to Snowflake methods, including SuiteAnalytics Connect, SuiteQL over REST, CSV exports, and Estuary, with setup steps and fixes for deletes, line tables, and amount precision.

NetSuite to Snowflake integration guide cover showing the NetSuite and Snowflake logos
Share this article

NetSuite data reaches Snowflake through saved-search CSV exports loaded with COPY INTO, scheduled queries over SuiteAnalytics Connect (Oracle's ODBC, JDBC and ADO.NET service, sold as an add-on), scheduled SuiteQL queries over the REST API, or a managed pipeline. Connect suits many tables and long histories; SuiteQL over REST needs no add-on but caps each query at 100,000 results, so it fits a few small tables.

Every method polls: changes are found by a last-modified date, and deletions come from a deletion log that skips transaction lines. In Snowflake, each load resumes a warehouse, and amounts stored as FLOAT pick up rounding errors in sums. Estuary is one managed option for NetSuite to Snowflake: its SuiteAnalytics Connect capture polls each NetSuite table into a durable collection, and its Snowflake materialization applies the changes on a configurable schedule.

Choosing a NetSuite to Snowflake method

ApproachBest fitFreshnessOperational burdenExample implementation
Saved-search CSV exportOne-time migrationPoint in timeManual each runExport - CSV → stage → COPY INTO
Scheduled SuiteQL over RESTNo Connect licence, a few small tablesThe scheduleHigh: paging, concurrency pool, table listScript calling /services/rest/query/v1/suiteql
Scheduled Connect extractConnect licence and an orchestratorThe scheduleHigh: cursors, MERGE, deletesJDBC or ODBC with Airflow or dbt
Managed pipelineMany tables, no extraction codePer-table polls, scheduled loadsLow: configurationEstuary

Snowflake has no native NetSuite connector; Openflow lists none (October 2026). Where interfaces differ, this guide assumes Connect, which avoids that cap and publishes its schema in OA_* tables.

How NetSuite to Snowflake replication works

Connect's metadata gives each table an extraction rule:

  • Incremental tables have a last-modified column, flagged M in OA_COLUMNS: lastmodifieddate, or linelastmodifieddate on transactionLine. Each poll reads rows changed since the checkpoint.
  • Re-read tables have a key but no reliable last-modified column, like TransactionAccountingLine. Re-read them on a schedule, or reload the lines of changed transactions.
  • Snapshot tables have no usable key and are read whole.
  • Deletes come from DeletedRecord, which logs each deleted record's type and internal ID.

Batches are staged in Snowflake and merged on the key, so overlapping re-reads are harmless.

NetSuite tables read by per-table rules, with DeletedRecord deletes, merged into Snowflake.
Connect's metadata gives each table an extraction rule; DeletedRecord logs each deleted record's type and ID.

NetSuite requirements

The licence decides the interface:

SuiteAnalytics ConnectSuiteQL over REST
LicenceAdd-on; not offered on Small Business accountsIncluded
FeaturesSuiteAnalytics Connect (Analytics subtab), Token-Based AuthenticationREST Web Services, SuiteAnalytics Workbook, Token-Based Authentication (closed to new REST integrations from 2027.1)
Role permissionsSuiteAnalytics Connect, plus view access to your recordsREST Web Services, Log in using Access Tokens, SuiteAnalytics Workbook
Result limitNo 100,000 cap100,000 per query

If SuiteAnalytics Connect is missing from Enable Features, it isn't provisioned. Then set up token-based authentication:

  1. Create an integration record with token-based authentication (Setup > Integration > Manage Integrations > New). Its consumer key and secret are shown once.
  2. Choose a role. NetSuite2.com, the Connect data source, refuses Administrator (except over OAuth 2.0), roles requiring two-factor authentication, and IP-restricted roles. Use the Data Warehouse Integrator role, which sees all standard and custom data and can't sign in, or a custom role covering every subsidiary. Connect returns only what the role sees in SuiteAnalytics Workbook.
  3. Create an access token for a dedicated user with that role (Setup > Users/Roles > Access Tokens > New). Its token ID and secret are shown once.
  4. Set that user's time zone. NetSuite2.com returns created and last-modified dates in the user's or company's time zone, not GMT.

Snowflake requirements

Give NetSuite its own database and schema (one schema per NetSuite account), a warehouse, and a service user whose role can create tables there. Authenticate it with a key pair: Snowflake is phasing out single-factor passwords for service users.

  • Polls carry only changed rows, so start with an X-Small warehouse and AUTO_SUSPEND = 60. Each resume bills at least 60 seconds.
  • Pick the timestamp type after fixing the integration user's time zone: TIMESTAMP_TZ or TIMESTAMP_LTZ keep the instant, TIMESTAMP_NTZ keeps wall-clock time. Any account-level TIMESTAMP_TYPE_MAPPING must agree.
  • Keep QUOTED_IDENTIFIERS_IGNORE_CASE at its default, FALSE, for the loading user.

Setting up NetSuite to Snowflake replication

  1. Pick the interface and list your tables. For a ledger model, include transaction, transactionLine, TransactionAccountingLine, Account, accountingPeriod and Subsidiary.
  2. Create the role, user and token, and prepare Snowflake.
  3. Classify each table as incremental, re-read or snapshot.
  4. Backfill history, paging on an indexed integer key. Filter by date to skip unneeded years.
  5. Start incremental polls from slightly before the last checkpoint, and let the MERGE absorb the overlap:
sql
SELECT id, lastmodifieddate /* plus the columns you load */ FROM transaction WHERE lastmodifieddate >= TO_DATE('2026-10-05T23:00:00', 'YYYY-MM-DD"T"HH24:MI:SS') ORDER BY lastmodifieddate
  1. Validate. Compare row counts. Edit a transaction, delete a customer and add a custom field, then check each in Snowflake after one poll and load. Finally, sum accounting-line amounts by account and period for one accounting book and compare with a NetSuite trial balance using the same filters.

What happens after the initial load?

  • Deletes of transactions, customers, vendors, items, employees, custom records and many other types are logged; line deletes aren't. Deleting a transaction logs only its header, so its lines stay in Snowflake until a re-read.
  • Custom fields arrive only if the role can see them and the query selects them; a new one appears only in a new Connect session.
  • Releases. NetSuite2.com changes version the day your account upgrades. If a release changes a column's type, rebuild the table.

NetSuite to Snowflake data type mapping

NetSuite (Connect metadata)Snowflake targetNotes
INTEGER, BIGINT (IDs, keys)NUMBER(38,0)Lossless
NUMBER, DOUBLE (amounts, rates)NUMBER(p,s)Avoid FLOAT; see below
VARCHAR, VARCHAR2, CHAR, CLOBVARCHARCLOB holds long text
Checkbox fieldsBOOLEAN or VARCHAR(1)Connect returns T and F
TIMESTAMPTIMESTAMP_TZ/LTZ if it carries a time zone, else TIMESTAMP_NTZRecord the zone of NTZ values

Currency amounts and FLOAT sums

Snowflake FLOAT is a 64-bit double with about 15 significant digits, and Snowflake warns that rounding errors accumulate when SUM or AVG runs over many rows, as a trial balance does, so cast amounts to NUMBER(38,2) before aggregating, or store them as NUMBER(p,s). Keep currencies apart too: transactionLine amounts are in the transaction currency, TransactionAccountingLine amounts in the subsidiary's base currency, one row per accounting book. Oracle recommends joining Transaction > Transaction Line > Transaction Accounting Line to avoid duplicates.

Performance, latency and Snowflake cost

Each NetSuite change waits for a poll, then a Snowflake load. Oracle positions Connect for data that changes infrequently and warns that real-time access slows it, so poll each table only as often as its users need. Watch three things:

  • Calculated, custom and hidden columns: NetSuite computes calculated columns per row, and these columns can stall a query. Select only what you use.
  • Offset paging re-scans the table for every page; page on an indexed integer key instead.
  • Loads far more frequent than polls add 60-second warehouse minimums without making a table fresher than its last poll.

Staying inside NetSuite's connection and concurrency limits

REST web services and RESTlets share one account-level concurrency limit:

Service tier (contracts from June 2020)Base concurrent requests
Standard5
Premium15
Enterprise, Ultimate20

Each SuiteCloud Plus licence adds 10; development accounts stay at 5. Excess requests fail with HTTP 429 CONCURRENCY_LIMIT_EXCEEDED: retry with backoff, or cap the pipeline with a per-integration Concurrency Limit in Integration Governance. For Connect, Oracle warns that more than 50 open connections may fail, and each session runs one query at a time.

Production considerations

  • Retries: Oracle expects Connect connections and queries to fail occasionally at peak load and during server restarts. Retry, keep queries short, and merge on the key.
  • Source impact: cap connections; backfill outside business hours.
  • Upgrades: test in a Release Preview account first, with tokens created there; Oracle's test plan includes a Connect check.
  • Monitoring: alert on each table's checkpoint age; reconcile the ledger after each close.
  • Security: a dedicated user and role, tokens per environment, key-pair auth on Snowflake. Connect accepts only TLS connections.

Common problems and troubleshooting

Deleted NetSuite records that stay in Snowflake

  • They're lines. Line deletes aren't logged, and a transaction delete logs only the header. Re-read those tables periodically and delete rows the re-read didn't touch.
  • The pipeline skips the log. A lastmodifieddate query never returns deleted rows; only DeletedRecord does.
  • The deletion cursor has no time zone.deleteddate is stored in NetSuite's system time zone without an offset, so comparing it with UTC bounds can skip deletions made just after a poll. Poll deleteddateusertz, which carries one.
  • The loader soft-deletes. Filter on its delete flag, or enable hard deletes.

Connect queries that time out or drop after a NetSuite release

  • TCP/IP error, connection reset by peer at a fixed time of day usually means a server restart; NetSuite restarts servers during updates and maintenance. After two hours of running, it was closed as idle; set TCP keepalive below that and below any firewall timeout.
  • Connection liveness limit reached means a token-based session passed 60 minutes. Reconnect.
  • No rows until a timeout points to calculated or custom columns, or a cursor NetSuite can't use as an index. Drop those columns first, then try another cursor.

Tables or columns missing from the pipeline

  • The role changed. A global permission set to None hides data even from the Data Warehouse Integrator role, and a role that gains two-factor authentication or IP restrictions loses NetSuite2.com.
  • Over 1,000 columns: the query fails with Error: Could not find any column information for table:transactions. List columns explicitly.
  • NetSuite.com: unavailable from 2026.1; NetSuite2.com names differ, so rewrite the queries.

Using Estuary for NetSuite to Snowflake

Estuary pairs a NetSuite SuiteAnalytics Connect or NetSuite SuiteQL capture with the Snowflake materialization:

  • SuiteAnalytics Connect, for licensed accounts, reads NetSuite2.com with token-based authentication and discovers every table the role can see. A core set is enabled by default, including transaction, transactionLine, TransactionAccountingLine, DeletedRecord, Customer and item; the rest are listed disabled.
  • SuiteQL (Beta) needs no licence but only runs full refreshes, daily by default, of a fixed table list. It supports only token-based authentication, returns date-times as dates, returns no rows past 100 selected columns, keys transactionLine on (transaction, linesequencenumber), and doesn't apply DeletedRecord to other tables.

Against the concepts above:

  • Intervals: incremental bindings poll hourly by default. Snowflake loads run every 30 minutes by default once caught up, backfills ignore that schedule, and repeated changes to a row between loads become one write.
  • Deletes:DeletedRecord tombstones flag rows with _meta/op = 'd' unless Hard Delete is on; line tables get none.
  • Types: the capture reads NUMBER and DOUBLE values as 64-bit floats, and amounts land as FLOAT unless you give them a DDL override (see Map collections to tables below); checkboxes stay T/F text. Date-times that carry a time zone keep their offset; the rest are labelled UTC without conversion. CLOB values past 160,000 characters (fewer on wide tables) are truncated, with a logged warning.
  • Columns: new bindings exclude calculated, custom and hidden columns; re-add fields with Additional Columns.

Implementing NetSuite to Snowflake with Estuary

These steps adapt Estuary's general data flow guide to the SuiteAnalytics Connect capture.

  1. Gather what the NetSuite and Snowflake requirements produced: the account ID (Setup > Company > Company Information), the four token values, and the objects from the Snowflake setup script with a public key registered for key-pair authentication.
  2. Create the NetSuite capture. Go to Sources, click New Capture, pick Netsuite SuiteAnalytics, name it, and complete the endpoint properties:
    • Account: the account ID.
    • Suiteanalytics Data Source: keep NetSuite2.com.
    • Authentication (collapsed): Token Auth, with Consumer Key, Consumer Secret, Token Id and Token Secret. Username/Password Auth (default Role ID 3, Administrator, which NetSuite2.com refuses) is for the retired NetSuite.com source.
    • Advanced (collapsed): Start Date limits backfill history; Connection Limit (default 10) and Task Limit (default 5) cap load on NetSuite; MetadataReadMethod set to Per-Table Batches (instead of Full Catalog Scan) helps if discovery hangs.
NetSuite SuiteAnalytics Connect capture form with Token Auth selected and secrets redacted.
NetSuite Advanced options: Start Date, Connection Limit and Task Limit.
  1. Review the bindings. Click Next. Discovery lists each table under Output Collections, with the core set enabled.
    • Enable the other tables you need.
    • An Incremental Cursor such as lastmodifieddate means polling every Interval; a Backfill Cursor with a Schedule means a scheduled re-read.
    • Keep DeletedRecord enabled; deletions are emitted only while it's bound.
    • Re-add custom fields under Additional Columns, and keep Automatically keep schemas up to date on.

Click Save and publish to start the backfills.

  1. Add Snowflake as the destination. From the published capture's details page, click Materialize, pick Snowflake, and complete its endpoint properties:
    • Host (Account URL) without https://, plus Database, Schema, Warehouse and Role.
    • Authentication: choose Private Key (JWT), then enter User and the unencrypted PKCS#8 Private Key.
    • Snowflake Timestamp Type: the type chosen in the requirements.
    • Hard Delete: on removes deleted rows; off flags them.
Snowflake endpoint form with Private Key (JWT) authentication selected.
  1. Match loads to polls.Sync Frequency, in the Sync Schedule section, defaults to 30 minutes; keep it near the capture's Interval. For business-hours loads, set Timezone, Fast Sync Start Time and Fast Sync Stop Time; outside that window, loads run every 4 hours (sync schedules).
Snowflake Sync Schedule section with the Sync Frequency dropdown open.
  1. Map collections to tables. Click Next, pick a naming convention, and review the bindings under Source Collections, renaming targets with Table or Alternative Schema. Leave Delta Updates off, because NetSuite records change after creation. To keep sums exact, add DDL: "NUMBER(38,2)" to each amount field under its binding's fields.include in the specification editor before publishing (custom column types). Snowflake then stores exact decimals; the capture has already read each value as a 64-bit float, so this holds for amounts up to about 15 significant digits. The override applies when the table is created.
  2. Publish. Click Save and publish; the backfill loads without waiting for the schedule.
  3. Verify. Open a collection from the Collections list on the capture details page; its Data Preview shows rows. The materialization details page charts Data Read. Then run the validation checks.
Snowflake materialization Overview: Connector Status Running and a Data Read chart.

How Estuary handles NetSuite sync modes, line tables and rebuilds

The capture writes each table to a collection, a durable log of JSON files in your cloud storage bucket, and Snowflake loads from the collection, not from NetSuite.

Sync modes chosen from Connect metadata

Discovery keys each table on its declared key, takes the column NetSuite flags as last-modified for the cursor, and applies built-in hints where the metadata misleads. transactionLine is keyed on the declared (transaction, id) and polled on linelastmodifieddate. Keyed tables without a last-modified cursor are re-read daily at midnight UTC; keyless tables become daily snapshots that emit deletes by comparing runs.

Accounting lines loaded with their transactions

TransactionAccountingLine is backfilled once, then refreshed only when an incremental pass of transaction reloads the accounting lines of the transactions it returns; that reload never deletes rows.

Deletes routed from DeletedRecord

Deletes go to transaction for transaction types, item for item types, and the record type's collection for custom records. New bindings poll DeletedRecord on deleteddateusertz; bindings discovered before the 5 October 2026 change still poll deleteddate (see troubleshooting), so switch their cursor and backfill.

Rebuilding Snowflake without re-querying NetSuite

SituationRe-reads NetSuite?What to do
Table dropped, or a column needs a new typeNoMaterialization backfill
Second destinationNoNew materialization on the same collections
Stale line rowsThat tableBackfill the binding, then delete rows whose flow_published_at predates it

Collections in Estuary's trial storage expire after about 20 days, so full replay needs your own bucket.

When another approach may make more sense

  • One-off migration: export saved searches with Export - CSV, including the Internal ID column to keep relationships, then COPY INTO (Snowsight takes files up to 250 MB). No increments or deletes.
  • Existing Connect extracts in Airflow or dbt: keep them if they handle cursors, line re-reads and deletes.
  • No Connect licence: scripted SuiteQL over REST. Estuary's SuiteQL connector covers this case with daily full refreshes of a fixed table list.
  • Analytics without Snowflake: Oracle's NetSuite Analytics Warehouse.

To try the route, create a NetSuite capture in the Estuary dashboard. Pricing: estuary.dev/pricing.

FAQs

    How do I replicate a NetSuite sandbox to Snowflake?

    Treat it as a separate account with its own ID (a suffix such as _SB1), tokens and Snowflake schema. A sandbox refresh deletes its tokens, so recreate them. Give it its own integration record too: regenerating a shared consumer key in a sandbox can break production.
    Governance units are a SuiteScript budget: query.runSuiteQL costs 10 units in a script. SuiteQL over REST web services is governed by the account's concurrency limit.
    Oracle publishes no throughput figure; it depends on rows, columns and connections. Load recent history only (Estuary's Start Date), drop unneeded columns, and add connections for large tables.

Start streaming your data for free

Build a Pipeline

About the author

Picture of Seán Whelan
Seán WhelanSolutions Engineer

Seán Whelan is a Solutions Engineer at Estuary, helping customers design, troubleshoot, and run production data pipelines. Seán works across change data capture, database replication, and SaaS integrations, and writes about what actually comes up when real teams move data.

Real-Time & Batch Pipelines.Simple to Deploy.Simply Priced.

  • $0.50/GB of data moved + $100/month per connector instance
  • Simple usage-based pricing with volume discounts
  • Sub-100ms end-to-end latency for real-time pipelines