# NetSuite — Accounting API: gotchas

> Canonical URL: https://developers.apideck.com/apis/accounting/netsuite/gotchas

## GET /accounting/tax-rates

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

Listing tax rates requires NetSuite's legacy "Not SuiteTax" feature. On an account with
SuiteTax enabled, every tax-rates request fails outright rather than returning an empty or
partial result — this is a NetSuite platform restriction on the underlying search, not a
connector limitation, and there is no workaround available. This is specific to the
tax-rates resource: bills and expenses already support per-line tax on SuiteTax accounts
through a separate mechanism.

NetSuite supports compound taxes through the `components` array, allowing multi-tier tax structures (e.g. state + county).

**A `null` `effective_tax_rate` does not mean 0%.** It means the tax code carries no single stored rate, and that happens in two cases which must not be conflated:
- **Exempt / zero-rated / not-liable** codes — effectively `0%`.
- **US sales-tax jurisdiction codes** (State / County / City / District) — these are **taxable**, but NetSuite assembles the rate from the jurisdiction at transaction time rather than storing it on the code. Treating them as `0%` understates tax.

Key on `effective_tax_rate`. `total_tax_rate` is a secondary non-compounded sum of components: it may be `null` or `0` on a code with no stored rate, and on accounts without SuiteTax it is never populated.

## POST /accounting/tax-rates

Creating a tax rate requires NetSuite's legacy "Not SuiteTax" feature. On an account with SuiteTax enabled, the request fails outright rather than returning a validation error about a specific field — this is a NetSuite platform restriction, not a connector limitation, and there is no workaround available.

## GET /accounting/bills

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.
Vendor Credits / Bill Credits are not returned by this resource, nor by `credit-notes` — NetSuite models them as a separate transaction type that neither resource's query is scoped to. Requesting one by id returns a `400` (`INVALID_TRANS_TYP`); a list or `updated_since` poll that happens to cover only vendor-credit activity returns an empty `200` with no error and no other signal that matching records were withheld.

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.

## POST /accounting/bills

NetSuite bills have two kinds of line: expense lines (posted to a ledger account)
and item lines (posted against an item). Set `line_items[].type` to choose:
`expense_account` (requires `ledger_account.id`) or `expense_item` (requires
`item.id`).

An explicit `type` always wins. When `type` is omitted, the line is routed on the
reference it carries: a line with `item.id` is sent as an item line, otherwise a
line with `ledger_account.id` is sent as an expense line. A line that carries both
and no `type` is sent as an item line and its `ledger_account` is not used; set
`type: expense_account` to post it to the ledger account instead.

A line with `type: other`, or with no `type` and neither reference, is not sent.
When other lines remain the bill is created without it; when no line remains,
NetSuite rejects the request with `USER_ERROR: "You must enter at least one line
item for this transaction."`

`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:

```json
"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.

## GET /accounting/bills/{id}

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.

## PATCH /accounting/bills/{id}

NetSuite bills have two kinds of line: expense lines (posted to a ledger account)
and item lines (posted against an item). Set `line_items[].type` to choose:
`expense_account` (requires `ledger_account.id`) or `expense_item` (requires
`item.id`).

An explicit `type` always wins. When `type` is omitted, the line is routed on the
reference it carries: a line with `item.id` is sent as an item line, otherwise a
line with `ledger_account.id` is sent as an expense line. A line that carries both
and no `type` is sent as an item line and its `ledger_account` is not used; set
`type: expense_account` to post it to the ledger account instead.

A line with `type: other`, or with no `type` and neither reference, is not sent.

Sending `line_items` on an update replaces every existing line of the same kind (expense
lines or item lines): lines of that kind that are left out of the request are removed, and
lines sent without a `line_number` are re-created rather than matched. Include every line
you want to keep, with its `line_number`. Lines of the other kind are left untouched.

`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. `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.

## GET /accounting/invoices

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.
`status` is `authorised` for an open invoice, `paid` when paid in full, `submitted` while pending approval, `draft` when rejected and `void` when voided. `partially_paid` is returned only by lists that use a filter: NetSuite reports how much of an invoice has been paid only there, so the unfiltered list and a single invoice return a partly paid open invoice as `authorised`.
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.

Tax can be recorded per line or once for the whole invoice, depending on how the account is set up. Where it is recorded per line, `line_items[].tax_rate` carries that line's own code and rate. Where it is recorded for the whole invoice, `line_items[].taxable` reports which lines the tax applies to, `line_items[].tax_rate` carries the invoice's code and rate on those lines only, and `tax_code` carries the same code at the invoice level. `taxable` is read-only and is reported only on accounts that record taxability per line, so its absence means "not reported", not "not taxable" — and an absent `tax_rate` likewise does not by itself mean a line is untaxed. No per-line `tax_amount` is reported for invoice-level tax, because NetSuite does not store one.

`line_items[].taxable` and `tax_code` are reported by `GET /invoices/{id}` and by an unfiltered `GET /invoices` only. Adding any `filter`, `sort` or `pass_through` parameter switches to a query path that cannot return them, so the same invoice read two ways can differ on those two fields.

## GET /accounting/invoices

