NetSuite SOAP to REST Migration: What to Do and in What Order

MokuHub8 min read
netsuitesuitescriptsoaprest-apisuiteqlmigration

The deadline is fixed, but the migration order is not obvious

Oracle will shut off SOAP web services in the 2028.2 release. That is not a rumour or a soft deprecation notice. The 2025.2 endpoint was the last one. Starting with 2026.1, new NetSuite capabilities only ship through REST. By 2027.1, the sole surviving WSDL is 2025.2. After 2028.2, SOAP calls return errors.

You have roughly two years. That sounds like a lot until you open your integration list and realise that "migrate to REST" is not one task. SOAP was a single protocol that handled record CRUD, saved searches, batch writes, metadata lookups and file attachments. The replacement is three different tools, each with its own mechanics, and they need to be adopted in a specific order or the project stalls.

What follows is that order.

Step one: inventory every SOAP call before you touch the code

This is the step that most teams skip, and the reason most migrations take longer than they should.

Open every integration that talks to NetSuite. For each SOAP call, write down three things: the operation (add, update, get, search, upsertList, attach, getSelectValue), the record type, and what consumes the result. That is it. No code changes, no architecture decisions. Just a list.

The list splits your work into categories that map to different migration paths:

  • get, add, update, upsert, delete on a record type → SuiteTalk REST API
  • search, searchMoreWithId → SuiteQL or a RESTlet bridge
  • addList, updateList, upsertList, deleteList → REST batch endpoint
  • attach, detach → REST attach/detach (available since 2026.1)
  • getSelectValue, other metadata calls → REST metadata endpoints (partial coverage)

The inventory takes a day on a typical integration set. It determines everything that follows. Without it, your team will start with the first SOAP call they find, hit a wall when it turns out to be a saved search, and lose a week figuring out that searches work differently.

Step two: migrate authentication first

Authentication looks like the hard part from the outside. It is not.

SOAP uses Token-Based Authentication with OAuth 1.0 signatures. Every request requires a consumer key, consumer secret, token ID, token secret, a nonce, a timestamp, and an HMAC-SHA256 hash of the concatenation. Libraries handle it, but when a signature fails, debugging means comparing base strings character by character.

OAuth 2.0 with JWT client credentials is simpler: generate a signed assertion, POST it to the token endpoint, receive a bearer token, attach it to requests until it expires, then get a new one. No per-request signature computation. No nonce. No timestamp ordering.

Do this first for two reasons. It is the smallest piece of work in the entire migration, which means you ship something in the first week. And every subsequent step requires OAuth 2.0 to be working anyway, so it unblocks everything else.

Set up a new Integration Record in NetSuite with OAuth 2.0 enabled. Generate a certificate for JWT, or use the authorization code flow if your integration runs in a context where a user can authenticate interactively. Either way, you now have a bearer token that works for both REST record calls and SuiteQL queries.

Step three: move record CRUD to SuiteTalk REST

This is the straightforward part. If your SOAP call is a get, add, update, upsert or delete on a standard or custom record, the REST equivalent exists and works the way you expect.

SOAP sent XML. REST sends and receives JSON. SOAP used operation names like add and update. REST uses HTTP methods: POST to create, PATCH to update, PUT to upsert, DELETE to remove, GET to read.

Record type coverage in REST is broad as of 2026.1. Entities, transactions, custom records, activities, support cases, attach/detach operations - nearly everything that had a SOAP endpoint has a REST endpoint. A handful of legacy record types will not get REST equivalents because Oracle is retiring the underlying features rather than porting them.

One thing changes that is easy to miss: sublists. SOAP returned a transaction with its line items embedded in one XML response. REST returns a flat resource. Request a sales order and you get header fields. Line items come back as a link, not as data. To get the actual lines you make a second GET to the sublist URL.

For a batch export of 200 sales orders with line details, that is 1 request for the list plus 200 requests for the lines. 201 API calls where SOAP needed one. This is the N+1 problem, and it matters for step four.

Step four: migrate searches, and do not try to use the REST Record API for it

This is where the migration either goes smoothly or stalls for weeks. The answer depends on how many saved-search-based integrations you have.

SOAP let you execute a saved search by internal ID and page through full results, including formula columns, joins and sublist data. The REST Record API cannot execute a saved search. There is no endpoint for it.

You have two options.

Option A: rewrite searches as SuiteQL. SuiteQL is a SQL dialect that runs against NetSuite's tables through POST /services/rest/query/v1/suiteql. It supports JOINs, subqueries, window functions - things saved searches cannot express. It also solves the N+1 sublist problem from step three:

SELECT
  t.tranid,
  t.trandate,
  tl.item,
  tl.quantity,
  tl.rate,
  tl.netamount
