# Working with NetSuite's SuiteTalk REST Web Services API

> Source: https://netray.co/erp-help/netsuite-suitetalk-rest-api-record-operations
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: ERP Help

## Short answer

SuiteTalk REST exposes NetSuite records at https://ACCOUNTID.suitetalk.api.netsuite.com/services/rest/record/v1/{recordType}/{id} and ad hoc SQL-like queries at /services/rest/query/v1/suiteql, both authenticated with TBA or OAuth 2.0. It returns JSON, paginates large result sets with limit/offset and a hasMore flag, and needs expandSubResources or a specific fields query to pull sublist and related data efficiently.

## Kind

advanced

## System

netsuite

## Erp

Oracle NetSuite

## Module

SuiteTalk REST Web Services

## Query

how to use NetSuite SuiteTalk REST Web Services API

## Alternate Queries

- netsuite rest record api pagination expandSubResources
- netsuite suiteql query endpoint
- netsuite rest api vs soap webservices
- netsuite rest api limit offset hasMore

## Applies To

NetSuite SuiteTalk REST Web Services, all supported record types and SuiteQL

## Steps

### Call SuiteTalk REST correctly

**Items:**

Build the base URL from the account ID: https://ACCOUNTID.suitetalk.api.netsuite.com/services/rest/record/v1/ for records, replacing dashes appropriately for sandbox accounts (ACCOUNTID-sb1, all lowercase, dash not underscore, for the REST host specifically).

Authenticate every request with a TBA Authorization header or an OAuth 2.0 bearer token; there is no separate REST-specific credential beyond the standard NetSuite auth methods.

GET a single record with /record/v1/{recordType}/{id}, for example /record/v1/salesorder/12345, which returns core fields and links to sublists rather than inlining them by default.

Add ?expandSubResources=true to inline sublist and subrecord data directly in the response instead of requiring a follow-up call per sublist link.

For search-style listing, GET /record/v1/{recordType} with q= for simple filters, or use limit and offset query parameters to page through results; the response includes totalResults, hasMore and links.next for the following page.

For arbitrary joins and aggregation, POST SuiteQL to /query/v1/suiteql with the query in the request body and a Prefer: transient header, then page using the same limit/offset pattern on the response.

Use PATCH for partial record updates (only the fields sent are changed) and PUT for a full replace; POST creates a new record and returns its id in the Location response header rather than the body.

Handle 404 (record does not exist or the role cannot see it), 400 with INVALID_FLD_VALUE-style error bodies for bad field values, and 403/INSUFFICIENT_PERMISSION for role restrictions, all returned as a JSON error object with a 'o:errorDetails' array.

## Sections

### REST versus SOAP SuiteTalk

**Body:**

SOAP SuiteTalk (the older platform.core / platform.messages WSDL-based API) is still supported and remains common in older integrations built before the REST API matured, but new integrations are steered toward REST because it is simpler to authenticate, easier to consume from modern HTTP clients, and returns plain JSON instead of SOAP envelopes.

REST does not yet cover every corner of SOAP's functionality for every record type, so some legacy integrations (particularly around certain financial or advanced order management records) still use SOAP for operations the REST layer does not expose, and it is worth checking NetSuite's REST record catalog before assuming full parity.

### Pagination and expandSubResources in practice

**Body:**

By default, list endpoints return a compact representation with just id and a self link for each item, which keeps payloads small but means a naive client ends up making one follow-up GET per record. Passing specific fields via the q parameter or a fields projection, and expandSubResources where sublists are needed, cuts this down significantly.

hasMore in the response tells you whether to continue paging; do not assume the absence of a 'next' link means you are done, and always check totalResults against the count of records actually retrieved when reconciling data, since some filtered views can undercount if permissions hide rows the querying role cannot see.

**Code:**

GET /services/rest/record/v1/salesorder?limit=100&offset=200
Authorization: Bearer <token>

{
  "links": [...],
  "count": 100,
  "hasMore": true,
  "totalResults": 4820,
  "items": [ { "id": "98765", "links": [...] }, ... ]
}

### SuiteQL for read-heavy integrations

**Body:**

SuiteQL lets you write a SQL-like SELECT against NetSuite's underlying tables (transaction, transactionline, customer, item, and many more), which is far more efficient than record-by-record REST calls when you need joins, aggregation or a large filtered extract for a data warehouse or BI tool.

SuiteQL is read-only and does not support DDL or DML; use it to query, and use the record endpoints (or SuiteScript) for writes. The Prefer: transient header is required on the query POST or NetSuite rejects it, which is a common first-time integration mistake.

## Pitfalls

- Forgetting Prefer: transient on SuiteQL POST requests and getting a confusing error instead of results.
- Assuming REST covers every field and sublist that SOAP or the UI exposes for a given record type; some are still SOAP or SuiteScript-only.
- Not paging past the default limit and silently processing only the first page of a large dataset.
- Using PUT when PATCH was intended, wiping fields that were not included in the request body.
- Hitting concurrency or rate limits on burst imports without backing off, which returns 429-style throttling errors under SuiteTalk's own governance separate from SuiteScript governance.

## Ai Angle

ERPray grounded on a customer's NetSuite schema can translate a plain-English data request straight into a validated SuiteQL query or the correct REST call sequence, including the expandSubResources and pagination details that usually take a few failed attempts to get right by hand. That is often the fastest path from 'I need this data out of NetSuite' to a working integration script.

## Faq

### Can SuiteTalk REST create records with sublists in one call?

**Answer:** Yes, POST the parent record with a sublist array in the body (for example a sales order with an item sublist) and NetSuite creates the parent and its lines together, similar to how record.create with sublists works in SuiteScript.

### Is SuiteQL the same as saved search formulas?

**Answer:** No. SuiteQL is a direct SQL-like query language against NetSuite's tables, giving you full joins and aggregation, whereas saved searches are built through NetSuite's search UI/API abstraction. SuiteQL is generally more flexible for ad hoc reporting and BI extracts.

### How do I find the exact REST endpoint for a custom record type?

**Answer:** Custom records are exposed under /record/v1/{customRecordScriptId} once the record type has REST access enabled on its definition (Customization > Lists, Records, & Fields > Record Types > the record > Access tab), not automatically for every custom record.

### Why do I get INSUFFICIENT_PERMISSION on a record that exists in the UI?

**Answer:** The role behind the token needs explicit view/edit permission on that record type and any restricted fields; being able to see a record as an administrator in the browser does not imply the integration role has the same access.

## Search terms

netsuite suitetalk rest api, netsuite rest record v1, netsuite suiteql query endpoint, netsuite rest api pagination, netsuite expandSubResources, netsuite rest api prefer transient, netsuite rest vs soap suitetalk, netsuite rest api hasMore, netsuite rest patch vs put

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
