Connecting NetSuite takes a one-time, ~10-minute setup by a NetSuite administrator. Unlike HubSpot or GitHub, NetSuite has no central “log in and click confirm” app store for third-party tools: every external application must be registered inside your own NetSuite account by an admin. The good news: you do this exactly once, no password or secret is ever copied, and afterwards syncs run entirely on their own.
What Zencount reads (so you can scope the role tightly)
The connection runs read-only SuiteQL queries. Which records are touched depends on the data scopes you enable on the connection — listed here in the same order as the Data scopes picker, which is the recommended setup order: subsidiaries and the chart of accounts feed every scope below them, so set them up top-down and the later scopes find their entity/account mappings already in place.
| Scope | Records | Used for |
|---|---|---|
| Connection test | subsidiary | the live probe behind “Test connection” |
| Subsidiaries | subsidiary · currency | create missing Zencount entities from the subsidiary tree + keep their active state in sync |
| Chart of accounts | account (P&L types, incl. inactive) | create missing Zencount accounts and keep name + active state in sync |
| Vendors | vendor · vendorcategory · currency | supplier master, so spend-per-vendor resolves |
| Customers | customer · customercategory · currency | customer master, so revenue-per-customer resolves |
| Employees | employee (+ subsidiary/department/location names) | roster → planning positions. Only id, name, title, email, org assignment and hire/release dates — no compensation fields are queried |
| Financial actuals | accountingperiod · consolidatedexchangerate · transaction/transactionline/transactionaccountingline · account · subsidiary · department | consolidated P&L per account × department × month — the summed posted amounts of every posting transaction type (invoices, journal entries, bills, …) in the primary book, converted at the average consolidation rate |
| Transaction lines | the same transaction records, un-aggregated (+ the transaction's entity display name) | document-level P&L lines — date, document number, memo, counterparty — with a deep link per line back to the NetSuite transaction |
| Budgets | budgets · budgetsmachine · account · accountingperiod · consolidatedexchangerate (· department) | budget amount per account × department × month → a Zencount plan version of your choice |
Nothing else is queried — no custom records today — and no write endpoint is ever called. Optional joined records (vendor/customer categories, departments, locations) are allowed to be missing: if your account doesn’t have one, the sync leaves that field empty instead of failing.
Before you start (once per NetSuite account)
- You need the Administrator role in NetSuite.
- Check the required features are on under Setup → Company → Enable Features → SuiteCloud: in the SuiteTalk (Web Services) section tick REST WEB SERVICES; in the Manage Authentication section tick OAUTH 2.0. Save. In most accounts both are already enabled.
Step 1 — Get the certificate from Zencount
- In Zencount, open Integrations, add (or open) a NetSuite connection.
- On the Connect step, keep the OAuth 2.0 · recommended tab, click Generate certificate, then Download certificate (.pem). Keep the file — you’ll upload it in step 4.
Step 2 — Create the integration record (→ Client ID)
- In NetSuite: Setup → Integration → Manage Integrations → New (the New link sits in the hover flyout of Manage Integrations; in accounts with many bundles the item is in the Integration Management group, next to the SOAP Web Services entries).
- Name: e.g. Zencount. State: Enabled. Description is optional but gold for the next admin who wonders what this record is — suggestion: “Zencount FP&A — read-only consolidated P&L sync via SuiteQL (REST web services), certificate-based OAuth 2.0 M2M, no UI login. Owner: finance team.” Note and Concurrency Limit keep their defaults.
- On the Authentication tab, set the checkboxes exactly like this:
- ⬜ AUTHORIZATION CODE GRANT — untick it. NetSuite pre-ticks it on new records, but it’s the browser-login flow and stays unused here — and while it’s ticked, NetSuite treats Redirect URI as a required field. Unticked, the Redirect URI stays empty.
- ✅ CLIENT CREDENTIALS (MACHINE TO MACHINE) GRANT — at the bottom of the OAuth 2.0 section.
- ✅ Scope: REST WEB SERVICES (leave RESTlets, SuiteAnalytics Connect and NetSuite AI Connector Service unticked).
- ⬜ Token-based Authentication section — leave everything unticked (legacy; already off by default).
- Everything else (Refresh Token Validity, Consent Policy, logo/links) keeps its default.
- Save. The confirmation page shows the Client ID and a client secret exactly once — after you leave the page they can’t be viewed again. Copy the Client ID and paste it straight into the Client ID field on Zencount’s Connect step — that’s the only place it goes (step 6 saves it together with the other two ids; filling it in now is fine). The client secret is not needed — the certificate replaces it, so you don’t have to store it anywhere. Left the page without copying? Edit the integration record and reset the credentials (Reset Credentials) — NetSuite issues a new Client ID; use that one.
Step 3 — Create a dedicated read-only role
The M2M mapping (step 4) ties the connection to a NetSuite user + role, and that role decides what Zencount can see. Create a dedicated role so the access is minimal, auditable and survives personnel changes:
- Setup → Users/Roles → Manage Roles → New. Name: e.g. Zencount Integration. Leave ID empty — NetSuite generates a script id (
customrole…) on save; you never need it for this setup. Centre Type (Accounting Centre) and Employee Restrictions (none — no default) keep their defaults — they only shape the UI a person with this role would see, and this role never logs in to the UI. - Under Subsidiary Restrictions, set ACCESSIBLE SUBSIDIARIES to All — the default is User Subsidiary, so this needs an active change. A consolidated P&L must read every subsidiary (including elimination subsidiaries); if you restrict this, consolidated numbers will silently miss those subsidiaries.
- Under Authentication:
- Tick WEB SERVICES ONLY ROLE — hardening: the role can then never be used to log in to the NetSuite UI, only for API access.
- TWO-FACTOR AUTHENTICATION REQUIRED stays on Not required (the default) — certificate auth is the second factor here; 2FA prompts can’t be answered by a machine.
- On the Permissions tab, work through the sub-tabs in their on-screen order — Transactions → Reports → Lists → Setup. On each sub-tab, pick the permission from the dropdown, set the Level, and click Add. The full set:
The why column names the data scope that needs each row — for scopes you won’t enable (say, Employees), leave the rows off and add them when you switch the scope on.Permissions sub-tab Permission Level Why (and which scope needs it) Transactions Find Transaction View actuals — query transaction lines Transactions the posting types you use, e.g. Invoice, Credit Memo, Make Journal Entry, Bills (+ Expense Report … whatever posts to your P&L) View actuals — row-level access to those transactions’ amounts. The dropdown’s spelling is authoritative; a type your account doesn’t offer can simply be skipped Reports SuiteAnalytics Workbook Edit all scopes — SuiteQL queries run through the analytics engine Lists Accounts View actuals — chart of accounts (numbers, names, types) Lists Subsidiaries View all scopes — subsidiary list, consolidation parent, connection test Lists Currency View actuals + master-data scopes — currency metadata Lists Consolidated Exchange Rates View actuals — the average consolidation rate. Not every account offers this permission; if your dropdown doesn’t list it, skip it — the first sync flags it (403) in the rare case it’s enforced Lists Employees View employees scope — the active roster (no compensation fields are read) Lists Departments and Locations View employees scope — resolve department and location names Lists Vendors View vendors scope — supplier master Lists Customers View customers scope — customer master Setup REST Web Services Full all scopes — call the SuiteQL REST endpoint (this permission only exists as Full) Setup Log in using OAuth 2.0 Access Tokens Full all scopes — authenticate with the certificate Setup Manage Accounting Periods View actuals — monthly posting periods Transactions Set Up Budgets View budgets scope — SuiteQL reads the budget tables through this permission; raise to Full only if the sync reports a 403 - The Custom Record sub-tab stays empty for the standard scopes — Zencount reads no custom records today. Only if your setup is extended to pull from a custom record type (for example contract data that another system syncs into NetSuite) does that record type need View here.
- Assign the role to a user. Best practice is a dedicated service user (an employee record like Zencount Service with a mailbox your team owns): on the employee record, open the Access tab, tick Give Access, and add the Zencount Integration role. Licensing heads-up: every user with Give Access consumes a NetSuite user license — even for a web-services-only role. If NetSuite blocks the save with an “exceeds your license limit” error, either free up or buy a seat, or add the Zencount Integration role to an existing user instead: the M2M mapping doesn’t affect that person’s normal login, but if they are ever deactivated the connection stops — which is exactly why the dedicated service user is the best practice when a seat is available.
Step 4 — Upload the certificate (→ Certificate ID)
- Setup → Integration → OAuth 2.0 Client Credentials (M2M) Setup → Create New.
- Fill the mapping:
- Entity: the user from step 3 (the service user)
- Role: Zencount Integration (the step-3 role)
- Application: Zencount (the step-2 integration record)
- Certificate: upload the
.pemfile from step 1
- Save. The M2M list now shows your new row with a Certificate ID column (a short id like
aBc12dEf) — copy it. This id is also called the kid in NetSuite’s docs.
Step 5 — Find your Account ID
Setup → Company → Company Information → ACCOUNT ID — e.g. 1234567 (production) or 1234567_SB1 (sandbox). It’s also the first part of your NetSuite URL: https://1234567.app.netsuite.com.
Step 6 — Back in Zencount: paste, save, test
- Paste Account ID, Client ID and Certificate ID into the Connect step and click Save setup.
- Click Test connection — a live query should report your subsidiaries (e.g. “Connected — 7 subsidiaries visible”). If the count is lower than expected, re-check Accessible Subsidiaries: All on the role.
- Set the connection’s Destination (entity + version, typically Actual), then use the Sync step to pull your consolidated P&L for a month range.
Why not just “Log in with NetSuite”?
Two NetSuite platform rules make the browser-login route a bad fit for a finance sync. First, third-party apps aren’t centrally registered, so even the browser flow would require the same admin setup in your account. Second, NetSuite’s browser-consent tokens expire after seven days — a nightly actuals sync would break every week and ask someone to log in again. The certificate handshake above is NetSuite’s intended way for exactly this kind of server-to-server integration: one setup, valid for two years, no logged-in user needed.
Troubleshooting
| Symptom | Most likely cause → fix |
|---|---|
| token request failed … invalid_grant | The three ids don’t match one M2M row: Client ID ↔ integration record, Certificate ID ↔ the M2M row’s certificate, and the row’s user/role must be active. NetSuite’s Login Audit Trail saved search (Setup → Users/Roles) shows the exact server-side reason for every failed attempt. |
| token request failed … invalid_client | NetSuite doesn’t recognize the client id it was sent. Check, in this order: the Client ID is the long value from the integration record’s confirmation page (not the Application ID, not the client secret, no stray spaces); the integration record is Enabled and still has the client-credentials grant + REST Web Services scope ticked; and the Account ID belongs to the same account the record lives in — the token endpoint is per-account, so a production Client ID tested against a sandbox Account ID (…_SB1) or vice versa reads as unknown. Still stuck? Reset Credentials on the integration record, paste the fresh Client ID into Zencount and test again — that rules out a stale or mangled id; the Login Audit Trail (row above) shows the server-side detail per attempt. |
| SuiteQL failed (403 / INSUFFICIENT_PERMISSION) | The role is missing a permission from the step-3 table — the error names the record; most often it’s SuiteAnalytics Workbook or a transaction-type View permission. |
| Discover shows only 2 source fields for Financial actuals | Expected — the actuals scope has exactly two mappable dimensions: GL account → account and subsidiary → entity (the discover summary also counts their values, e.g. “358 p&l accounts · 7 subsidiaries”). Transactions and journal entries don’t show up as extra fields because they arrive summed into the P&L amount per account × month — they are in the numbers, not in the field list. Department/location are not dimensions of the actuals pull today. |
| Test works, but numbers miss a subsidiary | Accessible Subsidiaries on the role isn’t All — elimination subsidiaries count too. |
| Regenerated the certificate in Zencount | The old key stops working immediately — upload the new .pem on the existing M2M row (edit it or create a new row) in NetSuite. |
| Sandbox vs. production | The whole setup lives per account. Use the sandbox Account ID (…_SB1) and repeat steps 2–5 inside the sandbox; after a sandbox refresh, redo them (refreshes wipe integrations). |
Legacy: Token-Based Auth
If you already have TBA tokens, the Token-Based Auth tab accepts the classic five values (Account ID, Consumer Key/Secret, Token ID/Secret). The role behind the access token needs the same step-3 permissions, with Log in using Access Tokens (Full) instead of the OAuth 2.0 login permission. NetSuite ends support for new TBA integrations with release 2027.1, so prefer OAuth 2.0 for anything new.
Next steps
- Connect a data source — the general Connect → Discover → Map → Sync flow.
- Chart of accounts — where mapped NetSuite accounts land.