---
# === IDENTITY ===
id: business/erp-integration/netsuite-tba-vs-oauth2/2026
canonical_question: "What are the differences between NetSuite TBA (OAuth 1.0) and OAuth 2.0 — deprecation, SOAP limitation?"
aliases:
  - "NetSuite Token-Based Authentication vs OAuth 2.0"
  - "NetSuite TBA deprecation timeline"
  - "NetSuite OAuth 2.0 SOAP limitation"
  - "NetSuite machine-to-machine authentication OAuth 2.0 client credentials"
  - "NetSuite TBA to OAuth 2.0 migration"
  - "Which NetSuite auth method should I use — TBA or OAuth 2.0?"
entity_type: erp_integration
domain: business > erp-integration > NetSuite TBA vs OAuth 2.0
region: global
jurisdiction: global
temporal_scope: 2025-2028

# === SYSTEM PROFILE ===
systems:
  - name: "Oracle NetSuite"
    vendor: "Oracle"
    version: "2026.1"
    edition: "All editions"
    deployment: cloud
    api_surface: "REST, SOAP, RESTlet, SuiteAnalytics Connect"

# === VERIFICATION ===
last_verified: 2026-03-01
confidence: 0.93
version: 1.0
first_published: 2026-03-01

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2024.2 — TBA removed from SuiteCloud SDK; 2027.1 — no new TBA integrations allowed"
  next_review: 2026-08-28
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "OAuth 2.0 does NOT support SOAP web services — TBA is the only programmatic auth for SOAP"
  - "As of 2027.1: no new integrations can use TBA for SOAP, REST, or RESTlets"
  - "All SOAP endpoints disabled at 2028.2 — must migrate to REST before then"
  - "OAuth 2.0 client credentials (M2M) requires X.509 certificate (RSA 3072 or 4096 bit)"
  - "Max 5 active certificates per integration record for M2M flow"
  - "M2M setup is not copied between accounts — must re-create in sandbox/Release Preview"
  - "OAuth 2.0 access tokens expire (60 min default) — TBA tokens never expire unless revoked"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs NetSuite REST API capabilities and rate limits (not auth-specific)"
    use_instead: "business/erp-integration/netsuite-suitetalk-api-capabilities/2026"
  - condition: "User needs SuiteScript governance unit limits"
    use_instead: "business/erp-integration/netsuite-suitescript-governance/2026"
  - condition: "User needs RESTlet development patterns"
    use_instead: "business/erp-integration/netsuite-restlet-capabilities/2026"
  - condition: "User needs SuiteQL query capabilities"
    use_instead: "business/erp-integration/netsuite-suiteql-capabilities/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What type of integration are you building?"
    type: choice
    options:
      - "server-to-server / unattended (M2M, scheduled jobs, middleware)"
      - "user-context (portal, interactive app on behalf of user)"
      - "SOAP-based legacy integration"
      - "SuiteCloud SDK / SDF development"
  - key: api_surface
    question: "Which NetSuite API surface are you using?"
    type: choice
    options:
      - "REST Web Services (SuiteTalk REST)"
      - "RESTlets (custom SuiteScript endpoints)"
      - "SOAP Web Services (SuiteTalk SOAP)"
      - "SuiteAnalytics Connect (ODBC/JDBC)"
  - key: timeline
    question: "Is this a new integration or migration of an existing one?"
    type: choice
    options:
      - "New integration (starting fresh)"
      - "Migrating existing TBA integration to OAuth 2.0"
      - "Maintaining existing SOAP + TBA (need to know sunset dates)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/netsuite-tba-vs-oauth2/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-01)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/netsuite-suitetalk-api-capabilities/2026"
      label: "NetSuite SuiteTalk REST API Capabilities"
    - id: "business/erp-integration/netsuite-restlet-capabilities/2026"
      label: "NetSuite RESTlet Capabilities"
    - id: "business/erp-integration/netsuite-suitescript-governance/2026"
      label: "NetSuite SuiteScript Governance Limits"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "business/erp-integration/oracle-erp-cloud-authentication/2026"
      label: "Oracle ERP Cloud Authentication — different product, different auth model"

