Skip to main content

OAuth 2.0 Setup Guide for NetSuite

This comprehensive guide walks you through implementing OAuth 2.0 authentication in NetSuite, from initial configuration to testing with Postman.


Overview

What is OAuth 2.0?

OAuth 2.0 is the industry-standard protocol for authorization. NetSuite supports two OAuth 2.0 flows:

FlowUse CaseDescription
Client CredentialsMachine-to-MachineServer-to-server integration without user interaction
Authorization CodeUser ContextUser grants access to their NetSuite data

OAuth 2.0 Flow Diagram


Prerequisites

Before starting, ensure you have:

  • NetSuite Administrator role or equivalent permissions
  • Access to Setup > Company > Enable Features
  • OpenSSL installed (or Git Bash on Windows)
  • Postman installed for testing

Setup Checklist & Results Summary

Use this checklist to track your progress and understand how each step's output is used later.

StepActionResultUsed In
1Enable OAuth 2.0 Feature✅ OAuth 2.0 & REST Web Services enabledStep 2 (allows integration creation)
2Create Integration RecordConsumer Key (Client ID)Step 6 (Scheduler config)
Consumer SecretStore securely (backup only)
3.1Generate RSA Keypairprivate_key.pemStep 6 (upload to File Cabinet)
public_cert.pemStep 3.3 (upload to NetSuite)
3.2Verify Certificate & Key✅ Keys validated-
3.3Upload Certificate to NetSuite✅ Certificate registered-
3.4Note Certificate IDCertificate IDStep 6 (Scheduler config)
4Find NetSuite URLsAccount IDStep 6 (Scheduler config)
Token Endpoint URLStep 6 (token request)
REST API Base URLStep 8 (API calls)
5Deploy Token Proxy ScriptsScheduler + Suitelet deployedStep 6
6Configure Scheduler Parameters✅ All config values setStep 7
7Test Scheduler Execution✅ Bearer token generatedStep 8
8Test with Postman✅ API call successfulProduction ready

Quick Reference: Values You'll Need

┌────────────────────────────────────────────────────────────────────────┐
│ OAUTH 2.0 CONFIGURATION VALUES │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ From Step 2 (Integration Record): │
│ ├─ Consumer Key (Client ID): ____________________________ │
│ └─ Consumer Secret: ____________________________ │
│ │
│ From Step 3 (Certificate): │
│ ├─ private_key.pem location: ____________________________ │
│ └─ Certificate ID: ____________________________ │
│ │
│ From Step 4 (URLs): │
│ ├─ Account ID: ____________________________ │
│ ├─ Token URL: https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/ │
│ │ services/rest/auth/oauth2/v1/token │
│ └─ API URL: https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/ │
│ services/rest/query/v1/suiteql │
│ │
└────────────────────────────────────────────────────────────────────────┘

Dependency Flow Diagram


Step 1: Enable OAuth 2.0 Feature

First, enable OAuth 2.0 in your NetSuite account.

┌──────────────────────────────────────────────────────────────────────────┐
│ ENABLE OAUTH 2.0 FEATURE │
└──────────────────────────────────────────────────────────────────────────┘

Navigate to:
Setup → Company → Enable Features → SuiteCloud

Enable:
┌────────────────────────────────────────────────────────────────────┐
│ ☑ OAuth 2.0 │
│ ☑ REST Web Services │
│ ☑ SuiteScript (if using RESTlets) │
└────────────────────────────────────────────────────────────────────┘

Detailed Steps:

  1. Go to Setup → Company → Enable Features
  2. Click on the SuiteCloud subtab
  3. Under SuiteScript, ensure these are checked:
    • ☑ Client SuiteScript
    • ☑ Server SuiteScript
  4. Under Manage Authentication, check:
    • OAuth 2.0
  5. Under SuiteTalk (Web Services), check:
    • REST Web Services
  6. Click Save
Important

You must have the Administrator role to enable these features. Changes take effect immediately.

Step 1 Result

What you now have:

  • ✅ OAuth 2.0 feature enabled
  • ✅ REST Web Services enabled

Next: Step 2 - Create Integration Record


Step 2: Create Integration Record

The integration record identifies your external application to NetSuite.

