NetSuite SuiteTalk SOAP API Capabilities

Type: ERP Integration System: Oracle NetSuite (WSDL 2025.2) Confidence: 0.93 Sources: 8 Verified: 2026-03-01 Freshness: 2026-03-01

TL;DR

System Profile

This card covers Oracle NetSuite SuiteTalk SOAP Web Services, the original programmatic API for NetSuite. SuiteTalk uses document-style SOAP over HTTPS with XML serialization. It supports all major NetSuite record types (customers, transactions, items, custom records) and provides search, CRUD, and bulk list operations. The WSDL is versioned in lockstep with NetSuite releases (e.g., 2025.2 corresponds to WSDL 2025_2). All NetSuite editions (Standard, Premium, Enterprise, Ultimate) support SOAP web services, but concurrency limits vary dramatically by tier and SuiteCloud Plus licensing. [src1, src3]

Critical deprecation notice: Oracle has announced that SOAP web services will be fully removed in the 2028.2 release. The 2025.2 WSDL is the last planned SOAP endpoint. Starting with 2027.1, no new SOAP integrations can be created. All existing SOAP integrations must migrate to REST web services with OAuth 2.0 before 2028.2. [src4]

PropertyValue
VendorOracle
SystemOracle NetSuite (WSDL 2025.2)
API SurfaceSOAP (Document-style, XML/HTTPS)
Current API VersionWSDL 2025.2 (final planned)
Editions CoveredStandard, Premium, Enterprise, Ultimate
DeploymentCloud
API DocsSuiteTalk SOAP Web Services Platform Overview
StatusSupported (deprecation announced — full removal at 2028.2)

API Surfaces & Capabilities

NetSuite offers multiple API surfaces. This card focuses on SuiteTalk SOAP, but understanding where it fits relative to alternatives is critical for integration architecture decisions. [src1, src4]

API SurfaceProtocolBest ForMax Records/RequestConcurrencyReal-time?Status
SuiteTalk SOAPHTTPS/XMLLegacy integrations, SOAP-centric ESBs1,000 (search), 200 (addList)Shared poolYesDeprecated (removal 2028.2)
SuiteTalk RESTHTTPS/JSONAll new integrations post-2026.11,000 (default), configurableShared poolYesGA — recommended
SuiteQLHTTPS/JSONAnalytics, complex queries, reporting100,000 rows maxShared poolYesGA
RESTlets (SuiteScript)HTTPS/JSONCustom endpoints, business logicScript-dependent5 per userYesGA
CSV ImportFile-basedBulk data loads, migrationsUnlimited (file-size limited)N/ANoGA

Rate Limits & Quotas

Per-Request Limits

NetSuite governs SOAP web services through record limiting and request limiting. These are hard caps — exceeding them raises SOAP faults. [src2]

Limit TypeValueApplies ToNotes
Search pageSize (sync)1,000 max, 5 minsearch, searchMoreWithIdDefault is 1,000 if not specified
Search pageSize (async)2,000 max, 5 minasyncSearchUse async for larger page sizes
addList records200 maxSynchronous createasyncAddList allows 400
deleteList records200 maxSynchronous deleteasyncDeleteList allows 400
getList records1,000 maxSynchronous readasyncGetList allows 2,000
updateList records100 maxSynchronous updateasyncUpdateList allows 200
upsertList records100 maxSynchronous upsertasyncUpsertList allows 200
getItemAvailability10,000 maxItem availability queriesLargest single-operation limit
SOAP request body100 MB maxAll SOAP requestsExceeding raises ExceededRequestSizeFault

Concurrency Limits (Rolling)

Concurrency is the number of simultaneous API requests NetSuite will process for your account. This pool is shared across all SOAP, REST, and RESTlet requests. [src3, src6, src7]

Service TierBase Concurrent RequestsWith 1 SC+ LicenseWith 3 SC+ LicensesWith 5 SC+ Licenses
Standard5153555
Premium15254565
Enterprise20305070
Ultimate20305070
Developer/Sandbox5 (fixed)5 (fixed)5 (fixed)5 (fixed)

Formula: Total concurrency = Base tier limit + (10 x number of SuiteCloud Plus licenses).

Governance Error Faults