# === SOURCES ===
sources:
  - id: src1
    title: "NetSuite Applications Suite — OAuth 2.0"
    author: Oracle
    url: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_157769826287.html
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src2
    title: "NetSuite Applications Suite — Token-based Authentication (TBA)"
    author: Oracle
    url: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_4247329078.html
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src3
    title: "NetSuite Applications Suite — OAuth 2.0 Client Credentials Setup"
    author: Oracle
    url: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_162686838198.html
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src4
    title: "NetSuite Applications Suite — SOAP Removal Plans FAQ"
    author: Oracle
    url: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/article_2104046421.html
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src5
    title: "OAuth 2.0 vs 1.0 for NetSuite in 2025"
    author: LST Consultancy
    url: https://lstconsultancy.com/oauth2-vs-oauth1-for-netsuite/
    type: technical_blog
    published: 2025-01-15
    reliability: moderate_high
  - id: src6
    title: "NetSuite Login & Authentication: Guide for Admins & CFOs"
    author: Houseblend
    url: https://www.houseblend.io/articles/netsuite-login-authentication-guide
    type: technical_blog
    published: 2025-06-01
    reliability: moderate_high
  - id: src7
    title: "NetSuite SOAP Web Services Deprecation: What the 2026.1 Change Means"
    author: Teknuro
    url: https://teknuro.com/netsuite-soap-web-services-deprecation-what-the-2026-1-change-means-for-you/
    type: technical_blog
    published: 2025-11-01
    reliability: moderate_high
  - id: src8
    title: "Removal of OAuth 1.0 TBA Support in SuiteCloud SDK"
    author: Oracle NetSuite Community
    url: https://community.oracle.com/netsuite/english/discussion/4503202/removal-of-oauth-1-0-token-based-authentication-support-in-suitecloud-sdk
    type: community_resource
    published: 2024-08-01
    reliability: moderate_high
---

# NetSuite TBA (OAuth 1.0a) vs OAuth 2.0: Differences, Deprecation & Migration

## TL;DR

- **Bottom line**: Use OAuth 2.0 for all new NetSuite integrations. TBA (OAuth 1.0a) is legacy — no new TBA integrations allowed after 2027.1. The one exception: SOAP web services still require TBA because OAuth 2.0 does not support SOAP. [src1, src2]
- **Key limit**: OAuth 2.0 cannot authenticate SOAP web services calls. If you depend on SOAP, you must use TBA until you migrate to REST (SOAP fully disabled at 2028.2). [src1, src4]
- **Watch out for**: OAuth 2.0 M2M (client credentials) requires X.509 certificate upload and per-account setup — the M2M mapping is NOT copied between production, sandbox, or Release Preview accounts. [src3]
- **Best for**: Deciding which authentication method to use for a NetSuite integration, understanding the deprecation timeline, and planning TBA-to-OAuth 2.0 migration.
- **Authentication**: OAuth 2.0 authorization code flow for user-context apps; OAuth 2.0 client credentials (M2M) with certificate for server-to-server; TBA for SOAP-only legacy. [src1, src2]

## System Profile

This card covers authentication methods for Oracle NetSuite's API surfaces. NetSuite supports two primary programmatic authentication protocols: Token-Based Authentication (TBA, built on OAuth 1.0a) and OAuth 2.0. The choice between them depends on the API surface, integration pattern, and timeline — TBA is being phased out in favor of OAuth 2.0 across all API surfaces except SOAP (which itself is being deprecated). [src1, src2]

| Property | Value |
|---|---|
| **Vendor** | Oracle |
| **System** | Oracle NetSuite 2026.1 |
| **API Surface** | REST Web Services, RESTlets, SOAP Web Services, SuiteAnalytics Connect |
| **Editions Covered** | All NetSuite editions |
| **Deployment** | Cloud |
| **API Docs URL** | [NetSuite Help Center — Authentication](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_157769826287.html) |
| **Status** | OAuth 2.0: GA (preferred); TBA: GA (legacy, sunset in progress) |

## API Surfaces & Capabilities

Not all API surfaces support both authentication methods. This is the single most important table for deciding which auth method to use. [src1, src2, src6]

| API Surface | TBA (OAuth 1.0a) | OAuth 2.0 Auth Code | OAuth 2.0 Client Credentials (M2M) | Notes |
|---|---|---|---|---|
| REST Web Services | Yes | Yes | Yes | OAuth 2.0 preferred for all new integrations |
| RESTlets | Yes | Yes | Yes | OAuth 2.0 preferred; user credentials deprecated since 2021 |
| SOAP Web Services | Yes | **No** | **No** | TBA is the ONLY programmatic auth for SOAP |
| SuiteAnalytics Connect | Yes | Yes | Yes | OAuth 2.0 resolves 2FA incompatibility issues |
| SuiteCloud SDK (SDF) | **No** (removed 2024.2) | Yes | N/A | SDK 24.2+ requires OAuth 2.0 |

## Rate Limits & Quotas

Authentication method does not directly change API rate limits — limits are governed by the API surface and account tier. However, token lifetime and refresh behavior differ significantly. [src1, src2]

### Token Lifetime Comparison