┌──────────────────────────────────────────────────────────────────────────┐
│ CREATE INTEGRATION RECORD │
└──────────────────────────────────────────────────────────────────────────┘

Navigate to:
Setup → Integration → Manage Integrations → New

Integration Record Configuration

FieldValueNotes
NameMy OAuth2 IntegrationDescriptive name
StateEnabledMust be enabled
OAuth 2.0☑ CheckedEnable OAuth 2.0 for this integration
Token-Based Authentication☐ UncheckedNot needed for OAuth 2.0
Authorization Code Grant☑ or ☐Check if using Authorization Code flow
Client Credentials (M2M)☑ CheckedRequired for machine-to-machine
REST Web Services☑ CheckedEnable REST API access

Detailed Steps:

  1. Go to Setup → Integration → Manage Integrations
  2. Click New
  3. Fill in the form:
┌────────────────────────────────────────────────────────────────────────┐
│ INTEGRATION RECORD FORM │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Name: [My OAuth2 Integration ] │
│ │
│ Description: [OAuth 2.0 integration for external API access] │
│ │
│ State: [● Enabled ○ Blocked] │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ AUTHENTICATION │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ☑ OAuth 2.0 │
│ ├─ ☑ Client Credentials (Machine to Machine) Grant │
│ ├─ ☐ Authorization Code Grant │
│ └─ Callback URL: [ ] │
│ │
│ ☐ Token-Based Authentication │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ ACCESS │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ☑ REST Web Services │
│ ☐ User Access Tokens │
│ │
└────────────────────────────────────────────────────────────────────────┘
  1. Click Save

After Saving - IMPORTANT!

After saving, NetSuite displays your credentials ONLY ONCE:

┌────────────────────────────────────────────────────────────────────────┐
│ ⚠️ SAVE THESE CREDENTIALS IMMEDIATELY │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Consumer Key / Client ID: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Consumer Secret / Client Secret: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0f9e8d7c6b5a4z3y2x1w0 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ Copy these now! They cannot be retrieved later. │
│ │
└────────────────────────────────────────────────────────────────────────┘
Critical

Copy and securely store the Consumer Key and Consumer Secret immediately. They are shown only once and cannot be retrieved later. If lost, you must create a new integration.

Step 2 Result

What you now have:

  • Consumer Key (Client ID) → Used in Step 3.3 and Step 5 (JWT iss claim)
  • Consumer Secret → Store securely as backup

Next: Step 3 - Create OAuth 2.0 Certificate


Step 3: Create OAuth 2.0 Certificate

For Client Credentials flow, you need a certificate keypair (public certificate + private key) to sign your requests.

Generate your own keypair locally using OpenSSL and upload the public certificate to NetSuite. This approach ensures:

  • ✅ Private key never leaves your machine
  • ✅ Full control over key storage
  • ✅ Can use existing key management systems
  • ✅ Easier key rotation

Step 3.1: Generate RSA Keypair with X.509 Certificate

# Generate 4096-bit RSA private key and self-signed X.509 certificate (valid for 2 years)
openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_cert.pem -nodes -days 730

# When prompted, enter certificate details (or press Enter for defaults)
# Common Name (CN) can be your integration name, e.g., "NetSuite Integration"

# Verify the files were created
ls -la *.pem
Windows Users

If OpenSSL is not installed, you can use Git Bash (comes with Git for Windows) which includes OpenSSL.

Note: In Git Bash, use double slashes for the subject parameter to avoid path conversion:

openssl req -x509 -newkey rsa:4096 -keyout private_key.pem -out public_cert.pem -nodes -days 730 -subj "//CN=NetSuite Integration"

Step 3.2: Verify Generated Certificate and Key

# View private key info (don't share this!)
openssl rsa -in private_key.pem -check -noout

# View certificate info
openssl x509 -in public_cert.pem -text -noout

Expected output for private key:

RSA key ok

Expected output for certificate (truncated):

Certificate:
Data:
Version: 3 (0x2)
Serial Number: ...
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = NetSuite Integration
Validity
Not Before: ...
Not After : ...
Subject: CN = NetSuite Integration
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)

Step 3.3: Upload Certificate to NetSuite

┌──────────────────────────────────────────────────────────────────────────┐
│ UPLOAD CERTIFICATE TO NETSUITE │
└──────────────────────────────────────────────────────────────────────────┘

