Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The NetSuite Pro

The NetSuite Pro Logo The NetSuite Pro Logo

The NetSuite Pro Navigation

  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • Advanced PDF Templates in NetSuite
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask A Question
  • Home
  • About Us
  • Tutorials
    • NetSuite Scripting
    • Advanced PDF Templates in NetSuite
  • Blog
  • Contact Us
Home/ NetSuite Scripting/RESTlets & API Integrations (SuiteScript 2.1)

RESTlets & API Integrations (SuiteScript 2.1)

🔹 What is a RESTlet?

A RESTlet is a SuiteScript that exposes endpoints (GET/POST/PUT/DELETE) so external systems can read/write NetSuite data over HTTPS. Think of it as your custom NetSuite API.

Typical use cases

  • Pull customers, items, balances to an external app
  • Create/update orders, invoices, or custom records from outside NetSuite
  • Integrate with storefronts, ERPs, WMS, billing gateways, data pipelines

Auth options

  • Token-Based Authentication (TBA) – recommended (HMAC, least privilege, revocable)
  • NLAuth (User/Pass/Role) – legacy; only if you must
  • OAuth 2.0 – supported via REST Web Services (not RESTlets directly)

🔹 RESTlet Structure (SuiteScript 2.1)

A RESTlet exports named handlers:

  • get(context)
  • post(context)
  • put(context)
  • delete(context)

Each handler returns JSON (object/array/primitive) → NetSuite serializes to JSON HTTP response.


🔹 Example 1 — Read: GET Customer by ID (returns JSON)

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/record'], (record) => {
  // GET /restlet?customerId=123
  const get = (requestParams) => {
    try {
      // 1) Extract query param from URL
      const customerId = parseInt(requestParams.customerId || '0', 10);
      if (!customerId) return { ok: false, error: 'Missing customerId' };

      // 2) Load record
      const cust = record.load({ type: record.Type.CUSTOMER, id: customerId });

      // 3) Return only the fields you want to expose (never dump entire record blindly)
      return {
        ok: true,
        data: {
          id: customerId,
          entityid: cust.getValue('entityid'),
          email: cust.getValue('email'),
          phone: cust.getValue('phone'),
          isinactive: !!cust.getValue('isinactive')
        }
      };
    } catch (e) {
      // 4) Log internally, return redacted message externally
      log.error('RESTlet GET error', JSON.stringify(e));
      return { ok: false, error: e.message || 'Unexpected error' };
    }
  };

  return { get };
});

Notes

  • Keep responses explicit (whitelist fields).
  • Always try–catch and log full errors on server; return safe messages to caller.

🔹 Example 2 — Search: GET with Filters + Pagination (runPaged)

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/search'], (search) => {
  // GET /restlet?emailContains=@example.com&page=0&pageSize=50
  const get = (params) => {
    try {
      const emailContains = (params.emailContains || '').trim();
      const page = Math.max(parseInt(params.page || '0', 10), 0);
      const pageSize = Math.min(Math.max(parseInt(params.pageSize || '50', 10), 1), 1000);

      const filters = [['isinactive', 'is', 'F']];
      if (emailContains) filters.push('AND', ['email', 'contains', emailContains]);

      const s = search.create({
        type: search.Type.CUSTOMER,
        filters,
        columns: ['internalid', 'entityid', 'email']
      });

      // 1) Paged results for large sets
      const paged = s.runPaged({ pageSize });
      if (page >= paged.pageRanges.length) {
        return { ok: true, page, pageSize, totalPages: paged.pageRanges.length, data: [] };
      }

      // 2) Load requested page
      const pageData = paged.fetch({ index: page });
      const rows = pageData.data.map(r => ({
        id: r.id,
        entityid: r.getValue('entityid'),
        email: r.getValue('email')
      }));

      return { ok: true, page, pageSize, totalPages: paged.pageRanges.length, data: rows };
    } catch (e) {
      log.error('RESTlet search error', JSON.stringify(e));
      return { ok: false, error: e.message || 'Search failed' };
    }
  };

  return { get };
});