| Property | TBA (OAuth 1.0a) | OAuth 2.0 Authorization Code | OAuth 2.0 Client Credentials (M2M) |
|---|---|---|---|
| Access token lifetime | Never expires (until revoked) | 60 minutes (configurable via company settings) | 60 minutes |
| Refresh token | N/A — token is permanent | Yes — valid until revoked | No — request new token each time via JWT assertion |
| Request signing | HMAC-SHA256 signature per request | Bearer token in Authorization header | Bearer token in Authorization header |
| Certificate required | No | No | Yes — X.509 (RSA 3072 or 4096 bit) |

[src1, src2, src3]

### Per-Request Overhead

| Auth Method | Overhead per Request | Notes |
|---|---|---|
| TBA | ~1-3ms (signature computation) | HMAC-SHA256 computed client-side for every request |
| OAuth 2.0 | ~0ms (bearer token attach) | Token cached; no per-request computation |
| OAuth 2.0 M2M (token fetch) | ~200-500ms (JWT signing + HTTP roundtrip) | Only when token expires (every 60 min) |

[src1, src5]

## Authentication

### Flow Comparison

| Flow | Protocol | Use When | Token Lifetime | Refresh? | SOAP Support | Setup Complexity |
|---|---|---|---|---|---|---|
| TBA | OAuth 1.0a | Legacy SOAP integrations; existing integrations not yet migrated | Permanent | N/A | Yes | Medium — requires consumer key/secret + token ID/secret |
| Authorization Code | OAuth 2.0 | User-context apps (portals, interactive tools) | 60 min | Yes | No | Medium — requires redirect URI, integration record |
| Client Credentials (M2M) | OAuth 2.0 | Server-to-server, unattended, middleware, scheduled jobs | 60 min | No (re-authenticate) | No | High — requires X.509 certificate, M2M entity/role mapping |

[src1, src2, src3]

### TBA Setup (OAuth 1.0a)

1. **Enable features**: Setup > Company > Enable Features > SuiteCloud > Token-Based Authentication. [src2]
2. **Create integration record**: Setup > Integration > Manage Integrations > New. Check "Token-Based Authentication". Note the Consumer Key and Consumer Secret. [src2]
3. **Create access token**: Setup > Users/Roles > Access Tokens > New. Select the integration record, user, and role. Note the Token ID and Token Secret. [src2]
4. **Sign requests**: Each API request must include an OAuth 1.0a Authorization header with HMAC-SHA256 signature computed from consumer key, consumer secret, token ID, token secret, nonce, and timestamp. [src2, src6]

### OAuth 2.0 Authorization Code Setup

1. **Enable features**: Setup > Company > Enable Features > SuiteCloud > OAuth 2.0. Also enable REST Web Services under SuiteTalk. [src1]
2. **Create integration record**: Setup > Integration > Manage Integrations > New. Check "Authorization Code Grant". Enter Redirect URI. Note Client ID and Client Secret. [src1]
3. **Authorization**: Redirect user to `https://<accountID>.app.netsuite.com/app/login/oauth2/authorize.nl` with client_id, redirect_uri, scope, response_type=code, state. [src1]
4. **Token exchange**: POST to `https://<accountID>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token` with grant_type=authorization_code, code, redirect_uri, client_id, client_secret. [src1]
5. **Refresh**: POST same token endpoint with grant_type=refresh_token when access token expires. [src1]

### OAuth 2.0 Client Credentials (M2M) Setup

1. **Enable features**: Same as authorization code — OAuth 2.0 + REST Web Services. [src1]
2. **Create integration record**: Check "Client Credentials (Machine to Machine) Grant". Note Client ID (Consumer Key). [src3]
3. **Generate certificate**: `openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 730`. Keep key.pem private. [src3]
4. **Create M2M mapping**: Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup > Create New. Select entity (user), role, application (integration record). Upload cert.pem. Save and note Certificate ID. [src3]
5. **Get token**: POST to `https://<accountID>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token` with grant_type=client_credentials, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, client_assertion={JWT signed with private key}. [src3]

### Authentication Gotchas

- **M2M setup is per-account**: The entity/role/certificate mapping created in production is NOT copied to sandbox or Release Preview. You must re-create it in each account. Sandbox setup is also cleared on refresh. [src3]
- **Max 5 active certificates**: Each integration record can have at most 5 active certificates for M2M. Revoked and expired certificates do not count. [src3]
- **TBA tokens survive password rotation**: TBA tokens are not affected by password rotation policies. This is a security advantage (no breakage) but also a risk (tokens persist indefinitely). [src2, src6]
- **2FA bypass**: Both TBA and OAuth 2.0 bypass UI-level 2FA since they operate at the API level. The role must have "Log in using Access Tokens" permission. [src6]
- **OAuth 2.0 token endpoint varies by account**: The base URL uses your account ID — e.g., `https://TSTDRV1234567.suitetalk.api.netsuite.com`. Getting this wrong is the #1 auth failure for new integrations. [src1]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **OAuth 2.0 cannot authenticate SOAP calls**: If you have SOAP-dependent integrations, you must use TBA until you migrate to REST. There is no workaround. [src1, src4]
- **2027.1 hard cutoff for new TBA**: After NetSuite 2027.1, no new integration records can enable TBA for SOAP, REST, or RESTlets. Existing TBA integrations continue working. [src2]
- **SOAP fully disabled at 2028.2**: All SOAP endpoints will be turned off in the 2028.2 release. SOAP + TBA integrations must migrate to REST + OAuth 2.0 before then. [src4]
- **M2M certificate format**: Public key must be X.509 (.cer, .pem, .crt) with RSA 3072 or 4096 bit key. Smaller keys are rejected. [src3]
- **SuiteCloud SDK requires OAuth 2.0**: As of SDK version 24.2, TBA is no longer supported for SDF connections. SDK 24.1 was the last version with TBA. [src8]
- **No client secret for M2M**: OAuth 2.0 M2M uses JWT assertion signed with private key — not client_id + client_secret. This is different from standard OAuth 2.0 client credentials. [src3]

