NetSuite

Oracle NetSuite SuiteTalk: REST record API, SuiteQL, SOAP and RESTlets, with OAuth 1.0 token-based auth or OAuth 2.0, reading and writing orders, items, stock and invoices.

NetSuite is Oracle's cloud ERP, accounting and order management suite, and for a mid-market commerce brand it is usually the system of record for everything financial: customers, items, sales orders, fulfilments, invoices, credit memos and inventory by location. It is a single multi-tenant platform with one account per customer, and every account gets the same API surface, which makes it one of the more pleasant enterprise systems to integrate. There are four distinct ways in: the REST record API, SuiteQL over the same REST service, the older SOAP SuiteTalk web service, and RESTlets, which are custom server-side scripts the customer deploys. A production connector normally uses two of them at once, REST record calls for writes and SuiteQL for bulk reads, because the REST record API has no good way to ask for "everything that changed since".

At a glance

Note

NetSuite's help centre pages under docs.oracle.com returned 404 to an unauthenticated fetcher on 2026-09-21, and the REST API Browser is only reachable from inside a logged-in account. The field names, paths and request shapes below come from OpenAPI 3.0.1 documents that NetSuite itself generates from an account's metadata catalog, and from Oracle's own NetSuite SuiteCloud SDK repository. They are therefore first-party, but they were read through GitHub rather than from the help centre.

What it is

NetSuite was founded in 1998, acquired by Oracle in 2016, and reports more than 41,000 customers. It is sold as a full suite (financials, order management, inventory, CRM, and SuiteCommerce for the storefront) rather than as separate products, which is why a NetSuite customer tends to run their whole back office in it. For commerce specifically it holds the item master, the price levels, inventory by location including in-transit and committed quantities, sales orders and their fulfilment and billing, and the receivables ledger.

NetSuite is not a sales channel. It has no listings, no ASINs, no channel-specific product ids. Marketplace orders arrive in NetSuite through a connector or a middleware layer, and stock flows out of NetSuite to those channels. The externalId field on every record exists precisely for this: it is the place to put your own identifier so that re-sending the same order is idempotent.

Accounts have an account id, sometimes called the realm. Production ids look like 1234567 or TSTDRV1303059. Sandboxes append _SB1. The domain form lowercases it and replaces the underscore with a hyphen, so account 1234567_SB1 uses https://1234567-sb1.suitetalk.api.netsuite.com. Getting this transformation wrong is the first thing that breaks.

API access

There is no public developer programme. You get credentials from the customer's NetSuite administrator, who must:

  1. Enable the relevant features under Setup, Company, Enable Features, SuiteCloud tab: REST Web Services, SOAP Web Services if you need SOAP, Token-Based Authentication or OAuth 2.0, and Client SuiteScript and Server SuiteScript if RESTlets are involved.
  2. Create an Integration Record (Setup, Integration, Manage Integrations, New). This produces a consumer key and consumer secret, both shown exactly once. Tick the auth methods you need on this record.
  3. Create a role with the specific permissions your integration needs, including REST Web Services and Log in using Access Tokens, and assign it to a user.
  4. Create an Access Token (Setup, Users/Roles, Access Tokens, New) binding that user, that role and that integration record. This produces a token id and a token secret, again shown once.

The resulting credential is a four-part tuple: consumer key, consumer secret, token id, token secret, plus the account id. Store all five.

Base URLs

Versions

The REST record API is versioned v1 in the path and has been stable since 2019. The underlying record schemas change with NetSuite's two annual releases (for example 2025.1 and 2025.2), which land on every account within a release window the customer cannot fully control. New fields appear without notice. Write the connector so that unknown fields are tolerated. The SOAP API is versioned in the WSDL URL and Oracle supports a rolling window of recent versions; older endpoints are retired annually.

SDKs

