> For the complete documentation index, see [llms.txt](https://docs.omnata.com/omnata-product-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.omnata.com/omnata-product-documentation/omnata-sync-for-snowflake/apps/netsuite.md).

# NetSuite

The Omnata NetSuite plugin ingests data from Oracle NetSuite directly into Snowflake using NetSuite's SuiteAnalytics Connect JDBC driver. Streams are auto-discovered from the NetSuite system catalog, and incremental syncs use NetSuite's modification-tracking columns to retrieve only new and changed records.

### Prerequisites

#### NetSuite

* A NetSuite account with **SuiteAnalytics Connect** enabled (this is the entitlement that exposes the JDBC endpoint).
* **Token-based Authentication (TBA)** enabled, with a TBA-enabled integration record and access tokens issued for a user that has the **SuiteAnalytics Connect** permission, plus read access to the tables you intend to sync.
* The NetSuite JDBC driver (`NQjc.jar`), downloaded from your NetSuite account.

If you have not previously set up TBA, [NetSuite's documentation](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4247337262.html) cover this in detail. You will need the following values to complete the Omnata connection:

| Value           | Where to find it                                                                                                                     |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Server host     | **Setup → Company → Company Information → Company URLs** (the SuiteAnalytics Connect host, e.g. `1234567.connect.api.netsuite.com`). |
| Account ID      | **Setup → Company → Company Information → Account ID**.                                                                              |
| Role ID         | The internal ID of the role used by the integration user.                                                                            |
| Consumer Key    | From the TBA-enabled integration record on the **Integrations → Manage Integrations** screen.                                        |
| Consumer Secret | Issued together with the Consumer Key (only viewable once at creation time).                                                         |
| Token ID        | From the access token issued on **Setup → Users/Roles → Access Tokens**.                                                             |
| Token Secret    | Issued together with the Token ID (only viewable once at creation time).                                                             |

#### Snowflake

* ACCOUNTADMIN or equivalent — required to install the plugin's native application and to upload the NetSuite JDBC driver into the plugin's custom packages stage (see [Install the NetSuite JDBC driver](https://claude.ai/cowork/local_ac998585-ae8f-4a4b-8de9-ac3222701e64#install-the-netsuite-jdbc-driver) below).

#### Network

The Omnata plugin opens an outbound JDBC connection from Snowflake to NetSuite on TCP port **1708**, targeting the SuiteAnalytics Connect host you supply on the connection form.

If your NetSuite account restricts inbound traffic — via account-level IP allowlists (**Setup → Company → Enable Features → Company → IP Address Rules**) or role-based IP restrictions — the NetSuite admin must permit inbound connections from your Snowflake region's egress IP range. Omnata provides a query to get the egress IP range for your account on the Connectivity step.&#x20;

### Authentication

The plugin currently supports **Token-based Authentication (TBA)** only.

#### Install the NetSuite JDBC driver

NetSuite's JDBC driver, `NQjc.jar`, is licensed to you by Oracle and is not redistributed by Omnata. You upload it once per Snowflake account where the plugin is installed, and it is then loaded at connection time by the plugin's Java UDTFs.

**Step 1: Download `NQjc.jar` from NetSuite**

In NetSuite, go to **Setup → Company → SuiteAnalytics Connect Driver Download** (the exact path can vary by NetSuite version) and download the JDBC driver. You only need the `NQjc.jar` file.

**Step 2: Upload `NQjc.jar` into a Snowflake stage you own**

Create a stage in a database/schema you control (the plugin's installed application cannot accept direct file uploads through the Snowsight UI), then upload `NQjc.jar` to it. For example:

```sql
CREATE STAGE my_db.public.netsuite_plugin_location;
```

Upload `NQjc.jar` to `@my_db.public.netsuite_plugin_location` using Snowsight (Data → Add Data → Load Files Into a Stage), SnowSQL `PUT`, or any other supported method.

Verify the file is present:

```sql
LIST @my_db.public.netsuite_plugin_location;
```

You should see `NQjc.jar` listed.

**Step 3: Copy the driver into the plugin's custom packages stage**

The plugin exposes a stage called `CUSTOM_PACKAGES.PACKAGES` inside its installed application schema. Copy the driver from your stage into it:

```sql
COPY FILES INTO @OMNATA_NETSUITE_PLUGIN.CUSTOM_PACKAGES.PACKAGES
FROM @my_db.public.netsuite_plugin_location;
```

The plugin's Java UDTFs load `NQjc.jar` from `CUSTOM_PACKAGES.PACKAGES` each time a NetSuite connection is established. You do not need to repeat this step for each sync, but you should re-upload the driver whenever NetSuite issues a new version that you wish to adopt.

#### Create the connection in Omnata

In Omnata, create a new connection using the **NetSuite** plugin. Select the **Token-based Authentication** connection method and provide the following:

| Field           | Required | Notes                                                                                                       |
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| Server host     | Yes      | The SuiteAnalytics Connect host (e.g. `1234567.connect.api.netsuite.com`). Do not include a scheme or port. |
| account\_id     | Yes      | Your NetSuite account ID.                                                                                   |
| role\_id        | Yes      | Internal ID of the role the integration user will assume.                                                   |
| Consumer Key    | Yes      | From the TBA integration record.                                                                            |
| Consumer Secret | Yes      | Stored as a secret in Snowflake; not visible after creation.                                                |
| Token Id        | Yes      | From the access token issued to the integration user.                                                       |
| Token Secret    | Yes      | Stored as a secret; not visible after creation.                                                             |

On save, Omnata issues a small test query through the JDBC driver (`SELECT 1`) and reports a clear error if any credential is wrong or the driver is missing.

### Inbound Syncs

The plugin supports inbound sync only.

#### Sync strategies per stream

Streams are discovered from NetSuite's system catalog at sync configuration time. Each table's available sync strategies are determined automatically:

| Table has a cursor field? | Supported sync strategies |
| ------------------------- | ------------------------- |
| Yes                       | Incremental, Full Refresh |
| No                        | Full Refresh only         |

**Incremental** — uses a timestamp cursor (the table's `lastmodifieddate` column where available, otherwise a known system cursor) to retrieve only records changed since the previous sync.&#x20;

**Full Refresh** — fetches the entire table. For tables that do support a cursor, the plugin uses the same partitioning as Incremental. For tables without a cursor, the entire table is fetched in a single query.

#### Stream discovery

When you configure an inbound sync, the plugin lists every table NetSuite reports through `OA_TABLES`. This includes:

* Standard NetSuite tables (e.g. `transaction`, `customer`, `item`, `systemnote`).
* Custom records (`customrecord*`), custom lists (`customlist*`), and user notes (`usernote`) — the plugin auto-detects `id` as the primary key for these.
* Many-to-many mapping tables (`map_*`) — the plugin auto-detects the composite key `(mapone, maptwo)` for these.

Calculated columns (those marked as calculated in NetSuite's column metadata) are excluded from the schema, because they cannot be retrieved with a standard `SELECT *` against SuiteQL.

#### Inbound tuning parameters

These parameters can be adjusted on an existing sync without triggering a change-management workflow.

<table><thead><tr><th width="187.65234375">Parameter</th><th width="96.67578125">Default</th><th width="175.2734375">Range</th><th>Notes</th></tr></thead><tbody><tr><td>Maximum Concurrent Streams</td><td><code>2</code></td><td>1 – 64</td><td>Number of streams the plugin will run in parallel. NetSuite enforces a per-account concurrent connection limit (commonly 15 for most plans, up to 55 on Tier 5 / SuiteCloud Plus). Keep this comfortably below your account limit, especially if other tools share it.</td></tr><tr><td>JDBC Fetch Size</td><td><code>1000</code></td><td>100 – 100,000</td><td>Rows the JDBC driver fetches per round-trip. NetSuite's documentation recommends 1,000 as a sensible default. Setting this too high can exhaust Snowflake's function memory, but it too low slows the sync down.</td></tr><tr><td>Maximum Records Per Query</td><td><code>100,000</code></td><td>50,000 – 5,000,000</td><td>The soft batch size used for cursor-based incremental queries allowing the sync engine to checkpoint. NetSuite documentation treats 100,000 as the practical upper bound; larger values reduce overhead but increase the amount of work lost if a sync run times out.</td></tr><tr><td>Query retry count</td><td><code>3</code></td><td>1 – 20</td><td>How many times a query will be retried in response to transient errors between Snowflake and NetSuite.</td></tr></tbody></table>

{% hint style="info" %}
The right balance of fetch size and records-per-query depends on table shape. For a wide table (e.g. `transaction` with several hundred columns), keep both numbers conservative. For a narrow table (e.g. `systemnote`), larger values can dramatically shorten sync runs.
{% endhint %}

### Troubleshooting

#### **"Connection failed" on the connect step**

* Confirm the TBA integration record is enabled and the access token has not been revoked.
* Confirm the integration user's role has SuiteAnalytics Connect permission and read access to the tables you plan to sync.
* Confirm the Server host value exactly matches the SuiteAnalytics Connect host (no scheme, no path, no port).
* Confirm `NQjc.jar` is present in `<plugin_application_name>.CUSTOM_PACKAGES.PACKAGES` — if the driver is missing the test query will fail with a `ClassNotFoundException` surfaced in the error message.
* If your Snowflake account uses an external-access integration with explicit allow-listing, confirm that `<server_host>:1708` is on the allow-list.

#### **Connection succeeds but stream discovery is slow**

The first time you list streams on a new connection, the plugin reads NetSuite's `OA_TABLES` catalog. This can take several minutes on larger NetSuite instances — it is not a hang. Subsequent listings reuse cached data.

#### **Sync times out mid-run**

For cursor-supporting tables, the sync engine checkpoints between batches sized by **Maximum Records Per Query**. After a timeout, the next sync resumes from the last checkpointed cursor value. If you find timeouts losing too much work, lower Maximum Records Per Query to shorten the gap between checkpoints. For tables without a cursor, there is no checkpointing — a timed-out full refresh must restart from the beginning.

#### **A custom record's related child tables are empty**

When you select a `customrecord*` or `customlist*` table that has multi-select relationships, the corresponding `map_*` tables (and any sub-tables) are **not** auto-selected for you. Select them explicitly in the stream list to populate the join.

### Limitations

* **Outbound sync is not supported.** This plugin is read-only against NetSuite.
* **OAuth 2.0 is not yet supported.** TBA is the only authentication method.
* **Wide tables (>1,000 columns).** SuiteQL imposes a hard limit of 1,000 columns per `SELECT`. The plugin currently issues a single `SELECT *` per stream, so tables exceeding 1,000 columns are not supported. NetSuite's standard tables sit comfortably below this limit; very wide custom records may not.
* **Calculated columns are excluded.** Columns flagged as calculated in NetSuite's column metadata are removed from the schema before sync.
* **Foreign-key relationships are not modelled.** NetSuite exposes table relationships via `OA_FKEYS`, but the plugin does not currently consume it. Related tables (especially `map_*` join tables for custom multi-selects) must be selected explicitly.
* **Cursor start date is not user-configurable.** Full Refresh always rewinds the cursor to `1970-01-01`.&#x20;
* **Per-account concurrent connection limit.** NetSuite typically limits an account to 15 concurrent SuiteAnalytics Connect sessions (55 on Tier 5 / SuiteCloud Plus). The plugin's Maximum Concurrent Streams setting needs to respect this, together with any other tools using the same account.

***

Last updated


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.omnata.com/omnata-product-documentation/omnata-sync-for-snowflake/apps/netsuite.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