Navigate to:
Setup → Integration → OAuth 2.0 Client Credentials (M2M) Setup
  1. Go to Setup → Integration → OAuth 2.0 Client Credentials (M2M) Setup
  2. Click Create New
  3. Fill in the form:
┌────────────────────────────────────────────────────────────────────────┐
│ OAUTH 2.0 CLIENT CREDENTIALS SETUP │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Entity: [Select User/Employee ▼] │
│ Role: [Select Role (e.g., Administrator) ▼] │
│ Application: [My OAuth2 Integration ▼] │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ CERTIFICATE │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ Public Key Certificate: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ [Choose File] public_cert.pem │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ - OR paste PEM content: - │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ -----BEGIN CERTIFICATE----- │ │
│ │ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA... │ │
│ │ ... │ │
│ │ -----END CERTIFICATE----- │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
  1. Either:
    • Click Choose File and select your public_cert.pem, OR
    • Copy/paste the contents of public_cert.pem into the text area
  2. Click Save

Step 3.4: Note the Certificate ID

After saving, NetSuite assigns a Certificate ID:

┌────────────────────────────────────────────────────────────────────────┐
│ CERTIFICATE CREATED SUCCESSFULLY │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Certificate ID: 1234567 ← SAVE THIS! │
│ │
│ Entity: John Developer │
│ Role: Administrator │
│ Application: My OAuth2 Integration │
│ Public Key: Uploaded │
│ │
└────────────────────────────────────────────────────────────────────────┘
Step 3 Result

What you now have:

  • private_key.pem → Used in Step 6 (upload to File Cabinet)
  • public_cert.pem → Already uploaded to NetSuite
  • Certificate ID → Used in Step 6 (Scheduler config)

Collected so far:

ValueFrom StepUsed In
Consumer Key (Client ID)Step 2Step 6 (Scheduler config)
private_key.pemStep 3.1Step 6 (File Cabinet)
Certificate IDStep 3.4Step 6 (Scheduler config)

Next: Step 4 - Understand Your NetSuite URLs


Step 4: Understand Your NetSuite URLs

You need these URLs for OAuth 2.0:

Account-Specific URLs

┌────────────────────────────────────────────────────────────────────────┐
│ NETSUITE OAUTH 2.0 ENDPOINTS │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Token Endpoint (for getting access tokens): │
│ https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/auth/ │
│ oauth2/v1/token │
│ │
│ REST API Base URL: │
│ https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/ │
│ │
│ SuiteQL Endpoint: │
│ https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/ │
│ query/v1/suiteql │
│ │
└────────────────────────────────────────────────────────────────────────┘

How to Find Your Account ID

Your Account ID format depends on your NetSuite environment:

EnvironmentAccount ID FormatExample
ProductionNumeric or with suffix1234567 or 1234567_SB1
SandboxWith _SB# suffix1234567_SB1, 1234567_SB2

To find your Account ID:

  1. Go to Setup → Company → Company Information
  2. Look for Account ID field

Or check your NetSuite URL:

https://1234567.app.netsuite.com/...
↑↑↑↑↑↑↑
Account ID
Step 4 Result

What you now have:

  • Account ID → Used in Step 6 (Scheduler config)
  • Token Endpoint URL → Used in Step 6 (token request)
  • REST API Base URL → Used in Step 8 (API calls)

All values collected - Ready for Step 6:

ValueFrom StepUsed In Step 6
Consumer Key (Client ID)Step 2Scheduler CONFIG
private_key.pemStep 3.1Upload to File Cabinet
Certificate IDStep 3.4Scheduler CONFIG
Account IDStep 4Scheduler CONFIG

Next: Step 5 - Deploy Token Proxy Scripts


Step 5: Implement Token Proxy Pattern (Suitelet + Scheduler)

For scenarios where external systems cannot generate JWTs or manage private keys, you can implement a Token Proxy Pattern using a Suitelet and Scheduled Script within NetSuite.

Architecture Overview

When to Use This Pattern

Use CaseRecommended?
External system can't manage private keys✅ Yes
Simple internal tools/dashboards✅ Yes
Single trusted external system✅ Yes
Multiple untrusted external systems❌ No - use standard OAuth
High security requirements❌ No - use standard OAuth

