NetSuite – Gotchas

Service ID: netsuite

NetSuite is the leading integrated cloud business software suite, including business accounting, ERP, CRM and ecommerce software.

⚠️

42 gotchas across 23 resources

These are connector-specific behaviors and limitations to be aware of when integrating.

Attachments1 gotcha

deleteattachmentsDelete

Detaches the file from the parent record. The file itself stays in the File Cabinet.

onebalanceSheetOne

NetSuite date filtering resolves filter[end_date] to accounting period IDs via SuiteQL, then filters the SOAP request by periods up to that date. This requires the SuiteAnalytics Workbook permission for period resolution. If unavailable, returns all-time data as fallback. For Balance Sheet, only filter[end_date] is meaningful (point-in-time snapshot).

addbankFeedAccountsAdd

target_account_name is required. NetSuite has no "register an existing account as a feed" concept, so this operation creates a new General Ledger account and uses target_account_name as its (required) account name. If it is omitted, NetSuite rejects the request with a cryptic INVALID_FLD_VALUE ("Field must contain a value"). target_account_number is optional.

target_account_id is not used — this operation always creates a new GL account and never links an existing one. To attach the feed to an account that already exists in NetSuite, skip this create call entirely: list accounts with GET /bank-feed-accounts, take the id of the account you want, and pass it as bank_feed_account_id when creating bank feed statements.

For reconciliation to work, the bank feed and the transactions it should match (e.g. expenses posted as credit card charges) must live on the same NetSuite account. The reliable pattern is to reuse the account both already post to — set bank_feed_account_id on the statement to that account's id — rather than creating a separate account with this endpoint.

Bill Payments3 gotchas

allbillPaymentsAll

In order to read or write the status field, you must enable Approval Routing for Vendor Payments in Netsuite. Navigate to Setup > Accounting Preferences > Approval Routing > Vendor Payments > Approval Routing. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addbillPaymentsAdd

In order to read or write the status field, you must enable Approval Routing for Vendor Payments in Netsuite. Navigate to Setup > Accounting Preferences > Approval Routing > Vendor Payments > Approval Routing.

onebillPaymentsOne

In order to read or write the status field, you must enable Approval Routing for Vendor Payments in Netsuite. Navigate to Setup > Accounting Preferences > Approval Routing > Vendor Payments > Approval Routing.

Bills4 gotchas

allbillsAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

Bill line-item tracking_categories[].name is normalized to the leaf class name: a hierarchical class path such as "Fruit : Apple" returns name: "Apple" and parent_name: "Fruit" (deeper paths return the deepest leaf). NetSuite bill line items do not carry a department or location name.

addbillsAdd

For creating or updating a bill in Netsuite, its required to provide the line type.

line_items[].tracking_categories maps to the NetSuite per-line class (Classification), which accepts only one value — entries at index 1 or beyond are dropped silently.

When line_items[].customer is set without an explicit line_items[].rebilling.rebillable, NetSuite defaults rebillable to true. Pass rebilling: { rebillable: false } explicitly if you want a customer-linked line that is not rebillable.

tax_inclusive: true is supported via a connector-side gross-down. NetSuite's SOAP VendorBill has no native tax-inclusive flag, so when tax_inclusive: true the connector divides each line's total_amount by (1 + tax_rate.rate / 100) before sending. NetSuite then applies the taxCode and produces the correct tax-inclusive total. Both line_items[].tax_rate.id and line_items[].tax_rate.rate must be provided per line when tax_inclusive: true — if either is absent the gross-down is skipped and the amount is sent as-is (without a valid taxCode NetSuite will not add tax, resulting in an incorrect total). tax_inclusive is not returned on read — NetSuite's SOAP schema has no equivalent read field. Due to intermediate rounding (net is computed to 4 decimal places), the posted total may differ from the consumer gross by ±0.01 in rare rate/amount combinations where the exact net is not representable in 4dp.

onebillsOne

NetSuite populates payment_allocations only on bills.get (detail), not on the bills list. To list the payments applied to a bill, use bill-payments.list?filter[bill_id]=<bill_id>.

Bill line-item tracking_categories[].name is normalized to the leaf class name: a NetSuite hierarchical class path such as "Fruit : Apple" returns name: "Apple" and parent_name: "Fruit" (deeper paths return the deepest leaf). NetSuite bill line items do not carry a department or location name.

