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
- 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
| Header | Description | Example |
|---|---|---|
Prefer | Control response format | return=representation |
X-NetSuite-Idempotency-Key | Prevent duplicate creates | uuid-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
| Component | Description | Example |
|---|---|---|
{account_id} | Your NetSuite account ID | 1234567 |
{record_type} | The record type | salesOrder, 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"
}
PATCH only sends fields you want to change. Omitted fields remain unchanged.
DELETE - Remove Record
DELETE /record/v1/{record_type}/{id}
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
| Code | Meaning | Description |
|---|---|---|
200 | OK | Request successful |
201 | Created | Record created successfully |
204 | No Content | Record deleted successfully |
400 | Bad Request | Invalid request payload |
401 | Unauthorized | Authentication failed |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Record doesn't exist |
409 | Conflict | Duplicate record or concurrency issue |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | NetSuite 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
| Parameter | Description | Example |
|---|---|---|
limit | Records per page (max 1000) | limit=100 |
offset | Starting record | offset=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:
| License | Concurrent Requests | Requests per Second |
|---|---|---|
| Standard | 1 | 10 |
| Premium | 5 | 25 |
| Premium Plus | 10 | 50 |
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
- Sales: Sales Order, Invoice, Cash Sale
- Purchase: Purchase Order, Vendor Bill
- Financial: Journal Entry, Check, Deposit
Items
- Inventory Item
- Assembly Item
- Service Items
- And more...
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
- OAuth 2.0 Setup Guide - Configure authentication
- Integration Patterns - Common integration architectures
- SuiteQL - Query NetSuite data with SQL