Implementation Components

Create a custom record to store the token:

FieldTypeDescription
custrecord_oauth_tokenText AreaThe access token
custrecord_oauth_expiresDate/TimeToken expiration timestamp
custrecord_oauth_updatedDate/TimeLast refresh time
custrecord_oauth_statusListActive/Error/Expired

2. Scheduled Script (Token Refresher)

/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
* @NModuleScope SameAccount
*/
define(['N/https', 'N/record', 'N/encode', 'N/crypto', 'N/file'],
function(https, record, encode, crypto, file) {

const CONFIG = {
CLIENT_ID: 'your_consumer_key_here',
CERTIFICATE_ID: 'your_certificate_id_here',
ACCOUNT_ID: 'your_account_id_here',
PRIVATE_KEY_FILE_ID: 12345, // File Cabinet ID of private_key.pem
TOKEN_RECORD_ID: 1 // Custom record ID to update
};

function execute(context) {
try {
// 1. Load private key from File Cabinet
const privateKeyFile = file.load({ id: CONFIG.PRIVATE_KEY_FILE_ID });
const privateKey = privateKeyFile.getContents();

// 2. Generate JWT
const jwt = generateJWT(privateKey);

// 3. Request access token
const tokenResponse = requestAccessToken(jwt);

// 4. Store token in custom record
storeToken(tokenResponse);

log.audit('Token Refresh', 'Access token refreshed successfully');
} catch (e) {
log.error('Token Refresh Failed', e.message);
// Optionally send alert email
}
}

function generateJWT(privateKey) {
const tokenUrl = `https://${CONFIG.ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token`;

const now = Math.floor(Date.now() / 1000);
const exp = now + 3600;

const header = {
typ: 'JWT',
alg: 'RS256',
kid: CONFIG.CERTIFICATE_ID
};

const payload = {
iss: CONFIG.CLIENT_ID,
scope: ['rest_webservices'],
aud: tokenUrl,
exp: exp,
iat: now
};

// Base64URL encode
const headerB64 = base64UrlEncode(JSON.stringify(header));
const payloadB64 = base64UrlEncode(JSON.stringify(payload));
const signingInput = headerB64 + '.' + payloadB64;

// Sign with RS256
const signer = crypto.createSigner({
algorithm: crypto.HashAlg.SHA256,
key: crypto.createSecretKey({ secret: privateKey })
});
signer.update({ input: signingInput });
const signature = signer.sign({ outputEncoding: encode.Encoding.BASE_64_URL_SAFE });

return signingInput + '.' + signature;
}

function requestAccessToken(jwt) {
const tokenUrl = `https://${CONFIG.ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token`;

const response = https.post({
url: tokenUrl,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `grant_type=client_credentials&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=${jwt}`
});

if (response.code !== 200) {
throw new Error('Token request failed: ' + response.body);
}

return JSON.parse(response.body);
}

function storeToken(tokenResponse) {
const tokenRecord = record.load({
type: 'customrecord_oauth_token',
id: CONFIG.TOKEN_RECORD_ID
});

tokenRecord.setValue({
fieldId: 'custrecord_oauth_token',
value: tokenResponse.access_token
});

tokenRecord.setValue({
fieldId: 'custrecord_oauth_expires',
value: new Date(Date.now() + (tokenResponse.expires_in * 1000))
});

tokenRecord.setValue({
fieldId: 'custrecord_oauth_updated',
value: new Date()
});

tokenRecord.setValue({
fieldId: 'custrecord_oauth_status',
value: 1 // Active
});

tokenRecord.save();
}

function base64UrlEncode(str) {
const base64 = encode.convert({
string: str,
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.BASE_64
});
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

return { execute };
});

Deployment Configuration:

  • Schedule: Every 15 minutes
  • Status: Released

3. Suitelet (Token Endpoint)

/**
* @NApiVersion 2.1
* @NScriptType Suitelet
* @NModuleScope SameAccount
*/
define(['N/record', 'N/log'],
function(record, log) {

// ============================================
// Configuration
// ============================================
const CONFIG = {
TOKEN_RECORD_ID: 1,
// Hardcoded credentials for authentication
VALID_CREDENTIALS: {
'integration_user': 'SecureP@ssw0rd123',
'external_app': 'An0therS3cretKey!'
}
};

function onRequest(context) {
const response = context.response;
response.setHeader({ name: 'Content-Type', value: 'application/json' });

try {
// 1. Validate Username & Password
const username = context.request.parameters.username ||
context.request.headers['X-Username'];
const password = context.request.parameters.password ||
context.request.headers['X-Password'];

if (!validateCredentials(username, password)) {
log.audit('Auth Failed', 'Invalid credentials from: ' + username);
response.write(JSON.stringify({
error: 'unauthorized',
message: 'Invalid username or password'
}));
return;
}

// 2. Load token from custom record
const tokenRecord = record.load({
type: 'customrecord_oauth_token',
id: CONFIG.TOKEN_RECORD_ID
});

const accessToken = tokenRecord.getValue({ fieldId: 'custrecord_oauth_token' });
const expiresAt = tokenRecord.getValue({ fieldId: 'custrecord_oauth_expires' });
const status = tokenRecord.getValue({ fieldId: 'custrecord_oauth_status' });

// 3. Check if token is valid
if (status !== 1 || new Date() > expiresAt) {
response.write(JSON.stringify({
error: 'token_expired',
message: 'Token has expired, please wait for refresh'
}));
return;
}

// 4. Return token
response.write(JSON.stringify({
access_token: accessToken,
token_type: 'Bearer',
expires_at: expiresAt.toISOString()
}));

log.audit('Token Requested', 'Token provided to: ' + username);

} catch (e) {
log.error('Token Endpoint Error', e.message);
response.write(JSON.stringify({
error: 'server_error',
message: 'Failed to retrieve token'
}));
}
}

/**
* Validate username and password against hardcoded credentials
*/
function validateCredentials(username, password) {
if (!username || !password) {
return false;
}
return CONFIG.VALID_CREDENTIALS[username] === password;
}

return { onRequest };
});

Deployment Configuration:

  • Status: Released
  • Execute As Role: Role with REST permissions
  • Available Without Login: ✅ Yes (for external access)

Usage from External System

Suitelet External URL Format

When deployed with Available Without Login enabled, NetSuite provides an external URL:

┌────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL SUITELET URL STRUCTURE │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ https://{ACCOUNT_ID}.extforms.netsuite.com/app/site/hosting/ │
│ scriptlet.nl?script={SCRIPT_ID}&deploy={DEPLOY_ID}&compid={ACCOUNT} │
│ &ns-at={AUTH_TOKEN}&username={USER}&password={PASS} │
│ │
└────────────────────────────────────────────────────────────────────────┘
ParameterDescriptionExample
{ACCOUNT_ID}NetSuite account ID7051733_SB1
scriptScript internal ID350
deployDeployment ID1
compidCompany/Account ID7051733_SB1
ns-atNetSuite auth token (auto-generated)AAEJ7tMQ...
usernameYour integration usernameintegration_user
passwordYour integration passwordSecureP@ssw0rd123

Example Request

# Request token from external Suitelet
curl -X GET \
"https://7051733-sb1.extforms.netsuite.com/app/site/hosting/scriptlet.nl?script=350&deploy=1&compid=7051733_SB1&ns-at=AAEJ7tMQhgzW1OmUhus5KwaB7XwSQ2mRpOnplFnXKX-2o87tcNQ&username=integration_user&password=SecureP@ssw0rd123"
Finding the External URL

After deploying the Suitelet with "Available Without Login" enabled:

  1. Go to the Script Deployment record
  2. Look for the External URL field
  3. Copy the base URL and append your username and password parameters

Response

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMzQ1Njc4OTAifQ...",
"token_type": "Bearer",
"expires_at": "2024-01-15T10:30:00.000Z"
}