FROM transaction t
JOIN transactionline tl ON tl.transaction = t.id
WHERE t.type = 'SalesOrd'
  AND t.trandate > TO_DATE('2026-01-01', 'YYYY-MM-DD')

One query, headers and lines together, no extra calls. For new integrations or simple searches this is the right path.

The cost: every formula column, every join, every filter in the saved search needs to be translated into SQL, tested against the schema, and maintained in code. If you have five saved searches backing your integrations, this is manageable. If you have fifty, it is a project.

Option B: keep the saved searches, expose them through a RESTlet. Deploy a SuiteScript RESTlet that loads a saved search by ID via the N/search module and returns the results as JSON:

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/search'], (search) => {
    const get = (params) => {
        const results = [];
        const s = search.load({ id: params.searchId });

        s.run().each((result) => {
            const row = {};
            s.columns.forEach((col) => {
                row[col.name] = result.getValue(col);
            });
            results.push(row);
            return results.length < 1000;
        });

        return results;
    };

    return { get };
});

One deployment covers every saved search you have. The formulas, filters and role-based restrictions stay in NetSuite where they already work. Your external integration just changes the endpoint and the auth header.

This is the faster path for teams with a large inventory of existing saved searches. Rewrite the critical ones in SuiteQL later, when you have time and a reason, not under deadline pressure.

Step five: rebuild batch operations

SOAP had addList, updateList, upsertList and deleteList. Each accepted up to 1,000 records in a single call. Simple and synchronous.

REST introduced homogeneous batch operations in 2026.1. POST a batch of creates, updates or upserts to /services/rest/record/v1/batch. There is also an asynchronous batch endpoint for larger jobs.

The mechanics differ in one place that will break your error handling. SOAP batches returned a flat success-or-failure array. REST batches return individual HTTP status codes per operation in a multipart response. If your SOAP integration retried the entire batch on a partial failure, you need to rewrite it to identify and retry only the failed operations.

The volume capacity is comparable. The error model is not. Budget time for error handling, not just for switching the HTTP client.

Step six: find the gaps and decide what to do about each one

Oracle has said explicitly that full parity between SOAP and REST is not planned. After steps one through five, you will have a residual list of SOAP operations that do not map cleanly to anything. Here is what to expect and how to handle it.

Legacy tax engine. If you have integrations that read or write tax configurations through the old engine, the migration path is to SuiteTax first, then REST. This is a tax system migration, not an API migration. It has its own timeline and its own testing requirements. Do not treat it as part of the SOAP project.

Translation sublists. Cannot be modified through REST. If you need to manage translations programmatically, a RESTlet using the N/record module is the workaround.

Metadata operations. SOAP's getSelectValue returned dropdown options for a field. REST metadata endpoints cover some of this as of 2026.1, but not all of it. For gaps, use a RESTlet that calls N/record to load field options.

iPaaS connectors. If you use Celigo, you are probably fine - their connector uses RESTlets by default. Boomi shipped a REST connector in late 2025. Workato supports both but requires manual recipe migration. Check your specific platform and version before assuming your middleware handles the transition for you.

For each gap, the right question is not "when will REST support this" but "is Oracle retiring the feature itself." Check the release notes. Building a workaround for a feature that is being removed is work that expires.

What catches teams off guard

Three things consistently surprise teams mid-migration.

The N+1 problem with sublists. Step three mentioned it, but it deserves repeating because it shows up in performance testing, not in functional testing. Your integration works. It is just ten times slower than it was. Route transaction reads through SuiteQL instead of the REST Record API and the problem disappears.

OAuth 2.0 token expiration. SOAP tokens lasted indefinitely. OAuth 2.0 bearer tokens expire, typically after 60 minutes. If your integration runs long batch jobs, it needs to handle token refresh mid-flight. This is not hard, but if you do not think about it during development, it surfaces in production at 2am during a data sync.

Governance units still apply. RESTlets run SuiteScript, and SuiteScript has governance limits. A RESTlet that loads and iterates a saved search with 50,000 results will hit the governance ceiling before it finishes. Page the results, use searchMoreWithId patterns in your RESTlet, or push large extracts to SuiteQL where governance is not a factor.

Start with the inventory

The migration has a deadline, and the deadline is real. But the risk is not the deadline itself. Teams that stall on this project almost always stall because they started writing code before they understood what they had. They hit a saved search on day three, discovered it does not translate to a REST call, lost a week evaluating options, and carried that uncertainty into every subsequent decision.

The inventory takes a day. It tells you exactly how much work each step is and which steps you can skip. Everything after it is straightforward. Tedious in places, but straightforward.

Do the inventory this week. The code can wait.