# SuiteBilling Audit Logs Streams the NetSuite SystemNote audit trail scoped to SuiteBilling record types — subscriptions, billing accounts, invoices, price plans, and related billing records. Each event captures who changed which field, the old and new values, and the context in which the change was made, making it well suited for finance/SOX audit of billing data. **Sync Type: Incremental** ## Overview NetSuite records every field-level change to a record as a **System Note**. This input queries the `SystemNote` table via SuiteQL, filtered to the NetSuite record types that make up the SuiteBilling module, and streams new changes in ascending `date` order. The connector tracks the timestamp of the last processed change and resumes from there on each subsequent sync, so no events are missed and none are duplicated. ## Authentication This integration uses NetSuite's **OAuth 2.0 Client Credentials (machine-to-machine) flow** with a signed JWT client assertion. Rather than a shared client secret, the connector signs a short-lived JWT with an **RSA private key** (PS256); NetSuite verifies the signature against the **public certificate** you upload during setup. The signed assertion is exchanged for a one-hour bearer token at the per-account token endpoint (`https://.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token`), which is then used for all SuiteQL requests. ## Prerequisites ### 1. SuiteAnalytics / SuiteQL enabled The integration role must be able to run SuiteQL queries. Ensure **SuiteAnalytics Workbook** is enabled for your account and granted to the role used by this integration. ### 2. Permissions The role mapped to the integration needs: - **REST Web Services** and **Log in using OAuth 2.0 Access Tokens** permissions - **SuiteAnalytics Workbook** permission (SuiteQL runs under workbook role restrictions) - View access to **System Notes** and to the **SuiteBilling record types** in scope (Subscriptions, Billing Accounts, Invoices, Price Plans, etc.) ## Setup ### 1. Enable required features In NetSuite, go to **Setup > Company > Enable Features > SuiteCloud** and enable **REST Web Services** and **OAuth 2.0**. ### 2. Create an Integration Record Go to **Setup > Integration > Manage Integrations > New**. - Give it a name (e.g. `Monad SuiteBilling Audit`). - Under **OAuth 2.0**, check **Client Credentials (Machine to Machine) Grant** and the **REST Web Services** scope. - Save. Copy the generated **Client ID** (Consumer Key) — this is your **Client ID**. ### 3. Generate an RSA key pair Generate a 2048-bit (or larger) RSA key pair. Keep the **private key** for the connector; you will upload the **public certificate** to NetSuite. ```bash openssl genrsa -out monad-netsuite-private.pem 2048 openssl req -new -x509 -key monad-netsuite-private.pem -out monad-netsuite-public.pem -days 730 ``` ### 4. Map the certificate (OAuth 2.0 Client Credentials Setup) Go to **Setup > Integration > OAuth 2.0 Client Credentials (M2M) Setup > Create New**. - **Entity**: the user the integration acts as. - **Role**: a role with the permissions listed above. - **Application (Integration)**: the Integration Record from step 2. - **Certificate**: upload `monad-netsuite-public.pem`. - Save. Copy the generated **Certificate ID** — this is your **Certificate ID**. ### 5. Identify your SuiteBilling record type IDs This input filters `SystemNote` by numeric **record-type internal IDs**. Determine the internal IDs for the SuiteBilling record types you want to audit (e.g. Subscription, Subscription Line, Billing Account, Billing Schedule, Invoice, Price Plan). You can confirm these in your account by running a SuiteQL query such as `SELECT DISTINCT recordtypeid, BUILTIN.DF(recordtypeid) FROM systemnote`, or by inspecting the record types in **Customization > Lists, Records, & Fields**. ## Details The connector executes a SuiteQL query against the `SystemNote` table filtered to the configured record types, ordered by change date ascending. Results are paginated using `limit`/`offset` (1000 rows per page) and streamed as individual audit events. The cursor advances on the `date` of the most recent change processed. > **Timezone note:** the SuiteQL date filter is evaluated in the NetSuite account's timezone, while the connector formats its cursor in UTC. For exact incremental boundaries, the NetSuite account should be configured to UTC. ## Configuration The following configuration defines the input parameters. Each field's specifications, such as type, requirements, and descriptions, are detailed below. #### Settings | Setting | Type | Required | Description | |---------|------|----------|-------------| | Account ID | string | Yes | NetSuite account ID / realm, e.g. `1234567` or `1234567_SB1` for a sandbox. Used to build the per-account SuiteTalk host. | | Certificate ID | string | Yes | ID of the public certificate uploaded to NetSuite for this integration. Sent as the JWT `kid` header. | | Record Type IDs | array of strings | Yes | Numeric NetSuite record-type internal IDs to scope the audit trail to SuiteBilling records (e.g. Subscription, Billing Account, Invoice, Price Plan). At least one is required. | | Backfill Start Time | string | No | The date to start fetching data from, in RFC 3339 format. If not specified, the first sync starts from now and all subsequent syncs are incremental. | #### Secrets | Secret | Type | Required | Description | |---------|------|----------|-------------| | Client ID | string | Yes | OAuth 2.0 client ID (the consumer key of the NetSuite Integration Record). Used as the `iss` claim of the signed JWT client assertion. | | Private Key | string | Yes | PEM-encoded RSA private key used to sign the JWT client assertion (PS256). Its public certificate must be registered in NetSuite. | ## Connector Limitations 1. **Record-type scoping is explicit.** The connector filters `SystemNote` by numeric record-type internal IDs you provide. These IDs must be confirmed against your own account; there is no auto-discovery. 2. **System Notes (v1) only.** The connector reads the SuiteQL-queryable `SystemNote` table. System Notes v2 (available only through SuiteAnalytics Workbook) is not used. 3. **SuiteQL result limits.** A single SuiteQL query returns at most ~100,000 rows; the connector paginates within that and windows by date across runs. Concurrency is governed by your account's SuiteCloud tier. 4. **RSA certificate required.** The connector signs assertions with PS256, so the certificate uploaded to NetSuite must be an RSA certificate. ## Troubleshooting ### Common Issues 1. **Authentication Errors (401 / `INVALID_LOGIN`)** - Confirm the **Client ID** is the consumer key of the Integration Record, with no leading/trailing whitespace. - Verify the **Private Key** is the RSA private key that matches the **public certificate** uploaded to NetSuite. A mismatched key pair causes signature verification to fail. - Confirm the **Certificate ID** matches the certificate in **OAuth 2.0 Client Credentials (M2M) Setup**, and that the certificate is mapped to the same Integration Record, entity, and role. - Ensure the uploaded certificate is an **RSA** certificate (the connector signs with PS256); an EC certificate will be rejected. - Check that the certificate has not passed its expiration date — regenerate and re-upload if it has. - Verify the mapped role has the **Log in using OAuth 2.0 Access Tokens** and **REST Web Services** permissions. 2. **Connection / DNS Errors** - The **Account ID** must be your realm, e.g. `1234567` (production) or `1234567_SB1` (sandbox). The connector lowercases it and converts `_` to `-` to build the host `https://.suitetalk.api.netsuite.com`. - A wrong Account ID typically surfaces as a DNS resolution failure or a 404. For sandboxes, use the full sandbox ID (e.g. `1234567_SB1`), not the base account number. 3. **`INSUFFICIENT_PERMISSION` or Feature Errors on SuiteQL** - SuiteQL requires the **SuiteAnalytics Workbook** feature to be enabled and granted to the integration role. - The **REST Web Services** feature must be enabled under **Setup > Company > Enable Features > SuiteCloud**. 4. **No Records / Empty Syncs** - Verify the **Record Type IDs** are the correct numeric internal IDs for the SuiteBilling record types in your account. Incorrect IDs return zero rows. Confirm them with `SELECT DISTINCT recordtypeid, BUILTIN.DF(recordtypeid) FROM systemnote`. - If **Backfill Start Time** is not set, the first sync starts from *now*, so only changes made after the connection is created are captured. Set a backfill time to pull historical changes. - The cursor advances on the change date; if no billing records changed since the last run, emitting no records is expected behavior. 5. **Records Missing Near the Time Boundary (Timezone)** - The SuiteQL date filter is evaluated in the NetSuite account's timezone, while the connector formats its cursor in UTC. If the account is not on UTC, changes near the boundary can appear shifted. Set the NetSuite account timezone to **UTC** for exact incremental boundaries. 6. **Rate Limiting / `SSS_REQUEST_LIMIT_EXCEEDED` (429)** - SuiteQL concurrency is governed by your account's SuiteCloud tier. The connector rate-limits itself, but running many integrations against the same account concurrently can still hit limits. Reduce concurrent usage or upgrade to SuiteCloud Plus. ## Related Articles - [NetSuite SystemNote / System Notes Overview](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_158644279544.html) - [NetSuite SuiteQL](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_156257770590.html) - [OAuth 2.0 Client Credentials (M2M) Flow](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_162755359851.html) ## Sample Record ```json { "id": "4582931", "recordid": "738201", "recordtypeid": "245", "recordtypename": "Subscription", "record": "SUB-1042", "field": "status", "oldvalue": "Draft", "newvalue": "Active", "type": "change", "userid": "1087", "username": "jane.smith@example.com", "rolename": "Billing Manager", "contextname": "UI", "eventdate": "2026-05-10T09:00:00" } ``` ## Sync frequency By default this input polls approximately every 10 seconds, with each sync beginning after the previous one completes. A cron schedule configured on the pipeline overrides this cadence. See [Input Sync Frequency](../../guides/sync-frequency) for details.