Notes

  • runPaged handles very large result sets.
  • Provide page and pageSize to clients for reliable pagination.

🔹 Example 3 — Create: POST Customer (validate input; return new ID)

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/record'], (record) => {
  // POST /restlet  body = { entityid, email, phone }
  const post = (body) => {
    try {
      // 1) Basic validation
      const entityid = (body.entityid || '').trim();
      const email = (body.email || '').trim();
      if (!entityid) return { ok: false, error: 'entityid is required' };

      // 2) Create record
      const rec = record.create({ type: record.Type.CUSTOMER, isDynamic: true });
      rec.setValue({ fieldId: 'entityid', value: entityid });
      if (email) rec.setValue({ fieldId: 'email', value: email });
      if (body.phone) rec.setValue({ fieldId: 'phone', value: String(body.phone) });

      // 3) Save and return ID
      const id = rec.save();
      return { ok: true, id };
    } catch (e) {
      log.error('RESTlet POST error', JSON.stringify(e));
      // Optionally map certain errors to 409/400 semantics in the payload
      return { ok: false, error: e.message || 'Create failed' };
    }
  };

  return { post };
});

Notes

  • Validate required fields.
  • Consider de-dupe checks before create (e.g., search existing by email).

🔹 Example 4 — Update: PUT Customer (partial update with submitFields)

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/record'], (record) => {
  // PUT /restlet  body = { id, email?, phone?, isinactive? }
  const put = (body) => {
    try {
      const id = parseInt(body.id || '0', 10);
      if (!id) return { ok: false, error: 'id is required' };

      const values = {};
      if (body.email !== undefined) values.email = String(body.email);
      if (body.phone !== undefined) values.phone = String(body.phone);
      if (body.isinactive !== undefined) values.isinactive = !!body.isinactive;

      if (Object.keys(values).length === 0) {
        return { ok: false, error: 'No updatable fields provided' };
      }

      // submitFields = faster governance than load/save
      record.submitFields({ type: record.Type.CUSTOMER, id, values });
      return { ok: true, id };
    } catch (e) {
      log.error('RESTlet PUT error', JSON.stringify(e));
      return { ok: false, error: e.message || 'Update failed' };
    }
  };

  return { put };
});

Notes

  • Use submitFields for fast partial updates.
  • Treat PUT as idempotent (safe to retry).

🔹 Example 5 — Delete: soft vs hard

Most integrations prefer soft delete (e.g., set isinactive = true) instead of physical delete.

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/record'], (record) => {
  // DELETE /restlet  body = { id, softDelete: true }
  const doDelete = (body) => {
    try {
      const id = parseInt(body.id || '0', 10);
      const soft = body.softDelete !== false; // default true

      if (!id) return { ok: false, error: 'id is required' };

      if (soft) {
        record.submitFields({
          type: record.Type.CUSTOMER,
          id,
          values: { isinactive: true }
        });
      } else {
        record.delete({ type: record.Type.CUSTOMER, id });
      }

      return { ok: true, id, softDeleted: soft };
    } catch (e) {
      log.error('RESTlet DELETE error', JSON.stringify(e));
      return { ok: false, error: e.message || 'Delete failed' };
    }
  };

  // Expose as "delete" handler name
  return { delete: doDelete };
});

🔹 Calling Your RESTlet from Outside (cURL / Postman)

Token-Based Auth (recommended) – cURL sample

curl -X GET "https://<account>.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=customscript_my_rl&deploy=1&customerId=123" \
  -H "Authorization: NLAuth nlauth_account=<ACCOUNT_ID>, nlauth_consumer_key=<CK>, nlauth_token=<TK>, nlauth_signature=<SIG>, nlauth_version=2" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"

Build the OAuth 1.0 signature (nlauth_signature) with your Consumer/Token secrets. Most teams use Postman or an OAuth library to generate headers. (In Postman, choose OAuth 1.0 and set the NetSuite values.)

Legacy NLAuth (user/pass/role) – only if you must