## Integration Pattern Decision Tree

```
START — New NetSuite integration, choosing auth method
|
+-- Which API surface?
|   |
|   +-- SOAP Web Services?
|   |   +-- YES --> TBA is required (OAuth 2.0 not supported for SOAP)
|   |   |   +-- Is this a new integration?
|   |   |       +-- YES --> STOP. Do NOT build new SOAP integrations.
|   |   |       |   Migrate to REST + OAuth 2.0 instead. SOAP sunsets 2028.2.
|   |   |       +-- NO (existing) --> Keep TBA, plan REST migration before 2028.2
|   |   +-- NO --> Continue to next question
|   |
|   +-- REST Web Services or RESTlets?
|   |   +-- Is this server-to-server (no user context)?
|   |   |   +-- YES --> OAuth 2.0 Client Credentials (M2M)
|   |   |   |   Requirements: X.509 cert, M2M mapping per account
|   |   |   +-- NO --> OAuth 2.0 Authorization Code
|   |   |   |   Requirements: Redirect URI, user consent flow
|   |
|   +-- SuiteAnalytics Connect (ODBC/JDBC)?
|   |   +-- OAuth 2.0 (resolves 2FA issues with legacy passwords)
|   |
|   +-- SuiteCloud SDK / SDF?
|       +-- OAuth 2.0 (mandatory since SDK 24.2)
|
+-- Migrating existing TBA integration?
    +-- REST/RESTlet? --> Switch to OAuth 2.0 M2M or Auth Code
    +-- SOAP? --> Migrate to REST first, then OAuth 2.0
    +-- Timeline: Complete before 2027.1 (new TBA blocked)
```

## Quick Reference

### TBA vs OAuth 2.0 — Side-by-Side Comparison

| Capability | TBA (OAuth 1.0a) | OAuth 2.0 |
|---|---|---|
| Protocol standard | OAuth 1.0a | OAuth 2.0 (RFC 6749) |
| Request signing | HMAC-SHA256 per request | None (bearer token) |
| Token expiration | Never (until revoked) | 60 minutes (access token) |
| Refresh mechanism | N/A | Refresh token (auth code) or re-authenticate (M2M) |
| SOAP support | Yes | No |
| REST support | Yes | Yes |
| RESTlet support | Yes | Yes |
| SuiteAnalytics Connect | Yes | Yes |
| SuiteCloud SDK | No (removed 2024.2) | Yes (required) |
| Certificate required | No | Only for M2M (X.509 RSA 3072/4096) |
| Implementation complexity | Medium (signature logic) | Low (auth code) / High (M2M cert management) |
| Security model | Shared secrets (4 values) | Short-lived tokens, certificate-based (M2M) |
| Status (2026) | Legacy — sunset in progress | Preferred — actively developed |
| New integrations after 2027.1 | Blocked | Required |

[src1, src2, src3, src5, src6]

### Deprecation Timeline

| Date/Release | Event | Impact |
|---|---|---|
| 2024.2 (Aug 2024) | TBA removed from SuiteCloud SDK | SDK users must switch to OAuth 2.0 |
| 2025.2 | Last planned SOAP endpoint released | No new SOAP versions after this |
| 2026.1 | Oracle recommends REST + OAuth 2.0 for all new integrations | Soft recommendation, not enforced |
| 2027.1 | **No new TBA integrations** for SOAP, REST, or RESTlets | Hard enforcement — existing TBA continues |
| 2027.2 | Only 2025.2 SOAP endpoint supported; older endpoints unsupported | Older SOAP endpoints still operational |
| 2028.2 | **All SOAP endpoints disabled** | SOAP integrations stop working entirely |

[src2, src4, src7, src8]

## Step-by-Step Integration Guide

### 1. Determine your authentication method