Oracle ships the SuiteCloud SDK (oracle/netsuite-suitecloud-sdk, Node and Java) for deploying SuiteScript and SDF projects, not for calling the API from outside. For client work the community libraries are the practical choice: netsuite (Python, SOAP and REST), netsuite-sdk-py, netsuite-rest-api-php, ballerina-platform/module-ballerinax-netsuite, and the Postman collections that circulate in the SuiteAnswers community. NetSuite also generates a per-account OpenAPI 3 document on demand, which is the best possible SDK input:

GET /services/rest/record/v1/metadata-catalog/salesOrder HTTP/1.1
Host: 1234567.suitetalk.api.netsuite.com
Accept: application/swagger+json
Authorization: OAuth realm="1234567", ...

Authentication

Token-based authentication (OAuth 1.0a), the default

This is what almost every server-to-server NetSuite integration uses. There is no token exchange and no refresh; the four secrets are long lived and each request is signed.

  1. Collect consumer_key, consumer_secret, token_id, token_secret and the account id.
  2. Build the OAuth 1.0a signature base string from the HTTP method, the percent-encoded URL and the sorted, percent-encoded parameters, exactly as RFC 5849 specifies.
  3. Sign with HMAC-SHA256. The signing key is percent_encode(consumer_secret) + "&" + percent_encode(token_secret). NetSuite removed HMAC-SHA1 support; using it returns INVALID_LOGIN_ATTEMPT.
  4. Send the result in an Authorization: OAuth header, and set realm to the account id in its uppercase, underscore form.
  5. Nothing expires. Revocation is done by the administrator deleting the access token.
GET /services/rest/record/v1/salesOrder?limit=2 HTTP/1.1
Host: 1234567.suitetalk.api.netsuite.com
Authorization: OAuth realm="1234567",
  oauth_consumer_key="8a1b...",
  oauth_token="c4d5...",
  oauth_signature_method="HMAC-SHA256",
  oauth_timestamp="1758470400",
  oauth_nonce="k2Jf9sQ1",
  oauth_version="1.0",
  oauth_signature="Zk3%2Bq9..."
Accept: application/json

Note that realm uses the uppercase account id with an underscore (1234567_SB1) while the Host uses the lowercase hyphen form (1234567-sb1.suitetalk.api.netsuite.com). Both are required and they differ.

OAuth 2.0

Two grants are supported, and the scopes are rest_webservices, restlets and suiteanalytics_connect.

Authorization code, for acting as a named user:

GET /app/login/oauth2/authorize.nl?response_type=code
  &client_id=<consumer key>
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=rest_webservices%20restlets
  &state=<opaque>
HTTP/1.1
Host: 1234567.app.netsuite.com
POST /services/rest/auth/oauth2/v1/token HTTP/1.1
Host: 1234567.suitetalk.api.netsuite.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=authorization_code&code=<code>&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback

The response is a standard OAuth 2.0 body with access_token, refresh_token, expires_in and token_type. Access tokens are short lived (one hour at the time of writing) and refresh tokens are long lived but expire if unused; re-consent is then required, which is why unattended integrations prefer the machine-to-machine grant or stay on OAuth 1.0a.

Machine to machine uses grant_type=client_credentials with a client_assertion that is an RS256 or PS256 signed JWT whose certificate the administrator uploaded to the integration record. No user is involved and no refresh token is issued; mint a new assertion each time.

Multi-account

One NetSuite account is one customer. There is no concept of an application acting across accounts under one credential, so the credential store keys on account id and holds a full credential tuple per account. A customer with multiple subsidiaries is still one account; subsidiary is a field on every transaction, not a separate login. If a customer has a sandbox as well, treat it as a separate account with its own id, its own integration record and its own tokens.

Objects we can read

Two reading strategies, and you need both.

REST record API. GET /services/rest/record/v1/<recordType> returns a collection of links, not records. The response body has links, count, hasMore, offset, totalResults and an items array where each item is only {"links": [...], "id": "123"}. You then GET each id. That is one request per record, which is why it is not used for bulk reads. The q parameter takes a simple filter expression, limit caps at 1000 and defaults to 10, and offset pages.

