> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.fincome.co/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# NetSuite integration mapping

![](https://storage.crisp.chat/users/helpdesk/website/-/6/2/0/e/620ea71851239000/netsuite_1lm1zla.svg =100x100)
||| Warning: The connector relies on several custom fields and records (custcol_iw_*, CUSTOMLIST_IW_*, CUSTOMRECORD_CSEG_IW_*).
||| These elements depend on the NetSuite account's configuration. A new instance must expose equivalent fields (or be specifically mapped) before onboarding.

# Connection & authentication

* **API used:** SuiteQL (NetSuite's SQL query language), exposed via a single REST endpoint:
  `POST&#32;https://{account_id}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql`
* **Authentication:** OAuth 2.0 Machine-to-Machine (M2M), via a JWT assertion signed in ES256 (grant_type=client_credentials). There is no interactive OAuth flow on the user side.
* Token endpoint: `https://{account_id}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token`
* Request headers: `Authorization: Bearer <token>` and `Prefer: transient`.
* On a `401` response, the token is automatically regenerated and the request is replayed once; on failure, an `InvalidCredentials` error is raised.
* **Prerequisites on the NetSuite side:**
1. Enable the *REST Web Services* and *OAuth 2.0* features.
2. A role with read rights on `customer`, `item`, `transaction`, `transactionLine`, `currency`, `classification`, and any custom records, as well as access to *REST Web Services* / SuiteQL.

### Setting up the connection

The connection is *machine-to-machine*: you register Fincome as a trusted application in **your** NetSuite account, then you enter three identifiers in Fincome. The signing private key never leaves Fincome — you only import the Fincome **public certificate**.

1. **Create the integration record.**
*Setup ▸ Integration ▸ Manage Integrations ▸ New*. Check all the boxes **Token-Based Authentication**, **OAuth 2.0**, and **Client Credentials (Machine to Machine) Grant**, save, then note the **Client ID** (Consumer Key).
2. **Upload the Fincome certificate.**
Download the Fincome public certificate: <https://fincome-public-assets.s3.eu-west-3.amazonaws.com/netsuite/fincome-netsuite.pem>
Then go to *Setup ▸ Integration ▸ OAuth 2.0 Client Credentials (M2M) Setup ▸ New*: select the integration from step 1, import the certificate and attach it to an **entity** and a **role** with *REST Web Services / SuiteQL* rights (see prerequisites). Note the returned **Certificate ID**.
3. **Fill in Fincome.**
In Fincome, open the NetSuite connector and enter your **Account ID** (*Setup ▸ Company ▸ Company Information*), your **Client ID** (step 1), and your **Certificate ID** (step 2). Optionally, indicate the IDs of your custom revenue recognition fields and your recurring revenue category(ies).

### Synchronized objects

The connector processes five flows, in this order:

| Flow | NetSuite source | Fincome objects created | Filter condition |
| ---- |
| customer | `customer` table | Customer | none (full sync) |
| product | `item` table | Product + Price | none |
| subscription | `salesorder` transactions | Subscription | `taxline = 'F'` AND `tranDate > TRUNC(CURRENT_DATE)` |
| invoice | `salesorder` transactions | Invoice + InvoiceLineItem | `taxline = 'F'` AND `tranDate <= TRUNC(CURRENT_DATE)` |
| credit_note | `creditmemo` transactions | Invoice + InvoiceLineItem | `taxline = 'F'` |


|| Key point — invoices vs subscriptions. Invoices and subscriptions come from the same record type (salesorder). They are separated only by the transaction date:
|| -tranDate in the future → subscription (Subscription),
|| -tranDate today or in the past → invoice (Invoice).


||| Only lines where taxline = 'F' are imported (whitelist): any other value — notably tax lines — is excluded. Tax is therefore never imported and tax_amount is always 0.
---
# Field mapping

### Customers (customer)

| NetSuite field | Fincome field | Note |
| ---- |
| id | original_id |  |
| entitytitle | name |  |
| email | email |  |
| toplevelparent | root_parent_id | Only set if `toplevelparent ≠ id` (parent/child hierarchy), resolved via a Customer foreign key |

### Products & prices (product)

From the `item` table, the connector creates a Product and a Price:

| NetSuite field | Fincome field (Product) |
| ---- |
| id | original_id |
| itemid | name |

| NetSuite field | Fincome field (Price) |
| ---- |
| id | original_id |
| id | product_id (foreign key to Product) |
| itemid | name |
| itemrevenuecategory | type → `subscription` if = "1", otherwise `one_off` |

### Invoices (invoice)

###### Header (Invoice):

| NetSuite field | Fincome field | Note |
| ---- |
| id | original_id |  |
| tranId | invoice_number |  |
| entity | customer_id | foreign key to Customer |
| tranDate | date | DD/MM/YYYY format; if absent, the invoice is ignored |
| status | status | mapped via the `salesorder` table (see § Status mapping) |

###### Lines (InvoiceLineItem):

| NetSuite field | Fincome field | Note |
| ---- |
| transactionLine.id | original_id (combined with the invoice id) |  |
| — | invoice_id | foreign key to Invoice (line ignored if the invoice is absent) |
| itemrevenuecategory + period dates | type | `subscription` if the period is valid (`period_start < period_end`) and `itemrevenuecategory = "1"`, otherwise `one_off` |
| creditforeignamount / debitforeignamount | amount_excluding_tax_after_discount | see § Amounts |
| — | tax_amount | always 0 (tax lines excluded) |
| quantity | quantity | transformed into `-1 × ceiling(quantity)` |
| currency.symbol | currency_code |  |
| transactionLine.memo | description |  |
| custcol_iw_rr_start_date | period_start | revenue recognition start date |
| custcol_iw_rr_end_date | period_end | +1 day (NetSuite bounds the end inclusively) |
| item | price_id | foreign key to Price |

### Custom dimensions (custom_axis_field):

| NetSuite record | Fincome field |
| ---- |
| CUSTOMLIST_IW_CONTRACT_TYPE.name | contract_type |
| CUSTOMRECORD_CSEG_IW_CA_COUNTRY.name | country_name |
| classification.name | class |

### Subscriptions (subscription)

| NetSuite field | Fincome field | Note |
| ---- |
| id (+ line id) | original_id | combined identifier |
| id (transaction) | subscription_set_id | groups the lines of the same `salesorder` |
| entity | customer_id | foreign key to Customer |
| custcol_iw_rr_start_date | subscription_start_date |  |
| line amount ÷ number of months of the period | monthly_value | partial months are calculated pro rata |
| — | monthly_value_main | `monthly_value` converted into the base currency (see § Amounts) |

### Credit notes (credit_note)

Identical structure to invoices, with three differences:

* source = `creditmemo` transactions (and not `salesorder`);
* status mapped via the `creditmemo` table (see § Status mapping);
* `original_id` = the credit note's own id (it is not a link to an original invoice).
---
# Status mapping

The statuses depend on the source record type.

### Sales orders (salesorder) → used for invoices

| NetSuite code | NetSuite label | Fincome status |
| ---- |
| A | Pending Approval | pending |
| B | Pending Fulfillment | open |
| C | Cancelled | *ignored* |
| D | Partially Fulfilled | open |
| E | Pending Billing/Part. Fulf. | open |
| F | Pending Billing | open |
| G | Billed | open |
| H | Closed | *ignored* |
| Y | Undefined | *ignored* |

### Credit notes (creditmemo)

| NetSuite code | NetSuite label | Fincome status |
| ---- |
| A | Open | open |
| B | Fully Applied | paid |
| C | Undefined | *ignored* |
| D | Voided | *ignored* |

A record moved to an *ignored* status (canceled/closed order, voided credit note…) is marked as deleted on the Fincome side during the next sync.
---
# Amounts, quantities, dates & currencies

* **Amounts.** NetSuite splits each line into two accounting legs. The connector reads the amounts in foreign currency:
* `creditforeignamount` → positive amount;
* otherwise `debitforeignamount` → negative amount (`-1 ×`).
* The `netamount` field is not used.
* **Quantities.** Inverted and rounded up: `-1 × ceil(quantity)`.
* **Dates.** Expected format DD/MM/YYYY. Period end dates (`period_end`) are shifted by +1 day.
* **Currencies.** Amounts are expressed in the transaction's currency (`*foreignamount`). They are converted into the company's base currency at the transaction-date rate (`*_main` fields). Note: for subscriptions, this date is in the future (`tranDate > today`), so the conversion uses the exchange rate associated with that future date.

# Incremental synchronization & pagination

* **Pagination:** by `limit` (1000) and `offset` on SuiteQL.
* **Cursor:** `{ offset, last_modified_date }`. Once a flow is exhausted (`hasMore = false`), `offset` resets to 0 and `last_modified_date` advances to the start date of the last sync.
* **Last modification field:**
* customer → `lastmodifieddate`;
* subscription / invoice / credit_note → `t.lastmodifieddate`.
* **Deletions.** Permanent deletions on the NetSuite side are not propagated: a deleted record simply stops appearing, without being removed from Fincome. However, a record moved to an *ignored* status (see § Status mapping) is marked as deleted.
---
# Required custom fields & records

These elements must exist in the NetSuite instance for a complete mapping:

| Element | Fincome usage |
| ---- |
| custcol_iw_rr_start_date | period start (period_start, subscription_start_date) |
| custcol_iw_rr_end_date | period end (period_end) |
| CUSTOMLIST_IW_CONTRACT_TYPE | contract_type custom dimension |
| CUSTOMRECORD_CSEG_IW_CA_COUNTRY | country_name custom dimension |

|| Note: itemrevenuecategory (which distinguishes subscription / one-off) is a standard field of the item object, not a custom field.
