
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
| Approach | Best fit | Freshness | Operational burden | Example implementation |
|---|---|---|---|---|
| Saved-search CSV export | One-time migration | Point in time | Manual each run | Export - CSV → stage → COPY INTO |
| Scheduled SuiteQL over REST | No Connect licence, a few small tables | The schedule | High: paging, concurrency pool, table list | Script calling /services/rest/query/v1/suiteql |
| Scheduled Connect extract | Connect licence and an orchestrator | The schedule | High: cursors, MERGE, deletes | JDBC or ODBC with Airflow or dbt |
| Managed pipeline | Many tables, no extraction code | Per-table polls, scheduled loads | Low: configuration | Estuary |
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
MinOA_COLUMNS:lastmodifieddate, orlinelastmodifieddateontransactionLine. 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 requirements
The licence decides the interface:
| SuiteAnalytics Connect | SuiteQL over REST | |
|---|---|---|
| Licence | Add-on; not offered on Small Business accounts | Included |
| Features | SuiteAnalytics Connect (Analytics subtab), Token-Based Authentication | REST Web Services, SuiteAnalytics Workbook, Token-Based Authentication (closed to new REST integrations from 2027.1) |
| Role permissions | SuiteAnalytics Connect, plus view access to your records | REST Web Services, Log in using Access Tokens, SuiteAnalytics Workbook |
| Result limit | No 100,000 cap | 100,000 per query |
If SuiteAnalytics Connect is missing from Enable Features, it isn't provisioned. Then set up token-based authentication:
- Create an integration record with token-based authentication (Setup > Integration > Manage Integrations > New). Its consumer key and secret are shown once.
- 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.
- 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.
- 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_TZorTIMESTAMP_LTZkeep the instant,TIMESTAMP_NTZkeeps wall-clock time. Any account-levelTIMESTAMP_TYPE_MAPPINGmust agree. - Keep
QUOTED_IDENTIFIERS_IGNORE_CASEat its default, FALSE, for the loading user.
Setting up NetSuite to Snowflake replication
- Pick the interface and list your tables. For a ledger model, include
transaction,transactionLine,TransactionAccountingLine,Account,accountingPeriodandSubsidiary. - Create the role, user and token, and prepare Snowflake.
- Classify each table as incremental, re-read or snapshot.
- Backfill history, paging on an indexed integer key. Filter by date to skip unneeded years.
- Start incremental polls from slightly before the last checkpoint, and let the
MERGEabsorb the overlap:
sqlSELECT 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- 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 target | Notes |
|---|---|---|
INTEGER, BIGINT (IDs, keys) | NUMBER(38,0) | Lossless |
NUMBER, DOUBLE (amounts, rates) | NUMBER(p,s) | Avoid FLOAT; see below |
VARCHAR, VARCHAR2, CHAR, CLOB | VARCHAR | CLOB holds long text |
| Checkbox fields | BOOLEAN or VARCHAR(1) | Connect returns T and F |
TIMESTAMP | TIMESTAMP_TZ/LTZ if it carries a time zone, else TIMESTAMP_NTZ | Record 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 |
|---|---|
| Standard | 5 |
| Premium | 15 |
| Enterprise, Ultimate | 20 |
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
lastmodifieddatequery never returns deleted rows; onlyDeletedRecorddoes. - The deletion cursor has no time zone.
deleteddateis stored in NetSuite's system time zone without an offset, so comparing it with UTC bounds can skip deletions made just after a poll. Polldeleteddateusertz, 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 peerat 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 reachedmeans 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,Customeranditem; 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
transactionLineon(transaction, linesequencenumber), and doesn't applyDeletedRecordto 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:
DeletedRecordtombstones flag rows with_meta/op = 'd'unless Hard Delete is on; line tables get none. - Types: the capture reads
NUMBERandDOUBLEvalues as 64-bit floats, and amounts land asFLOATunless you give them aDDLoverride (see Map collections to tables below); checkboxes stayT/Ftext. Date-times that carry a time zone keep their offset; the rest are labelled UTC without conversion.CLOBvalues 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.
- 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.
- 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.
- 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
lastmodifieddatemeans polling every Interval; a Backfill Cursor with a Schedule means a scheduled re-read. - Keep
DeletedRecordenabled; 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.
- 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.
- Host (Account URL) without
- 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).
- 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'sfields.includein 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. - Publish. Click Save and publish; the backfill loads without waiting for the schedule.
- 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.
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
| Situation | Re-reads NetSuite? | What to do |
|---|---|---|
| Table dropped, or a column needs a new type | No | Materialization backfill |
| Second destination | No | New materialization on the same collections |
| Stale line rows | That table | Backfill 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.
Related resources
- Estuary: SuiteAnalytics Connect connector, SuiteQL connector, Snowflake materialization, sync schedules, backfilling data
- Oracle: Connect FAQ, NetSuite2.com roles, SuiteQL over REST, concurrency limits, deleted records
- Snowflake: key-pair authentication, floating-point rounding errors
To try the route, create a NetSuite capture in the Estuary dashboard. Pricing: estuary.dev/pricing.
FAQs
Does SuiteQL through the REST API use governance units?
How long does the first NetSuite backfill take?

About the author
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.


