SuiteQL. POST /services/rest/query/v1/suiteql with Prefer: transient and a body of {"q": "SELECT ..."} runs read-only SQL against NetSuite's own tables and returns whole rows. limit and offset are query string parameters, limit caps at 1000, and the response carries hasMore, count, offset, totalResults and links with a next relation. This is the only sane way to do an incremental sync.

POST /services/rest/query/v1/suiteql?limit=1000&offset=0 HTTP/1.1
Host: 1234567.suitetalk.api.netsuite.com
Authorization: OAuth realm="1234567", ...
Content-Type: application/json
Prefer: transient

{
  "q": "SELECT id, tranid, trandate, entity, foreigntotal, status, lastmodifieddate FROM transaction WHERE recordtype = 'salesorder' AND lastmodifieddate >= TO_DATE('2026-09-20', 'YYYY-MM-DD') ORDER BY id"
}

Orders

GET /services/rest/record/v1/salesOrder/{id} and GET /services/rest/record/v1/salesOrder?q=...&limit=1000&offset=0.

Sales orders also expose record transforms, which are the supported way to move an order down the pipeline: POST /salesOrder/{id}/!transform/itemFulfillment, !transform/invoice, !transform/cashSale, !transform/returnAuthorization and !transform/fulfillmentRequest.

{
  "links": [
    { "rel": "self", "href": "https://1234567.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder/18452" }
  ],
  "id": "18452",
  "tranId": "SO10241",
  "tranDate": "2026-09-19",
  "createdDate": "2026-09-19T11:02:13Z",
  "lastModifiedDate": "2026-09-20T07:41:55Z",
  "entity": { "id": "456", "refName": "Acme Corporation", "links": [] },
  "subsidiary": { "id": "1", "refName": "Parent Company" },
  "location": { "id": "3", "refName": "Bhiwandi DC" },
  "currency": { "id": "1", "refName": "Indian Rupee" },
  "status": { "id": "B", "refName": "Pending Fulfillment" },
  "orderStatus": { "id": "B", "refName": "Pending Fulfillment" },
  "otherRefNum": "D2C-98217",
  "externalId": "channel:amazon:404-1234567-1234567",
  "shipDate": "2026-09-22",
  "shipMethod": { "id": "22", "refName": "Delhivery Surface" },
  "subtotal": 10592.0,
  "discountTotal": -500.0,
  "shippingCost": 120.0,
  "total": 12499.0,
  "memo": "Marketplace order",
  "email": "orders@acmecorp.example.com",
  "shippingAddress": {
    "addressee": "Acme Corporation",
    "attention": "Receiving",
    "addr1": "123 Main Street",
    "addr2": "Suite 400",
    "city": "Mumbai",
    "state": "MH",
    "zip": "400001",
    "country": { "id": "IN", "refName": "India" },
    "addrPhone": "+91 22 5550 0123"
  },
  "item": {
    "links": [
      { "rel": "self", "href": ".../salesOrder/18452/item" }
    ]
  }
}

All address fields and email are PII. NetSuite does not mask them.

status.id is a single letter and the meanings are transaction-type specific. On a sales order the enum is A through H, surfaced as refName values such as Pending Approval, Pending Fulfillment, Partially Fulfilled, Pending Billing, Billed, Closed and Cancelled. Read refName, not the letter, unless you have pinned the mapping for that record type.

The item sublist is returned as a link, not inline. To get it in one call, use ?expandSubResources=true on the record GET, or fetch GET /salesOrder/{id}/item.

Order items

GET /services/rest/record/v1/salesOrder/{id}/item returns the line collection.