`quotes` lists the estimate the invoice was created from. An invoice created from a sales
order has no `quotes`, even when that sales order was created from an estimate. On a list
without filters, sorting or `pass_through`, NetSuite does not say which kind of transaction
an invoice was created from, so the page's invoices are checked with one extra request.
When that request fails, `quotes` is omitted and `meta.warnings` has an entry with
`operation: quotesAll`.

## POST /accounting/invoices

`reference` 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.

## POST /accounting/invoices

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.

## GET /accounting/invoices/{id}

`quotes` lists the estimate the invoice was created from. An invoice created from a sales
order has no `quotes`, even when that sales order was created from an estimate. NetSuite
does not say which kind of transaction an invoice was created from, so it is checked with
one extra request. When that request fails, `quotes` is omitted and `meta.warnings` has an
entry with `operation: quotesAll`.

## PATCH /accounting/invoices/{id}

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.

## PATCH /accounting/invoices/{id}

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
## GET /accounting/ledger-accounts

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.
`filter[name]` matches the account's display name by case-insensitive substring (contains); the value is matched literally, so `%` and `_` are not treated as wildcards. NetSuite does not enforce unique account names the way QuickBooks does, so a name filter can legitimately return more than one account — check `id` before assuming a single match.

## POST /accounting/ledger-accounts

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.

## GET /accounting/invoice-items

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.
`filter[name]` matches the item's name by case-insensitive substring (contains); the value is matched literally, so `%` and `_` are not treated as wildcards. NetSuite does not enforce unique item names the way QuickBooks does, so a name filter can legitimately return more than one item — check `id` before assuming a single match.

## POST /accounting/invoice-items

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.

`sales_details.unit_price` is stored on the item's Base Price level, and NetSuite requires a currency for it. When `currency` is omitted, the price is put in the currency of the subsidiary the request is scoped to (the `x-apideck-company-id` header or the connection's Default Subsidiary), otherwise in the account's base currency. On a OneWorld account with several base currencies and no subsidiary scope, neither applies and NetSuite rejects the request with "Missing required field: currency" — send `currency` in that case.

## PATCH /accounting/invoice-items/{id}

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.

`sales_details.unit_price` is stored on the item's Base Price level, and NetSuite requires a currency for it. When `currency` is omitted, the price is put in the currency the item is already priced in, otherwise in the currency of the subsidiary the request is scoped to (the `x-apideck-company-id` header or the connection's Default Subsidiary), otherwise in the account's base currency. On a OneWorld account with several base currencies and no subsidiary scope, none of these apply and NetSuite rejects the request with "Missing required field: currency" — send `currency` in that case.

