> ## Documentation Index
> Fetch the complete documentation index at: https://www.integrate.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# ETL: NetSuite REST Destination

> How to configure the NetSuite REST component to write records, including transactions with lines, in your Integrate.io ETL pipeline.

Use the NetSuite REST destination component to write records to NetSuite through NetSuite's REST web services. It can insert new records, update existing records by internal ID or external ID, or upsert records by external ID. It writes standard and custom records, including transactions with lines such as vendor bills, invoices, and journal entries, and it sets standard and custom fields. A field that points to another record (a vendor, an account, a department) can be given as that record's internal ID or external ID.

## Connection Setup

Select an existing NetSuite SOAP or NetSuite OAuth connection, or create a new one. The component works with both connection types:

| Connection type | Authentication | How to create it |
| :- | :- | :- |
| NetSuite SOAP | Token-based authentication: account ID, consumer key and secret, token ID and secret | Follow [Connection Setup: NetSuite (SOAP)](/docs/etl/using-components-netsuite-source#connection-setup-netsuite-soap) on the NetSuite source page. |
| NetSuite OAuth | OAuth 2.0 | Create a **NetSuite OAuth** connection, enter your NetSuite account ID, click **Authenticate**, then sign in to NetSuite and approve access. |

Whichever type you use, NetSuite must allow REST web services for the account and for the role behind the connection:

<Steps>
  <Step>
    Enable REST web services: go to **Setup > Company > Enable Features**, open the **SuiteCloud** subtab, and check **REST Web Services** in the **SuiteTalk (Web Services)** section. For a NetSuite OAuth connection, also check **OAuth 2.0** under **Manage Authentication**.
  </Step>

  <Step>
    On the role's **Permissions > Setup** subtab, add **REST Web Services**, plus **Log in using Access Tokens** for token-based authentication or **Log in using OAuth 2.0 Access Tokens** for OAuth 2.0.
  </Step>

  <Step>
    Give the role permission to create and edit the record types you write, and to view the records they point to, such as vendors, accounts, and items.
  </Step>
</Steps>

## Destination Properties

| Setting | Description |
| :- | :- |
| Record type | The NetSuite record type to write, standard or custom. The list holds the record types the connection can reach through REST web services, by record type ID. NetSuite lists these IDs in lowercase, for example `vendorbill`, `journalentry`, or `customrecord_invoice_batch`, so search the list by ID rather than by display name. The list is read from NetSuite each time you open the component, so a custom record type added since shows up when you open it again. If NetSuite refuses to list record types, the field becomes a text box where you type the record type ID. |
| Operation type | **Insert** (default), **Update**, or **Upsert**. See [Operations](#operations). |
| Update key | Update only. The field that identifies the record to update: **External ID** (`externalId`, the default) or **Internal ID** (`id`). |
| Upsert key | Upsert only. Always **External ID** (`externalId`), because NetSuite upserts records by external ID. |
| Existing lines | Update and upsert only. What happens to a record's existing lines when you send lines: **Replace** (default) or **Append**. See [Transactions with lines](#transactions-with-lines). |

Choosing another record type clears the schema mapping, because the new record type has different fields. An update or upsert keeps an empty row for its key.

### Operations

| Operation | What it does | Finds the record by | When no record matches |
| :- | :- | :- | :- |
| Insert | Creates a record for each input record (for each group of rows, when line fields are mapped). | Nothing | Always creates a record. |
| Update | Changes the mapped fields of an existing record. Other fields keep their values. | The update key: external ID or internal ID | The record fails. Nothing is created. |
| Upsert | Updates the record with the given external ID, or creates one with that external ID. | External ID | Creates the record. |

### Errors and Result File

| Setting | Default | Description |
| :- | :- | :- |
| Maximum errors | 0 | How many failed records to tolerate before the job fails. With 0, the first failed record fails the job. With -1, failed records never fail the job. |
| Output record results (CSV) | Off | Writes a row for each record the job processes, written or failed, with its status, internal ID and NetSuite's error, to a CSV file at the cloud storage connection and path you choose. See [Result file](#result-file). |

## Schema Mapping

Map each input field to a NetSuite field. The **NetSuite field** list holds the record type's writable fields:

* **Header fields**, by field ID, for example `memo`, `tranDate`, or `entity`. Custom body fields appear by their script ID, for example `custbody_project_code`.
* **Line fields**, as `sublist.field`, for example `expense.account`, `expense.amount`, or `item.quantity`. Custom column fields appear the same way, for example `expense.custcol_cost_center`.

Read-only fields are not listed, except `id`. Subrecords (such as addresses) and multi-select fields are not listed either. The list is read from NetSuite each time you open the component. If a custom field is missing, check that the connection's role can see it, then open the component again.

For an update or an upsert, the grid starts with a row for the key field (`externalId` or `id`). Its NetSuite field is fixed, and the row cannot be removed: pick the input field that holds each record's key.

**Reference type** appears for fields that point to another record. It tells NetSuite whether the input value is the other record's internal ID or its external ID:

| Field kind | Example | Reference type |
| :- | :- | :- |
| Reference | `entity` (vendor), `account`, `department`, `item` | **Internal ID** (default) or **External ID** |
| Select (a list value) | `approvalStatus` | **Internal ID** only |

Internal ID means NetSuite's internal ID of the record, not an account number or a name. Use **External ID** when your source data carries the external IDs of the records you point to.

**Auto-fill** maps input fields to header fields with the same name, keeping the rows already mapped. **Clear unmapped** removes rows that do not map an input field to a NetSuite field.

How values are sent:

* Empty values are not sent, so they never clear a value that is already in NetSuite.
* Date fields take `YYYY-MM-DD` strings. A Datetime value sent to a date field is sent as its date.
* A numeric internal ID such as `42.0` is sent as `42`.
* A value that cannot be written to NetSuite, such as a map or a bag, fails its record.
* NetSuite stores `<` and `>` in text fields as `&lt;` and `&gt;`, so reading such a value back from NetSuite returns the entities. Other characters, including `&`, quotes, and non-ASCII letters, are stored as sent.

### Transactions with Lines

Mapping a line field changes how input rows become records. The rows that share the same record key value become one NetSuite record:

* The header fields come from the first row of the group.
* Every row adds one line to each sublist it has values for, in input order. A row with no values for a sublist adds no line to it, so one input can carry, for example, expense lines and item lines side by side.
* Rows with an empty record key cannot be grouped. Each one fails as a record of its own and counts against **Maximum errors**.

Which input field is the record key depends on the operation:

| Operation | Record key |
| :- | :- |
| Update, Upsert | The input field mapped to the key (`externalId` or `id`). The rows of one record share its key, so there is nothing to choose. |
| Insert | The **Record key** setting, which appears below the mapping grid once a line field is mapped and is required from then on. It starts at the input field mapped to `externalId`, if any. A document number is a typical choice. |

For an update or an upsert, **Existing lines** decides what happens to the lines a record already has:

| Existing lines | Result |
| :- | :- |
| Replace | Each sublist you send lines to ends up with exactly the lines sent. Sublists you send no lines to are left as they are. |
| Append | The lines sent are added to the record's existing lines. |

#### Example: A Vendor Bill with Expense Lines

Input rows:

| bill\_number | vendor\_code | memo | tran\_date | account\_id | amount |
| :- | :- | :- | :- | :- | :- |
| INV-1001 | V-ACME | October services | 2026-10-01 | 58 | 1200.00 |
| INV-1001 | V-ACME | October services | 2026-10-01 | 59 | 300.00 |

Settings and mapping:

* Record type: `vendorbill`. Operation type: **Upsert**. Existing lines: **Replace**.
* `bill_number` to `externalId` (the upsert key row), `vendor_code` to `entity` with reference type **External ID**, `memo` to `memo`, `tran_date` to `tranDate`, `account_id` to `expense.account` with reference type **Internal ID**, `amount` to `expense.amount`.
* No record key to choose: an upsert groups the rows by `bill_number`, the input field mapped to its key.

The two rows become one vendor bill with the external ID `INV-1001` and two expense lines. NetSuite creates the bill on the first run, and a later run with the same rows updates it and replaces its expense lines instead of adding them again.

## Result File

The result file has a row for each record the job processed, written or failed:

| Column | Contents |
| :- | :- |
| Record Key | With line fields mapped, the record key value. Without them, the value of the update or upsert key; for an insert, the mapped external ID, if any. |
| Status | `success` when NetSuite accepted the record. `failed` when NetSuite rejected it, or when it could not be sent. |
| Internal ID | The ID NetSuite gave or found. Empty for a failed record. |
| Operation | `insert`, `update`, or `upsert`. |
| Error Code | NetSuite's error code, for example `NONEXISTENT_EXTERNAL_ID`, or `HTTP_<status>` when NetSuite sent none. `REQUEST_FAILED` means the request failed in transit, so the change may or may not have been applied. Empty for a written record, and for a record that failed before it was sent, such as one with a missing key. |
| Error | The error message. Empty for a written record. |
| (header fields) | The values of the mapped header fields, one column per NetSuite field. |

The file is written even when the job fails, so it is available after a run stopped by **Maximum errors**. If NetSuite rejects the connection's credentials, the job stops at once: the file has the records processed before that, and none after. A following package can read it to post each record's result back to the source system.

## How Records Are Written

* **One request per record.** Records are written one at a time, so throughput depends on how fast NetSuite answers. Plan large loads around your NetSuite account's concurrency limits.
* **Rate limits are retried.** A rate-limited request (HTTP 429) is retried with increasing waits.
* **Server errors are retried only when that is safe.** A server error (HTTP 5xx) is retried for an update or upsert that replaces every sublist it sends. An insert, or a write that appends lines, is reported as failed instead, because NetSuite may already have applied it and a retry could duplicate the record or its lines.
* **Rejected credentials stop the job.** NetSuite would reject every other record the same way. Test the connection, and reconnect it if it is a NetSuite OAuth connection.
* **Rejected records do not stop the job until Maximum errors is exceeded.** Each one is in the result file with the status `failed`.
* **Failed tasks are not retried.** Task retries are turned off for this component, so a task that fails partway is not run again and its records are not written twice.

## Best Practices

* **Prefer upsert by external ID for loads you may re-run.** A re-run updates the same records instead of creating duplicates, and with **Replace** it does not duplicate lines either.
* **Use Append only to add lines on purpose.** Running the same append twice adds the lines twice.
* **Turn on the result file,** especially when **Maximum errors** is above 0. It lists each failed record with NetSuite's reason.
* **Test against a sandbox account first.** For a NetSuite SOAP connection, a sandbox account ID carries the `_SB<number>` suffix.
* **For an insert with lines, choose a stable record key,** such as a document number, so rows that belong together always group into the same record.

## FAQ

**Q: How is NetSuite REST different from the NetSuite SOAP destination?**

NetSuite REST writes through NetSuite's REST web services, with the operations, line handling, and references described on this page. The [NetSuite SOAP](/docs/etl/using-components-netsuite-soap-destination) destination writes through NetSuite's SOAP web services and can also delete records, which NetSuite REST does not do.

**Q: Why does the record type list show `vendorbill` instead of "Vendor Bill"?**

NetSuite lists record type IDs in lowercase, and the list shows them as NetSuite returns them. NetSuite accepts them in any case, so `vendorbill` and `vendorBill` name the same record type. Search the list by ID.

**Q: The record is already found by its external ID. Why do some fields need a reference type?**

The two settings identify different records. The update or upsert key finds the record being written, for example the vendor bill with the external ID `INV-1001`. A reference type belongs to a field that points to another record, for example the bill's vendor (`entity`) or an expense line's account. NetSuite needs to know whether the value in that field is the vendor's internal ID or its external ID. The reference type defaults to **Internal ID**, so you only change it when your data holds the external IDs of the records you point to.

**Q: How do I write a custom field?**

Custom fields of the record type appear in the NetSuite field list by script ID: `custbody_...` for header fields and `sublist.custcol_...` for line fields. Map them like any other field. If one is missing, check the field's access for the connection's role, then open the component again.

**Q: What happens when a referenced record does not exist?**

NetSuite rejects the record, for example with `NONEXISTENT_EXTERNAL_ID`, and the result file reports it as `failed`. It is never written without the reference.

**Q: Can I clear a field in NetSuite?**

No. Empty values are not sent, so an empty input value leaves the NetSuite field as it is.

**Q: Why did the job stop after the first failed record?**

**Maximum errors** defaults to 0. Raise it, or set it to -1, and turn on the result file to see every failure.

## Related

<CardGroup cols={2}>
  <Card title="NetSuite SOAP Destination" icon="arrow-right" href="/docs/etl/using-components-netsuite-soap-destination" horizontal />

  <Card title="NetSuite Source" icon="arrow-right" href="/docs/etl/using-components-netsuite-source" horizontal />

  <Card title="REST API Destination" icon="arrow-right" href="/docs/etl/using-components-rest-api-destination" horizontal />

  <Card title="Destinations Overview" icon="arrow-right" href="/docs/etl/category/destinations" horizontal />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.