Using the Token to Call NetSuite REST API

# Use the access_token from the response
curl -X POST \
"https://7051733-sb1.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs..." \
-H "Content-Type: application/json" \
-H "Prefer: transient" \
-d '{"q": "SELECT id, companyname FROM customer WHERE rownum <= 10"}'

Complete Example Flow

Authentication Options Comparison

MethodProsCons
Username/Password (Current)Simple, familiarCredentials in URL (if using params)
Headers (X-Username/X-Password)More secure than URL paramsStill plaintext
API KeySingle value, simplerLess granular control
Basic AuthStandard HTTP authRequires base64 encoding
Recommended: Use Headers

Pass credentials via headers (X-Username, X-Password) instead of URL parameters to avoid credentials appearing in server logs.

Security Enhancements

┌────────────────────────────────────────────────────────────────────────┐
│ TOKEN PROXY SECURITY BEST PRACTICES │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ RECOMMENDED: │
│ ├─► Add API key authentication to Suitelet │
│ ├─► Use IP allowlist if possible │
│ ├─► Log all token requests for audit │
│ ├─► Store private key in File Cabinet (not script) │
│ ├─► Use custom record instead of script parameters │
│ ├─► Add error alerting (email on refresh failure) │
│ └─► Refresh token more frequently than expiry (15 min for 60 min) │
│ │
│ ⚠️ CONSIDERATIONS: │
│ ├─► All clients share one token (audit trail limited) │
│ ├─► Single point of failure (scheduler) │
│ └─► Token exposed if Suitelet URL is discovered │
│ │
└────────────────────────────────────────────────────────────────────────┘