## GET /accounting/credit-notes

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.
`status` reflects how much of the credit has been applied: `authorised` when none of it has, `partially_paid` when part of it has, `paid` when all of it has, and `voided` for voided credit memos. NetSuite itself only reports Open, Fully Applied or Voided.
`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.
Vendor Credits / Bill Credits are not returned by this resource, nor by `bills` — NetSuite models them as a separate transaction type that neither resource's query is scoped to. Requesting one by id returns a `400` (`INVALID_TRANS_TYP`); a list or `updated_since` poll that happens to cover only vendor-credit activity returns an empty `200` with no error and no other signal that matching records were withheld.

## POST /accounting/credit-notes

Netsuite 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.

## PATCH /accounting/credit-notes/{id}

- 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.

## PATCH /accounting/credit-notes/{id}

Netsuite only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
## GET /accounting/bill-credit-notes

The minimum page size accepted by NetSuite is 5 on an unfiltered, unsorted request; `limit` values below that on that path fail with `INVALID_SEARCH_PAGE_SIZE`. Passing any `filter[]`, `sort[]` or `pass_through[q]` routes the request to a different transport with no such minimum, so e.g. `?limit=1&sort[by]=id` succeeds. The cursor expires if not used within 15 minutes of creation.
`filter[subsidiary_id]` is only meaningful on NetSuite OneWorld accounts. On a standard (single-entity) account the underlying `subsidiary` column is unpopulated on every record, so the filter is not ignored — it matches nothing, and the request returns an empty list rather than the unfiltered result.
Passing any `filter[]`, `sort[]` or `pass_through[q]` also switches which downstream API call serves the list, and that path returns a narrower record: `allocations`, `line_items`, `custom_fields`, `tracking_categories`, `department` and `location` are only populated on the plain unfiltered/unsorted list. This is a limitation of the summary-level query NetSuite's SuiteQL endpoint supports for this transaction type, not something this connector currently works around — the same gap exists on the AR-side `credit-notes` resource.
Vendor Credits have no draft/posted lifecycle in NetSuite (a credit posts on save), so `status` is derived from how much of the credit has been applied: `posted` (nothing applied), `partially_paid`, or `paid` (fully applied) — the same on every list and get path. `voided` is never reported; a voided credit has a zero total and reads as `paid`. `balance` and `remaining_credit` are the unapplied amount.
Header-level `location` is returned on read, but cannot be set on write (see the create gotcha below) — a value read back here can never be round-tripped through an update.

## POST /accounting/bill-credit-notes

Header-level `location` could not be set on the account this was tested against — NetSuite rejected it with `INSUFFICIENT_PERMISSION`. That error usually names a role or feature-access restriction rather than a field that is universally unsettable, and this has not been confirmed against a second account, so treat it as tested-account-specific rather than general NetSuite behavior. `line_items[].location` is accepted; on a SuiteTax-enabled account it also participates in nexus (and therefore tax) determination for the line, so it is worth setting even where it looks like pure tracking metadata. `location` set at header level via `GET` is returned unchanged, so a caller can read a header `location` back without ever being able to write it through this connector.
On accounts with SuiteTax enabled (Enable Features → Tax → SuiteTax; NetSuite's default engine is Legacy Tax, so most accounts are unaffected), creating a Vendor Credit fails with `USER_ERROR: ... tax calculation engine ... tax registrations setup for the subsidiary ... is incomplete` unless the subsidiary's tax registration for that nexus either has a tax engine assigned or the nexus itself is marked tax-exempt — both are one-time account configuration, not something this connector can resolve. Only the legacy per-line tax fields (`tax_rate.id`, `tax_rate.rate`) are written; a SuiteTax tenant's transaction-level tax-details override (the mechanism `bills.add` uses) is not yet implemented here, so tax on a SuiteTax account may not be calculated correctly even once the nexus is configured.
`reference` maps to NetSuite's `externalId`, which must be unique across vendor credits — reusing a value already on another vendor credit fails with `DUP_RCRD`. Sending `reference` also lets a create that exceeds the request timeout return the created record's id instead of a 504, and makes retrying a timed-out create safe (NetSuite rejects the retry with `DUP_RCRD` rather than creating a duplicate). Without `reference`, a timed-out create returns 504 with `outcome: uncertain` and a retry creates a duplicate.

## PATCH /accounting/bill-credit-notes/{id}

Header-level `location` could not be set on the account this was tested against — NetSuite rejected it with `INSUFFICIENT_PERMISSION`, not confirmed as general NetSuite behavior (see the create gotcha on `POST /accounting/bill-credit-notes` for detail). `line_items[].location` is accepted, and on a SuiteTax-enabled account also participates in nexus/tax determination for the line.

## GET /accounting/customers

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.

`filter[company_name]`, `filter[display_name]`, `filter[first_name]` and `filter[last_name]` match by case-insensitive substring (contains); the value is matched literally, so `%` and `_` are not treated as wildcards. `filter[email]` matches exactly.

`filter[status]` maps to NetSuite's `isinactive` column: `active` returns customers that are not inactive, `inactive` returns those that are. NetSuite has no archived state, so `archived` and `all` both apply no status filter and return every customer.

Because any `filter` reroutes the call to NetSuite's SuiteQL path, `filter[status]=archived` and `filter[status]=all` return the same records as an unfiltered call but with the reduced `addresses[]` shape described above — structured address fields are not returned. If you want every customer *with* structured addresses, omit `filter[status]` entirely rather than passing `all`.

## POST /accounting/customers

How much of each array is written depends on whether the customer is a company or an
individual — NetSuite keeps different contact fields on each. A company holds four phone
numbers (`primary`, `secondary`, `mobile`, `fax`), one email (`primary`) and one website;
an individual holds five phone numbers (those four plus `home`), two emails (`primary` and
`secondary`) and no website. Entries of any other type are ignored.

Three of those slots belong to one NetSuite layout or the other, and are ignored on the
wrong one. `home` and the `secondary` email exist only on a customer marked as an
individual, so send `individual: true` in the same request or both are dropped.
`websites[]` is the reverse — it exists only on a company customer, and is dropped when
`individual` is `true`. When two entries share a type, the first one is used.

## PATCH /accounting/customers/{id}

How much of each array is written depends on whether the customer is a company or an
individual — NetSuite keeps different contact fields on each. A company holds four phone
numbers (`primary`, `secondary`, `mobile`, `fax`), one email (`primary`) and one website;
an individual holds five phone numbers (those four plus `home`), two emails (`primary` and
`secondary`) and no website. Entries of any other type are ignored.

Three of those slots belong to one NetSuite layout or the other, and are ignored on the
wrong one. `home` and the `secondary` email exist only on a customer that is an
individual; `websites[]` is the reverse, existing only on a company customer. On an
update you do not need to restate `individual` — the layout is read from the existing
record when the request touches one of those fields. When two entries share a type, the
first one is used.

## GET /accounting/suppliers

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.
`filter[company_name]`, `filter[display_name]`, `filter[first_name]` and `filter[last_name]` match by case-insensitive substring (contains); the value is matched literally, so `%` and `_` are not treated as wildcards. `filter[email]` matches exactly.
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.
`tax_number` comes back on `GET /accounting/suppliers/{id}` and on an unfiltered `GET /accounting/suppliers`. It is absent the moment any `filter[*]`, `sort[*]` or `pass_through[*]` parameter is present, because that routes the list through SuiteQL, which exposes no VAT registration number column. For an incremental sync, list with the filter to collect ids, then fetch the records you need `tax_number` for via `GET /accounting/suppliers/{id}`.

## POST /accounting/suppliers

`custom_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 with `INVALID_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".

How much of each array is written depends on whether the supplier is a company or an
individual — NetSuite keeps different contact fields on each. A company holds four phone
numbers (`primary`, `secondary`, `mobile`, `fax`), one email (`primary`) and one website;
an individual holds five phone numbers (those four plus `home`), two emails (`primary` and
`secondary`) and no website. Entries of any other type are ignored.

Three of those slots belong to one NetSuite layout or the other, and are ignored on the
wrong one. `home` and the `secondary` email exist only on a supplier marked as an
individual, so send `individual: true` in the same request or both are dropped.
`websites[]` is the reverse — it exists only on a company supplier, and is dropped when
`individual` is `true`. When two entries share a type, the first one is used.

## PATCH /accounting/suppliers/{id}

`custom_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 with `INVALID_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".

How much of each array is written depends on whether the supplier is a company or an
individual — NetSuite keeps different contact fields on each. A company holds four phone
numbers (`primary`, `secondary`, `mobile`, `fax`), one email (`primary`) and one website;
an individual holds five phone numbers (those four plus `home`), two emails (`primary` and
`secondary`) and no website. Entries of any other type are ignored.

Three of those slots belong to one NetSuite layout or the other, and are ignored on the
wrong one. `home` and the `secondary` email exist only on a supplier that is an
individual; `websites[]` is the reverse, existing only on a company supplier. On an
update you do not need to restate `individual` — the layout is read from the existing
record when the request touches one of those fields. When two entries share a type, the
first one is used.

## GET /accounting/payments

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.

## POST /accounting/payments

The 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.

## PATCH /accounting/payments/{id}

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.

## GET /accounting/refunds

`total_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.

