NetSuite – Gotchas
NetSuite is the leading integrated cloud business software suite, including business accounting, ERP, CRM and ecommerce software.
63 gotchas across 25 resources
These are connector-specific behaviors and limitations to be aware of when integrating.
Attachments1 gotcha
attachmentsDeleteDetaches the file from the parent record. The file itself stays in the File Cabinet.
Balance Sheet1 gotcha
balanceSheetOneNetSuite 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).
Bank Feed Accounts1 gotcha
bankFeedAccountsAddtarget_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 Payments4 gotchas
billPaymentsAllIn 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.
billPaymentsAddIn 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.
reference maps to NetSuite's externalId, which NetSuite requires to be unique across every vendor payment in the account — reusing a value already on another payment fails with DUP_RCRD ("This record already exists"). Send a distinct reference per payment, or delete the record holding it first (DELETE /accounting/bill-payments/{id}). This is also why retrying a create that timed out returns DUP_RCRD rather than creating a second record: read the retriable and outcome fields on the error before retrying.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting: Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it, and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup > Company > Company Information only supplies the default, so a user who overrode it needs their own zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
billPaymentsOneIn 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.
billPaymentsUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Bills4 gotchas
billsAllThe 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.
custom_fields are returned on the get-by-id call (GET /accounting/bills/{id}) and on unfiltered list calls, but not on filtered or sorted list calls, which NetSuite serves through its SuiteQL endpoint. To retrieve custom fields when a filter[*] or sort is applied (for example incremental or large-dataset syncs), fetch each bill individually via GET /accounting/bills/{id}.
On a SuiteTax account, line_items[].tax_rate is resolved from NetSuite's transaction-level tax details on the get-by-id call (GET /accounting/bills/{id}) and on unfiltered list calls. When a line's tax splits across several jurisdictions — common for US sales tax, where one line can carry state, county, city and district codes — rate is the combined rate and id, code and name are omitted, because the unified tax_rate holds a single code and no one component names the line's tax. Single-jurisdiction lines, such as UK VAT, return the full id/code/name/rate. tax_amount always carries the line's full tax either way, so reconcile against that rather than against rate.
On filtered or sorted list calls, which NetSuite serves through its SuiteQL endpoint, line_items[].tax_rate carries only id — and on a SuiteTax account not even that, because the per-line tax code SuiteQL exposes is empty there. Fetch the bill individually via GET /accounting/bills/{id} when you need the code or the rate alongside a filter.
tax_inclusive is not returned on reads. NetSuite does not store it: line_items[].total_amount is always the net amount, with the tax in tax_amount and the gross in total. On writes it only tells NetSuite how to interpret the total_amount you send, so a value read back can be re-sent unchanged without setting it.
billsAddFor creating or updating a bill in Netsuite, its required to provide the line type: each line_items[] entry must set type to expense_account (requires ledger_account.id) or expense_item (requires item.id). Lines are routed to different sublists on that discriminator, so a line with no type is dropped before the request is sent and the create fails with USER_ERROR: "You must enter at least one line item for this transaction." — an error that points away from the actual cause.
reference, bill_number and notes map to externalId, the transaction ID and the memo respectively, so all three round-trip independently. Because reference is the externalId it must be unique — reusing a value already on another bill fails with DUP_RCRD.
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.
pass_through must target a node inside the SOAP envelope. NetSuite takes an XML request body, and a root-level extend_object (or an extend_paths path that does not start at soap:Envelope) would place the value outside the XML document element, so it is rejected with UnsupportedPassThroughError instead of being sent. To set a NetSuite field the mapping does not cover — e.g. the customForm RecordRef — use an extend_paths entry addressing the record node, and note that a RecordRef is expressed as an internalId attribute rather than a text node:
"pass_through": [
{
"service_id": "netsuite",
"extend_paths": [
{
"path": "$['soap:Envelope']['soap:Body']['platformMsgs:add']['platformMsgs:record']['tranPurch:customForm']['_attributes']['internalId']",
"value": "229"
}
]
}
]
On update the message node is platformMsgs:update instead of platformMsgs:add.
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. line_items[].tax_rate.id is required per line when tax_inclusive: true; line_items[].tax_rate.rate is optional — when omitted, the connector resolves the rate server-side from the tax code (a caller-supplied rate still takes precedence). If a tax-inclusive tax-rated line's rate genuinely cannot be resolved, the request fails fast with a 400 naming the line rather than silently sending the un-grossed amount. 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.
On SuiteTax-enabled tenants the per-line tax_rate.id is applied through NetSuite's SuiteTax tax-details override, so the tax is included in the bill total; the legacy per-line tax fields are used only on non-SuiteTax (legacy) tenants. The tax code must be valid for the bill's subsidiary/nexus — NetSuite rejects a mismatch with a 400 (Invalid taxtype reference key … for nexus …). On a SuiteTax tenant a tax-rated line whose tax code cannot be resolved fails fast with a 400 naming the line, for both tax_inclusive: true and tax_inclusive: false, rather than posting the line untaxed.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
billsOneNetSuite 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.
billsUpdateline_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. line_items[].tax_rate.id is required per line when tax_inclusive: true; line_items[].tax_rate.rate is optional — when omitted, the connector resolves the rate server-side from the tax code (a caller-supplied rate still takes precedence). If a tax-inclusive tax-rated line's rate genuinely cannot be resolved, the request fails fast with a 400 naming the line rather than silently sending the un-grossed amount. 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.
On SuiteTax-enabled tenants the per-line tax_rate.id is applied through NetSuite's SuiteTax tax-details override, so the tax is included in the bill total; the legacy per-line tax fields are used only on non-SuiteTax (legacy) tenants. The tax code must be valid for the bill's subsidiary/nexus — NetSuite rejects a mismatch with a 400 (Invalid taxtype reference key … for nexus …). On a SuiteTax tenant a tax-rated line whose tax code cannot be resolved fails fast with a 400 naming the line, for both tax_inclusive: true and tax_inclusive: false, rather than posting the line untaxed.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Credit Notes4 gotchas
creditNotesAllThe 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.
creditNotesAddNetsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
reference maps to NetSuite's externalId, which NetSuite requires to be unique across every
credit memo in the account — reusing a value already on another credit note fails with DUP_RCRD
("This record already exists"). Send a distinct reference per credit note, or delete the record
holding it first (DELETE /accounting/credit-notes/{id}). This is also why retrying a create that
timed out returns DUP_RCRD rather than creating a second record: read the retriable and
outcome fields on the error before retrying.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
creditNotesUpdate- 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.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
creditNotesUpdateNetsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
Customers1 gotcha
customersAllThe 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
departmentsAllThe 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.
Employees2 gotchas
employeesAddDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
employeesUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Expense Reports4 gotchas
expenseReportsAllfilter[subsidiary_id] scopes results to a single subsidiary and is only supported on NetSuite OneWorld accounts; on standard (single-entity) accounts it is ignored.
expenseReportsAddWhen 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.
NetSuite does not support attaching receipts inline via line_items[].receipt_url; providing a non-empty
value returns a 422. To attach a receipt: (1) create the expense report, (2) upload the file via
POST /accounting/attachments/expense-report/{id} — encode the line reference in the filename
(e.g. receipt-line-1-taxi.pdf) since NetSuite attaches at the report level only, (3) store the
attachment id returned from the upload. Note: GET /accounting/attachments/expense-report/{id}
(list all) is not supported on NetSuite — retrieve individual attachments via
GET /accounting/attachments/expense-report/{id}/{attachment_id} (returns file_url and description).
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
expenseReportsOnetracking_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.
expenseReportsUpdateWhen 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.
NetSuite does not support attaching receipts inline via line_items[].receipt_url; providing a non-empty
value returns a 422. To attach a receipt: (1) create or update the expense report, (2) upload the file via
POST /accounting/attachments/expense-report/{id} — encode the line reference in the filename
(e.g. receipt-line-1-taxi.pdf) since NetSuite attaches at the report level only, (3) store the
attachment id returned from the upload. Note: GET /accounting/attachments/expense-report/{id}
(list all) is not supported on NetSuite — retrieve individual attachments via
GET /accounting/attachments/expense-report/{id}/{attachment_id} (returns file_url and description).
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Expenses4 gotchas
expensesAllListing expenses returns identifiers only. Each expense in the list has id set and the remaining fields — including transaction_date — empty, because NetSuite's credit card charge list endpoint returns references rather than full records. Fetch an expense by id to read its details.
expensesAddNetSuite 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 a per-line tax code, NetSuite applies the account's default tax code to every line (a 0% line under a 20%-default account posts at 20%). Every tax-rated line must resolve a tax type and rate; if any cannot, the request fails with a 400 naming the line rather than silently posting at the account default. Some third-party tax-engine configurations (e.g. Vertex/Avalara) may reject the override — use pass_through for tenant-specific tax fields if needed.
tax_rate.id alone is sufficient: when a tax-rated line omits tax_rate.rate, the connector resolves the rate server-side from the tax code (no extra call is needed on a warm per-connection cache). If a tax-rated line's tax type or rate genuinely cannot be resolved (e.g. an unknown tax code), the request now fails fast with a 400 naming the offending line rather than silently posting at the account default. A caller-supplied tax_rate.rate still takes precedence when present.
Applying per-line tax also requires the NetSuite role to hold the Lists → Tax Details Tab permission at Full, because the connector writes a tax-details block on the Credit Card Charge. Without it the create fails as a tax-engine error rather than a permission error: Unable to save the transaction due to an error being reported by the tax calculation engine: … Permission Violation: You need the 'Lists -> Tax Details Tab' permission. Reading an expense's per-line tax_rate does not require it.
expensesOneLine-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.
expensesUpdateNetSuite 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%). Every tax-rated line must resolve a tax type; if any cannot, the override is skipped for the whole expense and the account default applies. Some third-party tax-engine configurations (e.g. Vertex/Avalara) may reject the override — use pass_through for tenant-specific tax fields if needed.
Invoice Items3 gotchas
invoiceItemsAllThe 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.
invoiceItemsAddDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
invoiceItemsUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Invoices5 gotchas
invoicesAllThe 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.
invoicesAddreference maps to NetSuite's externalId, which NetSuite requires to be unique across every
invoice in the account — reusing a value already on another invoice fails with DUP_RCRD
("This record already exists"). Send a distinct reference per invoice, or delete the record
holding it first (DELETE /accounting/invoices/{id}). This is also why retrying a create that
timed out returns DUP_RCRD rather than creating a second record: read the retriable and
outcome fields on the error before retrying.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
invoicesAddNetsuite 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.
invoicesUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
invoicesUpdateNetsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
Journal Entries3 gotchas
journalEntriesAllThe 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.
journalEntriesAddNetsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
journalEntriesUpdateNetsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Ledger Accounts2 gotchas
ledgerAccountsAllThe 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.
ledgerAccountsAddThe 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
locationsAllThe 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.
Payments3 gotchas
paymentsAllThe 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.
paymentsAddThe number property could be used to override the Netsuite tranId property.
Date-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
paymentsUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Profit And Loss1 gotcha
profitAndLossOneNetSuite 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
projectsAllfilter[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.
Purchase Orders3 gotchas
purchaseOrdersAllThe 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.
purchaseOrdersAddDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
purchaseOrdersUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Quotes3 gotchas
quotesAllUsing 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.
quotesAddDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
quotesUpdateDate-only fields need no configuration: NetSuite converts the datetime it receives into the SOAP/integration user's time zone before storing the date part, and these fields are anchored so they store on the intended calendar day for any integration user between UTC-10 and UTC+13.
Four zones fall outside that range and need the optional timezone connection setting:
Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway (UTC-11) store one day early without it,
and Pacific/Kiritimati (UTC+14) stores one day late. Set it to the IANA zone of the NetSuite user
this connection authenticates as, read from that user's Home > Set Preferences > Time Zone — Setup >
Company > Company Information only supplies the default, so a user who overrode it needs their own
zone.
When the setting is used its value must be within 12 hours of that user's real zone in either direction: more than ~12h east stores dates one day early, more than ~12h west stores them one day late.
This applies to every NetSuite date-only write field across the accounting resources.
Refunds6 gotchas
refundsAlltotal_amount is not writable. NetSuite derives a refund's amount from the credits it is
applied to, so set the amount per entry in allocations[].amount. A total_amount sent
on its own is ignored and the refund is created for 0.
A refund must apply to an open credit. Allocations pointing at a draft credit note, or at one already fully applied, are rejected with "Unable to find a matching line for sublist apply" — NetSuite only offers open credits in the apply sublist.
number is assigned by NetSuite and cannot be set on create; sending one fails with
"You do not have permissions to set a value for element tranid".
Customer-deposit allocations cannot be expressed on the write side (the unified
allocations[].type enum has no deposit member). They still read back correctly,
surfacing as type: other.
A refund that has been applied to a credit cannot be deleted. NetSuite rejects it with "This transaction cannot be deleted because it is linked to by one or more transactions", and since a refund's amount comes from the credits it is applied to, this is true of every refund with a non-zero amount. Unapply it in NetSuite first, or void it instead of deleting.
refundsAllA NetSuite CustomerRefund settles against an existing customer credit via an internal apply list. allocations[].type decodes invoice, journal_entry and credit_memo from the underlying NetSuite document type; every other apply-list entry — including one backed by a customer deposit, which NetSuite tracks separately from invoice/credit-memo/ journal-entry applications — decodes to other. There is no way to tell a genuine "other" allocation apart from a customer-deposit application by reading the response alone.
The unified status reflects NetSuite's CustomerRefund transaction status on a best-effort basis: Refunded maps to paid and Voided to voided; any other NetSuite status defaults to paid. The full CustomerRefund status vocabulary was not confirmed against a live tenant, so an unusual status may report paid.
This list also includes NetSuite CashRefund records — itemized refunds tied to a cash sale or return authorization rather than an apply-list — surfaced with type: sale_refund and populated line_items instead of allocations. CashRefund's own status field has no documented enum (unlike CustomerRefund's Refunded/Voided), so its status mapping reuses the same connectorMapping and defaults to paid for any value that isn't a literal Refunded/Voided match — unconfirmed against a live tenant.
Every CashRefund line in line_items[].type reports sales_item, regardless of the underlying NetSuite item/line kind. NetSuite's CashSale/CashRefund item sublist can contain discount, shipping, non-inventory and other non-sales-item lines, and the connector cannot currently distinguish them from the captured response shape, so a non-sales-item line is mislabeled as sales_item rather than reported accurately.
Whenever a filter, sort or pass_through parameter is present, this list is served by a SuiteQL search rather than the SOAP list operation. That path never returns line_items or allocations for either refund type — pre-existing for CustomerRefund, and now also true for CashRefund. type still correctly distinguishes the two records; only the line-level detail is missing under a filtered/sorted/pass_through list.
refundsAddA refund requires an existing customer credit to settle against — an open credit memo, unapplied customer deposit, invoice overpayment or journal entry — since a NetSuite CustomerRefund is always applied against a prior document. A refund with no allocations is not a meaningful request.
Only invoice, credit_memo and journal_entry allocations can be created through the unified allocations array; they map to NetSuite's apply list. Customer-deposit allocations cannot be written through this endpoint: the unified allocations[].type enum has no deposit member, and this connector does not populate NetSuite's separate deposit list. Refunds against a customer deposit must be created directly in NetSuite — they will still read back correctly afterwards (as allocations[].type: other).
Set type: sale_refund to create a NetSuite CashRefund instead of the default CustomerRefund — an itemized refund using line_items (not allocations), for a cash sale or return authorization rather than an existing credit. Omitting type (or sending anything else) always creates a CustomerRefund, unchanged from today. CashRefund's line-item fields (price on write, rate on read) reuse NetSuite's sibling CreditMemo item sublist shape, live-verified against a sandbox tenant (create, update, get, list, and delete all round-trip correctly).
refundsOneA NetSuite CustomerRefund settles against an existing customer credit via an internal apply list. allocations[].type decodes invoice, journal_entry and credit_memo from the underlying NetSuite document type; every other apply-list entry — including one backed by a customer deposit, which NetSuite tracks separately from invoice/credit-memo/ journal-entry applications — decodes to other. There is no way to tell a genuine "other" allocation apart from a customer-deposit application by reading the response alone.
The unified status reflects NetSuite's CustomerRefund transaction status on a best-effort basis: Refunded maps to paid and Voided to voided; any other NetSuite status defaults to paid. The full CustomerRefund status vocabulary was not confirmed against a live tenant, so an unusual status may report paid.
A NetSuite internalId does not reveal whether it belongs to a CustomerRefund or a CashRefund, so getting a refund by id makes one extra internal lookup first to resolve the record type before fetching it. If that lookup can't determine the type (a transient downstream issue, for example), the request falls back to fetching it as a CustomerRefund, which fails for a genuine CashRefund id rather than returning the wrong record.
This extra lookup is a second signed SOAP round-trip on every refund get, for every connection — including accounts that have never created a CashRefund. It is not an opt-in cost paid only by CashRefund users; every existing CustomerRefund read now takes twice as many NetSuite calls as before this connector supported CashRefund. A cheaper alternative (attempt as CustomerRefund first, resolve and retry only when NetSuite rejects it as the wrong type) was evaluated but not built — NetSuite SOAP faults return HTTP 200 with the fault in the response body rather than an error status, so the retry would need to key off body content in the same way detectConcurrentRequestFault.ts already does for concurrency errors, and the exact fault text for a wrong-record-type RecordRef has not been captured against a live tenant. Revisit if the extra round-trip proves costly in practice.
Every CashRefund line in line_items[].type reports sales_item, regardless of the underlying NetSuite item/line kind. NetSuite's CashSale/CashRefund item sublist can contain discount, shipping, non-inventory and other non-sales-item lines, and the connector cannot currently distinguish them from the captured response shape, so a non-sales-item line is mislabeled as sales_item rather than reported accurately.
refundsUpdateOnly invoice, credit_memo and journal_entry allocations can be updated through the unified allocations array; they map to NetSuite's apply list. Customer-deposit allocations cannot be written through this endpoint: the unified allocations[].type enum has no deposit member, and this connector does not populate NetSuite's separate deposit list. A refund that settles against a customer deposit must be created and maintained directly in NetSuite — it will still read back correctly through this endpoint (as allocations[].type: other).
A refund requires an existing customer credit to settle against — an open credit memo, unapplied customer deposit, invoice overpayment or journal entry — since a NetSuite CustomerRefund is always applied against a prior document.
Updating a CashRefund requires type: sale_refund in the request body — which NetSuite record shape gets sent is picked from the body itself, not looked up from the id (unlike get/delete). A GET response already includes type, so a normal read-modify-write cycle carries it forward automatically. Omitting it (or including it on an id that is actually a CustomerRefund) is rejected with a 422 naming the mismatch, before any request reaches NetSuite.
That mismatch check itself costs a second signed SOAP round-trip on every refund update, for every connection, not only ones that use CashRefund — the same permanent added-call cost as get/delete (see the refundsOne gotcha for why an attempt-then-retry alternative wasn't built instead).
refundsDeleteA NetSuite internalId does not reveal whether it belongs to a CustomerRefund or a CashRefund, so deleting a refund by id makes one extra internal lookup first to resolve the record type. If that lookup can't determine the type, the request falls back to deleting it as a CustomerRefund, which fails for a genuine CashRefund id rather than deleting the wrong record.
Same permanent cost as get/update: this is a second signed SOAP round-trip on every refund delete, for every connection, whether or not it has ever created a CashRefund (see the refundsOne gotcha for the alternative considered and why it wasn't built).
Separately, and true of every *.delete operation on this connector: NetSuite's SOAP delete response echoes success regardless of outcome, so a 200 here does not confirm the record was actually removed — read the record back to verify.
Subsidiaries1 gotcha
subsidiariesAllThe 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.
Suppliers3 gotchas
suppliersAllThe 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).
custom_fields come back on GET /accounting/suppliers/{id} and on an unfiltered GET /accounting/suppliers, each entry carrying the NetSuite internal id, the script id and the value — for example {"id": "987", "name": "custentity_vendor_code", "value": "ACME-123"}. They are absent the moment any filter[*], sort[*] or pass_through[*] parameter is present, because that routes the list through SuiteQL, where custom fields would require a secondary lookup per batch. So GET /accounting/suppliers?limit=5 returns them and GET /accounting/suppliers?filter[company_name]=Acme does not. For an incremental sync, list with the filter to collect ids, then fetch the records you need custom fields for via GET /accounting/suppliers/{id}.
A field whose script id starts with custpage_ is omitted. Those are injected into the NetSuite UI form by SuiteScript rather than stored on the record: they have no internal id and their value is page markup — custpage_inlinejavascript returns a string of inline HTML script elements pointing at NetSuite media files. Custom segments (cseg_*) are returned normally.
Each value's type comes from NetSuite's own field type, not from the shape of the text: a check box returns true/false, a numeric field returns a number, and everything else returns a string — so a text field holding 12345 returns the string "12345". The one exception is a text field whose stored value is JSON, which is parsed before you receive it: a field holding [] returns "value": [], not "[]". Send that value back unchanged on a write and NetSuite stores the original text.
suppliersAddcustom_fields are writable. Identify each field either by its NetSuite internal id or by
its script id — both forms work, and id wins if you send both:
"custom_fields": [{"id": "987", "value": "ACME-123"}, {"name": "custentity_vendor_flag", "value": true}]
The NetSuite field type is derived from the JSON value you send, not from the field's real type, so the value's shape decides how it is transmitted:
true/false, or the strings"true"/"false"→ check box- a number, and any string made only of digits —
7,10.5,"12345"→ decimal number. A numeric code that belongs in a text field must therefore not be sent as a digits-only string, or it reaches NetSuite as a number and is rejected withINVALID_FLD_VALUE. - a date — both
"2026-08-18"and a full"2026-08-18T09:00:00Z"→ date field - anything else → free-form text
- an object or array is serialized to JSON text, so
"value": []stores[]
A cseg_* custom segment needs both keys — its internal id in id and the segment script id
in name, with the referenced record's internal id as the value:
{"id": "801", "name": "cseg_region", "value": "42"}.
A value read from GET /accounting/suppliers/{id} can be sent back unchanged. A text field
holding {"a":1} reads back as the object {"a": 1}, and sending that object back stores
{"a":1} again.
Date-only values need no configuration between UTC-10 and UTC+13: the connector anchors them to
10:00 UTC before sending, because NetSuite resolves the instant against the account's time zone
and an unanchored value would store the previous calendar day. Four zones fall outside that
range and still shift by a day — Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway
(UTC-11) store one day early, Pacific/Kiritimati (UTC+14) one day late. Unlike the date fields
on other resources, the optional timezone connection setting does not cover custom fields:
that anchoring runs in the mapping layer, which this path does not go through. In those four
zones, send the value as a full dateTime you have offset yourself.
Two rejections to expect, both naming the offending field. A field that is read-only or owned
by a NetSuite bundle returns INSUFFICIENT_PERMISSION — "You do not have permissions to set
a value for element custentity_x". A value whose shape does not match the field's real
NetSuite type returns INVALID_FLD_VALUE — "You have entered an Invalid Field Value hello
for the following field: custentity_x".
suppliersUpdatecustom_fields are writable. Identify each field either by its NetSuite internal id or by
its script id — both forms work, and id wins if you send both:
"custom_fields": [{"id": "987", "value": "ACME-123"}, {"name": "custentity_vendor_flag", "value": true}]
The NetSuite field type is derived from the JSON value you send, not from the field's real type, so the value's shape decides how it is transmitted:
true/false, or the strings"true"/"false"→ check box- a number, and any string made only of digits —
7,10.5,"12345"→ decimal number. A numeric code that belongs in a text field must therefore not be sent as a digits-only string, or it reaches NetSuite as a number and is rejected withINVALID_FLD_VALUE. - a date — both
"2026-08-18"and a full"2026-08-18T09:00:00Z"→ date field - anything else → free-form text
- an object or array is serialized to JSON text, so
"value": []stores[]
A cseg_* custom segment needs both keys — its internal id in id and the segment script id
in name, with the referenced record's internal id as the value:
{"id": "801", "name": "cseg_region", "value": "42"}.
A value read from GET /accounting/suppliers/{id} can be sent back unchanged. A text field
holding {"a":1} reads back as the object {"a": 1}, and sending that object back stores
{"a":1} again.
Date-only values need no configuration between UTC-10 and UTC+13: the connector anchors them to
10:00 UTC before sending, because NetSuite resolves the instant against the account's time zone
and an unanchored value would store the previous calendar day. Four zones fall outside that
range and still shift by a day — Pacific/Pago_Pago, Pacific/Niue and Pacific/Midway
(UTC-11) store one day early, Pacific/Kiritimati (UTC+14) one day late. Unlike the date fields
on other resources, the optional timezone connection setting does not cover custom fields:
that anchoring runs in the mapping layer, which this path does not go through. In those four
zones, send the value as a full dateTime you have offset yourself.
Two rejections to expect, both naming the offending field. A field that is read-only or owned
by a NetSuite bundle returns INSUFFICIENT_PERMISSION — "You do not have permissions to set
a value for element custentity_x". A value whose shape does not match the field's real
NetSuite type returns INVALID_FLD_VALUE — "You have entered an Invalid Field Value hello
for the following field: custentity_x".
Tax Rates1 gotcha
taxRatesAllThe 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).