updatebillsUpdate

line_items[].tracking_categories maps to the NetSuite per-line class (Classification), which accepts only one value — entries at index 1 or beyond are dropped silently.

line_items[].rebilling.rebillable cannot be changed after the bill is created — NetSuite rejects updates to isbillable on VendorBill. The field is silently dropped from update requests; create a new bill if you need to change the flag. Additionally, any PATCH that updates a line with customer set will cause NetSuite to reset rebilling.rebillable to its default (true) because the SOAP update omits the field and NetSuite re-applies the default for billable lines.

tax_inclusive: true is supported via a connector-side gross-down. NetSuite's SOAP VendorBill has no native tax-inclusive flag, so when tax_inclusive: true the connector divides each line's total_amount by (1 + tax_rate.rate / 100) before sending. NetSuite then applies the taxCode and produces the correct tax-inclusive total. Both line_items[].tax_rate.id and line_items[].tax_rate.rate must be provided per line when tax_inclusive: true — if either is absent the gross-down is skipped and the amount is sent as-is (without a valid taxCode NetSuite will not add tax, resulting in an incorrect total). tax_inclusive is not returned on read — NetSuite's SOAP schema has no equivalent read field. Due to intermediate rounding (net is computed to 4 decimal places), the posted total may differ from the consumer gross by ±0.01 in rare rate/amount combinations where the exact net is not representable in 4dp.

Credit Notes4 gotchas

allcreditNotesAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addcreditNotesAdd

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.

updatecreditNotesUpdate
  • Netsuite doesn't support voiding credit notes via API. Options: 1) Remove allocations and delete, or 2) Create a reversing invoice for the same amount.
updatecreditNotesUpdate

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.

Customers1 gotcha

allcustomersAll

The minimum page size accepted for Netsuite is 5. The cursor for Netsuite expires if it has not been used within 15 minutes after its creation.

The taxable field is read directly from the NetSuite customer.taxable boolean in both Legacy Tax and SuiteTax accounts. Under SuiteTax, customer.taxable continues to be readable but does not reflect per-jurisdiction tax exemptions — the meaningful per-nexus tax state lives in NetSuite's customerTaxRegistration records, which the unified API does not currently surface as first-class fields. Consumers needing per-nexus tax registration data should gate their logic on connection.suitetax_enabled and query customerTaxRegistration via pass-through SuiteQL, e.g. ?pass_through[q]=SELECT entity, nexus, taxRegistrationNumber FROM customerTaxRegistration WHERE entity = {customer_id}.

When filter, sort, or pass_through parameters are applied to GET /accounting/customers, addresses[] returns only the default billing and default shipping addresses, each with type and string (a multi-line formatted address). Structured fields like line1, city, state, postal_code, and country, and any additional custom-labeled addresses, are returned only on unfiltered list calls and on GET /accounting/customers/{id}. filter[subsidiary_id] scopes results to customers assigned to (or shared with) the given subsidiary, and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

Departments1 gotcha

alldepartmentsAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation.

allexpenseReportsAll

filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addexpenseReportsAdd

When creating expense report line items, the line_items[].account.id field is not written to NetSuite. In NetSuite, the GL account is automatically derived from the expense category. Setting line_items[].account.id has no effect and may trigger an INSUFFICIENT_PERMISSION error in strict tenant configurations. Use line_items[].expense_category.id to specify the category (which implicitly sets the GL account).

The line_items[].reimbursable field maps to the NetSuite corporateCreditCard boolean on each expense line. Setting reimbursable: false sets corporateCreditCard: true, marking the line as a corporate card expense. NetSuite will automatically create an offsetting accounting entry against the corporate card liability account, and the line's amount will not count toward the employee reimbursement total (resulting in total_amount: 0 if all lines are non-reimbursable). Setting reimbursable: true (or omitting the field) leaves corporateCreditCard unset, meaning the employee is expected to be reimbursed out-of-pocket.

The employee record linked to the expense report must have a corporate credit card configured in NetSuite when reimbursable: false is used, otherwise the API will return a validation error.

