Skip to content

Connect to NetSuite

Pull financial and operational data out of NetSuite into PlaidCloud tables with SuiteQL. This guide takes you from an empty NetSuite integration to a scheduled, typed pull. For the field-by-field reference behind it, see the NetSuite REST Connector.

You need, in NetSuite:

  • OAuth 2.0 and REST Web Services enabled (Setup > Company > Enable Features > SuiteCloud).
  • An RSA key pair — the public certificate uploaded to NetSuite, the private key kept for PlaidCloud.
  • A certificate id (the JWT kid, from uploading the public cert), a client id (from the integration record), and your account id (production 1234567 or sandbox 1234567_SB1).
  • A role with REST Web Services, Log in using OAuth 2.0 Access Tokens, SuiteAnalytics Workbook (if your queries touch analytics), and view access to the records you’ll read.

The full NetSuite-side steps — generating the key pair, uploading the certificate, and creating the integration — are in the reference’s NetSuite-Side Setup. Complete those first; this guide assumes you have the four values in hand.

  1. Open Tools > Connections and click New Connection.

  2. Choose NetSuite from the menu.

  3. Fill in the fields:

    Field Value
    Name A friendly name, e.g. NetSuite Prod.
    Oauth2 client id The client id from the integration record.
    Netsuite certificate id The certificate id (kid) from the public-cert upload.
    Netsuite account id Your account id, e.g. 1234567 or 1234567_SB1.
    Netsuite private certificate The full private-key PEM block, pasted multi-line.
  4. Click Create.

Confirm the credentials work before you build anything on top of them.

  1. Add a REST Request step to a workflow.

  2. On the Request tab, set Connection to your NetSuite connection, Method to POST, and Endpoint to /services/rest/query/v1/suiteql.

  3. In the Body, put a tiny bounded query:

    {"q": "SELECT id FROM account WHERE ROWNUM <= 1"}
  4. Click Send Test Request.

With the smoke test green, turn the step into a real pull.

  1. In the Body, write your SuiteQL query with a deterministic ORDER BY. For copy-paste starting points — journal entries, trial balance, P&L, balance sheet, open A/R, sales orders, POs, inventory — see NetSuite SuiteQL Query Examples.

    Financial — GL transaction lines for a period:

    {"q": "SELECT tl.transaction AS transaction_id, TO_CHAR(t.trandate, 'YYYY-MM-DD') AS tran_date, a.acctnumber AS account_number, tl.subsidiary AS subsidiary_id, tl.foreignamount AS amount FROM transactionline tl JOIN transaction t ON t.id = tl.transaction JOIN account a ON a.id = tl.expenseaccount WHERE t.trandate BETWEEN TO_DATE('2026-01-01','YYYY-MM-DD') AND TO_DATE('2026-01-31','YYYY-MM-DD') ORDER BY tl.transaction, tl.id"}

    Operational — open sales orders by customer:

    {"q": "SELECT so.tranid AS order_number, so.entity AS customer_id, TO_CHAR(so.trandate, 'YYYY-MM-DD') AS order_date, so.status AS order_status FROM transaction so WHERE so.type = 'SalesOrd' AND so.status = 'SalesOrd:B' ORDER BY so.id"}
  2. Leave the Pagination & Parsing controls and the Prefer/Content-Type headers alone — the NetSuite connection injects them.

  3. On the Response tab, keep the destination as Table and define the columns with real types: Currency/Decimal for amounts, Date/Datetime for dates, Boolean for T/F flags. SuiteQL returns everything as strings; the typed columns tell PlaidCloud how to coerce them.

  1. Run the step (see Running one step in a workflow) and check the target table — row counts, and that amount/date columns arrived typed, not as text.

  2. For an ongoing feed, schedule the workflow.

Symptom Likely cause
Test request 400 with a scope or role message The role is missing REST Web Services / OAuth 2.0 login / SuiteAnalytics, or the scope array was rejected — the surfaced detail names it.
Token exchange fails Wrong account id form (missing _SB1), wrong certificate id (kid), or a private key that doesn’t match the uploaded public cert.
Type/conversion error on import (names the column and value) A typed column got a value it can’t parse — most often a Date/Datetime column where SuiteQL returned the account’s display format. Wrap the field in TO_CHAR(col, 'YYYY-MM-DD') in the query, or set the column type to Text.
Totals don’t tie across subsidiaries amount is in each transaction’s own currency — use consolidated/converted fields.
Duplicate or missing rows across a large pull No deterministic ORDER BY on an offset-paged query.