## GET /accounting/refunds

A 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.

## POST /accounting/refunds

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. 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).

## GET /accounting/refunds/{id}

A 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.

## PATCH /accounting/refunds/{id}

Only `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).

## DELETE /accounting/refunds/{id}

A 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.

## GET /accounting/company-info

Subsidiary scoping (NetSuite OneWorld) applies to transactions, entities, reference data and the financial reports. It does **not** apply to these, which are returned in full whatever subsidiary is selected: currencies, payment-methods, accounting-periods, subsidiaries, attachments, server-time, company-info and the bank-feed resources — NetSuite keeps them at account level. `tax-rates` and `expenses` are also returned in full, but for a different reason: those records **are** subsidiary-specific in NetSuite (tax nexuses are registered per subsidiary), and the surface this connector reads them through offers no way to filter by it, so a scoped consumer still receives every tax code and every expense in the account. See their own gotchas. Two further limits. **Changing the Default Subsidiary invalidates pages you have already started**: a pagination cursor carries the scope it was created under, so continuing an older cursor after changing the setting keeps returning the previous scope's records and record count. Start the list again after changing it. And **writes are never scoped** — the subsidiary of a created or updated record comes from the request body, or is derived by NetSuite from the record's own customer or vendor when the body does not name one. Neither the setting nor the `x-apideck-company-id` header moves a write. A consequence worth planning for: a scoped connection can create a record that its own list requests will not return, because the created record belongs to the subsidiary its customer does rather than to the one selected. Two behaviours that hold for every scoped resource. A per-request override is available as the `x-apideck-company-id` header, and it wins over the connection's setting while an explicit `filter[subsidiary_id]` wins over both. And a record fetched directly by its ID is returned whatever subsidiary is selected — an id is an explicit request for one record, so it is not filtered out of scope.

## GET /accounting/balance-sheet

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).
If the connection's Default Subsidiary names a subsidiary that no longer exists, this report returns every figure at zero with a 200 rather than an error — that is indistinguishable from an entity with no activity. A statement of zeros for a company you expect to have data means the selected subsidiary is stale.

## GET /accounting/profit-and-loss

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).
If the connection's Default Subsidiary names a subsidiary that no longer exists, this report returns every figure at zero with a 200 rather than an error — that is indistinguishable from an entity with no activity. A statement of zeros for a company you expect to have data means the selected subsidiary is stale.

## GET /accounting/journal-entries

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.
`number` is the journal number NetSuite assigns to the entry (for example `JE1493`) and is returned on every list and get. Look an entry up by it with `filter[number]`, an exact match (for example `?filter[number]=JE1493`). This is the supported way to check whether an entry exists after a create that timed out or returned a 5xx, before retrying.

## POST /accounting/journal-entries

Netsuite 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.

`number` cannot be set on create: NetSuite assigns the journal number itself and any value
sent is ignored. Read `number` back from the response and use it with `filter[number]` on
the list endpoint to find the entry again — for example after a create that timed out or
returned a 5xx, before retrying.

Send an `Idempotency-Key` to make retrying a create safe: it reaches NetSuite as the journal entry's `externalId`. A retry of a create that already landed, or of one that timed out after NetSuite saved it, returns the original journal entry's id instead of NetSuite's `DUP_RCRD` error. NetSuite never forgets an `externalId`, so after the 24-hour window a key reused for a different journal entry gets the original journal entry back, with the new body ignored. Use a new key for every journal entry. A `pass_through` that sets `externalId` replaces the key, and a duplicate of that value stays a `DUP_RCRD` error.

## PATCH /accounting/journal-entries/{id}

Netsuite 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.

## GET /accounting/purchase-orders

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.

## POST /accounting/purchase-orders

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.

## PATCH /accounting/purchase-orders/{id}

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.

## GET /accounting/goods-receipts

Only receipts created from a purchase order are returned; receipts for returns, transfer
orders and other inbound movements are excluded. The restriction is applied by the search
itself, so pages are full and `meta.total_count` is exact. NetSuite rejects a page size
below 5, so `limit` must be at least 5.

Fetching a single receipt by id applies the same purchase-order restriction, so a receipt
that was not created from a purchase order, or an id that does not exist, returns 404.

Receipt lines carry a unit cost but no line total, so `unit_price` is populated and
`total_amount` is null. `quantity_ordered`, `po_number`, `reference` and `posted_date` are
not available.

`status` is always `received`, because a receipt carries no status of its own. A receipt
that has been voided is still returned, and still reports `status: received`, so a voided
receipt cannot be told apart from an active one; re-check the originating purchase order if
that distinction matters. `supplier.address` is not available either — only `supplier.id`,
`supplier.display_name` and `supplier.company_name` are populated.

No filters or sorts are supported in this release: any `filter[...]` is rejected with a 400
`UnsupportedFiltersError` and any `sort[by]` with a 400 `UnsupportedSortFieldError`;
receipts are returned in NetSuite's default search order.

## GET /accounting/goods-receipts/{id}

Fetching by id applies the same purchase-order restriction as the list endpoint: a receipt
that was not created from a purchase order, or an id that does not exist, returns 404.
`x-apideck-company-id` narrows the list only; a receipt that belongs to another subsidiary
can still be fetched by its id. `total_amount` is null, and `status` is always `received`
even for a voided receipt — see the list endpoint.

## GET /accounting/subsidiaries

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.

## GET /accounting/locations

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.
A **Default Subsidiary** on the connection scopes this list, and this list does not follow the same rule transactions do: a record marked "include children" in NetSuite **is** returned for every descendant subsidiary, whereas filtering transactions by a parent subsidiary is **not**.

## GET /accounting/departments

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.
A **Default Subsidiary** on the connection scopes this list, and this list does not follow the same rule transactions do: a record marked "include children" in NetSuite **is** returned for every descendant subsidiary, whereas filtering transactions by a parent subsidiary is **not**.

## DELETE /accounting/attachments/{reference_type}/{reference_id}/{id}

Detaches the file from the parent record. The file itself stays in the File Cabinet.
## GET /accounting/fiscal-periods

Read from NetSuite's accounting periods. `type` follows NetSuite's year and quarter flags, so years and quarters are returned alongside posting periods. `status` is `closed` when the period is closed (NetSuite stops everyone from posting to a closed period until it is reopened), `locked` when all, AP, AR or payroll transactions are locked (users with the Override Period Restrictions permission can still post), otherwise `open`; `future` is never returned. `fiscal_year` is the calendar year in which the period's fiscal year ends, from the end date of the parent year period (`2026` for `FY 2026`); the year period itself is a record with `type: year`. Inactive periods are included. With multiple fiscal calendars enabled, the periods of every calendar are returned together and are not distinguished. `locked_through`, `period_number`, `created_at` and `updated_at` are not available.

## GET /accounting/fiscal-periods/{id}

An id with no accounting period answers 404.

## GET /accounting/trial-balance

Built from NetSuite SuiteQL over posted ledger lines (`transactionaccountingline`, primary accounting book). NetSuite reports by posting period, so `filter[end_date]` is resolved to the posting month that contains it and the report is as of that period's end, returned as `end_date`, which can be later than the date requested. Balance sheet accounts are cumulative to that period; income and expense accounts run from the start of the fiscal year that contains it (`start_date`). NetSuite computes Retained Earnings virtually, so earlier years' income and expense is returned as one line named "Result of previous fiscal years (not yet appropriated)" with `line_type` `unappropriated_result` and null `account_id` and `code`; it is taken from the ledger's earlier-year income and expense, not from the other lines, so a ledger whose debits and credits differ returns a `502`. Every other line has `line_type` `account` and the account's internal id as `account_id`, the id ledger accounts use. Statistical and non-posting accounts are left out. The report covers the subsidiary selected by `x-apideck-company-id`, else the connection's default subsidiary, by transaction subsidiary; a subsidiary that does not exist, or that the integration role cannot see, returns a `404`. With none selected it sums all subsidiaries without intercompany elimination, and returns a `422` if their base currencies differ, so select one subsidiary. Amounts are in the subsidiary's base currency, returned as `currency`. A date outside every posting period returns a `400`. `filter[accounting_method]=cash` returns a `400`: cash basis is a NetSuite report option, not ledger data. Amounts are attributed to a subsidiary by the transaction's header subsidiary, so on intercompany journals a subsidiary's report can differ from NetSuite's own. Without OneWorld the report covers the whole account; if NetSuite's answer to the OneWorld check is anything other than the missing subsidiary table, the request returns a `502` instead of assuming a single entity. If the account has several fiscal calendars whose periods differ for the date, the request returns a `422`. Without `filter[end_date]`, today's date in UTC is used. Accounts with a zero balance are left out; `code` is null for accounts without an account number. A report whose debits and credits differ by half a cent or more returns a `502`.
## GET /accounting/tracking-categories

A **Default Subsidiary** on the connection scopes this list, and this list does not follow the same rule transactions do: a record marked "include children" in NetSuite **is** returned for every descendant subsidiary, whereas filtering transactions by a parent subsidiary is **not**.

## GET /accounting/bill-payments

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.
A vendor credit applied through a bill payment is **not** returned in that payment's `allocations`. NetSuite records the credit against the bill rather than the payment, so the payment reports only its bill allocations and each `amount` is the net cash applied after the credit — a bill of 500 settled by a 300 credit plus 200 cash comes back as a single `bill` allocation of 200. To see the credit, read the bill.
`allocations` is only populated on an unfiltered list and on get-by-id. Adding any `filter`, `sort` or `pass_through` parameter to the list returns bill payments without `allocations`; fetch the individual bill payment by `id` to read them.

## POST /accounting/bill-payments

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.
`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.
`payment_method` and `payment_method_id` are not supported on NetSuite bill payments. The NetSuite SOAP `VendorPayment` record does not expose a `paymentMethod` field (it is present on the AR-side `CustomerPayment` record only). Both fields will always be `null` in responses, and any value sent on create or update is silently ignored by NetSuite.
`total_amount` is not sent to NetSuite. The payment's total is the sum of the allocations NetSuite applies, so a `total_amount` you send on create or update is ignored.
A bill payment can only be allocated to the supplier's most recent open items. NetSuite returns a bounded window of a supplier's outstanding bills and credits, and an allocation naming anything outside it is rejected with a `400` reading `Unable to find a matching line for sublist apply` — even though the bill is genuinely open. Suppliers with a few thousand open items can fall outside the window entirely, so every allocation against them fails. Clear or narrow the supplier's open items, or allocate through NetSuite directly.
To apply a vendor credit to a bill payment, add an entry to `allocations` with `type: credit_memo`, the vendor credit's id, and a negative `amount`. The credit must still be open and belong to the same supplier as the payment — NetSuite matches every allocation against that supplier's outstanding items by id, so an id that is not among them is not applied. `allocations[].amount` is sent to NetSuite exactly as supplied; the connector does not adjust its sign.
The created payment covers only the cash portion: NetSuite applies the credit to the bill and the payment records the remainder. The credit will not appear in the payment's `allocations` when you read it back.
If the credits cover the bills in full so that no cash is due, NetSuite applies the credits but creates **no bill payment record**, while still reporting success. The response is `201` with `id: "0"`, and fetching that id returns a `400` carrying NetSuite's `RCRD_DSNT_EXIST` — "That record does not exist." Treat an `id` of `0` as "the credits were applied, no payment was needed" rather than as a record you can read back.