Note: corporateCreditCard is the current NetSuite SOAP field for this purpose. The legacy isNonReimbursable field was removed in NetSuite 18.2 for accounts that did not use it prior to that version.

Important read limitation: The list endpoint (GET /expense-reports) reads line_items[].reimbursable from the NetSuite SuiteQL transactionline table, which exposes only the legacy isNonReimbursable column. Because writing reimbursable: false sets corporateCreditCard (not isNonReimbursable), the list endpoint will always return reimbursable: true for lines created with reimbursable: false via this API. To get accurate reimbursable values for corporate card lines, use the single record endpoint (GET /expense-reports/{id}), which reads directly from the NetSuite SOAP API where corporateCreditCard is available.

oneexpenseReportsOne

tracking_categories[].name is normalized to the leaf class name at both the expense-report header and each line item: a NetSuite hierarchical class path such as "Fruit : Apple" returns name: "Apple" and parent_name: "Fruit" (deeper paths return the deepest leaf). department.name and location.name (header and line items) are likewise normalized to the leaf segment.

updateexpenseReportsUpdate

When updating expense report line items, the line_items[].account.id field is not written to NetSuite. In NetSuite, the GL account is automatically derived from the expense category. Setting line_items[].account.id has no effect and may trigger an INSUFFICIENT_PERMISSION error in strict tenant configurations. Use line_items[].expense_category.id to specify the category (which implicitly sets the GL account).

The line_items[].reimbursable field maps to the NetSuite corporateCreditCard boolean on each expense line. Setting reimbursable: false sets corporateCreditCard: true, marking the line as a corporate card expense. NetSuite will automatically create an offsetting accounting entry against the corporate card liability account, and the line's amount will not count toward the employee reimbursement total (resulting in total_amount: 0 if all lines are non-reimbursable). Setting reimbursable: true (or omitting the field) leaves corporateCreditCard unset, meaning the employee is expected to be reimbursed out-of-pocket.

The employee record linked to the expense report must have a corporate credit card configured in NetSuite when reimbursable: false is used, otherwise the API will return a validation error.

Note: corporateCreditCard is the current NetSuite SOAP field for this purpose. The legacy isNonReimbursable field was removed in NetSuite 18.2 for accounts that did not use it prior to that version.

Important read limitation: The list endpoint (GET /expense-reports) reads line_items[].reimbursable from the NetSuite SuiteQL transactionline table, which exposes only the legacy isNonReimbursable column. Because writing reimbursable: false sets corporateCreditCard (not isNonReimbursable), the list endpoint will always return reimbursable: true for lines created with reimbursable: false via this API. To get accurate reimbursable values for corporate card lines, use the single record endpoint (GET /expense-reports/{id}), which reads directly from the NetSuite SOAP API where corporateCreditCard is available.

Expenses3 gotchas

addexpensesAdd

NetSuite creates every expense as a Credit Card Charge, so the top-level account must reference a credit-card–type ledger account. A bank account (or any non–credit-card account) is rejected with Invalid Field Value … for the following field: account. There is no bank-account expense path on NetSuite; employee reimbursements are not modelled here (use expense-reports).

line_items[].tracking_categories maps to the NetSuite per-line class (Classification), which accepts only one value — entries at index 1 or beyond are dropped silently.

When line_items[].customer is set without an explicit line_items[].rebilling.rebillable, NetSuite defaults rebillable to true. Pass rebilling: { rebillable: false } explicitly if you want a customer-linked line that is not rebillable.

line_items[].tax_rate.id is applied on create when the connection has SuiteTax enabled (the suitetax_enabled setting), so mixed-rate expenses post the correct per-line tax. Without it, NetSuite applies the account's default tax code to every line (a 0% line under a 20%-default account posts at 20%). Tenants running a third-party tax engine (e.g. Vertex/Avalara) reject the per-line tax code — use pass_through there.

oneexpensesOne

Line-item tracking_categories[].name is normalized to the leaf class name: a NetSuite hierarchical class path such as "Fruit : Apple" returns name: "Apple" and parent_name: "Fruit" (deeper paths return the deepest leaf as name and its immediate parent as parent_name). Line-item department.name and location.name are likewise normalized to the leaf segment.

updateexpensesUpdate

