Generated page. Written from IdaMilk/SunApps_NetSuite at commit 44cfdc8 on 2026-07-28, version 1.2.8. The repository has no README, so everything below is inferred from source.
SunApps NetSuite Service
What is it?
The single service that talks to NetSuite. Every SunApps request bound for the ERP passes through here, where it is signed with OAuth 1.0a, rate limited, attributed to a user, and forwarded to the NetSuite REST API.
Its own package.json describes it as the "OAuth gateway for all NetSuite communication", which is exactly right.
Purpose
NetSuite authentication is awkward: OAuth 1.0a token-based auth with a consumer key/secret pair, a token id/secret pair, and a realm. Doing that correctly in every service that needs NetSuite would mean distributing those credentials widely and re-implementing signing repeatedly.
Concentrating it here means the NetSuite credentials live in exactly one place, the rate limit is enforced globally rather than per-caller, and every write can be consistently attributed to the user who triggered it.
How it Works
Two kinds of route sit in front of the same signing layer.
Generic passthrough. Wildcard handlers for GET, POST, PUT, PATCH and DELETE forward any path straight to the NetSuite REST API. A caller can reach any NetSuite record type without this service needing to know about it. stripNsRestBaseUrl() normalises incoming URLs, so callers may pass either a bare path or a full NetSuite REST URL.
Named operations. Specific endpoints wrap multi-step work that would be clumsy to express as raw REST — creating an invoice, receiving items, triggering an inventory count, unshipping an inventory assignment, attaching files, reprinting pallet labels.
Requests are queued through a Bottleneck limiter set to 6 concurrent and a 200 ms minimum spacing — roughly 5 requests per second. NetSuite enforces concurrency limits per account, and exceeding them produces failures that look like random errors; the limiter is what keeps that from happening.
The service also stamps who did it. getLastModifiedByField() maps the target endpoint to the right custom field, and the caller's identity — passed as an x-netsuite-user header — is written into it. NetSuite's own audit trail would otherwise attribute every change to the single integration user.
| Record | "Last modified by" field |
|---|---|
| Transactions (work order, item receipt, item fulfilment, transform) | custbody_tran_last_modified_by |
customrecord_pallet | custrecord_pallet_last_modified_by |
customrecord_pallet_details | custrecord_pd_last_modified_by |
customrecord_sample | custrecord_sample_last_modified_by |
customrecord_qc_inspection | custrecord_qci_last_modified_by |
API
| Method | Path | Purpose |
|---|---|---|
GET | / | Health check |
POST | /api/netsuite/restlet | Call an arbitrary RESTlet |
POST | /api/netsuite/create-invoice | Create a fulfilment invoice from a sales order |
POST | /api/netsuite/create-item-receipt | Create an item receipt |
POST | /api/netsuite/item-fulfillment | Create an item fulfilment |
POST | /api/netsuite/count-inventory | Trigger an inventory count |
GET | /api/netsuite/inventory-progress/:taskId | Poll a count's Map/Reduce task status |
POST | /api/netsuite/unship-inventory-assignment | Reverse an inventory assignment |
POST | /api/netsuite/trigger-reprint | Reprint a pallet label via Suitelet |
POST | /api/netsuite/attach-file-to-record | Attach a file to a record |
POST | /api/netsuite/upload-netsuite-file | Upload to the File Cabinet |
POST | /api/netsuite/pallet-details-export | Export pallet details |
| any | /api/netsuite/* | Generic signed passthrough |
The wildcard routes are registered after the named ones, so a specific handler always wins. Adding a named route that collides with an existing path would silently change behaviour for existing callers.
Interfaces
| System | Direction | Purpose |
|---|---|---|
| SunApps Gateway | inbound | Proxies /api/netsuite here |
| SunApps MES | inbound | Posts ERP transactions via the Core API |
| NetSuite REST API | outbound | OAuth 1.0a signed requests |
| SuiteScripts | outbound | Calls RESTlets and Suitelets by script ID |
Gateway /internal/notify | outbound | Sends the "invoice created" notification email |
The link back to SuiteScripts is direct: NETSUITE_CREATE_INVOICE_SCRIPT_ID defaults to customscript_create_invoice_from_so, which is the deployment of createInvoiceFromSO.js in that repository.
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| Express | 5.2.1 | HTTP framework |
| oauth-1.0a | 2.2.6 | NetSuite request signing |
| bottleneck | 2.19.5 | Rate limiting (6 concurrent, 200 ms spacing) |
| morgan | 1.10.1 | Request logging |
| rotating-file-stream | 3.2.9 | Log rotation |
| cors | 2.8.6 | Cross-origin support |
| dotenv | 17.4.2 | Configuration |
No database — this service holds no state of its own.
Operations
Deployed by GitHub Actions to an internal application server under a process supervisor, using the same development / production / tagging workflow trio as the rest of the platform.
Configuration, names only:
| Group | Variables |
|---|---|
| Server | PORT (default 4001) |
| NetSuite auth | NETSUITE_ACCOUNT_ID, NETSUITE_REALM, NETSUITE_CONSUMER_KEY, NETSUITE_CONSUMER_SECRET, NETSUITE_TOKEN_ID, NETSUITE_TOKEN_SECRET |
| Script references | NETSUITE_CREATE_INVOICE_SCRIPT_ID, NETSUITE_CREATE_INVOICE_DEPLOY_ID, NETSUITE_PALLET_REPRINT_SUITLET_ID, NETSUITE_UNSHIP_INVENTORY_ASSIGNMENT_SCRIPT_ID |
| Notification | CORE_API_URL, INVOICE_NOTIFY_EMAILS |
Two behaviours in the config are easy to trip over:
INVOICE_NOTIFY_EMAILSuses??, not||. Setting it to an empty string suppresses the invoice email; only leaving it unset falls back to the built-in recipient list. That distinction is deliberate — it is how the catch-up script runs without emailing anyone — and it is invisible unless you read the config.- Several script and deployment IDs have hardcoded defaults. If a script is redeployed in NetSuite under a new ID and the environment variable is not set, the service keeps calling the old default.
scripts/ holds operational one-offs run by hand: a bin migration Map/Reduce, an invoice catch-up, and an unbilled-tail export.
Repository
| Repository | IdaMilk/SunApps_NetSuite |
| Version | 1.2.8 |
| Primary language | JavaScript (Node, CommonJS) |
| Files | 18 |
| Last activity | 2026-07-27 |
| Status | Active |
| README | None — the highest-value documentation gap in the platform, given every ERP write passes through here |