Use the decision tree above. For new server-to-server integrations, OAuth 2.0 M2M is the recommended path. [src1]

**Verify**: Check Setup > Integration > Manage Integrations to confirm OAuth 2.0 and REST Web Services are enabled in your account.

### 2. Set up OAuth 2.0 M2M authentication (recommended for server-to-server)

Generate an RSA 4096-bit X.509 certificate and configure the M2M mapping. [src3]

```bash
# Generate RSA 4096-bit key pair and self-signed X.509 certificate (valid 2 years)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 730 \
  -subj "/CN=netsuite-integration/O=YourCompany"
```

**Verify**: `openssl x509 -in cert.pem -text -noout | grep "Public-Key"` --> expected: `Public-Key: (4096 bit)`

### 3. Create integration record and M2M mapping in NetSuite

In NetSuite UI: [src3]

1. Setup > Integration > Manage Integrations > New
2. Name: "My M2M Integration"
3. Check: "Client Credentials (Machine to Machine) Grant"
4. Check: "REST Web Services" (under Scope)
5. Save. Copy the **Client ID (Consumer Key)**.
6. Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup
7. Click "Create New". Select entity, role, application. Upload cert.pem. Save. Note the **Certificate ID**.

**Verify**: The M2M Setup page shows your mapping as "Active".

### 4. Request an access token using JWT assertion

Build a JWT, sign it with your private key, and exchange it at the token endpoint. [src3]

```python
# Input:  client_id, certificate_id, account_id, private key (key.pem)
# Output: access_token (valid 60 minutes)

import jwt
import time
import requests

ACCOUNT_ID = "TSTDRV1234567"  # Your NetSuite account ID
CLIENT_ID = "your-client-id-from-integration-record"
CERTIFICATE_ID = "your-certificate-id-from-m2m-setup"
TOKEN_URL = f"https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token"

# Load private key
with open("key.pem", "r") as f:
    private_key = f.read()

# Build JWT assertion
now = int(time.time())
payload = {
    "iss": CLIENT_ID,
    "scope": "rest_webservices",
    "aud": TOKEN_URL,
    "exp": now + 300,  # 5 min expiry for the assertion itself
    "iat": now,
}
assertion = jwt.encode(payload, private_key, algorithm="RS256",
                       headers={"kid": CERTIFICATE_ID})

# Exchange JWT for access token
resp = requests.post(TOKEN_URL, data={
    "grant_type": "client_credentials",
    "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
    "client_assertion": assertion,
})
resp.raise_for_status()
access_token = resp.json()["access_token"]
print(f"Token obtained, expires in {resp.json()['expires_in']}s")
```

**Verify**: Response contains `access_token` and `expires_in: 3600` (60 minutes).

### 5. Make an authenticated API call

Use the bearer token to call NetSuite REST Web Services. [src1]

```python
# Input:  access_token from step 4
# Output: JSON response from NetSuite REST API

headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
    "Prefer": "respond-async"  # optional: for long-running operations
}

# Example: GET a customer record
response = requests.get(
    f"https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/record/v1/customer/123",
    headers=headers
)
print(response.json())
```

**Verify**: HTTP 200 response with customer record JSON.

## Code Examples

### Python: TBA (OAuth 1.0a) request signing

```python
# Input:  consumer_key, consumer_secret, token_id, token_secret, account_id
# Output: Authenticated REST API call using TBA

import requests
from requests_oauthlib import OAuth1

ACCOUNT_ID = "TSTDRV1234567"
BASE_URL = f"https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/record/v1"

auth = OAuth1(
    client_key="your-consumer-key",
    client_secret="your-consumer-secret",
    resource_owner_key="your-token-id",
    resource_owner_secret="your-token-secret",
    realm=ACCOUNT_ID,
    signature_method="HMAC-SHA256"
)

response = requests.get(f"{BASE_URL}/customer/123", auth=auth)
print(response.status_code, response.json())
```

[src2, src6]

### JavaScript/Node.js: OAuth 2.0 M2M token request

```javascript
// Input:  clientId, certificateId, privateKey (PEM), accountId
// Output: access_token string

const jwt = require("jsonwebtoken"); // v9.x
const axios = require("axios");      // v1.x
const fs = require("fs");

const ACCOUNT_ID = "TSTDRV1234567";
const CLIENT_ID = "your-client-id";
const CERT_ID = "your-certificate-id";
const TOKEN_URL = `https://${ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token`;

const privateKey = fs.readFileSync("key.pem", "utf8");

const assertion = jwt.sign(
  {
    iss: CLIENT_ID,
    scope: "rest_webservices",
    aud: TOKEN_URL,
    exp: Math.floor(Date.now() / 1000) + 300,
    iat: Math.floor(Date.now() / 1000),
  },
  privateKey,
  { algorithm: "RS256", header: { kid: CERT_ID } }
);

