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