Fault CodeMeaningResolution
ExceededRecordCountFaultRequest exceeds per-operation record limitReduce batch size to within limits above
ExceededRequestSizeFaultSOAP request body exceeds 100 MBSplit payload into smaller requests
ExceededRequestLimitFaultAccount concurrency limit reachedImplement queuing with backoff; check Integration Governance dashboard
WS_REQUEST_BLOCKEDRequest throttled or blockedReduce request frequency; review concurrent usage

Authentication

SuiteTalk SOAP uses Token-Based Authentication (TBA), which is based on OAuth 1.0. OAuth 2.0 is explicitly NOT supported for SOAP web services. [src5]

FlowUse WhenToken LifetimeRefresh?Notes
Token-Based Authentication (TBA)All SOAP integrationsUntil revokedNo (permanent tokens)Based on OAuth 1.0; recommended for all SOAP
User Credentials (legacy)Testing onlySession-basedNoDeprecated — subject to password policies and MFA issues

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START -- User needs to integrate with NetSuite via SOAP
|-- Is this a NEW integration (2026+)?
|   |-- YES --> STOP. Use REST API with OAuth 2.0 instead (SOAP deprecated)
|   +-- NO --> Continue (maintaining existing SOAP integration)
|
|-- What is the integration pattern?
|   |-- Real-time individual record operations
|   |   |-- Single record? --> use get/add/update/upsert/delete
|   |   +-- Multiple records (< 200)? --> use addList/updateList/upsertList
|   |
|   |-- Search/query operations
|   |   |-- Simple lookup by internal ID? --> use get or getList (up to 1,000)
|   |   |-- Complex criteria search? --> use search with SearchPreferences
|   |   |   |-- Expect < 1,000 results? --> single search call
|   |   |   +-- Expect > 1,000 results? --> search + searchMoreWithId pagination
|   |   +-- Need to reuse existing saved search? --> use search with savedSearchId
|   |
|   |-- Batch/bulk processing
|   |   |-- < 10,000 records? --> Loop with list operations + rate control
|   |   |-- < 100,000 records? --> Async operations + parallel execution
|   |   +-- > 100,000 records? --> CSV Import (not SOAP) or SuiteQL for reads
|   |
|   +-- Event-driven (real-time notifications)
|       +-- SOAP cannot push events. Use SuiteScript User Event Scripts + RESTlets
|
+-- What is the error tolerance?
    |-- Zero-loss required --> Implement idempotent upsert + external tracking table
    +-- Best-effort --> Fire-and-forget with retry on ExceededRequestLimitFault

Quick Reference

Core SOAP Operations

OperationMethodRecords/CallAsync VariantAsync Records/CallNotes
getRead single1N/AN/ABy internal ID or external ID
getListRead multiple1,000asyncGetList2,000Returns full records
searchQuery1,000/pageasyncSearch2,000/pageUse SearchPreferences for pageSize
searchMoreWithIdPagination1,000/pageN/AN/ARequires searchId from initial search
addCreate single1N/AN/AReturns internal ID on success
addListCreate multiple200asyncAddList400Partial success possible
updateUpdate single1N/AN/ARequires internal ID
updateListUpdate multiple100asyncUpdateList200Partial success possible
upsertCreate or update1N/AN/AUses external ID for matching
upsertListBatch upsert100asyncUpsertList200External ID required on each record
deleteDelete single1N/AN/ABy internal ID
deleteListDelete multiple200asyncDeleteList400Partial success possible
getDeletedDeleted recordsN/AN/AN/AReturns records deleted within date range
initializeInit record1N/AN/APre-populates from source record
attach/detachLink records1N/AN/ACreates/removes relationships
getSelectValuePicklist valuesN/AN/AN/AReturns valid values for select fields

Step-by-Step Integration Guide

1. Set up TBA credentials in NetSuite

Navigate to Setup > Integration > Manage Integrations > New. Enable Token-Based Authentication. Note the Consumer Key and Consumer Secret. Then create a token: Setup > Users/Roles > Access Tokens > New. Select the integration, user, and role. Note the Token ID and Token Secret. [src5]

Consumer Key:    abc123...
Consumer Secret: def456...
Token ID:        ghi789...
Token Secret:    jkl012...

Verify: Go to Setup > Integration > Integration Governance. Your integration should appear with its concurrency allocation.