const params = new URLSearchParams({
  grant_type: "client_credentials",
  client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
  client_assertion: assertion,
});

const response = await axios.post(TOKEN_URL, params);
console.log("Token:", response.data.access_token);
console.log("Expires in:", response.data.expires_in, "seconds");
```

[src1, src3]

### cURL: Test OAuth 2.0 M2M token endpoint

```bash
# Input:  JWT assertion (pre-built), account ID
# Output: JSON with access_token, token_type, expires_in

# Step 1: Build JWT assertion (use jwt.io or a CLI tool)
# Step 2: Exchange for access token
curl -X POST \
  "https://TSTDRV1234567.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=YOUR_JWT_ASSERTION_HERE"

# Expected response:
# { "access_token": "...", "token_type": "Bearer", "expires_in": 3600 }

# Step 3: Use token
curl -X GET \
  "https://TSTDRV1234567.suitetalk.api.netsuite.com/services/rest/record/v1/customer/123" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

[src1, src3]

## Data Mapping

### Authentication Credential Mapping (TBA to OAuth 2.0 Migration)

| TBA Credential | OAuth 2.0 Equivalent | Where to Find | Migration Notes |
|---|---|---|---|
| Consumer Key | Client ID | Integration record | Same field, different label in UI |
| Consumer Secret | Client Secret (auth code) / N/A (M2M) | Integration record | M2M uses certificate, not secret |
| Token ID | Access Token (auto-generated) | Token endpoint response | OAuth 2.0 tokens are ephemeral, not manually created |
| Token Secret | N/A | N/A | OAuth 2.0 uses bearer tokens, no per-request secrets |
| N/A | Certificate ID | M2M setup page | New concept — not present in TBA |
| N/A | Refresh Token | Token endpoint response | Only in authorization code flow |

[src1, src2, src3]

### Data Type Gotchas

- **Account ID format**: Use the account ID exactly as shown in Setup > Company > Company Information. Underscores for sandbox (e.g., TSTDRV1234567_SB1). Getting the account ID wrong is the #1 cause of "invalid_grant" errors. [src1]
- **Realm parameter**: TBA requires `realm=ACCOUNT_ID` in OAuth header. OAuth 2.0 encodes account in the URL instead. When migrating, remove realm logic. [src2]
- **Token storage**: TBA tokens can be stored permanently (they never expire). OAuth 2.0 access tokens must be refreshed — your integration needs token caching with TTL logic. [src5]

## Error Handling & Failure Points

### Common Error Codes

| Error | Auth Method | Cause | Resolution |
|---|---|---|---|
| `invalid_grant` | OAuth 2.0 M2M | JWT assertion expired, wrong audience, or certificate not mapped | Verify JWT exp/aud claims; confirm M2M mapping exists in this account |
| `invalid_client` | OAuth 2.0 | Client ID not found or integration record disabled | Check integration record is active; verify Client ID |
| `unauthorized` (401) | TBA | Invalid signature — wrong secret, timestamp skew, or nonce reuse | Verify all 4 credentials; check system clock sync (<5min skew) |
| `INVALID_LOGIN_ATTEMPT` | Both | Role does not have "Log in using Access Tokens" permission | Add permission to the role used by the integration |
| `SSS_REQUEST_LIMIT_EXCEEDED` | Both | SuiteScript governance limit hit | Reduce API call frequency; implement backoff |
| `INSUFFICIENT_PERMISSION` | Both | Integration user role lacks required record/field permissions | Audit role permissions; use minimum required |

[src1, src2, src6]

### Failure Points in Production

- **Certificate expiration (M2M)**: OAuth 2.0 M2M certificates have a finite lifetime (set during generation). When they expire, all API calls fail silently with `invalid_grant`. Fix: `Monitor certificate expiration; rotate certificates at least 30 days before expiry using the Certificate Rotation Endpoint`. [src3]
- **Sandbox refresh destroys M2M setup**: When a NetSuite sandbox is refreshed from production, the M2M setup is cleared even though the integration record is copied. Fix: `Automate M2M setup recreation as part of sandbox refresh runbook`. [src3]
- **TBA signature clock skew**: TBA uses a timestamp in the OAuth 1.0a header. If the client clock drifts >5 minutes from NetSuite servers, all requests fail. Fix: `Use NTP; include clock sync check in health monitoring`. [src2]
- **Token caching race condition**: When multiple processes share an OAuth 2.0 token and it expires, they may all simultaneously request new tokens, hitting rate limits. Fix: `Implement single-writer token refresh with distributed lock or leader election`. [src5]
- **Integration record deactivation**: An admin can deactivate an integration record, immediately breaking all connected systems. Fix: `Monitor integration record status; alert on state changes via Saved Search`. [src6]

## Anti-Patterns

