Connect NetSuite
Tasks reach NetSuite through qbash.integrations.netsuite, which calls NetSuite's SuiteTalk REST web services. qbash signs in with OAuth 2.0 client credentials. You create an integration record, upload a certificate for an employee and a role, and give qbash the account ID, client ID, certificate ID and the certificate's private key. qbash uses the key to get a new access token whenever the hour-long one runs out, so scheduled tasks keep running without anyone signing in.
After you connect NetSuite, you can add its tools to a qbash MCP server for ChatGPT, Claude and other MCP clients, or call it from a Python task. The NetSuite MCP server page lists what the connector can do.
Tasks can reach only what the mapped role can reach. Give the role the permissions your tasks need and no others.
Each NetSuite account is set up on its own. A sandbox has its own account ID, integration record and certificate mapping. Refreshing a sandbox removes its mapping.
Turn on REST web services and OAuth 2.0
- Go to Setup > Company > Enable Features and open the SuiteCloud subtab.
- Under SuiteTalk (Web Services), check REST Web Services.
- Under Manage Authentication, check OAuth 2.0.
- Click Save.
Create a role and give it to an employee
- Go to Setup > Users/Roles > Manage Roles > New and name the role, for example qbash.
- On the Permissions subtab, add these on Setup: REST Web Services and Log in using OAuth 2.0 Access Tokens, both at Full.
- On Reports, add SuiteAnalytics Workbook at View. SuiteQL queries need it.
- On Transactions and Lists, add each record type your tasks read at View, and each one they create or change at Create or Edit.
- In a OneWorld account, set the role's subsidiary restrictions to the subsidiaries tasks should see.
- Save the role, open the employee that tasks will act as, and add the role on the employee's Access subtab.
NetSuite's system notes record each change a task makes under that employee, so an employee kept for integrations makes those changes easy to tell apart.
Create the integration record
- Go to Setup > Integration > Manage Integrations > New.
- Name it qbash and leave State at Enabled.
- On the Authentication subtab, clear the token-based authentication options and Authorization Code Grant, then check Client Credentials (Machine to Machine) Grant.
- Under Scope, check REST Web Services.
- Click Save and copy the Client ID from the confirmation page. NetSuite shows it only once. qbash does not use the client secret.
Create a certificate
Run this on your computer. It writes a private key and a certificate that is valid for two years, the longest NetSuite accepts:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 730 \
-subj "/CN=qbash" -keyout netsuite-qbash-key.pem -out netsuite-qbash-cert.pem
NetSuite also accepts RSA keys of 3072 or 4096 bits, if your security policy calls for RSA. Keep netsuite-qbash-key.pem private. Only qbash needs it.
Map the certificate
- Go to Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup and click Create New.
- Choose the employee as Entity, the new role as Role, and the qbash integration record as Application.
- Upload
netsuite-qbash-cert.pemand click Save. - Copy the Certificate ID from the new row.
A certificate serves one combination of integration record, role and employee. An integration record can hold five active certificates, so a replacement can be mapped and switched over in qbash before the old certificate expires.
Add the account in qbash
- Open Connectors, then NetSuite, and click Connect.
- Enter the account ID from Setup > Company > Company Information. Sandbox IDs look like
1234567_SB1. The form also accepts the address of any NetSuite page in that account, such ashttps://1234567.app.netsuite.com/app/center/card.nl. - Paste the client ID, the certificate ID and the whole of
netsuite-qbash-key.pem, including its BEGIN and END lines. - Save. qbash asks NetSuite for an access token before saving, so a wrong value or an inactive mapping shows up in the form, before any task runs.
- In the task editor, open Resources → Integrations, and select the credential. See Connect resources.
Call NetSuite from a task
run_suiteql runs SuiteQL, NetSuite's SQL dialect, for reports and lookups across records:
orders = qbash.integrations.netsuite.run_suiteql(
query=(
"SELECT id, tranid, TO_CHAR(trandate, 'YYYY-MM-DD') AS trandate, foreigntotal "
"FROM transaction WHERE type = 'SalesOrd' AND trandate >= TO_DATE(?, 'YYYY-MM-DD') ORDER BY id"
),
query_params=["2026-10-01"],
)
for row in orders["items"]:
print(row["tranid"], row["trandate"], row["foreigntotal"])
create_record with an external_id creates a record once. A second call with the same external ID writes nothing and returns the existing record with created set to False:
result = qbash.integrations.netsuite.create_record(
record_type="salesOrder",
external_id="EDI-850-4471",
values={
"entity": {"id": "1042"},
"memo": "EDI 850 4471",
"item": {"items": [{"item": {"id": "77"}, "quantity": 12}]},
},
)
print(result["created"], result["id"])
To change a line on an existing order, read the order with its lines and send the line's line value with the new quantity. With if_last_modified, qbash compares the order's lastModifiedDate just before the update and refuses the update if the order changed after the task read it:
order = qbash.integrations.netsuite.get_record(
record_type="salesOrder", external_id="EDI-850-4471", expand_sub_resources=True
)["record"]
for line in order["item"]["items"]:
if line["item"]["id"] == "77" and line["quantity"] != 10:
qbash.integrations.netsuite.update_record(
record_type="salesOrder",
record_id=order["id"],
values={"item": {"items": [{"line": line["line"], "item": {"id": "77"}, "quantity": 10}]}},
if_last_modified=order["lastModifiedDate"],
)
Both write methods read the record back after the write and return it as record, so the task can check what NetSuite stored. If that read fails, record is None and warnings says why. list_record_types and describe_record_type find the account's custom record types and field IDs. The SDK Reference lists every method.
Details that change results
- SuiteQL returns column names in lowercase and checkboxes as
'T'or'F'. Dates come back in the account's date format unless the query converts them withTO_CHAR. - A SuiteQL query pages through at most 100,000 rows. For more, order by
idand filter on the lastidof the previous page. get_recordincludes sublists, such as an order's lines or a fulfillment's packages, only withexpand_sub_resources=True.update_recordmatches lines by theirlinevalue and adds any line sent without one.- External IDs can contain letters, numbers, underscores and hyphens.
create_recordandupdate_recordrefuse payments, refunds, deposits, checks, fund transfers, cash sales, credit card transactions and journal entries, which move money or post to the general ledger.- Transaction amounts are in the transaction's currency. GL lines in
transactionaccountinglineare in the base currency. - NetSuite caps how many requests an account runs at once, across all of its integrations. qbash waits and retries when NetSuite reports the cap, but a task that fires many calls at once can still hit it.
- When the certificate expires or a sandbox refresh clears the mapping, NetSuite stops issuing tokens. Map a new certificate, then update the certificate ID and private key in qbash.
Add test data
Run tasks against a sandbox before production. A sandbox is a copy of production from its last refresh, so it has customers, items and transactions to read. Writes made there never reach production. When you test writes, give every record an external ID that starts with TEST-, so the test records are easy to find and delete afterwards.
Oracle and NetSuite are registered trademarks of Oracle and/or its affiliates.