NetSuite SuiteTalk SOAP API Capabilities
What are Oracle NetSuite SuiteTalk SOAP API capabilities - 1000-record limit, concurrency by tier?
TL;DR
- Bottom line: SuiteTalk SOAP is NetSuite's legacy integration surface offering search, CRUD, and list operations via XML/HTTPS. It remains functional but is on a hard deprecation path — 2025.2 is the final SOAP endpoint, no new integrations after 2027.1, full shutdown at 2028.2. Migrate to REST. [src1, src4]
- Key limit: Search results capped at 1,000 records per page (sync); use
searchMoreWithIdfor pagination. Write operations limited to 100-200 records per batch call depending on operation type. [src2] - Watch out for: Concurrency is shared across SOAP, REST, and RESTlet calls in a single pool — a runaway SOAP integration can starve your REST API and RESTlet traffic. [src3, src6]
- Best for: Maintaining existing SOAP-based integrations that cannot be immediately migrated, or ESB platforms with mature SOAP stacks (MuleSoft, BizTalk, SAP PI/PO). [src1]
- Authentication: Token-Based Authentication (TBA) based on OAuth 1.0 — OAuth 2.0 is NOT supported for SOAP web services. [src5]
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]
| Property | Value |
|---|---|
| Vendor | Oracle |
| System | Oracle NetSuite (WSDL 2025.2) |
| API Surface | SOAP (Document-style, XML/HTTPS) |
| Current API Version | WSDL 2025.2 (final planned) |
| Editions Covered | Standard, Premium, Enterprise, Ultimate |
| Deployment | Cloud |
| API Docs | SuiteTalk SOAP Web Services Platform Overview |
| Status | Supported (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 Surface | Protocol | Best For | Max Records/Request | Concurrency | Real-time? | Status |
|---|---|---|---|---|---|---|
| SuiteTalk SOAP | HTTPS/XML | Legacy integrations, SOAP-centric ESBs | 1,000 (search), 200 (addList) | Shared pool | Yes | Deprecated (removal 2028.2) |
| SuiteTalk REST | HTTPS/JSON | All new integrations post-2026.1 | 1,000 (default), configurable | Shared pool | Yes | GA — recommended |
| SuiteQL | HTTPS/JSON | Analytics, complex queries, reporting | 100,000 rows max | Shared pool | Yes | GA |
| RESTlets (SuiteScript) | HTTPS/JSON | Custom endpoints, business logic | Script-dependent | 5 per user | Yes | GA |
| CSV Import | File-based | Bulk data loads, migrations | Unlimited (file-size limited) | N/A | No | GA |
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 Type | Value | Applies To | Notes |
|---|---|---|---|
| Search pageSize (sync) | 1,000 max, 5 min | search, searchMoreWithId | Default is 1,000 if not specified |
| Search pageSize (async) | 2,000 max, 5 min | asyncSearch | Use async for larger page sizes |
addList records | 200 max | Synchronous create | asyncAddList allows 400 |
deleteList records | 200 max | Synchronous delete | asyncDeleteList allows 400 |
getList records | 1,000 max | Synchronous read | asyncGetList allows 2,000 |
updateList records | 100 max | Synchronous update | asyncUpdateList allows 200 |
upsertList records | 100 max | Synchronous upsert | asyncUpsertList allows 200 |
getItemAvailability | 10,000 max | Item availability queries | Largest single-operation limit |
| SOAP request body | 100 MB max | All SOAP requests | Exceeding 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 Tier | Base Concurrent Requests | With 1 SC+ License | With 3 SC+ Licenses | With 5 SC+ Licenses |
|---|---|---|---|---|
| Standard | 5 | 15 | 35 | 55 |
| Premium | 15 | 25 | 45 | 65 |
| Enterprise | 20 | 30 | 50 | 70 |
| Ultimate | 20 | 30 | 50 | 70 |
| Developer/Sandbox | 5 (fixed) | 5 (fixed) | 5 (fixed) | 5 (fixed) |
Formula: Total concurrency = Base tier limit + (10 x number of SuiteCloud Plus licenses).
Governance Error Faults
| Fault Code | Meaning | Resolution |
|---|---|---|
ExceededRecordCountFault | Request exceeds per-operation record limit | Reduce batch size to within limits above |
ExceededRequestSizeFault | SOAP request body exceeds 100 MB | Split payload into smaller requests |
ExceededRequestLimitFault | Account concurrency limit reached | Implement queuing with backoff; check Integration Governance dashboard |
WS_REQUEST_BLOCKED | Request throttled or blocked | Reduce 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]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Token-Based Authentication (TBA) | All SOAP integrations | Until revoked | No (permanent tokens) | Based on OAuth 1.0; recommended for all SOAP |
| User Credentials (legacy) | Testing only | Session-based | No | Deprecated — subject to password policies and MFA issues |
Authentication Gotchas
- TBA tokens are permanent and do not expire, but they can be revoked by an administrator at any time. Build your integration to handle sudden auth failures gracefully. [src5]
- TBA requires a signed OAuth 1.0 header with consumer key, consumer secret, token ID, and token secret. The signature method is HMAC-SHA256. Getting the signature base string wrong is the #1 cause of auth failures. [src5]
- Each integration record must have TBA enabled explicitly (Setup > Integration > Manage Integrations). Forgetting this step causes
INVALID_LOGIN_ATTEMPTerrors. [src5] - OAuth 2.0 cannot be used for SOAP — if you see OAuth 2.0 examples in NetSuite docs, they apply to REST only. [src5, src4]
- Starting with 2027.1, no new integrations using TBA can be created for any API surface. Existing TBA integrations continue working. [src4]
Constraints
- SOAP sunset timeline: 2025.2 is the last SOAP endpoint. 2027.1 blocks new SOAP integrations. 2028.2 removes SOAP entirely. Any new integration should use REST. [src4]
- No OAuth 2.0 for SOAP: SOAP only supports TBA (OAuth 1.0). OAuth 2.0 is REST-only. [src5]
- Shared concurrency pool: SOAP, REST, and RESTlet requests share the same concurrency limit. A SOAP-heavy integration directly reduces capacity for other API surfaces. [src3]
- Record limits are per-call, not per-record:
updateListat 100 records max means you need 10 calls to update 1,000 records, each consuming a concurrent slot. [src2] - Sandbox concurrency fixed at 5: Regardless of production tier or SC+ licenses, sandbox/developer accounts are capped at 5. [src6, src7]
- No webhook/push mechanism: SOAP is pull-only. For real-time notifications, use SuiteScript User Event Scripts or RESTlets. [src1]
- WSDL backward compatibility: You can use an older WSDL against a newer NetSuite release, but NOT a newer WSDL against an older release. [src1]
- Async operations have different limits: Async variants have higher record limits (2x sync) but require polling for completion status. [src2]
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
| Operation | Method | Records/Call | Async Variant | Async Records/Call | Notes |
|---|---|---|---|---|---|
get | Read single | 1 | N/A | N/A | By internal ID or external ID |
getList | Read multiple | 1,000 | asyncGetList | 2,000 | Returns full records |
search | Query | 1,000/page | asyncSearch | 2,000/page | Use SearchPreferences for pageSize |
searchMoreWithId | Pagination | 1,000/page | N/A | N/A | Requires searchId from initial search |
add | Create single | 1 | N/A | N/A | Returns internal ID on success |
addList | Create multiple | 200 | asyncAddList | 400 | Partial success possible |
update | Update single | 1 | N/A | N/A | Requires internal ID |
updateList | Update multiple | 100 | asyncUpdateList | 200 | Partial success possible |
upsert | Create or update | 1 | N/A | N/A | Uses external ID for matching |
upsertList | Batch upsert | 100 | asyncUpsertList | 200 | External ID required on each record |
delete | Delete single | 1 | N/A | N/A | By internal ID |
deleteList | Delete multiple | 200 | asyncDeleteList | 400 | Partial success possible |
getDeleted | Deleted records | N/A | N/A | N/A | Returns records deleted within date range |
initialize | Init record | 1 | N/A | N/A | Pre-populates from source record |
attach/detach | Link records | 1 | N/A | N/A | Creates/removes relationships |
getSelectValue | Picklist values | N/A | N/A | N/A | Returns 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 Object | SOAP Record Type | Internal Name | Notes |
|---|---|---|---|
| Customer | Customer | customer | Includes leads, prospects, and customers (lifecycle stages) |
| Sales Order | SalesOrder | salesorder | Transaction record — requires item lines |
| Invoice | Invoice | invoice | Transaction record — can be created from sales order |
| Item (Inventory) | InventoryItem | inventoryitem | Stock items with quantity tracking |
| Vendor | Vendor | vendor | Supplier records |
| Purchase Order | PurchaseOrder | purchaseorder | Transaction record |
| Journal Entry | JournalEntry | journalentry | GL-level financial entries |
| Custom Record | CustomRecord | customrecord_{id} | Custom record types use dynamic internal names |
| Employee | Employee | employee | HR records — restricted by role permissions |
Data Type Gotchas
- NetSuite datetime fields use Pacific Time (PT) by default unless the user's timezone preference is set differently. Always convert to UTC before comparing with external systems. [src8]
- Currency amounts are stored as decimals with the currency's precision. Multi-currency transactions require specifying the
currencyfield. [src1] - Boolean fields in SOAP use
true/falsestrings, not 1/0. Sending1may cause silent data corruption. [src1] - Custom fields use the
customFieldListelement withinternalIdmatching the custom field's script ID. Wrong internal IDs are silently ignored. [src1] - External IDs (
externalId) are case-sensitive and must be unique per record type. They are the primary mechanism for idempotent upserts. [src1]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
ExceededRequestLimitFault | Concurrency limit exceeded | Too many simultaneous requests | Exponential backoff + queue pending requests |
ExceededRecordCountFault | Too many records in request | Batch size exceeds operation limit | Reduce batch size to within per-operation limits |
ExceededRequestSizeFault | Request body > 100 MB | Oversized XML payload | Split into multiple requests |
INVALID_LOGIN_ATTEMPT | Authentication failure | Wrong credentials or TBA not enabled | Verify consumer/token keys, enable TBA on integration |
INSUFFICIENT_PERMISSION | Role lacks access | Integration role missing permissions | Add missing permissions to integration role |
SSS_USAGE_LIMIT_EXCEEDED | Governance units exhausted | Too many governance units consumed | Optimize operations — reduce search complexity |
INVALID_KEY_OR_REF | Invalid ID reference | Record does not exist or wrong type | Validate IDs exist before referencing |
Failure Points in Production
- searchMoreWithId pagination drift: Records created between search and searchMoreWithId calls can shift between pages, causing duplicates or missed records. Fix:
Use timestamp-based filters and track processed records externally. [src8] - Silent custom field failures: Setting a custom field with incorrect internalId does not raise an error — the field is silently ignored. Fix:
Validate custom field script IDs against getCustomizationId before deployment. [src1] - Concurrency starvation from long-running searches: Complex saved searches holding slots for 30+ seconds reduce capacity for all integrations. Fix:
Break complex searches into narrower criteria; use async search for large result sets. [src3, src6] - TBA token revocation without notification: Admin can revoke tokens at any time with no notification. Fix:
Monitor for INVALID_LOGIN_ATTEMPT errors; implement alerting on auth failures. [src5] - WSDL version mismatch after NetSuite upgrade: Pinning a retired WSDL version causes request failures. Fix:
Monitor Oracle release notes; test against release preview accounts. [src1, src4] - Timezone mismatches in date comparisons: Pacific Time default causes off-by-one errors near midnight. Fix:
Always use full datetime (not date-only) in search criteria, explicitly in UTC. [src8]
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
- Sandbox concurrency is fixed at 5: Production may have 50+ slots, but sandbox is always 5. Integration tests that pass in production timing may fail in sandbox. Fix:
Design load tests with sandbox's 5-slot limit in mind. [src6, src7] - searchMoreWithId results are not stable: Creating/modifying records between pagination calls causes result set drift. Fix:
Track processed record IDs externally; use narrow date ranges. [src8] - updateList at 100 vs addList at 200: Asymmetric limits catch developers who assume uniform caps. Fix:
Always check per-operation limits; use asyncUpdateList (200) if needed. [src2] - WSDL version pinning vs auto-upgrade: Not pinning means NetSuite upgrades may change behavior. Pinning too old risks hitting end-of-support. Fix:
Pin to 2025.2; test against release preview before upgrades. [src1, src4] - Saved search via SOAP ignores SearchPreferences: The saved search's own row limit overrides SOAP SearchPreferences. Fix:
Set saved search to return all results; control pagination via SOAP pageSize. [src8] - Custom record type naming: Custom types use
customrecord_{id}where{id}is the numeric internal ID, not the script ID. Fix:Look up the numeric ID via Setup > Customization > Record Types. [src1]
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 Version | NetSuite Release | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| 2025.2 | 2025.2 | Current (final SOAP endpoint) | None | Last planned SOAP WSDL — pin to this |
| 2025.1 | 2025.1 | Supported | Minor field additions | Supported until 2027.2 |
| 2024.2 | 2024.2 | Supported | None | Will lose support in stages through 2028.1 |
| 2024.1 | 2024.1 | Supported | None | Plan migration to 2025.2 |
| Pre-2024.1 | Various | Available (unsupported) | Varies | All removed by 2028.2 |
SOAP Removal Timeline
| Release | What Happens |
|---|---|
| 2025.2 | Last planned SOAP endpoint published |
| 2026.1 | All new integrations should use REST + OAuth 2.0 |
| 2027.1 | No new SOAP integrations; no new TBA integrations |
| 2027.2 | Only 2025.2 endpoint supported with bug fixes |
| 2028.2 | All SOAP endpoints disabled — integrations stop working |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Maintaining an existing SOAP integration | Building 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 authentication | REST API (OAuth 2.0 supported) |
| Need saved search execution via API | Daily volume exceeds 100,000 records | CSV Import for writes, SuiteQL for reads |
| Record types better supported in SOAP for your version | Need real-time push notifications | SuiteScript User Event Scripts + RESTlets |
| Short-term project decommissioning before 2028.2 | Long-term integration (3+ years) | REST API (SOAP removed 2028.2) |
Important Caveats
- Concurrency limits vary dramatically by service tier and SC+ licensing. A Standard tier account with no SC+ licenses has only 5 concurrent slots shared across all API surfaces. [src3, src6]
- SOAP is on a hard deprecation path. The 2028.2 removal date is firm per Oracle's FAQ. Do not start new SOAP integrations. [src4]
- Governance unit costs per operation are not fully documented publicly. Approximate values in this card are based on community analysis. [src6]
- Sandbox/developer accounts are capped at 5 concurrent requests regardless of production tier. Performance testing in sandbox does not represent production throughput. [src6, src7]
- Rate limits (requests per minute/day) are enforced but exact thresholds are not publicly documented. Plan for thousands of calls per minute for large accounts. [src6]