{
  "links": [],
  "count": 1,
  "hasMore": false,
  "offset": 0,
  "totalResults": 1,
  "items": [
    {
      "line": 1,
      "item": { "id": "123", "refName": "FG-OIL-GN-1000" },
      "description": "Cold Pressed Groundnut Oil 1L",
      "quantity": 24.0,
      "rate": 441.33,
      "amount": 10592.0,
      "units": "EA",
      "location": { "id": "3", "refName": "Bhiwandi DC" },
      "price": { "id": "1", "refName": "Base Price" },
      "quantityCommitted": 24.0,
      "quantityFulfilled": 0.0,
      "quantityBackOrdered": 0.0,
      "quantityBilled": 0.0,
      "quantityAvailable": 1482.0,
      "quantityOnHand": 1542.0,
      "isClosed": false,
      "commitInventory": { "id": "1", "refName": "Available Qty" }
    }
  ]
}

The per-line quantityCommitted, quantityFulfilled, quantityBilled and quantityBackOrdered fields are what drive a line-level status in the unified model; NetSuite does not put a status string on the line.

Products and listings

GET /services/rest/record/v1/inventoryItem/{id}. Other item record types exist and must be handled separately: nonInventoryItem, serviceItem, assemblyItem, kitItem, itemGroup and matrix items (a parent with child records).

Key header fields: id, itemId (the SKU as the user sees it), displayName, storeDisplayName, salesDescription, purchaseDescription, upcCode (the GTIN), mpn, manufacturer, vendorName, itemType, subsidiary, weight, cost (purchase price), countryOfManufacture, isInactive, externalId, createdDate, lastModifiedDate, plus the price sublist (price levels and, for quantity pricing, price breaks) and the locations sublist.

There is no listing concept. NetSuite's storeDisplayName and the SuiteCommerce web site fields are the closest thing, and they describe the customer's own storefront, not a marketplace listing.

Inventory

This is NetSuite's strongest object for commerce, and it comes back on the item record rather than from a separate stock service. GET /services/rest/record/v1/inventoryItem/{id}?expandSubResources=true includes the locations sublist:

{
  "id": "123",
  "itemId": "FG-OIL-GN-1000",
  "displayName": "Cold Pressed Groundnut Oil 1L",
  "upcCode": "8901234567890",
  "isInactive": false,
  "lastModifiedDate": "2026-09-20T18:02:41Z",
  "locations": {
    "count": 1,
    "hasMore": false,
    "items": [
      {
        "locationId": 3,
        "location": { "id": "3", "refName": "Bhiwandi DC" },
        "location_display": "Bhiwandi DC",
        "quantityOnHand": 1542.0,
        "quantityAvailable": 1482.0,
        "quantityCommitted": 60.0,
        "quantityBackOrdered": 0.0,
        "quantityOnOrder": 600.0,
        "quantityInTransit": 240.0,
        "reorderPoint": 300.0,
        "preferredStockLevel": 2000.0,
        "averageCostMli": 312.44,
        "costAccountingStatus": "Pending"
      }
    ]
  }
}

That single structure maps cleanly onto the unified inventory table: quantityAvailable to quantity_available, quantityCommitted to quantity_reserved, and quantityOnOrder plus quantityInTransit to quantity_inbound.

For a bulk stock read, do not walk items one at a time. SuiteQL over the item location map is far cheaper:

{
  "q": "SELECT i.itemid, il.location, il.quantityonhand, il.quantityavailable, il.quantitycommitted, il.quantityonorder FROM item i JOIN inventoryitemlocations il ON il.item = i.id WHERE i.isinactive = 'F'"
}

Table and column names in SuiteQL are the underlying record catalogue names, which differ from the REST field names and differ slightly between releases. Verify them against the customer's account before relying on them; this is the single most brittle part of a NetSuite connector.

Shipments and tracking

Item fulfilments are the shipment record. Create one by transforming a sales order (POST /salesOrder/{id}/!transform/itemFulfillment) and read it at GET /services/rest/record/v1/itemFulfillment/{id}.