## GET /accounting/bill-payments/{id}

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.

## PATCH /accounting/bill-payments/{id}

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.

`allocations` on an update **replaces** the payment's bill allocations with the list you
send. A bill you leave out is removed from the payment and its open balance is restored.
Omitting `allocations`, or sending an empty list, leaves the existing allocations unchanged.
A payment must keep at least one bill, so to remove every allocation delete the payment
instead.

Send an `amount` on every allocation. A bill sent without one keeps its current amount if it
is already on the payment, but is paid in full, for its whole open balance, if it is not. A
bill sent with an `amount` of 0 or less is removed from the payment, and an update that would
leave no bill is rejected with a `400`. An `amount` larger than what the bill still owes,
counting what this payment already applies to it, is reduced to that figure without an
error, so read the payment back to confirm the amounts applied.

The payment's total is the sum of the allocations applied: a `total_amount` sent on an update
is ignored.

Vendor credits can only be applied when the payment is created. An update whose allocations
include a vendor credit (a bill credit note id) is rejected with a `400` and changes nothing,
so leave credits out of updates; on an update each allocation is matched by its `id`, and its
`type` is not used. A credit applied when the payment was created is not returned in its
`allocations`, and leaving it out of an update does not unapply it. The bill it settled
reads back with only the cash part: send that amount for the bill, not the amount you sent
when creating the payment, or the payment pays the difference in cash.