2. Construct the SOAP request with TBA header

Build an OAuth 1.0-signed SOAP request. The signature base string must include the account ID, consumer key, token, nonce, and timestamp. [src5]

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:ns="urn:messages_2025_2.platform.webservices.netsuite.com"
               xmlns:core="urn:core_2025_2.platform.webservices.netsuite.com">
  <soap:Header>
    <ns:tokenPassport>
      <core:account>YOUR_ACCOUNT_ID</core:account>
      <core:consumerKey>YOUR_CONSUMER_KEY</core:consumerKey>
      <core:token>YOUR_TOKEN_ID</core:token>
      <core:nonce>RANDOM_NONCE</core:nonce>
      <core:timestamp>UNIX_TIMESTAMP</core:timestamp>
      <core:signature algorithm="HMAC-SHA256">COMPUTED_SIGNATURE</core:signature>
    </ns:tokenPassport>
    <ns:searchPreferences>
      <ns:pageSize>1000</ns:pageSize>
      <ns:bodyFieldsOnly>false</ns:bodyFieldsOnly>
    </ns:searchPreferences>
  </soap:Header>
  <soap:Body>
    <!-- Operation goes here -->
  </soap:Body>
</soap:Envelope>

Verify: Send a simple getServerTime call. Expected response: <getServerTimeResponse> with a timestamp.

3. Execute a search with pagination

Use search for the first page, then searchMoreWithId for subsequent pages. Each page returns up to 1,000 records (sync). [src2, src8]

<!-- Initial search -->
<soap:Body>
  <ns:search>
    <ns:searchRecord xsi:type="listRel:CustomerSearchBasic">
      <listRel:lastModifiedDate operator="after">
        <core:searchValue>2026-01-01T00:00:00Z</core:searchValue>
      </listRel:lastModifiedDate>
    </ns:searchRecord>
  </ns:search>
</soap:Body>

<!-- For page 2+: -->
<soap:Body>
  <ns:searchMoreWithId>
    <ns:searchId>WEBSERVICES_TSTDRV1234567_01012026...</ns:searchId>
    <ns:pageIndex>2</ns:pageIndex>
  </ns:searchMoreWithId>
</soap:Body>

Verify: Check <totalRecords> and <totalPages> in response. Page through all pages until pageIndex == totalPages.

4. Implement error handling and retry logic

Handle the three governance fault types and implement exponential backoff for concurrency limits. [src2, src3]

import time, random

MAX_RETRIES = 5
BASE_DELAY = 2  # seconds

def call_with_retry(soap_client, operation, *args):
    for attempt in range(MAX_RETRIES):
        try:
            return operation(*args)
        except ExceededRequestLimitFault:
            delay = BASE_DELAY * (2 ** attempt) + random.uniform(0, 1)
            print(f"Concurrency limit hit, retry {attempt+1}/{MAX_RETRIES} in {delay:.1f}s")
            time.sleep(delay)
        except ExceededRecordCountFault:
            raise ValueError("Reduce batch size below operation limit")
        except ExceededRequestSizeFault:
            raise ValueError("Request exceeds 100MB - split into smaller requests")
    raise RuntimeError(f"Failed after {MAX_RETRIES} retries")

Code Examples

Python: Search all customers modified since a date with pagination

# Input:  NetSuite account with TBA credentials, target date
# Output: All customer records modified after the given date

# pip install zeep==4.2.1
from zeep import Client
import hashlib, hmac, time, secrets, base64

WSDL_URL = "https://{ACCOUNT_ID}.suitetalk.api.netsuite.com/wsdl/v2025_2_0/netsuite.wsdl"

def search_all_customers(client, since_date):
    search_prefs = client.get_type("ns0:SearchPreferences")(
        pageSize=1000, bodyFieldsOnly=False
    )
    search_record = {
        "basic": {
            "lastModifiedDate": {
                "operator": "after",
                "searchValue": since_date
            }
        }
    }
    result = client.service.search(searchRecord=search_record,
        _soapheaders={"tokenPassport": generate_tba_header(client),
                      "searchPreferences": search_prefs})
    all_records = list(result.searchResult.recordList.record)
    total_pages = result.searchResult.totalPages
    search_id = result.searchResult.searchId
    for page in range(2, total_pages + 1):
        page_result = client.service.searchMoreWithId(
            searchId=search_id, pageIndex=page,
            _soapheaders={"tokenPassport": generate_tba_header(client)})
        all_records.extend(page_result.searchResult.recordList.record)
        time.sleep(0.5)  # Respect concurrency pool
    return all_records