Tracking numbers live on the fulfilment. The sales order carries a read-only linkedTrackingNumbers string field that aggregates them, which is convenient for a quick lookup but is a delimited string, not a list. Package-level detail is on the fulfilment's package sublists (package, packageFedEx, packageUps, packageUsps depending on which integrated carrier is in use), each with its own tracking number field. Which sublist is populated depends on the customer's shipping integration, so probe rather than assume.

Sample response not published in a form we could verify for itemFulfillment; the record exists in the metadata catalog of every account and its schema should be read from GET /services/rest/record/v1/metadata-catalog/itemFulfillment.

NetSuite holds no carrier scan history. If the customer uses NetSuite's own shipping integration there is a status, but the event timeline has to come from the carrier, not from here.

Returns and cancellations

Returns are a two-step record chain. POST /salesOrder/{id}/!transform/returnAuthorization creates the authorisation, and GET /services/rest/record/v1/returnAuthorization/{id} reads it. The physical receipt is an itemReceipt transformed from the return authorisation, and the financial credit is a creditMemo.

Cancellation of a sales order is not a DELETE. It is a status change: set the order's lines closed, or set the order status to Cancelled via PATCH /salesOrder/{id}. DELETE /salesOrder/{id} exists but hard-deletes the record and will usually be blocked by the customer's accounting policy.

Payments and settlements

GET /services/rest/record/v1/invoice/{id} and GET /services/rest/record/v1/creditMemo/{id}. Both carry id, tranId, tranDate, dueDate, entity, status, subtotal, discountTotal, shippingCost, total, amountPaid, amountRemaining, currency, terms, subsidiary, location, createdFrom (the sales order it came from), billingAddress, memo, externalId, createdDate and lastModifiedDate, plus an item line collection.

Invoices support the same transform mechanism: POST /invoice/{id}/!transform/customerPayment, !transform/creditMemo, !transform/returnAuthorization.

createdFrom is the join back to the sales order and is what makes order-to-cash reconciliation possible. amountRemaining reaching zero is the practical definition of settled.

NetSuite has no marketplace settlement object. If the requirement is to reconcile Amazon or Flipkart payouts, that data has to come from the channel and be matched to NetSuite invoices on externalId or otherRefNum.

Customers

GET /services/rest/record/v1/customer/{id}. Fields include id, entityId, companyName, firstName, lastName, isPerson, email, phone, subsidiary, terms, salesRep, category, isInactive, externalId, plus an addressBook sublist holding the default billing and shipping addresses.

All of companyName, firstName, lastName, email, phone and every address line are PII. NetSuite returns them in full.

Locations

GET /services/rest/record/v1/location. A location is the warehouse, store or fulfilment centre, and its internal id is what appears as locationId on the item location rows and as location on order lines. The record carries name, isInactive, subsidiary, makeInventoryAvailable, useBins and a mainAddress. If the customer runs multi-location inventory, this is the table that must be synced before anything else, because every stock number is keyed on it.

Writing back: listings, price and stock

NetSuite holds no listings, so writing back means pushing orders, pushing invoices and adjusting stock. All three are single-record REST calls; there is no feed or bulk upload API in the marketplace sense.

Sales orders in

POST /services/rest/record/v1/salesOrder HTTP/1.1
Host: 1234567.suitetalk.api.netsuite.com
Authorization: OAuth realm="1234567", ...
Content-Type: application/json

{
  "entity": { "id": "456" },
  "externalId": "channel:amazon:404-1234567-1234567",
  "tranDate": "2026-09-19",
  "subsidiary": { "id": "1" },
  "location": { "id": "3" },
  "currency": { "id": "1" },
  "otherRefNum": "D2C-98217",
  "memo": "Marketplace order",
  "item": {
    "items": [
      { "item": { "id": "123" }, "quantity": 24, "rate": 441.33, "location": { "id": "3" } }
    ]
  }
}