### Wrong: Hardcoding TBA tokens in source code

```python
# BAD — tokens stored in code, impossible to rotate without redeployment
auth = OAuth1(
    client_key="abc123",
    client_secret="secret456",
    resource_owner_key="tok789",
    resource_owner_secret="toksecret012",
    realm="TSTDRV1234567",
    signature_method="HMAC-SHA256"
)
```

### Correct: Load credentials from environment / secrets manager

```python
# GOOD — credentials externalized, rotatable without code changes
import os

auth = OAuth1(
    client_key=os.environ["NS_CONSUMER_KEY"],
    client_secret=os.environ["NS_CONSUMER_SECRET"],
    resource_owner_key=os.environ["NS_TOKEN_ID"],
    resource_owner_secret=os.environ["NS_TOKEN_SECRET"],
    realm=os.environ["NS_ACCOUNT_ID"],
    signature_method="HMAC-SHA256"
)
```

[src6]

### Wrong: Building a new SOAP integration in 2026

```python
# BAD — SOAP is being sunset. New SOAP integrations blocked after 2027.1.
# All SOAP endpoints disabled at 2028.2.
from zeep import Client
wsdl = f"https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/wsdl/v2025_2_0/netsuite.wsdl"
client = Client(wsdl)
# ... building new business logic on SOAP ...
```

### Correct: Use REST Web Services with OAuth 2.0

```python
# GOOD — REST is the future. OAuth 2.0 is the preferred auth.
import requests

headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(
    f"https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder",
    headers=headers,
    params={"q": "status IS SalesOrd:B"}  # SuiteQL-style filter
)
```

[src4, src7]

### Wrong: Requesting a new OAuth 2.0 M2M token for every API call

```python
# BAD — unnecessary token endpoint calls; wastes time and may hit rate limits
def make_api_call(endpoint):
    token = get_new_token()  # 200-500ms overhead EVERY call
    return requests.get(endpoint, headers={"Authorization": f"Bearer {token}"})
```

### Correct: Cache the token and refresh on expiry

```python
# GOOD — cache token, refresh only when expired
import time

_token_cache = {"token": None, "expires_at": 0}

def get_token():
    if time.time() < _token_cache["expires_at"] - 60:  # 60s buffer
        return _token_cache["token"]
    resp = request_new_token()
    _token_cache["token"] = resp["access_token"]
    _token_cache["expires_at"] = time.time() + resp["expires_in"]
    return _token_cache["token"]
```

[src1, src5]

## Common Pitfalls

- **Assuming OAuth 2.0 works with SOAP**: It does not. If your integration uses SOAP web services, OAuth 2.0 will silently fail or return auth errors. Verify your API surface before choosing auth. [src1, src4]
- **Forgetting to recreate M2M setup in sandbox**: Sandbox refresh clears M2M mappings. Integration tests break immediately post-refresh. Fix: `Include M2M setup in your sandbox refresh checklist/automation`. [src3]
- **Using RSA 2048-bit keys for M2M**: NetSuite requires RSA 3072 or 4096 bit keys. 2048-bit keys (the default in many tutorials) are rejected. Fix: `Always specify -newkey rsa:4096 in your openssl command`. [src3]
- **Not planning for the 2027.1 cutoff**: After 2027.1, you cannot create new TBA integrations. If your deployment pipeline creates new integration records, it will break. Fix: `Audit all integration provisioning scripts; switch to OAuth 2.0 before 2027.1`. [src2]
- **Clock skew breaking TBA**: TBA signatures include a timestamp. NTP drift >5 minutes causes every request to fail with no clear error message. Fix: `Ensure NTP is running on all integration servers; add clock sync monitoring`. [src2, src6]
- **Ignoring certificate rotation for M2M**: Self-signed certificates expire. Unlike TBA tokens (which never expire), M2M certs have a hard expiration date. Fix: `Set calendar alerts for cert expiry; use the Certificate Rotation Endpoint to rotate proactively`. [src3]

## Diagnostic Commands

```bash
# Test OAuth 2.0 M2M token request (replace placeholders)
curl -s -X POST \
  "https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token" \
  -d "grant_type=client_credentials&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=JWT_HERE" \
  | python3 -m json.tool

# Check certificate expiration date
openssl x509 -in cert.pem -noout -enddate
# Expected: notAfter=Mar  1 00:00:00 2028 GMT

# Check certificate key length
openssl x509 -in cert.pem -noout -text | grep "Public-Key"
# Expected: Public-Key: (4096 bit)

# Test REST API with bearer token
curl -s -X GET \
  "https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/rest/record/v1/customer?limit=1" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  | python3 -m json.tool

# Test TBA authentication (using OAuth 1.0a header)
# Use a tool like Postman or oauth1-header-generator to compute the signature
# Then verify with:
curl -s -X GET \
  "https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/rest/record/v1/customer?limit=1" \
  -H "Authorization: OAuth realm=\"ACCOUNT_ID\",oauth_consumer_key=\"...\",oauth_token=\"...\",oauth_signature_method=\"HMAC-SHA256\",oauth_timestamp=\"...\",oauth_nonce=\"...\",oauth_version=\"1.0\",oauth_signature=\"...\""

# Check integration record status (via SuiteQL)
curl -s -X POST \
  "https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Prefer: transient" \
  -d '{"q": "SELECT id, name, isinactive FROM integration WHERE isinactive = '\''F'\'' ORDER BY id"}'
```