Token Proxy Flow Diagram

Step 5 Result

What you now have:

  • ✅ Scheduled Script deployed
  • ✅ Suitelet deployed with external URL

Next: Step 6 - Configure Scheduler Parameters


Step 6: Configure Scheduler Parameters

Now configure the Scheduled Script with all the values collected from previous steps.

6.1: Upload Private Key to File Cabinet

  1. Go to Documents → Files → File Cabinet
  2. Navigate to or create a folder: SuiteScripts/OAuth
  3. Click Add File
  4. Upload your private_key.pem file
  5. Note the File ID (shown in the URL or file list)
┌────────────────────────────────────────────────────────────────────────┐
│ FILE CABINET - PRIVATE KEY │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Folder: SuiteScripts/OAuth │
│ File: private_key.pem │
│ File ID: 12345 ← NOTE THIS! │
│ │
└────────────────────────────────────────────────────────────────────────┘

6.2: Configure Script Parameters

  1. Go to Customization → Scripting → Scripts
  2. Find your Scheduled Script (Token Refresher)
  3. Click Edit or go to Deployments
  4. Update the script parameters or CONFIG object:
┌────────────────────────────────────────────────────────────────────────┐
│ SCHEDULER CONFIGURATION VALUES │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ CLIENT_ID: [Consumer Key from Step 2] │
│ CERTIFICATE_ID: [Certificate ID from Step 3.4] │
│ ACCOUNT_ID: [Account ID from Step 4] │
│ PRIVATE_KEY_FILE_ID: [File ID from Step 6.1] │
│ TOKEN_RECORD_ID: [Custom Record ID for token storage] │
│ │
└────────────────────────────────────────────────────────────────────────┘

6.3: Update Script CONFIG

Edit the Scheduled Script and update the CONFIG object:

const CONFIG = {
CLIENT_ID: 'a1b2c3d4e5f6...', // From Step 2
CERTIFICATE_ID: '1234567', // From Step 3.4
ACCOUNT_ID: '7051733_SB1', // From Step 4
PRIVATE_KEY_FILE_ID: 12345, // From Step 6.1
TOKEN_RECORD_ID: 1 // Your custom record ID
};

6.4: Verify Custom Record Exists

Ensure the custom record for token storage exists:

  1. Go to Customization → Lists, Records, & Fields → Record Types
  2. Find or create OAuth Token record
  3. Note the Internal ID of the record instance to store tokens
Step 6 Result

What you now have:

  • ✅ Private key uploaded to File Cabinet
  • ✅ Scheduler configured with all parameters:
    • Consumer Key (Client ID)
    • Certificate ID
    • Account ID
    • Private Key File ID
    • Token Record ID

Next: Step 7 - Test Scheduler Execution


Step 7: Test Scheduler Execution

Run the Scheduled Script manually to verify it can generate a Bearer token.

7.1: Execute Script Manually

  1. Go to Customization → Scripting → Scripts
  2. Find your Scheduled Script
  3. Click ViewDeployments
  4. Click on the deployment
  5. Click Execute Now (or Save and Execute)