A 204 comes back with a Location header pointing at the new record; the body is empty unless you send Prefer: return=representation. Note the sublist shape: "item": { "items": [ ... ] }, a collection object wrapping an array, not a bare array. This differs from the SOAP API's itemList.item shape, and mixing the two up is a common first-attempt failure.

Idempotency has two mechanisms. externalId makes the record unique in NetSuite's own namespace, so a PUT /salesOrder/eid:<externalId> upserts by it. Separately, X-NetSuite-Idempotency-Key is accepted on asynchronous requests.

Updates are PATCH /salesOrder/{id} with only the changed fields. A full PUT replaces the record and will clear anything you omit.

Invoices and credits in

POST /invoice creates a standalone invoice, but the normal path is POST /salesOrder/{id}/!transform/invoice, which copies the order's lines, links createdFrom, and respects the customer's billing rules. POST /creditMemo and POST /invoice/{id}/!transform/creditMemo do the same for returns.

Stock adjustments in

POST /services/rest/record/v1/inventoryAdjustment. You never set an absolute quantity on the item; you post an adjustment document with either a delta or a target.

POST /services/rest/record/v1/inventoryAdjustment HTTP/1.1
Content-Type: application/json

{
  "subsidiary": { "id": "1" },
  "account": { "id": "214" },
  "adjLocation": { "id": "3" },
  "tranDate": "2026-09-21",
  "memo": "Cycle count correction",
  "inventory": {
    "items": [
      {
        "item": { "id": "123" },
        "location": { "id": "3" },
        "adjustQtyBy": -12,
        "units": "EA"
      }
    ]
  }
}

Each line takes either adjustQtyBy (a delta) or newQuantity (an absolute target, with quantityOnHand showing what NetSuite currently believes). unitCost is required by some accounting configurations. The account must be an adjustment account the customer has designated; ask for it rather than guessing.

For moving stock between locations use inventoryTransfer rather than two adjustments, so the value stays on the books.

Price

Prices are price levels on the item's price sublist. PATCH /inventoryItem/{id} with a price collection updates them. A quantity-break price list is modelled as multiple price rows per level. Customer-specific pricing lives on the customer record's itemPricing sublist, not on the item.

Bulk and async

There is no batch endpoint on the REST record API. Two mechanisms substitute for one:

  • Prefer: respond-async on a record request queues it and returns a job you poll. Paired with X-NetSuite-Idempotency-Key, this is the supported way to submit many writes without holding connections open.
  • For genuine bulk, the customer deploys a RESTlet or a Map/Reduce SuiteScript that accepts an array and processes it server-side. This is custom code on their side and has to be scoped as such.

Webhooks and notifications

NetSuite has no webhook subscription API and no event bus you can subscribe to from outside. This is the largest structural gap in the platform.

The two real options:

  1. SuiteScript user event script. The customer's developer deploys an afterSubmit user event script on the record types you care about, which calls https.post to your endpoint with the record id. You then read the record. This is the standard pattern and it is what every commercial NetSuite connector ships. It requires Server SuiteScript to be enabled and code to live in the customer's account, so it needs their sign-off and their change control.
  2. Poll with SuiteQL. Query lastmodifieddate on the transaction table per record type. This needs no code in the customer's account and is the sane default.
{
  "q": "SELECT id, recordtype, tranid, lastmodifieddate FROM transaction WHERE recordtype IN ('salesorder','itemfulfillment','invoice','creditmemo') AND lastmodifieddate > ? ORDER BY lastmodifieddate, id"
}

Recommended intervals, given the concurrency budget below: orders and fulfilments every 5 to 10 minutes, invoices and credit memos every 15 minutes, item and inventory every 15 to 30 minutes, customers and locations hourly, full reconciliation nightly. Keep a high water mark on lastmodifieddate and overlap it by a minute, because NetSuite's clock and yours will disagree.

Rate limits and pagination