NetSuite creates every expense as a Credit Card Charge, so the top-level account must reference a credit-card–type ledger account. A bank account (or any non–credit-card account) is rejected with Invalid Field Value … for the following field: account. There is no bank-account expense path on NetSuite; employee reimbursements are not modelled here (use expense-reports).

line_items[].tracking_categories maps to the NetSuite per-line class (Classification), which accepts only one value — entries at index 1 or beyond are dropped silently.

line_items[].tax_rate.id is applied on update when the connection has SuiteTax enabled (the suitetax_enabled setting), so mixed-rate expenses post the correct per-line tax. Without it, NetSuite applies the account's default tax code to every line (a 0% line under a 20%-default account posts at 20%). Tenants running a third-party tax engine (e.g. Vertex/Avalara) reject the per-line tax code — use pass_through there.

allinvoiceItemsAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] returns only items assigned to (or shared with) the given subsidiary, and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored. When a filter or sort triggers the SuiteQL path, currency and tax_schedule_id are not available (SOAP-only fields, absent from the SuiteQL item table) and quantity reflects total quantity on hand.

Invoices3 gotchas

allinvoicesAll

The minimum page size accepted for Netsuite is 5. The cursor for Netsuite expires if it has not been used within 15 minutes after its creation. When using filters or sort parameters (which trigger the SuiteQL path), quantity and unit_price on line items may reflect base-unit values (e.g., days) rather than selling-unit values (e.g., years) for items with Units of Measure (UOM) enabled. total_amount is always accurate regardless. Use the GET one endpoint for selling-unit quantities. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addinvoicesAdd

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used. While creating an invoice, revenue start date and revenue end date should be passed in the custom fields object inside the line items.

updateinvoicesUpdate

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.

alljournalEntriesAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addjournalEntriesAdd

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.

updatejournalEntriesUpdate

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.

allledgerAccountsAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] returns only accounts assigned to (or shared with) the given subsidiary, and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addledgerAccountsAdd

The currency field is supported only when Multi-Currency is enabled in NetSuite. It applies mainly to Bank and Credit Card accounts and becomes read-only after creation.

Locations1 gotcha

alllocationsAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation.

Payments2 gotchas

allpaymentsAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

addpaymentsAdd

The number property could be used to override the Netsuite tranId property.

oneprofitAndLossOne

NetSuite date filtering resolves filter[start_date] and filter[end_date] to accounting period IDs via SuiteQL, then filters the SOAP request by those periods. This requires the SuiteAnalytics Workbook permission for period resolution. If the permission is unavailable, the report returns all-time data as a fallback. For best results, keep date ranges under 16 years (200 monthly periods).

Projects1 gotcha

allprojectsAll

filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored. When a filter or sort triggers the SuiteQL path, actual_amount, is_billable and completion_percentage are not available (those are SOAP-only fields, absent from the SuiteQL job table) — use the unfiltered list or the GET one endpoint for those fields.

allpurchaseOrdersAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.

Quotes1 gotcha

allquotesAll

Using filter[number] switches quotes to the SuiteQL search path, which returns header fields only — line_items are omitted on filtered results. Use the GET one endpoint to retrieve full line-item detail for a quote. The minimum page size accepted for NetSuite is 5, and the pagination cursor expires if not used within 15 minutes of creation.

Subsidiaries1 gotcha

allsubsidiariesAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation.

Suppliers1 gotcha

allsuppliersAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. filter[subsidiary_id] scopes results to suppliers assigned to (or shared with) the given subsidiary, and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored. When any filter[*], sort[*], or pass_through[*] parameter is present, the list call is routed through NetSuite's SuiteQL Analytics REST API. In this path, addresses[] entries contain only type and string fields — structured fields (id, city, state, postal_code, country, contact_name) are not available because the SuiteQL Analytics replica does not expose the address sub-table join. Full structured address data is only returned by the unfiltered SOAP path (i.e., GET /accounting/suppliers with no filter, sort, or pass_through parameters).

Tax Rates1 gotcha

alltaxRatesAll

The minimun page size accepted for Netsuite is 5. The cursor for Netsuite expires if they have not been used within 15 minutes after its creation. NetSuite supports compound taxes through the components array, allowing multi-tier tax structures (e.g., state + county taxes).