JavaScript/Node.js: Upsert records with batch splitting

// Input:  Array of record objects to upsert
// Output: Results with success/failure per record

// npm install [email protected]
const soap = require('soap');
const BATCH_SIZE = 100; // upsertList max is 100 sync

async function upsertInBatches(client, records) {
  const results = [];
  for (let i = 0; i < records.length; i += BATCH_SIZE) {
    const batch = records.slice(i, i + BATCH_SIZE);
    try {
      const response = await client.upsertListAsync({ record: batch });
      const writeResponse = response[0].writeResponseList.writeResponse;
      writeResponse.forEach((wr, idx) => {
        results.push({
          index: i + idx,
          success: wr.status.isSuccess,
          internalId: wr.baseRef?.internalId || null,
          error: wr.status.statusDetail?.[0]?.message || null
        });
      });
    } catch (err) {
      if (err.root?.Envelope?.Body?.Fault?.faultstring?.includes('ExceededRequestLimitFault')) {
        await new Promise(r => setTimeout(r, 5000));
        i -= BATCH_SIZE; // Retry this batch
      } else { throw err; }
    }
    await new Promise(r => setTimeout(r, 1000));
  }
  return results;
}

cURL: Test TBA authentication

# Input:  TBA credentials (consumer key/secret, token ID/secret, account ID)
# Output: Server time response confirming auth works

curl -X POST \
  "https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/NetSuitePort_2025_2" \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H 'SOAPAction: "getServerTime"' \
  -d '<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns="urn:messages_2025_2.platform.webservices.netsuite.com"
  xmlns:core="urn:core_2025_2.platform.webservices.netsuite.com">
  <soap:Header>
    <ns:tokenPassport>
      <core:account>ACCOUNT_ID</core:account>
      <core:consumerKey>CONSUMER_KEY</core:consumerKey>
      <core:token>TOKEN_ID</core:token>
      <core:nonce>RANDOM_32_CHAR_NONCE</core:nonce>
      <core:timestamp>UNIX_TIMESTAMP</core:timestamp>
      <core:signature algorithm="HMAC-SHA256">BASE64_SIGNATURE</core:signature>
    </ns:tokenPassport>
  </soap:Header>
  <soap:Body><ns:getServerTime/></soap:Body>
</soap:Envelope>'

# Expected: <getServerTimeResponse> with <serverTime>2026-03-01T12:00:00.000Z</serverTime>

Data Mapping

Key Record Type Internal Names

Business ObjectSOAP Record TypeInternal NameNotes
CustomerCustomercustomerIncludes leads, prospects, and customers (lifecycle stages)
Sales OrderSalesOrdersalesorderTransaction record — requires item lines
InvoiceInvoiceinvoiceTransaction record — can be created from sales order
Item (Inventory)InventoryIteminventoryitemStock items with quantity tracking
VendorVendorvendorSupplier records
Purchase OrderPurchaseOrderpurchaseorderTransaction record
Journal EntryJournalEntryjournalentryGL-level financial entries
Custom RecordCustomRecordcustomrecord_{id}Custom record types use dynamic internal names
EmployeeEmployeeemployeeHR records — restricted by role permissions

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
ExceededRequestLimitFaultConcurrency limit exceededToo many simultaneous requestsExponential backoff + queue pending requests
ExceededRecordCountFaultToo many records in requestBatch size exceeds operation limitReduce batch size to within per-operation limits
ExceededRequestSizeFaultRequest body > 100 MBOversized XML payloadSplit into multiple requests
INVALID_LOGIN_ATTEMPTAuthentication failureWrong credentials or TBA not enabledVerify consumer/token keys, enable TBA on integration
INSUFFICIENT_PERMISSIONRole lacks accessIntegration role missing permissionsAdd missing permissions to integration role
SSS_USAGE_LIMIT_EXCEEDEDGovernance units exhaustedToo many governance units consumedOptimize operations — reduce search complexity
INVALID_KEY_OR_REFInvalid ID referenceRecord does not exist or wrong typeValidate IDs exist before referencing