## Version History & Compatibility

| Feature/Release | Date | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| TBA introduced | 2015 (2015.2) | GA | N/A | First programmatic auth beyond user credentials |
| HMAC-SHA1 deprecated for TBA | 2021.1 | Enforced | HMAC-SHA1 signatures rejected | Switch to HMAC-SHA256 |
| User credentials deprecated for RESTlets | 2021.x | Enforced | Username/password auth removed | Use TBA or OAuth 2.0 |
| OAuth 2.0 Authorization Code GA | 2021.2 | GA | N/A | New option for user-context integrations |
| OAuth 2.0 Client Credentials (M2M) GA | 2022.2 | GA | N/A | Server-to-server without user context |
| TBA removed from SuiteCloud SDK | 2024.2 | Enforced | SDK no longer accepts TBA tokens | Use OAuth 2.0 for SDF |
| 2025.2 — last SOAP endpoint | 2025 H2 | Current | N/A | Final SOAP version with planned support |
| 2027.1 — no new TBA integrations | 2027 H1 | Upcoming | Cannot create new TBA integration records | Migrate existing integrations before this |
| 2028.2 — SOAP disabled | 2028 H2 | Upcoming | All SOAP endpoints turned off | Must be on REST + OAuth 2.0 by then |

[src2, src4, src7, src8]

### Deprecation Policy

Oracle NetSuite supports each SOAP endpoint for 3 years from release. After 3 years, the endpoint becomes "unsupported but available" and is eventually disabled. For authentication, Oracle has signaled a clear direction: OAuth 2.0 is the long-term standard, TBA will be blocked for new integrations at 2027.1, and SOAP (the only API that requires TBA) will be fully disabled at 2028.2. [src4]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| New server-to-server integration (M2M) | Integrating with SOAP web services | TBA (OAuth 1.0a) — only auth that works with SOAP |
| New user-context app (portal, interactive) | You need permanent non-expiring tokens | TBA — tokens never expire (but plan migration) |
| SuiteCloud SDK / SDF development | Building against SOAP-only features with no REST equivalent | RESTlet wrapper + OAuth 2.0 |
| Migrating from TBA and timeline allows | Integration must work across sandbox without re-setup | TBA — simpler sandbox portability (but M2M mapping is per-account for OAuth 2.0) |
| Need short-lived, rotatable credentials | Legacy middleware that only supports OAuth 1.0a | TBA until middleware is updated |

## Important Caveats

- **SOAP + OAuth 2.0 incompatibility is permanent**: Oracle has no plans to add OAuth 2.0 support to SOAP. The strategy is to sunset SOAP entirely. If you need SOAP-only functionality, wrap it in a RESTlet. [src4]
- **"TBA still works" does not mean "TBA is recommended"**: Existing TBA integrations continue working past 2027.1, but you should migrate proactively. Security auditors increasingly flag permanent non-expiring tokens. [src2, src5]
- **M2M certificate management is an operational burden**: Unlike TBA (set and forget), OAuth 2.0 M2M requires certificate lifecycle management. Factor this into your operational cost comparison. [src3]
- **Sandbox parity**: Testing OAuth 2.0 M2M in sandbox requires re-creating the entity/role/certificate mapping. TBA tokens also don't copy between accounts, but TBA setup is simpler (no certificate infrastructure). [src3]
- **Rate limits are auth-method-agnostic**: Switching from TBA to OAuth 2.0 does not change your API rate limits. Limits are determined by account tier and API surface. [src1]
- **This information is subject to change**: Oracle may accelerate or adjust the deprecation timeline. Always verify against the latest NetSuite release notes and SOAP Removal Plans FAQ. [src4]

## Related Units

- [NetSuite SuiteTalk REST API Capabilities](/business/erp-integration/netsuite-suitetalk-api-capabilities/2026)
- [NetSuite RESTlet Capabilities](/business/erp-integration/netsuite-restlet-capabilities/2026)
- [NetSuite SuiteScript Governance Limits](/business/erp-integration/netsuite-suitescript-governance/2026)
- [NetSuite SuiteQL Capabilities](/business/erp-integration/netsuite-suiteql-capabilities/2026)