## DELETE /accounting/bill-payments/{id}

Deleting a payment does not undo a vendor credit applied when the payment was created: the
credit stays applied to the bill. Deleting the bill credit note removes the credit together
with that application and restores the bill's open balance.

## GET /accounting/expenses

Listing expenses returns Credit Card Charges and Checks, ordered by id. Each expense carries its header fields only: `line_items` are not included, so fetch an expense by id to read its line items. `payment_type` is `credit_card` for a card charge and `check` for a check, and `account.type` is `ledger_account` for a card charge and `bank_account` for a check. This list never includes Credit Card Refund records (the reversal of a corporate card charge, created via a negative-amount `expensesAdd` — see the create operation's gotcha). The maximum `limit` is 200; a higher value returns a `400`.
Expenses are **not** scoped by subsidiary — neither the connection's Default Subsidiary setting nor the `x-apideck-company-id` header narrows this list, so every subsidiary's expenses are returned. The records do carry a subsidiary in NetSuite, but this list cannot be separated by entity.

## POST /accounting/expenses

NetSuite expenses are either Credit Card Charges or Checks. Set `payment_type: "check"` to
create a NetSuite Check: `account` must then reference a **bank account** and `supplier` is
the payee. Any other `payment_type` (or none) creates a Credit Card Charge (or, when the
request has a negative amount, a Credit Card Refund — see below), 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`.
Employee reimbursements are not modelled here (use expense-reports).

NetSuite Checks expose no tax fields through the API. A check whose `line_items` carry
a `tax_rate` (an id, a code or a non-zero rate) or a non-zero `tax_amount` is rejected with a `400`, and a check's `total_tax` is empty:
`total_amount` and `sub_total` both equal the check total. Checks cannot be voided through
the API.

A negative overall total (or a negative `line_items[].total_amount` sum) creates a NetSuite
Credit Card Refund instead — NetSuite's transaction type for reversing a corporate/company
card charge, entered against a Vendor — unless `payment_type` is `check`, which takes
precedence (NetSuite then rejects the negative check). This is distinct from the unified
`refunds` resource, which models customer-facing (AR) refunds. NetSuite itself never stores a
negative amount on either record type (a negative total is rejected outright); this connector
sends the absolute value automatically, so the sign only decides which record type gets
created, not what NetSuite stores. `expensesOne`, `expensesUpdate` and `expensesDelete`
look the id up as a Credit Card Charge first and, if no card charge exists with that id, as
a Check, then read, update or delete that record. Reading a Credit Card Charge takes one
NetSuite request as before; reading a Check, and every update or delete, takes one more. An
id that exists as neither returns NetSuite's `404`; a Credit Card Refund id (or any other
transaction type) returns NetSuite's `400` "Transaction type specified is incorrect", and the
list never includes Credit Card Refunds, so capture the `id` from the `expensesAdd` response
if you need to reference the reversal elsewhere. `payment_type` in an
update body is ignored for routing, because a record's type cannot change. `expensesUpdate`
on a Credit Card Refund only routes correctly when the update body itself carries a negative
amount (a negative `total_amount`, or a negative `line_items[].total_amount` sum) — the
router only sees the body of the request in front of it. A field-only update that omits the
amount (e.g. changing just `memo` or the transaction date) evaluates to a total of `0`, and a
positive amount does too; both route to the Credit Card Charge/Check lookup instead and are
rejected with NetSuite's `400` "Transaction type specified is incorrect", since the id only
exists as a Credit Card Refund. Re-send the negative amount on every
update to one of these records, even when only a non-amount field is changing. A negative-total
update on a Check is also routed to Credit Card Refund update and fails at NetSuite.

`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 on a Credit Card Charge 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 record. 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.

## GET /accounting/expenses/{id}

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.

`expensesOne` reads the id as a Credit Card Charge first and, if no card charge exists with that id, as a Check (one extra NetSuite request for a check). `payment_type` is `credit_card` for a card charge and `check` for a check, and `account.type` is `ledger_account` or `bank_account` respectively. NetSuite Checks expose no tax fields through the API, so a check's `total_tax` is empty and `total_amount` and `sub_total` both equal the check total. An id that exists as neither returns NetSuite's `404`; a Credit Card Refund id or any other transaction type returns NetSuite's `400` "Transaction type specified is incorrect". A Credit Card Refund (the reversal of a corporate card charge, created via a negative-amount `expensesAdd` — see that operation's gotcha) cannot be read by id today.