curl -X POST "https://<account>.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=customscript_my_rl&deploy=1" \
  -H "Authorization: NLAuth nlauth_account=<ACCOUNT_ID>, nlauth_email=<EMAIL>, nlauth_signature=<PASSWORD>, nlauth_role=<ROLE_ID>" \
  -H "Content-Type: application/json" \
  -d '{"entityid":"ACME-1001","email":"ops@acme.com"}'

Security reminders

  • Prefer TBA with least-privilege role.
  • Whitelist the script/deployment permissions to only the records/fields needed.
  • Never hardcode secrets in code; store in Script Parameters (or NetSuite Secrets Manager if available).
  • Use HTTPS only; restrict IPs if possible.

🔹 Making Outbound Calls from NetSuite (to external APIs)

Use N/https for integrations from NetSuite (e.g., push order to WMS).

/**
 * @NApiVersion 2.1
 * @NScriptType Restlet
 */
define(['N/https', 'N/runtime'], (https, runtime) => {
  // POST /restlet to forward payload to external API
  const post = (body) => {
    try {
      // 1) Read endpoint/key from Script Parameters (never hardcode)
      const script = runtime.getCurrentScript();
      const endpoint = script.getParameter({ name: 'custscript_ext_api_url' });
      const apiKey   = script.getParameter({ name: 'custscript_ext_api_key' });

      // 2) Forward the incoming payload to the external API
      const resp = https.post({
        url: endpoint,
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`
        },
        body: JSON.stringify(body || {})
      });

      // 3) Return external response to caller (or transform it)
      return {
        ok: resp.code >= 200 && resp.code < 300,
        status: resp.code,
        response: resp.body
      };
    } catch (e) {
      log.error('Outbound API error', JSON.stringify(e));
      return { ok: false, error: e.message || 'Outbound failed' };
    }
  };

  return { post };
});

Notes

  • Keep timeouts and retries in mind (idempotency!).
  • For large payloads, consider file staging + IDs, not big JSON blobs.

🔹 CORS & Browsers

  • RESTlets are for server-to-server calls.
  • Directly calling from a browser app often hits CORS issues.
  • Solution: call your own server → your server calls the RESTlet.

🔹 Error Handling & Contracts

  • Always return a consistent JSON envelope: { ok, data?, error? }.
  • Log internal details, return safe messages.
  • Define clear status semantics in payload (e.g., ok:false, code:'VALIDATION').
  • For bulk endpoints, return per-item results to avoid all-or-nothing failures.

🔹 Governance & Performance

  • Prefer submitFields for small updates.
  • Use searches with runPaged for large reads.
  • For heavy processes triggered via RESTlet, enqueue a Map/Reduce (via N/task) and return a job ID.
  • Cache reference data (e.g., accounts/items) when possible to reduce repeated loads.

✅ Key Takeaway

RESTlets are your custom NetSuite API. With secure auth, clear request/response contracts, robust error handling, and attention to governance, you can integrate NetSuite with virtually any system—safely and at scale.

Share
  • Facebook

Sidebar

Ask A Question

Stats

  • Questions 6
  • Answers 6
  • Best Answers 0
  • Users 2
  • Popular
  • Answers
  • Rocky

    Issue in running a client script in NetSuite SuiteScript 2.0 ...

    • 1 Answer
  • admin

    How can I send an email with an attachment in ...

    • 1 Answer
  • admin

    How do I avoid SSS_USAGE_LIMIT_EXCEEDED in a Map/Reduce script?

    • 1 Answer
  • admin
    admin added an answer The issue is usually caused by following Wrong script file… September 14, 2025 at 10:33 pm
  • admin
    admin added an answer Steps to send an Invoice PDF by email: define(['N/email', 'N/render',… August 28, 2025 at 3:05 am
  • admin
    admin added an answer This error means your script hit NetSuite’s governance usage limit… August 28, 2025 at 3:02 am

Top Members

Rocky

Rocky

  • 1 Question
  • 21 Points
Begginer
admin

admin

  • 5 Questions
  • 2 Points

Trending Tags

clientscript netsuite scripting suitescript

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

© 2025 The NetSuite Pro. All Rights Reserved