Failure Points in Production

Anti-Patterns

Wrong: Fetching all records to find changes

# BAD -- Pulls every customer record to detect changes
result = client.service.search(searchRecord={"basic": {}})
for record in result.searchResult.recordList.record:
    if record.lastModifiedDate > last_sync_time:
        process(record)

Correct: Use lastModifiedDate filter in search criteria

# GOOD -- Only fetches records modified since last sync
result = client.service.search(searchRecord={
    "basic": {
        "lastModifiedDate": {
            "operator": "after",
            "searchValue": last_sync_time.isoformat()
        }
    }
})

Wrong: Using search for known internal IDs

# BAD -- Using search when you know the IDs
for internal_id in known_ids:
    result = client.service.search(searchRecord={
        "basic": {"internalId": {"operator": "is", "searchValue": internal_id}}
    })

Correct: Use getList for known IDs (up to 1,000 per call)

# GOOD -- getList is faster, cheaper, and supports up to 1,000 records
ref_list = [{"type": "customer", "internalId": id} for id in known_ids[:1000]]
result = client.service.getList(baseRef=ref_list)

Wrong: Ignoring ExceededRequestLimitFault

# BAD -- No retry logic; integration fails on concurrency limit
try:
    result = client.service.addList(record=records)
except Exception as e:
    log.error(f"Failed: {e}")
    return None  # Gives up immediately

Correct: Implement exponential backoff with jitter

# GOOD -- Retries with increasing delay on concurrency limits
import time, random
for attempt in range(5):
    try:
        result = client.service.addList(record=records)
        break
    except ExceededRequestLimitFault:
        delay = (2 ** attempt) + random.uniform(0, 1)
        time.sleep(delay)
else:
    raise RuntimeError("Exhausted retries")

Common Pitfalls

Diagnostic Commands

# Check current concurrency usage
# Navigate in NetSuite: Setup > Integration > Integration Governance

# Test TBA authentication
curl -X POST \
  "https://ACCOUNT_ID.suitetalk.api.netsuite.com/services/NetSuitePort_2025_2" \
  -H "Content-Type: text/xml" -H 'SOAPAction: "getServerTime"' \
  -d '<soap:Envelope...><soap:Header><!-- TBA header --></soap:Header><soap:Body><getServerTime/></soap:Body></soap:Envelope>'

# Check WSDL availability
curl -s "https://ACCOUNT_ID.suitetalk.api.netsuite.com/wsdl/v2025_2_0/netsuite.wsdl" | head -5

# Monitor async job status
# Use getAsyncResult with jobId from async operation
# Poll until status is "complete" or "failed"

Version History & Compatibility

WSDL VersionNetSuite ReleaseStatusBreaking ChangesMigration Notes
2025.22025.2Current (final SOAP endpoint)NoneLast planned SOAP WSDL — pin to this
2025.12025.1SupportedMinor field additionsSupported until 2027.2
2024.22024.2SupportedNoneWill lose support in stages through 2028.1
2024.12024.1SupportedNonePlan migration to 2025.2
Pre-2024.1VariousAvailable (unsupported)VariesAll removed by 2028.2

SOAP Removal Timeline

ReleaseWhat Happens
2025.2Last planned SOAP endpoint published
2026.1All new integrations should use REST + OAuth 2.0
2027.1No new SOAP integrations; no new TBA integrations
2027.2Only 2025.2 endpoint supported with bug fixes
2028.2All SOAP endpoints disabled — integrations stop working

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Maintaining an existing SOAP integrationBuilding a new integration (2026+)SuiteTalk REST API with OAuth 2.0
ESB platform is SOAP-centric (MuleSoft, BizTalk, SAP PI/PO)Need OAuth 2.0 authenticationREST API (OAuth 2.0 supported)
Need saved search execution via APIDaily volume exceeds 100,000 recordsCSV Import for writes, SuiteQL for reads
Record types better supported in SOAP for your versionNeed real-time push notificationsSuiteScript User Event Scripts + RESTlets
Short-term project decommissioning before 2028.2Long-term integration (3+ years)REST API (SOAP removed 2028.2)

Important Caveats

Related Units