Step-by-Step SOAP-to-REST Migration Playbook for NetSuite APIs (2028 Deadline)
Oracle is disabling all NetSuite SOAP endpoints by 2028.2. Use this engineering playbook to migrate to a hybrid REST, SuiteQL, and RESTlet architecture.
Oracle has set a hard deadline for legacy integrations. By the 2028.2 NetSuite release, all SOAP endpoints will be permanently disabled, and any integrations relying on them will stop working. If you maintain a NetSuite integration at a B2B SaaS company, this is a mandated migration with a fixed countdown. Engineering teams must immediately plan and execute a step-by-step SOAP-to-REST migration playbook for integrating the Oracle NetSuite API without SOAP complexity to avoid breaking core customer workflows.
The short version: you cannot do a straight SOAP-to-REST swap. NetSuite's SuiteTalk REST API is missing capabilities that SOAP had, so a modern integration requires a different mental model. Attempting to map legacy SOAP XML requests directly to NetSuite's SuiteTalk REST API will expose your infrastructure to severe performance bottlenecks, missing metadata, and aggressive concurrency throttling. A modern, reliable integration needs a hybrid of SuiteTalk REST, SuiteQL, and RESTlets (SuiteScript)—plus a plan for OAuth 2.0 and NetSuite's account-wide concurrency governance.
This guide provides the architectural playbook you need to survive this transition. We will examine the exact deprecation timeline, why a naive move to REST is an engineering trap, and how to architect a modern NetSuite integration. If you want the wider strategic context first, our practical NetSuite migration guide frames the business impact.
The 2028.2 Deadline: Why NetSuite SOAP Is Technical Debt
Oracle has been methodical about retiring legacy web services. The deprecation is a phased rollout, meaning your integration will degrade in capabilities long before the final shutdown date. Treat these dates as immovable:
- 2025.2 Release: The last planned SOAP endpoint ships. Future SOAP endpoints will only be released for critical, emergency requirements. No new capabilities will ever be added to SOAP.
- 2026.1 Release: Oracle NetSuite no longer includes a new SOAP endpoint by default. Every new capability in the 2026.1 release and beyond ships exclusively through the REST API.
- 2027.1 Release: No new integrations may be built using SOAP web services.
- 2027.2 Release: NetSuite will restrict SOAP usage exclusively to the final 2025.2 endpoint. Older endpoint versions will be rejected.
- 2028.2 Release: The hard stop. SOAP web services are completely discontinued, and integrations relying on SOAP cease to function.
The practical implication is that feature parity is already gone. SOAP is in maintenance-only mode, receiving security patches only. Any new NetSuite capability your customers ask about—AI close, expanded pricing rules, new SuiteAnalytics connections—is a REST-only conversation. Teams relying on legacy XML endpoints to sync accounting, inventory, or HRIS data are operating on borrowed time.
Watch for a silent 2026.1 landmine: The legacy NetSuite.com data source has been completely removed, requiring all remaining queries to use NetSuite2.com. Hardcoded connection strings will fail without a clear error path.
Step 1: Inventory Your Existing SOAP Operations
Before writing a single line of REST code, you must build a SOAP call inventory. Because NetSuite's data model is massive, you need to identify exactly which operations you perform and route them to the correct modern API surface. You cannot design mappings for calls you cannot see.
- Pull traffic logs. Use the Web Services Usage Log in NetSuite to identify traffic hitting older SOAP endpoints. Export per-operation counts and latencies for the last 90 days.
- Classify every call. Audit your codebase for these common SOAP operations and bucket them:
searchandsearchMoreWithId: These are your read operations. Do not map these to the REST Record API. Map them to SuiteQL via the REST Query service. SuiteQL allows for complex JOINs and filtering that the REST Record API cannot handle.add,addList,update, anddelete: These are your write operations. Map these to the SuiteTalk REST Record API (POST /services/rest/record/v1/{recordType}).getandgetList: Map single record fetches to the REST Record API. However, if you are fetching complex nested records, evaluate if SuiteQL provides the necessary fields.getCustomizationId(and other custom logic): Map metadata requests, PDF rendering, or custom business logic to a deployed RESTlet (SuiteScript).
- Flag the orphans. Tax rate details, some custom-form field metadata, and certain sublist behaviors do not map cleanly. These need explicit design decisions in Step 4.
A useful output artifact is a simple mapping table:
| SOAP operation | Target surface | Notes |
|---|---|---|
search (Vendor) |
SuiteQL | JOIN entityAddress, subsidiary |
addList (VendorBill) |
REST record | Batch client-side, respect concurrency |
get (SalesTaxItem) |
SOAP fallback | Temporary until REST parity ships (See Step 4) |
getList custom form fields |
RESTlet | REST metadata catalog is schema-only |
Step 2: Architecting the Hybrid REST, SuiteQL, and RESTlet Solution
NetSuite is widely considered the final boss of ERP integrations. Relying entirely on the SuiteTalk REST API will lead to failure. The REST API returns a single record at a time with limited filtering capabilities. To build a resilient integration, you must orchestrate across three distinct API surfaces.
flowchart LR
App["Your SaaS backend"] --> Router["Operation Router"]
Router -->|"Complex Reads, JOINs, filters"| SQL["SuiteQL<br>POST /query/v1/suiteql"]
Router -->|"CRUD Writes"| REST["SuiteTalk REST<br>/record/v1/{type}"]
Router -->|"PDFs, form metadata,<br>custom logic"| RL["RESTlet (SuiteScript)"]
Router -->|"Tax rate details (temporary)"| SOAP["SOAP getList"]
SQL --> NS[("NetSuite account")]
REST --> NS
RL --> NS
SOAP --> NS1. SuiteQL for Complex Reads
SuiteQL is NetSuite's SQL-like query language, accessed via POST /services/rest/query/v1/suiteql. This is how you should execute nearly all list and get operations. SuiteQL supports JOINs across related tables, complex WHERE clauses, aggregation, BUILTIN.DF() for display values, case-insensitive LIKE, and standard offset pagination.
For example, querying a vendor and joining their address and subsidiary relationships in a single call is trivial in SuiteQL. The same request pattern would take 4+ sequential API calls using the REST Record API.
-- Example SuiteQL Query for Vendor Data with Pagination
SELECT
v.id,
v.entityid,
v.companyname,
v.email,
BUILTIN.DF(v.currency) AS currency_name,
ea.addr1,
ea.city,
ea.state
FROM
vendor v
LEFT JOIN
entityAddress ea ON ea.entity = v.id
WHERE
v.isinactive = 'F'
ORDER BY
v.id
OFFSET 0 ROWS FETCH NEXT 1000 ROWS ONLYThe trade-off: SuiteQL is strictly read-only. Every write still goes through REST records.
2. SuiteTalk REST for CRUD Writes
While SuiteQL handles reads, creating, updating, or deleting records must use the SuiteTalk REST API (/services/rest/record/v1/{recordType}/{id}).
When writing data, you must dynamically adapt to the customer's specific NetSuite edition. For instance, if a customer uses NetSuite OneWorld (multi-subsidiary), your POST request to create a Vendor must include a valid subsidiary ID. If they use a standard edition, including that ID will throw an error. Your integration must detect these features at connection time and adjust the payload accordingly.
Additionally, keep payloads small and batch at the application layer. Not because REST supports batching natively (it does not), but because NetSuite's concurrency model rewards fewer, larger operations. Instead of 100 API calls to update 100 inventory items (burning 100 request slots), send 1 API call with a JSON payload containing 100 items to a custom RESTlet designed to process them.
3. RESTlets for Capabilities REST Cannot Provide
Certain operations are impossible through standard REST or SuiteQL. You will need to deploy a custom SuiteScript Suitelet (RESTlet) into the customer's NetSuite account to handle these edge cases.
Two primary use cases require a RESTlet:
- Purchase Order PDF Generation: The standard REST API has no PDF rendering capability. A RESTlet can use the server-side
N/rendermodule to generate a binary PDF of a transaction viarender.transaction(). - Dynamic Form Field Metadata: NetSuite records have dynamic structures. Different forms show different fields, and select options depend on the current record state (e.g., subsidiary selection affecting available departments). The standard REST metadata catalog is schema-level only and often fails to provide runtime form state. A RESTlet can create an in-memory record using
record.create()and introspect it to return the actual runtime field configuration, including visibility flags (isDisplay) and mandatory states.
For a deeper dive into this code, review our hands-on NetSuite API tutorial.
Step 3: Managing Authentication and Concurrency Limits
Migrating off SOAP also means modernizing your authentication and rate-limiting logic. NetSuite's constraints here are notoriously strict, and failing to respect them is the single most common cause of production incidents.
Upgrading to OAuth 2.0 and TBA
Legacy SOAP integrations often relied on basic credentials or outdated session management. Any integration built by a vendor who is gone or silent becomes your team's responsibility, including updating authentication from password-based auth to OAuth 2.0 or Token-Based Authentication (TBA), which NetSuite's REST API requires.
OAuth 2.0 is the target for new work. TBA (implementing OAuth 1.0a) is still widely deployed and supported for both REST and RESTlets, and remains a reasonable interim choice if your customer's admin already has TBA tokens issued.
TBA requires generating an HMAC-SHA256 signature for every single request. The signature base string must include the HTTP method, the exact URL, and all sorted query parameters. A single misplaced character will result in a cryptic 401 Unauthorized error. Ensure your HTTP client is rigorously tested against NetSuite's specific TBA implementation, and always externalize token storage—never bake credentials into code.
Navigating Strict Concurrency Limits
NetSuite enforces strict concurrency governance per account. The account governance limit applies to the combined total of web services (REST and SOAP) and RESTlet requests.
- Default tier: 15 concurrent requests per account.
- Tier 5: 55 concurrent requests.
- SuiteCloud Plus: Each license buys 10 more concurrent threads.
If your application fires off 20 parallel requests to sync invoices on a default tier, the excess requests are rejected immediately with a 429 Too Many Requests (REST) or SSS_REQUEST_LIMIT_EXCEEDED (SOAP/SuiteScript) error.
Design for this from day one:
- Cap client-side parallelism: Keep threads below the account limit (10 threads is safe on the default tier).
- Exponential backoff with jitter: Implement precise backoff logic on 429s. Do not retry synchronously in a tight loop, or you will burn the same slot you just freed.
- Prefer batch RESTlets: Use RESTlets over N chatty REST calls for bulk writes.
- Move long-running work off the concurrency pool: A RESTlet can place data into a custom record or send it to a Map/Reduce task, respond immediately with "200 OK - Received," and let NetSuite process it in the background using SuiteCloud Processors. This uses a different processing pool and does not count against your API concurrency limit.
One SuiteTalk REST limitation to plan around: Per Oracle's REST Web Services documentation, asynchronous request execution is not supported in the REST query service. You must hold the connection open while the query executes. If you were relying on async SOAP jobs for large extracts, the replacement pattern is a Map/Reduce script invoked via RESTlet, not the REST query endpoint.
Step 4: Handling Edge Cases (Tax Rates and Polymorphic Routing)
Even with a hybrid architecture, you will encounter edge cases where the modern APIs fall short. You must plan for these during your migration.
The Tax Rate SOAP Fallback
While we are migrating away from SOAP, there is currently one scenario where it remains a necessary evil: sales tax items.
The SuiteQL salestaxitem table exposes basic tax fields, but it does not expose the full record structure, such as tax type references with names or subsidiary assignments as record references. Until Oracle ships parity in the REST ecosystem, fetching detailed tax rates requires a narrow, temporary fallback to the legacy SOAP getList operation.
Isolate this fallback behind an interface so you can swap it out the moment REST parity ships. Route everything else through REST, and ensure your SOAP fallback uses the 2025.2 endpoint—anything older is scheduled for shutdown before 2028.2.
Polymorphic Resource Routing
NetSuite treats vendors and customers as entirely separate record types stored in separate database tables. From a business and accounting perspective, they are both simply "contacts" or "parties you transact with."
When migrating, do not force your application to understand NetSuite's internal schema. Implement polymorphic resource routing. Expose a single contacts resource in your internal data model with a contact_type discriminator. When a request comes in, your integration layer should dynamically route the request to either the vendor or customer NetSuite endpoint based on that parameter.
The same pattern applies to classes, departments, and locations, which are all forms of organizational segmentation and can be exposed as a single tracking_categories resource. This isolates your core application logic from NetSuite's legacy database decisions, ensuring your data model doesn't break when you later add QuickBooks or Xero.
How Truto Cuts This Migration Down to a Config Change
Everything above is doable in-house. It is also 6-12 months of senior engineering time to get right, plus ongoing maintenance every time NetSuite changes something.
Truto abstracts NetSuite's multi-surface complexity by automatically routing requests. Reads go to SuiteQL, writes go to the REST Record API, and complex metadata tasks go to deployed SuiteScript components. This happens without exposing the underlying routing logic to the developer.
- Edition-adaptive queries: At connection time, Truto detects whether the account is OneWorld, multi-currency, or single-subsidiary and dynamically includes or excludes the relevant JOINs (currency, subsidiary tables). No customer-specific branching in your code.
- Standardized rate limit signals: When an upstream NetSuite API returns an HTTP 429, Truto passes that error directly to the caller. We normalize the upstream rate limit information into standardized IETF headers (
ratelimit-limit,ratelimit-remaining,ratelimit-reset). We do not mask these errors or silently retry on your behalf, ensuring your retry/backoff logic stays precise and honest. - Polymorphic resources out of the box: Vendors and customers are exposed as a single
contactsresource; classes, departments, and locations as a singletracking_categoriesresource.
What to Do This Quarter
The 2028.2 deadline is approaching fast, and migrating a production NetSuite integration takes months of engineering effort. You do not need to migrate everything before 2028.2, but you do need a credible plan by the end of 2026.
A pragmatic sequence:
- This month: Run the Web Services Usage Log export. Get an actual count of SOAP calls per operation.
- Next 60 days: Migrate reads to SuiteQL. This is the lowest-risk change and typically the highest volume.
- Next 90 days: Move writes to REST records with concurrency-aware batching and 429 backoff.
- Before 2027.1: Deploy your RESTlet(s) for PDFs and dynamic metadata. After 2027.1, you cannot build new SOAP integrations at all.
- Before 2028.2: Retire the last SOAP fallback (including tax rates, assuming REST parity has shipped by then).
For more strategies on architecting resilient integrations and understanding why NetSuite is uniquely painful compared to other ERPs, read our guide on architecting a reliable NetSuite API integration.
FAQ
- When exactly does NetSuite SOAP stop working?
- The 2028.2 release is the hard cutoff—all SOAP endpoints are permanently disabled and any remaining SOAP-based integrations cease to function. Before that, 2026.1 stops shipping new SOAP endpoints by default and 2027.1 blocks new SOAP integrations entirely.
- Can I just swap SOAP calls for SuiteTalk REST calls one-for-one?
- No. The SuiteTalk REST Record API is missing capabilities SOAP had, including complex filtering, JOINs, PDF rendering, and runtime form metadata. A working migration requires a hybrid approach: SuiteQL for reads, REST records for writes, and RESTlets (SuiteScript) for PDFs and dynamic metadata.
- What is NetSuite's REST API concurrency limit?
- The default is 15 concurrent requests per account, shared across REST, SOAP, and RESTlets. Higher service tiers go up to 55, and each SuiteCloud Plus license adds 10 more threads. Exceeding the limit returns an HTTP 429 or SSS_REQUEST_LIMIT_EXCEEDED error.
- Does the NetSuite REST query service support asynchronous execution?
- No. According to NetSuite's REST Web Services documentation, asynchronous request execution is not supported in the REST query service. You must hold connections open while queries execute, or use a Map/Reduce script invoked via RESTlet for long-running jobs.
- Do I have to move to OAuth 2.0, or can I keep using Token-Based Auth?
- OAuth 2.0 is the direction for new work, but Token-Based Authentication (TBA, OAuth 1.0a with HMAC-SHA256) remains supported for both REST and RESTlets and is a reasonable interim choice. Password-based auth on SOAP is the highest-risk pattern and should be replaced immediately.