## PATCH /accounting/expenses/{id}

NetSuite expenses are either Credit Card Charges or Checks, and an update first reads the id as a Credit Card Charge, then as a Check if no card charge exists, and writes to the record it found (one extra NetSuite request per update). A Credit Card Charge's top-level `account` must reference a **credit-card–type** ledger account, and a Check's must reference a **bank account**; anything else is rejected with `Invalid Field Value … for the following field: account`. `payment_type` in the update body is ignored for routing: a record's type cannot change. An id that exists as neither returns NetSuite's `404`; a Credit Card Refund id that is not routed by amount (see below) returns NetSuite's `400` "Transaction type specified is incorrect". NetSuite Checks expose no tax fields through the API, so an update to a check whose `line_items` carry a `tax_rate` (an id, a code or a non-zero rate) or a non-zero `tax_amount` is rejected with a `400`. 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.

Updating a Credit Card Refund (created via a negative-amount `expensesAdd` — see that operation's gotcha) requires re-sending a negative amount in the update body every time, even when only a non-amount field (e.g. `memo`) is changing — the router only sees the body in front of it, not the record's existing type. Omitting the amount, or sending a positive one, routes this connector to the Credit Card Charge/Check lookup instead, which NetSuite rejects with `400` "Transaction type specified is incorrect" since the id only exists as a Credit Card Refund. A negative-total update on a Check is routed the same way, to Credit Card Refund update, and fails at NetSuite.

## GET /accounting/bank-feed-accounts

`feed_status` reads back as `pending` for every active account — NetSuite does not expose Format Profile
linking. From bundle version 1.3.2, `custom_fields` carries `last_import_requested_at`: when NetSuite's
bank data import last asked for the account (ISO 8601, UTC). NetSuite only asks for accounts linked in a
Format Profile, so the value shows the account was linked at that time; it is not proof the account is
still linked. It is set by the first import after the account is linked (on the profile's import schedule,
or when a user clicks **Update Imported Bank Data**), moves forward with each import while the account
stays linked, and stops once it is unlinked. It is absent for accounts that have never been requested.
NetSuite imports an account at most once every 60 minutes, so the value moves forward at most hourly.

## POST /accounting/bank-feed-accounts

`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.

## GET /accounting/bank-feed-statements

`status` is the staging state in NetSuite: `pending` until the next bank data import of an account linked in
the Format Profile, then `success`; from bundle version 1.3.2, `rejected` when the bundle's daily maintenance
script finds the statement still not imported after 7 days. There is no webhook for status changes — poll
this operation or Get Bank Feed Statement.

Pages are offset-based. From bundle version 1.3.2 statements are listed oldest first, so new statements
are added at the end; with earlier versions the order is not stable, so a statement can repeat or be
skipped across pages. From 1.3.2 the maintenance script deletes `success` and `rejected` statements 30 days
after they were created; reading a deleted statement returns `400 RCRD_DSNT_EXIST`.

## POST /accounting/bank-feed-statements

Requires the Apideck Bank Feed bundle in the customer's NetSuite account. `bank_feed_account_id` must be an
active Bank or Credit Card account (as listed by `GET /bank-feed-accounts`); a nonexistent or inactive
account is rejected with `400 INVALID_FLD_VALUE` and nothing is created. `id` and `status` in the request
are ignored: NetSuite assigns the id and every new statement starts as `pending`.

`pending` means staged in NetSuite and waiting for the next bank data import of an account linked in the
Format Profile (on the profile's import schedule, or when a user clicks **Update Imported Bank Data**). It
becomes `success` once imported into Match Bank Data. From bundle version 1.3.2, the bundle's daily
maintenance script marks a statement `rejected` once it is 7 days old and still not imported (for example
because its account is not linked in the Format Profile); a `rejected` statement is never imported. Before
1.3.2 statements are never marked `rejected`, and the maintenance script deletes staging statements older
than 30 days whatever their status — set its deployment (Customization → Scripting → Script Deployments →
Apideck Bank Feed Maintenance) to Not Scheduled until the bundle is updated.

Dates are stored and returned as the calendar date sent. With bundle versions before 1.3.2, a NetSuite user
whose date format is day-first (for example DD/MM/YYYY) gets day and month swapped on write and in list
responses, and a rejected statement (or a `null` date when listing) when the day is above 12; update the
bundle.

`source_transaction_id`, `counterparty` and `reference` are limited to 300 characters and `description` to
4,000 by NetSuite. From bundle version 1.3.2 a rejected line rolls back the whole statement — nothing is
saved — and the error message is prefixed with the failing line, e.g. `transactions[1]: …`; with earlier
bundle versions the statement and the lines before the failing one stay saved and are imported, so
resending the corrected statement duplicates them. From 1.3.2 a statement with more transactions than one
request can create and fully roll back (about 490) is rejected with `STATEMENT_TOO_LARGE` before anything
is saved; split it.
Deleting a statement is not supported.

## GET /accounting/quotes

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.

## POST /accounting/quotes

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.

`status` sets the estimate's sales stage: `sent` sets Proposal, `accepted` sets Purchasing and
`rejected` sets Closed Lost, which also closes the estimate in NetSuite. Proposal, Purchasing and
Closed Lost are matched by exact name: if one of them is renamed in NetSuite, even only in case, the
status that sets it returns 422 until the stage is named back. NetSuite has no draft stage: a quote
created with `draft`, or without `status`, starts at the account's default stage, which is Proposal
unless the account changed it, so it reads back as `sent`. A different default stage reads back as
the status that stage maps to, or as `draft` for a stage the account added itself. `converted`,
`void`, `expired` and `deleted` are set by NetSuite itself and return 422. When the quote's customer
is a prospect, NetSuite also moves the prospect's own status to the same stage; an existing
customer's status is not changed. A status that cannot be set rejects the whole request, including
its other fields.

## PATCH /accounting/quotes/{id}

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.

`status` sets the estimate's sales stage: `sent` sets Proposal, `accepted` sets Purchasing and
`rejected` sets Closed Lost, which also closes the estimate in NetSuite. A status the quote already
reads as changes nothing, so a quote read and sent back unchanged still saves, and a quote at
another stage that reads as `sent` (In Discussion, Identified Decision Makers or In Negotiation)
keeps that stage. Proposal, Purchasing and Closed Lost are matched by exact name: if one of them is
renamed in NetSuite, even only in case, the status that sets it returns 422 until the stage is named
back. A quote at a renamed stage, or at a stage the account added, reads as `draft`. `draft` is
accepted but sets no sales stage, as NetSuite has no draft stage. `converted`, `void` and `expired`
are set by NetSuite itself: they are accepted only when the quote already has that status, and
otherwise return 422, as does `deleted`. On a converted or expired quote, `accepted` changes the
sales stage, but the quote keeps reading as `converted` or `expired`. When the quote's customer is a
prospect, NetSuite also moves the prospect's own status to the same stage; an existing customer's
status is not changed. A status that cannot be set rejects the whole request, including its other
fields.

## GET /accounting/projects

`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.

## GET /accounting/employees

`filter[subsidiary_id]` is rejected on this resource, so the `x-apideck-company-id` header is the only per-request override of the connection's Default Subsidiary. The connector rejects every other filter here too, including the `updated_since` and `status` the schema declares.

## POST /accounting/employees

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.

## PATCH /accounting/employees/{id}

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.

## GET /accounting/expense-categories

A **Default Subsidiary** on the connection scopes this list, and this list does not follow the same rule transactions do: a record marked "include children" in NetSuite **is** returned for every descendant subsidiary, whereas filtering transactions by a parent subsidiary is **not**.

## GET /accounting/expense-reports

`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.

## POST /accounting/expense-reports

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.

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.

## GET /accounting/expense-reports/{id}

`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.

## PATCH /accounting/expense-reports/{id}

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.

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.


[API reference](https://developers.apideck.com/apis/accounting/reference.md) · [Coverage matrix](https://developers.apideck.com/apis/accounting/coverage)
