JSON Schemas & Field Mapping
Complete JSON structures for all RESTlet endpoints with field mapping to NetSuite.
Schema Overview
JSON TO NETSUITE MAPPING
===============================================================================
External JSON Mapping Logic NetSuite Record
+------------------+ +----------------+ +------------------+
| order_id | --------> | store in memo | ----------> | memo |
| customer.email | --------> | lookup/create | ----------> | entity |
| line_items[] | --------> | loop + add | ----------> | item sublist |
+------------------+ +----------------+ +------------------+
RESTlet: Receive E-commerce Orders
Endpoint
POST /app/site/hosting/restlet.nl?script=XXX&deploy=1
Request Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "E-commerce Order",
"type": "object",
"required": ["order_id", "customer", "line_items"],
"properties": {
"order_id": {
"type": "string",
"description": "External order ID (e.g., Shopify order number)"
},
"order_date": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp"
},
"customer": {
"type": "object",
"required": ["email"],
"properties": {
"email": { "type": "string", "format": "email" },
"name": { "type": "string" },
"phone": { "type": "string" },
"company": { "type": "string" }
}
},
"shipping_address": { "$ref": "#/definitions/address" },
"billing_address": { "$ref": "#/definitions/address" },
"line_items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["sku", "quantity", "price"],
"properties": {
"sku": { "type": "string" },
"name": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 },
"price": { "type": "number" },
"discount": { "type": "number", "default": 0 },
"tax_rate": { "type": "number" }
}
}
},
"totals": {
"type": "object",
"properties": {
"subtotal": { "type": "number" },
"tax": { "type": "number" },
"shipping": { "type": "number" },
"discount": { "type": "number" },
"total": { "type": "number" }
}
},
"payment_status": {
"type": "string",
"enum": ["pending", "paid", "refunded"]
},
"priority": { "type": "boolean", "default": false },
"notes": { "type": "string" }
},
"definitions": {
"address": {
"type": "object",
"properties": {
"name": { "type": "string" },
"address1": { "type": "string" },
"address2": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" },
"zip": { "type": "string" },
"country": { "type": "string", "default": "US" },
"phone": { "type": "string" }
}
}
}
}
Sample Request
{
"order_id": "SHOP-98765",
"order_date": "2024-03-15T10:30:00Z",
"customer": {
"email": "jane.smith@example.com",
"name": "Jane Smith",
"phone": "+1-555-123-4567",
"company": "Smith Enterprises"
},
"shipping_address": {
"name": "Jane Smith",
"address1": "456 Oak Avenue",
"address2": "Suite 100",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"country": "US",
"phone": "+1-555-123-4567"
},
"billing_address": {
"name": "Jane Smith",
"address1": "456 Oak Avenue",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"country": "US"
},
"line_items": [
{
"sku": "PAPER-A4-500",
"name": "A4 Printer Paper 500 sheets",
"quantity": 5,
"price": 12.00,
"discount": 0,
"tax_rate": 0.0825
},
{
"sku": "INK-HP-BLACK",
"name": "HP 65XL Black Ink Cartridge",
"quantity": 2,
"price": 34.99,
"discount": 5.00,
"tax_rate": 0.0825
}
],
"totals": {
"subtotal": 129.98,
"tax": 10.72,
"shipping": 8.99,
"discount": 5.00,
"total": 144.69
},
"payment_status": "paid",
"priority": false,
"notes": "Please leave at front door"
}
Field Mapping: JSON → NetSuite Sales Order
FIELD MAPPING TABLE
===============================================================================
+----------------------+----------------------------+--------------------------+
| JSON Field | NetSuite Field | Notes |
+----------------------+----------------------------+--------------------------+
| HEADER FIELDS |
+----------------------+----------------------------+--------------------------+
| order_id | memo | Store for reference |
| | externalid | Set as external ID |
| order_date | trandate | Convert to NS date |
| customer.email | entity | Lookup by email or |
| | | create new customer |
| customer.name | (used for new customer) | companyname |
| customer.phone | (used for new customer) | phone |
| payment_status | custbody_payment_status | Custom field |
| priority | custbody_priority_order | Custom checkbox |
| notes | memo (append) | Add to existing memo |
+----------------------+----------------------------+--------------------------+
| SHIPPING ADDRESS |
+----------------------+----------------------------+--------------------------+
| shipping_address.* | shipaddress | Combined address or |
| | shipaddressee | use subrecord: |
| | shipaddr1 | |
| | shipaddr2 | |
| | shipcity | |
| | shipstate | |
| | shipzip | |
| | shipcountry | |
+----------------------+----------------------------+--------------------------+
| BILLING ADDRESS |
+----------------------+----------------------------+--------------------------+
| billing_address.* | billaddress | Same pattern as ship |
+----------------------+----------------------------+--------------------------+
| LINE ITEMS (Item Sublist) |
+----------------------+----------------------------+--------------------------+
| line_items[].sku | item | Lookup by itemid/sku |
| line_items[].quantity| quantity | Direct mapping |
| line_items[].price | rate | Unit price |
| line_items[].discount| (calculate) | Apply to rate or line |
| line_items[].name | description | Line description |
+----------------------+----------------------------+--------------------------+
| TOTALS (Calculated) |
+----------------------+----------------------------+--------------------------+
| totals.shipping | shippingcost | Or add as line item |
| totals.discount | discounttotal | Or per-line |
| totals.tax | taxtotal | (auto-calculated by NS) |
+----------------------+----------------------------+--------------------------+
| FORM |
+----------------------+----------------------------+--------------------------+
| (hardcoded) | customform | Online Order form ID |
| (hardcoded) | orderstatus | 'A' = Pending Approval |
+----------------------+----------------------------+--------------------------+
Mapping Implementation (SuiteScript)
/**
* Maps incoming JSON to NetSuite Sales Order
*/
function mapOrderToNetSuite(orderData) {
var salesOrder = record.create({
type: record.Type.SALES_ORDER,
isDynamic: true
});
// ==== HEADER MAPPING ====
// Customer: lookup by email or create
var customerId = findOrCreateCustomer({
email: orderData.customer.email,
name: orderData.customer.name || orderData.customer.email,
phone: orderData.customer.phone,
company: orderData.customer.company
});
salesOrder.setValue('entity', customerId);
// Order reference
salesOrder.setValue('externalid', orderData.order_id);
salesOrder.setValue('memo', 'E-commerce Order: ' + orderData.order_id);
// Dates
if (orderData.order_date) {
salesOrder.setValue('trandate', parseDate(orderData.order_date));
}
// Custom fields
salesOrder.setValue('custbody_priority_order', orderData.priority || false);
// Custom form (Online Order form)
salesOrder.setValue('customform', ONLINE_ORDER_FORM_ID);
// ==== SHIPPING ADDRESS ====
if (orderData.shipping_address) {
var shipAddr = orderData.shipping_address;
// Option 1: Use string fields
salesOrder.setValue('shipaddressee', shipAddr.name);
salesOrder.setValue('shipaddr1', shipAddr.address1);
salesOrder.setValue('shipaddr2', shipAddr.address2 || '');
salesOrder.setValue('shipcity', shipAddr.city);
salesOrder.setValue('shipstate', shipAddr.state);
salesOrder.setValue('shipzip', shipAddr.zip);
salesOrder.setValue('shipcountry', shipAddr.country || 'US');
// Option 2: Use address subrecord (more reliable)
/*
var shipSubrecord = salesOrder.getSubrecord('shippingaddress');
shipSubrecord.setValue('addressee', shipAddr.name);
shipSubrecord.setValue('addr1', shipAddr.address1);
shipSubrecord.setValue('addr2', shipAddr.address2 || '');
shipSubrecord.setValue('city', shipAddr.city);
shipSubrecord.setValue('state', shipAddr.state);
shipSubrecord.setValue('zip', shipAddr.zip);
shipSubrecord.setValue('country', shipAddr.country || 'US');
*/
}
// ==== LINE ITEMS ====
orderData.line_items.forEach(function(lineItem) {
// Find item by SKU
var itemId = findItemBySku(lineItem.sku);
if (!itemId) {
log.error('Item not found', 'SKU: ' + lineItem.sku);
return; // Skip this line
}
salesOrder.selectNewLine('item');
salesOrder.setCurrentSublistValue('item', 'item', itemId);
salesOrder.setCurrentSublistValue('item', 'quantity', lineItem.quantity);
salesOrder.setCurrentSublistValue('item', 'rate', lineItem.price);
// Description (optional override)
if (lineItem.name) {
salesOrder.setCurrentSublistValue('item', 'description', lineItem.name);
}
// Line discount (if applicable)
if (lineItem.discount && lineItem.discount > 0) {
// Apply as rate adjustment
var adjustedRate = lineItem.price - (lineItem.discount / lineItem.quantity);
salesOrder.setCurrentSublistValue('item', 'rate', adjustedRate);
}
salesOrder.commitLine('item');
});
// ==== SHIPPING COST ====
if (orderData.totals && orderData.totals.shipping > 0) {
salesOrder.setValue('shippingcost', orderData.totals.shipping);
}
return salesOrder;
}
// Helper: Parse ISO date to NetSuite date
function parseDate(isoString) {
return new Date(isoString);
}
// Helper: Find item by SKU
function findItemBySku(sku) {
var results = search.create({
type: search.Type.ITEM,
filters: [['itemid', 'is', sku]],
columns: ['internalid']
}).run().getRange({ start: 0, end: 1 });
return results.length > 0 ? results[0].id : null;
}
// Helper: Find or create customer
function findOrCreateCustomer(custData) {
// Search by email
var results = search.create({
type: search.Type.CUSTOMER,
filters: [['email', 'is', custData.email]],
columns: ['internalid']
}).run().getRange({ start: 0, end: 1 });
if (results.length > 0) {
return results[0].id;
}
// Create new customer
var customer = record.create({ type: record.Type.CUSTOMER });
customer.setValue('companyname', custData.company || custData.name);
customer.setValue('email', custData.email);
if (custData.phone) customer.setValue('phone', custData.phone);
customer.setValue('isperson', !custData.company);
if (!custData.company && custData.name) {
var nameParts = custData.name.split(' ');
customer.setValue('firstname', nameParts[0]);
customer.setValue('lastname', nameParts.slice(1).join(' ') || nameParts[0]);
}
return customer.save();
}
Response Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Order Response",
"type": "object",
"properties": {
"success": { "type": "boolean" },
"netsuite_id": { "type": "integer" },
"order_number": { "type": "string" },
"status": { "type": "string" },
"message": { "type": "string" },
"error": { "type": "string" }
}
}
Sample Response
{
"success": true,
"netsuite_id": 12345,
"order_number": "SO-00567",
"status": "pending_approval",
"message": "Order created successfully"
}
RESTlet: Receive POS Payments
Endpoint
POST /app/site/hosting/restlet.nl?script=XXX&deploy=1
Request Schema
{
"invoice_number": "INV-00100",
"amount": 150.00,
"payment_method": 5,
"payment_date": "2024-03-15",
"reference": "POS-TXN-789456",
"terminal_id": "TERM-001",
"cashier_id": "EMP-042"
}
Field Mapping: JSON → NetSuite Customer Payment
+----------------------+----------------------------+--------------------------+
| JSON Field | NetSuite Field | Notes |
+----------------------+----------------------------+--------------------------+
| invoice_number | (lookup) | Find invoice by tranid |
| | apply[n].internalid | Apply payment to invoice |
| amount | payment | Payment amount |
| payment_method | paymentmethod | NS payment method ID |
| payment_date | trandate | Payment date |
| reference | memo | POS reference |
| terminal_id | custbody_terminal_id | Custom field |
| cashier_id | custbody_cashier | Custom field (employee) |
+----------------------+----------------------------+--------------------------+
Mapping Implementation
function mapPaymentToNetSuite(paymentData) {
// Find invoice
var invoiceId = findInvoiceByNumber(paymentData.invoice_number);
if (!invoiceId) {
throw error.create({
name: 'INVOICE_NOT_FOUND',
message: 'Invoice not found: ' + paymentData.invoice_number
});
}
// Transform invoice to payment
var payment = record.transform({
fromType: record.Type.INVOICE,
fromId: invoiceId,
toType: record.Type.CUSTOMER_PAYMENT
});
// Set payment fields
payment.setValue('payment', paymentData.amount);
payment.setValue('paymentmethod', paymentData.payment_method);
if (paymentData.payment_date) {
payment.setValue('trandate', new Date(paymentData.payment_date));
}
// Reference info
var memo = 'POS Payment: ' + paymentData.reference;
if (paymentData.terminal_id) {
memo += ' | Terminal: ' + paymentData.terminal_id;
}
payment.setValue('memo', memo);
return payment;
}
RESTlet: Stock Availability Query (GET)
Endpoint
GET /app/site/hosting/restlet.nl?script=XXX&deploy=1&sku=PAPER-A4
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sku | string | Yes* | Item SKU/itemid |
| itemId | integer | Yes* | NetSuite internal ID |
| location | integer | No | Location filter |
*Either sku or itemId required
Response Schema
{
"success": true,
"item_id": 12345,
"sku": "PAPER-A4",
"name": "A4 Printer Paper 500 sheets",
"inventory": {
"available": 150,
"on_hand": 200,
"on_order": 50,
"committed": 50,
"back_ordered": 0
},
"locations": [
{
"id": 1,
"name": "Main Warehouse",
"available": 100
},
{
"id": 2,
"name": "Store Front",
"available": 50
}
],
"last_updated": "2024-03-15T10:00:00Z"
}
Field Mapping: NetSuite → JSON Response
+----------------------------+----------------------+-------------------------+
| NetSuite Field | JSON Field | Notes |
+----------------------------+----------------------+-------------------------+
| internalid | item_id | |
| itemid | sku | |
| displayname | name | Or salesdescription |
| quantityavailable | inventory.available | Total across locations |
| quantityonhand | inventory.on_hand | |
| quantityonorder | inventory.on_order | From POs |
| quantitycommitted | inventory.committed | On SOs |
| quantitybackordered | inventory.back_ordered | |
| locationquantityavailable | locations[].available| Per-location |
+----------------------------+----------------------+-------------------------+
RESTlet: Customer Loyalty Lookup (GET)
Endpoint
GET /app/site/hosting/restlet.nl?script=XXX&deploy=1&email=john@example.com
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| string | Yes* | Customer email | |
| phone | string | Yes* | Customer phone |
| customerId | integer | Yes* | NetSuite customer ID |
*One of these required
Response Schema
{
"found": true,
"customer": {
"id": 789,
"name": "ABC Corporation",
"email": "contact@abc.com"
},
"loyalty": {
"tier_id": 3,
"tier_name": "Gold",
"points_balance": 1500,
"points_lifetime": 5200,
"total_spent": 15000.00,
"member_since": "2023-01-15",
"last_purchase": "2024-03-01",
"discount_percent": 10
}
}
Field Mapping: NetSuite → JSON Response
+-----------------------------------+-------------------------+---------------+
| NetSuite Field | JSON Field | Notes |
+-----------------------------------+-------------------------+---------------+
| CUSTOMER RECORD |
+-----------------------------------+-------------------------+---------------+
| customer.internalid | customer.id | |
| customer.companyname | customer.name | Or entityid |
| customer.email | customer.email | |
+-----------------------------------+-------------------------+---------------+
| CUSTOM RECORD: Customer Loyalty (customrecord_cust_loyalty) |
+-----------------------------------+-------------------------+---------------+
| custrecord_cl_tier | loyalty.tier_id | List value |
| custrecord_cl_tier (text) | loyalty.tier_name | Display value |
| custrecord_cl_points | loyalty.points_balance | |
| custrecord_cl_totalspent | loyalty.total_spent | |
| custrecord_cl_joindate | loyalty.member_since | |
| custrecord_cl_lastredem | loyalty.last_purchase | |
| (calculated) | loyalty.discount_percent| Based on tier |
+-----------------------------------+-------------------------+---------------+
Push Out: Send Order to Warehouse
NetSuite to External System
When a Sales Order is approved, NetSuite sends this payload to the Warehouse API:
Outbound Payload Schema
{
"nsInternalId": 12345,
"orderId": "SO-00567",
"orderDate": "2024-03-15",
"customer": "ABC Corporation",
"customerId": 789,
"shipAddress": "456 Oak Avenue, Suite 100, Springfield, IL 62701",
"shipMethod": "FedEx Ground",
"priority": true,
"requestedShipDate": "2024-03-17",
"lines": [
{
"lineId": 1,
"item": "A4 Printer Paper 500 sheets",
"sku": "PAPER-A4-500",
"quantity": 20,
"location": "Main Warehouse",
"locationId": 1,
"unitOfMeasure": "EA",
"giftWrap": false
}
]
}
Field Mapping: NetSuite → External JSON
+----------------------------+----------------------+-------------------------+
| NetSuite Field | JSON Field | Notes |
+----------------------------+----------------------+-------------------------+
| SALES ORDER HEADER |
+----------------------------+----------------------+-------------------------+
| internalid | nsInternalId | |
| tranid | orderId | SO number |
| trandate | orderDate | Format: YYYY-MM-DD |
| entity (text) | customer | Customer name |
| entity | customerId | Customer internal ID |
| shipaddress | shipAddress | Full address string |
| shipmethod (text) | shipMethod | |
| custbody_priority_order | priority | Custom checkbox |
| expectedshipdate | requestedShipDate | |
+----------------------------+----------------------+-------------------------+
| ITEM SUBLIST |
+----------------------------+----------------------+-------------------------+
| line | lines[].lineId | 1-based line number |
| item (text) | lines[].item | Item display name |
| item.itemid | lines[].sku | Item SKU |
| quantity | lines[].quantity | |
| location (text) | lines[].location | |
| location | lines[].locationId | |
| units (text) | lines[].unitOfMeasure| |
| custcol_gift_wrap | lines[].giftWrap | Custom line field |
+----------------------------+----------------------+-------------------------+
Build Payload (SuiteScript)
function buildWarehousePayload(salesOrder) {
var lines = [];
var lineCount = salesOrder.getLineCount('item');
for (var i = 0; i < lineCount; i++) {
lines.push({
lineId: i + 1,
item: salesOrder.getSublistText('item', 'item', i),
sku: salesOrder.getSublistValue('item', 'item', i, 'itemid'), // May need lookup
quantity: salesOrder.getSublistValue('item', 'quantity', i),
location: salesOrder.getSublistText('item', 'location', i),
locationId: salesOrder.getSublistValue('item', 'location', i),
unitOfMeasure: salesOrder.getSublistText('item', 'units', i) || 'EA',
giftWrap: salesOrder.getSublistValue('item', 'custcol_gift_wrap', i) || false
});
}
return {
nsInternalId: salesOrder.id,
orderId: salesOrder.getValue('tranid'),
orderDate: formatDate(salesOrder.getValue('trandate')),
customer: salesOrder.getText('entity'),
customerId: salesOrder.getValue('entity'),
shipAddress: salesOrder.getValue('shipaddress'),
shipMethod: salesOrder.getText('shipmethod'),
priority: salesOrder.getValue('custbody_priority_order') || false,
requestedShipDate: formatDate(salesOrder.getValue('expectedshipdate')),
lines: lines
};
}
function formatDate(nsDate) {
if (!nsDate) return null;
var d = new Date(nsDate);
return d.toISOString().split('T')[0]; // YYYY-MM-DD
}
Error Response Schema
All RESTlets should return consistent error responses:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Missing required field: customer.email",
"details": [
{
"field": "customer.email",
"issue": "required"
}
]
},
"timestamp": "2024-03-15T10:30:00Z"
}
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
| VALIDATION_ERROR | 400 | Invalid or missing fields |
| ITEM_NOT_FOUND | 400 | SKU doesn't exist in NetSuite |
| CUSTOMER_NOT_FOUND | 400 | Customer lookup failed |
| DUPLICATE_ORDER | 409 | Order already exists (externalid) |
| AUTHORIZATION_FAILED | 401 | Invalid credentials |
| PERMISSION_DENIED | 403 | Insufficient permissions |
| INTERNAL_ERROR | 500 | Unexpected server error |
Summary: All Mapping Tables
| Integration | Direction | Source | Destination | Doc Section |
|---|---|---|---|---|
| E-commerce Order | IN | Shopify JSON | Sales Order | Above |
| POS Payment | IN | POS JSON | Customer Payment | Above |
| Stock Query | OUT | Inventory Item | JSON Response | Above |
| Loyalty Lookup | OUT | Customer/Loyalty | JSON Response | Above |
| Warehouse Push | OUT | Sales Order | Warehouse JSON | Above |
Next Steps
- External API Setup → - Cloudflare Worker to receive pushes
- Integration Test Cases → - Complete integration scenarios