NetSuite governs by concurrency, not by requests per second. Two limits apply simultaneously to every integration, and both come from Oracle's own SuiteApp Architectural Fundamentals guide, version 2025.2.

Account limit, derived from the service tier, the number of SuiteCloud Plus licences and the account type:

Developer accounts have a base limit of 5.

Integration limit, an optional per-application allocation set on the integration record. The account limit is the sum of allocated integration limits plus an unallocated pool that every integration without its own limit competes for.

What happens when you exceed it:

Oracle's own advice is to implement retry with a gradually increasing delay, to poll getAccountGovernanceInfo and getIntegrationGovernanceInfo for the current allocation, and to move work outside the customer's peak hours. Note that these limits are shared with every other integration the customer runs, including their own scripts and any other vendor's connector, so the number you can actually use is smaller than the tier figure. Ask the customer for an allocated integration limit and size your worker pool to exactly that number.

Pagination:

A REST record collection returns only ids and links, so a page of 1000 costs 1 request plus 1000 detail requests. Prefer SuiteQL for anything above a handful of records.

Mapping to the unified model

orders

order_items

products

listings

Not mappable. NetSuite has no listing object, no channel_listing_id and no url. The only fields with a source are sku from itemId, price from the price sublist, stock from the locations sublist, status from isInactive, and updated_at from lastModifiedDate.

inventory

shipments

returns, settlements, customers, locations

Gaps and open questions

  • docs.oracle.com NetSuite help pages returned 404 to our fetcher, and the REST API Browser needs an account login. Field-level semantics for record types we did not have a generated OpenAPI document for (itemFulfillment, returnAuthorization, itemReceipt, inventoryTransfer) are unverified and should be read from each account's own metadata catalog.
  • SuiteQL table and column names are the internal record catalogue names and differ from REST field names. They also shift between the two annual releases. Every SuiteQL statement in a connector needs a smoke test per account per release.
  • Which tax total fields exist on a transaction depends on whether the account uses NetSuite's legacy tax, SuiteTax, or a third-party engine such as Avalara. There is no single taxTotal that is safe everywhere.
  • Tracking numbers have several possible homes depending on the customer's carrier integration. The mapping has to be established per account.
  • OAuth 2.0 access token lifetime is quoted here as one hour from community sources; the authoritative number is in the help centre we could not read.
  • The concurrency table above is from Oracle's SAFE Guide version 2025.2. Tiers and licence bundles change; confirm the customer's actual allocation with getAccountGovernanceInfo rather than trusting the table.
  • Whether the customer will accept a SuiteScript user event script in their account, which is the only way to get near-real-time notification, is a commercial question, not a technical one. Assume polling until told otherwise.

Sources

  • NetSuite REST Record API OpenAPI 3.0.1 documents generated by NetSuite from an account metadata catalog (salesOrder, inventoryItem, invoice, creditMemo, customer, inventoryAdjustment), at csshivampatel/api-connectors
  • Oracle NetSuite SuiteApp Architectural Fundamentals and Examples Guide 2025.2, concurrency governance cheat sheet, at oracle/netsuite-suitecloud-sdk
  • NetSuite OAuth 2.0 authorization and token endpoints and scopes, as implemented in nextauthjs/next-auth
  • SuiteQL request shape, Prefer: transient header and cursor pagination, as implemented in asimlqt/netsuite-rest-api-php
  • SOAP SuiteTalk WSDL URL template and version handling, as implemented in jacobsvante/netsuite
  • Token-based authentication realm handling and HMAC-SHA256 signature method, as implemented in anibalealvarezs/netsuite-api
  • Oracle NetSuite SuiteCloud samples, for SuiteQL usage inside SuiteApps, at oracle-samples/netsuite-suitecloud-samples
  • NetSuite Help Center, SuiteTalk REST Web Services (landing page referenced but not retrievable by our fetcher on 2026-09-21), at docs.oracle.com