Skip to main content

NetSuite REST API Reference

The NetSuite REST API v1 provides RESTful web services for creating, reading, updating, and deleting NetSuite records.


What is the REST API?​

The NetSuite REST API allows external applications to interact with NetSuite records using standard HTTP methods:

  • GET - Retrieve records
  • POST - Create new records
  • PATCH - Update existing records
  • DELETE - Remove records
When to Use REST API vs SuiteTalk (SOAP)
  • REST API: Simple CRUD operations, modern integrations, JSON payloads
  • SuiteTalk: Complex searches, bulk operations, legacy integrations

Authentication​

The REST API requires OAuth 2.0 authentication. See the OAuth 2.0 Setup Guide for detailed configuration steps.

Required Headers​

Every API request must include:

Authorization: Bearer {access_token}
Content-Type: application/json

Optional Headers​

HeaderDescriptionExample
PreferControl response formatreturn=representation
X-NetSuite-Idempotency-KeyPrevent duplicate createsuuid-12345

Base URL Structure​

All REST API endpoints follow this pattern:

https://{account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/{record_type}

URL Components​

ComponentDescriptionExample
{account_id}Your NetSuite account ID1234567
{record_type}The record typesalesOrder, customer, invoice

Example Endpoints​

# Get all sales orders
GET https://1234567.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder

# Get specific sales order by ID
GET https://1234567.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder/12345

# Create new customer
POST https://1234567.suitetalk.api.netsuite.com/services/rest/record/v1/customer

# Update invoice
PATCH https://1234567.suitetalk.api.netsuite.com/services/rest/record/v1/invoice/67890

HTTP Methods​

GET - Retrieve Records​

# List records (returns first page)
GET /record/v1/{record_type}

# Get specific record
GET /record/v1/{record_type}/{id}

# Filter with query parameters
GET /record/v1/customer?q=companyName LIKE 'Acme%'

POST - Create Record​

POST /record/v1/{record_type}
Content-Type: application/json

{
"entityId": "CUST-001",
"companyName": "Acme Corp",
"email": "contact@acme.com"
}

PATCH - Update Record​

PATCH /record/v1/{record_type}/{id}
Content-Type: application/json

{
"email": "newemail@acme.com",
"phone": "555-0123"
}
Partial Updates

PATCH only sends fields you want to change. Omitted fields remain unchanged.

DELETE - Remove Record​

DELETE /record/v1/{record_type}/{id}
Deletion Restrictions

Some records cannot be deleted if:

  • They have dependent transactions
  • They're system-required
  • Accounting periods are locked

Response Format​

Success Response (200 OK)​

{
"links": [
{
"rel": "self",
"href": "https://1234567.suitetalk.api.netsuite.com/services/rest/record/v1/customer/123"
}
],
"id": "123",
"refName": "Acme Corp",
"entityId": "CUST-001",
"companyName": "Acme Corp",
"email": "contact@acme.com",
"lastModifiedDate": "2025-12-25T10:30:00Z"
}

Error Response (400 Bad Request)​

{
"type": "https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1",
"title": "Bad Request",
"status": 400,
"o:errorDetails": [
{
"detail": "Invalid reference key 999999 for entity.",
"o:errorCode": "INVALID_KEY_OR_REF"
}
]
}

Common HTTP Status Codes​

CodeMeaningDescription
200OKRequest successful
201CreatedRecord created successfully
204No ContentRecord deleted successfully
400Bad RequestInvalid request payload
401UnauthorizedAuthentication failed
403ForbiddenInsufficient permissions
404Not FoundRecord doesn't exist
409ConflictDuplicate record or concurrency issue
429Too Many RequestsRate limit exceeded
500Internal Server ErrorNetSuite server error

Query Parameters​

Filtering​

Use the q parameter for filtering:

# Simple filter
GET /record/v1/customer?q=companyName LIKE 'Acme%'

# Multiple conditions
GET /record/v1/salesOrder?q=status='Pending Approval' AND totalAmount > 1000

# Date filters
GET /record/v1/invoice?q=tranDate BETWEEN '2025-01-01' AND '2025-12-31'

Pagination​

ParameterDescriptionExample
limitRecords per page (max 1000)limit=100
offsetStarting recordoffset=100
# Get next 100 records
GET /record/v1/customer?limit=100&offset=100

Expanding Sublists​

Use expandSubResources=true to include sublist items:

GET /record/v1/salesOrder/12345?expandSubResources=true

Rate Limits​

NetSuite enforces concurrency limits based on your license:

LicenseConcurrent RequestsRequests per Second
Standard110
Premium525
Premium Plus1050
Rate Limit Exceeded

When you hit the limit, you'll receive a 429 Too Many Requests response. Implement exponential backoff in your integration.


Idempotency​

Prevent duplicate record creation by using the X-NetSuite-Idempotency-Key header:

POST /record/v1/salesOrder
X-NetSuite-Idempotency-Key: order-20251225-001
Content-Type: application/json

{
"entity": {"id": "123"},
"tranDate": "2025-12-25"
}

If the request is sent twice with the same key:

  • First request: Creates record, returns 201 Created
  • Subsequent requests: Returns existing record, returns 200 OK

Field References​

Internal IDs vs Display Names​

Most reference fields accept either internal ID or display name:

{
"entity": {"id": "123"}, // Internal ID
"subsidiary": {"refName": "US"} // Display name
}

Common Reference Pattern​

{
"fieldName": {
"id": "internal_id", // Option 1: Use internal ID
"refName": "display_name" // Option 2: Use display name
}
}

Record Categories​

The REST API supports CRUD operations on these record types:

Entities​

Transactions​

Items​

Setup Records​


Best Practices​

1. Use PATCH for Updates​

Only send fields you're changing:

// Good - Only update email
{
"email": "newemail@example.com"
}

// Bad - Sending all fields
{
"entityId": "CUST-001",
"companyName": "Acme",
"email": "newemail@example.com",
"phone": "555-0100",
// ... 50 more fields
}

2. Handle Errors Gracefully​

try {
const response = await createCustomer(payload);
} catch (error) {
if (error.status === 409) {
// Duplicate - maybe update instead?
} else if (error.status === 400) {
// Validation error - check o:errorDetails
} else if (error.status === 429) {
// Rate limit - wait and retry
}
}

3. Use Expand Sparingly​

expandSubResources=true increases response size significantly. Only use when you need sublist data.

4. Implement Retry Logic​

For transient errors (500, 503), implement exponential backoff:

async function retryRequest(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000);
}
}
}

See Also​