NetSuite Token-Based Authentication (OAuth 1.0) Signature Code Generator
This generator writes Python and Node.js code that signs a NetSuite RESTlet or REST web services request with token-based authentication (OAuth 1.0a, HMAC-SHA256) and sends it. It is for developers debugging INVALID_LOGIN_ATTEMPT errors or replacing a Postman setup with a script.
Fill in
Production 1234567, sandbox 1234567_SB1 or 1234567-sb1. The code builds the realm as uppercase with an underscore.
Copy the External URL from the script deployment. The hostname uses lowercase and a hyphen for sandboxes.
1. Python 3 (standard library only)
import base64
import hashlib
import hmac
import json
import secrets
import time
import urllib.parse
import urllib.request
ACCOUNT_ID = "1234567"
HTTP_METHOD = "POST"
URL = "https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1"
CONSUMER_KEY = "<consumer_key>"
CONSUMER_SECRET = "<consumer_secret>"
TOKEN_ID = "<token_id>"
TOKEN_SECRET = "<token_secret>"
def enc(value):
# RFC 3986 percent encoding (same as PHP rawurlencode)
return urllib.parse.quote(str(value), safe="~")
def tba_header(method, url):
realm = ACCOUNT_ID.upper().replace("-", "_") # 1234567_SB1 style
parts = urllib.parse.urlsplit(url)
base_url = parts.scheme.lower() + "://" + parts.netloc.lower() + parts.path
oauth = {
"oauth_consumer_key": CONSUMER_KEY,
"oauth_token": TOKEN_ID,
"oauth_signature_method": "HMAC-SHA256",
"oauth_timestamp": str(int(time.time())),
"oauth_nonce": secrets.token_hex(16),
"oauth_version": "1.0",
}
params = list(oauth.items()) + urllib.parse.parse_qsl(parts.query, keep_blank_values=True)
normalized = "&".join(k + "=" + v for k, v in sorted((enc(k), enc(v)) for k, v in params))
base_string = method.upper() + "&" + enc(base_url) + "&" + enc(normalized)
key = enc(CONSUMER_SECRET) + "&" + enc(TOKEN_SECRET)
digest = hmac.new(key.encode(), base_string.encode(), hashlib.sha256).digest()
oauth["oauth_signature"] = base64.b64encode(digest).decode()
fields = ["realm=\"" + realm + "\""] + [k + "=\"" + enc(v) + "\"" for k, v in oauth.items()]
return "OAuth " + ", ".join(fields)
if __name__ == "__main__":
header = tba_header(HTTP_METHOD, URL)
print("Authorization:", header)
data = json.dumps({"ping": True}).encode() if HTTP_METHOD in ("POST", "PUT") else None
req = urllib.request.Request(URL, data=data, method=HTTP_METHOD,
headers={"Authorization": header, "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=60) as resp:
print(resp.status, resp.read().decode())2. Node.js (built-in crypto and fetch)
// Node.js 18 or later, no packages needed
const crypto = require('crypto');
const ACCOUNT_ID = '1234567';
const HTTP_METHOD = 'POST';
const URL_STRING = 'https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1';
const CONSUMER_KEY = '<consumer_key>';
const CONSUMER_SECRET = '<consumer_secret>';
const TOKEN_ID = '<token_id>';
const TOKEN_SECRET = '<token_secret>';
const enc = (value) => encodeURIComponent(String(value))
.replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
function tbaHeader(method, urlString) {
const realm = ACCOUNT_ID.toUpperCase().replace(/-/g, '_');
const url = new URL(urlString);
const baseUrl = url.protocol + '//' + url.host + url.pathname;
const oauth = {
oauth_consumer_key: CONSUMER_KEY,
oauth_token: TOKEN_ID,
oauth_signature_method: 'HMAC-SHA256',
oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
oauth_nonce: crypto.randomBytes(16).toString('hex'),
oauth_version: '1.0',
};
const pairs = Object.entries(oauth).concat(Array.from(url.searchParams.entries()))
.map(([k, v]) => [enc(k), enc(v)])
.sort((a, b) => (a[0] === b[0] ? (a[1] < b[1] ? -1 : 1) : (a[0] < b[0] ? -1 : 1)));
const normalized = pairs.map(([k, v]) => k + '=' + v).join('&');
const baseString = method.toUpperCase() + '&' + enc(baseUrl) + '&' + enc(normalized);
const key = enc(CONSUMER_SECRET) + '&' + enc(TOKEN_SECRET);
oauth.oauth_signature = crypto.createHmac('sha256', key).update(baseString).digest('base64');
const fields = ['realm="' + realm + '"'].concat(
Object.entries(oauth).map(([k, v]) => k + '="' + enc(v) + '"'));
return 'OAuth ' + fields.join(', ');
}
(async () => {
const header = tbaHeader(HTTP_METHOD, URL_STRING);
const options = { method: HTTP_METHOD, headers: { Authorization: header, 'Content-Type': 'application/json' } };
if (HTTP_METHOD === 'POST' || HTTP_METHOD === 'PUT') options.body = JSON.stringify({ ping: true });
const res = await fetch(URL_STRING, options);
console.log(res.status, await res.text());
})();Never commit real secrets: load them from environment variables or a vault in production. HMAC-SHA1 support ended in 2023.1, so the method is fixed at HMAC-SHA256. Query string parameters (script, deploy and any others) are part of the signature; a JSON body is not. Oracle has announced that from 2027.1 no new TBA integrations can be created for SOAP, REST web services or RESTlets; existing ones keep working, and new work should use OAuth 2.0.
Stuck on INVALID_LOGIN_ATTEMPT?
Send us the redacted header and audit trail detail and we will point to the part of the signature that is wrong.
Optional. The tool and every export work without this. No spam.
How to use it
Create an integration record with Token-Based Authentication checked (Setup > Integration > Manage Integrations) and copy the consumer key and secret, which are shown only once. Then create an access token for the user and role (Setup > Users/Roles > Access Tokens) and copy the token ID and secret. Paste all four, the account ID and the RESTlet External URL from the script deployment, choose the method, and run either block. The script prints the header and the response, so you can compare the header with what Postman generates when something fails.
- The role needs the User Access Tokens and Log in using Access Tokens permissions.
- For REST web services also grant REST Web Services permission.
- Signatures expire: timestamps must be close to NetSuite server time.
How the signature is built
The code follows the OAuth 1.0a rules Oracle documents for RESTlets and REST web services. It collects oauth_consumer_key, oauth_token, oauth_signature_method (HMAC-SHA256), oauth_timestamp, oauth_nonce and oauth_version (1.0), adds every query string parameter from the URL, percent-encodes each name and value per RFC 3986, sorts them and joins them with &. The base string is the uppercase method, the encoded base URL without the query string, and the encoded parameter string, joined by &. The key is the encoded consumer secret and token secret joined by &. The signature is the Base64 of the HMAC-SHA256 digest. The realm goes in the header but not in the base string.
Version differences and gotchas
The realm and the hostname spell sandbox accounts differently: the realm is uppercase with an underscore (1234567_SB1) while the hostname is lowercase with a hyphen (1234567-sb1.restlets.api.netsuite.com). Mixing them up gives INVALID_LOGIN_ATTEMPT with no further detail, and the Login Audit Trail then shows the reason. Other common causes: forgetting script and deploy in the signed parameters, a nonce reused within the timestamp window, a clock that is minutes off, and encoding with a form encoder that turns spaces into + instead of %20. HMAC-SHA1 no longer works after 2023.1. Because the header covers the query string, every distinct URL, including each paging offset, needs its own signature.
Checking the result in NetSuite
When a call fails, open Setup > Users/Roles > View Login Audit Trail, filter on the user, and add the Detail and Token-based Access Token Name columns. The detail column says whether the signature, nonce, timestamp, token or role was the problem. A 403 with a valid login usually means the role lacks permission for what the RESTlet does, not an authentication fault. For RESTlets, the script deployment must be Released with the calling role in the audience; a deployment in Testing runs only for the script owner. Check the RESTlet execution log for errors once authentication passes.
Frequently Asked Questions
How do I authenticate to a NetSuite RESTlet?
Use OAuth 2.0 for new work, or token-based authentication on existing integrations: an Authorization: OAuth header with realm, consumer key, token, nonce, timestamp, HMAC-SHA256 signature method, version 1.0 and the signature. The signature covers the method, the URL without query and all oauth and query parameters, including script and deploy.
Where do I get a NetSuite API key for TBA?
NetSuite has no single API key. The consumer key and secret come from an integration record with Token-Based Authentication enabled, shown once at save. The token ID and secret come from an access token created for a specific user, role and integration. Together the four values sign each request.
Why do I get INVALID_LOGIN_ATTEMPT with TBA?
Most often the signature does not match: wrong realm format for sandboxes, query parameters left out of the base string, HMAC-SHA1 instead of HMAC-SHA256, or + used for spaces. Expired or revoked tokens, a role without access token permissions and clock drift cause it too. The Login Audit Trail detail column names the reason.
Is NetSuite token-based authentication being retired?
Oracle states that as of 2027.1 no new TBA integrations can be created for SOAP web services, REST web services and RESTlets, while existing integrations continue working. New RESTlet and REST integrations should use OAuth 2.0, either authorization code or the client credentials machine-to-machine flow.
If you want integrations and an assistant that read NetSuite under the right role with keys kept on your own servers, we can set that up.
Related Tools
NetSuite OAuth 2.0 Client Credentials (M2M) JWT Code Generator
Generate the openssl commands, the signed JWT client assertion and the token request for NetSuite machine-to-machine OAuth 2.0, in Python and Node.js.
NetSuiteNetSuite Token-Based Authentication Setup Checklist
Every step to configure token-based authentication for NetSuite REST, SOAP or RESTlet calls, from features and roles to tokens and test calls.
Developer ToolsNetSuite SuiteScript 2.1 RESTlet Skeleton Generator
Fill in a script name, record type and fields to get a SuiteScript 2.1 RESTlet with GET lookup, POST create, logging and error handling.
Developer ToolsNetSuite REST API SuiteQL Request Generator
Turn a SuiteQL statement into a ready POST to the REST query/v1/suiteql endpoint, plus curl and a Python script that pages through every result.
More Developer Tools tools
See all- NetSuite SuiteScript Governance Units by Script Type
- NetSuite API Retry Backoff Wait Time Calculator
- NetSuite OAuth 2.0 Access Token Refresh Calculator
- NetSuite REST Web Services Headers and Query Parameters Reference
- SuiteScript 1.0 to 2.1 API Mapping Reference for NetSuite
- NetSuite REST API Sales Order and Journal Entry Request Generator
- SuiteScript 2.1 Scheduled Script Generator for NetSuite
- SuiteScript 2.1 Client Script Generator for NetSuite
- SuiteScript 2.1 Suitelet Form Generator for NetSuite
Keep exploring
Free tools
- NetSuite Token-Based Authentication Setup ChecklistEvery step to configure token-based authentication for NetSuite REST, SOAP or RESTlet calls, from features and roles to tokens and test calls.
- SuiteScript 1.0 to 2.1 Migration Checklist for NetSuiteA five-stage checklist for moving NetSuite scripts from SuiteScript 1.0 to 2.1: inventory, design, code changes, deployment cutover and testing.
- SuiteScript 1.0 to 2.1 API Mapping Reference for NetSuiteLook up the SuiteScript 2.x module and method that replaces each common nlapi or nlobj call, with governance and behaviour differences to watch during migration.
ERP fixes and how-tos
- Authenticating NetSuite RESTlets with OAuth 2.0 and Token-Based AuthenticationSet up an Integration record, choose between OAuth 2.0 client credentials and Token-Based Authentication for a NetSuite RESTlet, and fix the 401 Invalid Login Attempt errors that come from clock skew, realm or role permissions.
- Oracle ERP API Documentation: Where It Is and How to StartWhere the Oracle ERP API documentation lives: Fusion Cloud REST and SOAP, FBDI, BI reports, NetSuite SuiteTalk, plus auth methods and an example call.
- Working with NetSuite's SuiteTalk REST Web Services APICall NetSuite's SuiteTalk REST record and SuiteQL endpoints correctly: base URLs, pagination with limit/offset/hasMore, expandSubResources, the Prefer: transient header, and common 404 and permission errors.
Integrations
- How to add AI agents to Oracle NetSuiteHow to add AI agents to Oracle NetSuite: SuiteQL and REST tool layer, RESTlet writes, role-scoped tokens, human approval, and where a private model fits.
- How to build an MCP server for Oracle NetSuiteDesign an MCP server for Oracle NetSuite: which tools to expose, SuiteQL and RESTlet backing, OAuth scoping, transports, audit, and read-only first.