🔹 Why this matters
Most real-world NetSuite automations integrate with other systems. Those systems usually speak JSON (modern REST APIs) or XML (legacy/EDI/fintech). You’ll need to:
- Parse incoming payloads safely
- Validate required fields
- Transform NetSuite records → JSON/XML
- Call external endpoints (send/receive)
- Store payloads in File Cabinet for audit/debug
🔹 JSON Essentials (parse, build, validate)
Example 1 — Parse incoming JSON (RESTlet) and validate
/**
* @NApiVersion 2.1
* @NScriptType Restlet
*/
define(['N/record'], (record) => {
// POST /restlet body = { entityid, email? }
const post = (body) => {
try {
// 1) Body is already an object for RESTlets (SuiteScript parses JSON for you)
// Still validate its shape!
const entityid = (body?.entityid || '').trim();
const email = (body?.email || '').trim();
if (!entityid) {
return { ok: false, error: 'entityid is required' };
}
// 2) Create a Customer using validated fields
const rec = record.create({ type: record.Type.CUSTOMER, isDynamic: true });
rec.setValue({ fieldId: 'entityid', value: entityid });
if (email) rec.setValue({ fieldId: 'email', value: email });
const id = rec.save();
return { ok: true, id };
} catch (e) {
// Always log internal details; return a safe error to the caller
log.error('RESTlet JSON parse/validate error', JSON.stringify(e));
return { ok: false, error: e.message || 'Invalid JSON or business rules failed' };
}
};
return { post };
});
What to notice
- RESTlets already parse JSON into an object (
body
), but you must validate fields. - Return a consistent envelope:
{ ok, data?, error? }
.
Example 2 — Build JSON from a search (Scheduled Script)
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/search', 'N/file'], (search, file) => {
const execute = () => {
try {
// 1) Collect a small batch of customers
const results = search.create({
type: search.Type.CUSTOMER,
filters: [['isinactive', 'is', 'F']],
columns: ['internalid', 'entityid', 'email']
}).run().getRange({ start: 0, end: 10 });
// 2) Transform rows → array of plain objects
const payload = results.map(r => ({
id: r.id,
entityid: r.getValue('entityid'),
email: r.getValue('email') || null
}));
// 3) Stringify with indentation for readability (audit/debug)
const json = JSON.stringify({ customers: payload }, null, 2);
// 4) Save to File Cabinet (optional, great for auditing integrations)
const f = file.create({
name: `customers_${Date.now()}.json`,
fileType: file.Type.JSON,
contents: json
});
// f.folder = <your folder id>
const fileId = f.save();
log.debug('JSON built & saved', `File Cabinet ID: ${fileId}`);
} catch (e) {
log.error('Build JSON error', e.message);
}
};
return { execute };
});
Pro tip: For large result sets, move to Map/Reduce and stream results into chunks.
Example 3 — Call external JSON API and parse response
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/https', 'N/runtime'], (https, runtime) => {
const execute = () => {
try {
// 1) Read endpoint/key from parameters (never hardcode secrets)
const script = runtime.getCurrentScript();
const endpoint = script.getParameter({ name: 'custscript_ext_api_url' });
const apiKey = script.getParameter({ name: 'custscript_ext_api_key' });
// 2) Make request
const resp = https.get({
url: endpoint,
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiKey}`
}
});
// 3) Parse safely
let data;
try {
data = JSON.parse(resp.body);
} catch (e) {
throw new Error(`Response was not valid JSON (status ${resp.code})`);
}
// 4) Validate the shape you expect
if (!Array.isArray(data?.items)) {
throw new Error('Invalid payload: "items" array not found');
}
log.debug('External items received', data.items.length);
// ... process items ...
} catch (e) {
log.error('External JSON API error', e.message);
}
};
return { execute };
});
Key ideas
- Never assume response is valid JSON: wrap
JSON.parse
intry–catch
. - Validate the expected shape (e.g., must include
items
array).
🔹 XML Essentials (generate, consume)
Some partners still require XML payloads. You can:
- Build XML strings (simple cases).
- Parse basic XML using simple string techniques if the structure is trivial.
- For complex XML/XPath needs, prefer a robust parser/validator (and confirm the exact
N/xml
APIs available in your account build).
Tip: Regardless of approach, always escape special characters in data you place inside XML (
& < > " '
).
Utility — minimal XML escaper
function xmlEscape(s='') {
return String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Example 4 — Build a simple XML document to send
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/https', 'N/runtime'], (https, runtime) => {
const execute = () => {
try {
const script = runtime.getCurrentScript();
const endpoint = script.getParameter({ name: 'custscript_xml_endpoint' });
const apiKey = script.getParameter({ name: 'custscript_xml_api_key' });
// 1) Build an order XML (very simple example)
const order = {
id: 'SO1001',
customer: 'ACME & Co.',
total: 250.75
};
// 2) Escape all dynamic values!
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<Order>',
` <Id>${xmlEscape(order.id)}</Id>`,
` <Customer>${xmlEscape(order.customer)}</Customer>`,
` <Total>${order.total}</Total>`,
'</Order>'
].join('\n');
// 3) POST it
const resp = https.post({
url: endpoint,
headers: {
'Content-Type': 'application/xml',
'Authorization': `Bearer ${apiKey}`
},
body: xml
});
if (resp.code < 200 || resp.code >= 300) {
throw new Error(`XML endpoint returned ${resp.code}: ${resp.body}`);
}
log.debug('XML sent successfully', `Status ${resp.code}`);
} catch (e) {
log.error('Send XML error', e.message);
}
};
return { execute };
});
What to notice
- We escaped dynamic values to keep XML well-formed.
- Some partners require specific namespaces/schemas—follow their spec.
Example 5 — Parse a known simple XML response
If the response format is simple and predictable, you can extract values with lightweight string techniques. (For complex XML, use a full parser and XPath per your account’s available APIs.)
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/https', 'N/runtime'], (https, runtime) => {
const execute = () => {
try {
const script = runtime.getCurrentScript();
const endpoint = script.getParameter({ name: 'custscript_xml_status_url' });
const apiKey = script.getParameter({ name: 'custscript_xml_api_key' });
// 1) Call partner status endpoint (returns a tiny XML like: <Status><Code>OK</Code><Message>...</Message></Status>)
const resp = https.get({
url: endpoint,
headers: {
'Accept': 'application/xml',
'Authorization': `Bearer ${apiKey}`
}
});
const xml = resp.body;
// 2) Super-light extraction (ONLY if structure is tiny and stable)
const codeMatch = xml.match(/<Code>([^<]+)<\/Code>/i);
const msgMatch = xml.match(/<Message>([^<]+)<\/Message>/i);
const code = codeMatch ? codeMatch[1] : null;
const msg = msgMatch ? msgMatch[1] : null;
if (!code) throw new Error('Missing <Code> in XML response');
log.debug('XML status parsed', `Code=${code} | Message=${msg || ''}`);
} catch (e) {
log.error('Parse XML error', e.message);
}
};
return { execute };
});
Notes
- This lightweight parsing works only for tiny, stable payloads.
- For nested/variable XML, use a proper parser and XPath and validate against the partner’s XSD if provided.
🔹 JSON ⇄ XML transformations
Typical flows:
- NetSuite → Partner (XML): build XML from NetSuite data; send via HTTPS or SFTP (see your SFTP page).
- Partner → NetSuite (XML): download XML (SFTP/HTTP), parse, then upsert records.
- Consider staging files (File Cabinet), and keeping audit copies (saved JSON/XML) for reconciliation.
🔹 Storing payloads for audit
/**
* Store request/response safely for troubleshooting (avoid secrets).
*/
function savePayload(fileModule, name, mimeType, contents, folderId) {
try {
const f = fileModule.create({ name, fileType: mimeType, contents });
if (folderId) f.folder = folderId;
return f.save();
} catch (e) {
log.error('Save payload error', e.message);
return null;
}
}
- Use
file.Type.JSON
orfile.Type.PLAINTEXT
for XML. - Do not store secrets; redact tokens before saving.
🔹 Error handling & contracts
- Always wrap
JSON.parse
intry–catch
. - For XML, escape your dynamic values; validate partner responses.
- Use a consistent envelope in RESTlets:
{ ok, data?, error? }
. - Log context (endpoint, IDs, counts) but never log secrets.
🔹 Performance & governance tips
- Prefer
submitFields()
over fullload + save
when updating records after parsing payloads. - For large imports/exports, use Map/Reduce and batch by 100–1000 items.
- For huge XML files, consider SFTP + incremental processing (stream or chunk).
✅ Key Takeaway
- JSON: use
JSON.stringify/parse
, validate shapes, keep contracts consistent. - XML: escape dynamic values, follow partner schemas, use a proper parser for complex docs.
- Persist payloads (without secrets) for audit, and scale heavy jobs with Map/Reduce.