┌────────────────────────────────────────────────────────────────────────┐
│ SCRIPT DEPLOYMENT │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Script: OAuth Token Refresher │
│ Deployment: customdeploy_oauth_refresher │
│ Status: Released │
│ │
│ [Execute Now] [Edit] [View Log] │
│ │
└────────────────────────────────────────────────────────────────────────┘

7.2: Check Execution Log

  1. After execution, click View Log or go to Customization → Scripting → Script Execution Logs
  2. Look for your script execution
  3. Verify success message:

Expected Success Log:

Type: Audit
Title: Token Refresh
Details: Access token refreshed successfully

If Error:

Type: Error
Title: Token Refresh Failed
Details: [Error message - check JWT generation or network issues]

7.3: Verify Token in Custom Record

  1. Go to your custom record list
  2. Find the OAuth Token record
  3. Verify the token was stored:
┌────────────────────────────────────────────────────────────────────────┐
│ OAUTH TOKEN RECORD │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs... │
│ Expires: 2024-01-15 11:30:00 │
│ Updated: 2024-01-15 10:30:00 │
│ Status: Active │
│ │
└────────────────────────────────────────────────────────────────────────┘

7.4: Test Suitelet Returns Token

  1. Open your Suitelet external URL in a browser (with credentials):

    https://{ACCOUNT_ID}.extforms.netsuite.com/app/site/hosting/scriptlet.nl
    ?script=XXX&deploy=X&compid={ACCOUNT_ID}&username=XXX&password=XXX
  2. Verify JSON response:

    {
    "access_token": "eyJhbGciOiJSUzI1NiIs...",
    "token_type": "Bearer",
    "expires_at": "2024-01-15T11:30:00.000Z"
    }
Step 7 Result

What you now have:

  • ✅ Scheduler executed successfully
  • ✅ Bearer token generated and stored
  • ✅ Suitelet returns token correctly

Next: Step 8 - Test with Postman


Step 8: Test with Postman

Final validation: Use Postman to get the token from Suitelet and call NetSuite REST API.

8.1: Get Token from Suitelet

Create a new request in Postman:

SettingValue
MethodGET
URLhttps://{ACCOUNT_ID}.extforms.netsuite.com/app/site/hosting/scriptlet.nl?script=XXX&deploy=X&compid={ACCOUNT_ID}

Headers:

HeaderValue
X-Usernameintegration_user
X-Passwordyour_password

Expected Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMzQ1Njc4OTAifQ...",
"token_type": "Bearer",
"expires_at": "2024-01-15T11:30:00.000Z"
}

8.2: Call NetSuite REST API with Token

Create another request to test the SuiteQL endpoint:

SettingValue
MethodPOST
URLhttps://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql

Headers:

HeaderValue
AuthorizationBearer {access_token from step 8.1}
Content-Typeapplication/json
Prefertransient

Body (raw JSON):

{
"q": "SELECT id, companyname FROM customer WHERE rownum <= 5"
}

Expected Response:

{
"links": [...],
"count": 5,
"hasMore": true,
"items": [
{ "id": "1", "companyname": "ABC Company" },
{ "id": "2", "companyname": "XYZ Corp" },
...
],
"offset": 0,
"totalResults": 100
}

8.3: Create Postman Collection (Optional)

Save both requests in a Postman collection for easy reuse:

📁 NetSuite OAuth 2.0
├── 📄 1. Get Token (Suitelet)
└── 📄 2. SuiteQL Query (REST API)

Tip: Use Postman environment variables:

  • {{account_id}} - Your NetSuite account ID
  • {{access_token}} - Token from Suitelet response
  • {{suitelet_url}} - Your Suitelet external URL

8.4: Troubleshooting

ErrorCauseSolution
401 UnauthorizedInvalid or expired tokenGet fresh token from Suitelet
403 ForbiddenRole lacks permissionsCheck role permissions in NetSuite
invalid_grantJWT generation failedVerify Certificate ID and private key
Token expiredScheduler not runningCheck scheduler deployment status
Connection refusedWrong URL formatVerify Account ID in URL
Step 8 Result - Setup Complete!

What you now have:

  • ✅ End-to-end OAuth 2.0 flow working
  • ✅ Token generation automated via Scheduler
  • ✅ External access via Suitelet
  • ✅ REST API calls validated with Postman

Your OAuth 2.0 integration is production ready!