Introduction
Restlets offer a way to add custom endpoints to NetSuite. The can be used internaly in NetSuite, or externally by other systems.
When using a restlet internally, say with the https.get
function, no authentication is needed. When using a restlet externally, you will need to authenticate using either Oauth 1 or Oauth 2. With Oauth 1, you will need to generate an access token and secret. You will be logged in as the same user every time, the user set when you generate the token and secret. With Oauth 2, the user of the external application will be redirected to NetSuite to log in.
More details on Oauth 1 and 2 will be covered in a future post.
Here is a restlet generated by the VS Code SDF extension.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* @NApiVersion 2.1
* @NScriptType Restlet
*/
define([],
() => {
/**
* Defines the function that is executed when a GET request is sent to a RESTlet.
* @param {Object} requestParams - Parameters from HTTP request URL; parameters passed as an Object (for all supported
* content types)
* @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
* Object when request Content-Type is 'application/json' or 'application/xml'
* @since 2015.2
*/
const get = (requestParams) => {
}
/**
* Defines the function that is executed when a PUT request is sent to a RESTlet.
* @param {string | Object} requestBody - The HTTP request body; request body are passed as a string when request
* Content-Type is 'text/plain' or parsed into an Object when request Content-Type is 'application/json' (in which case
* the body must be a valid JSON)
* @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
* Object when request Content-Type is 'application/json' or 'application/xml'
* @since 2015.2
*/
const put = (requestBody) => {
}
/**
* Defines the function that is executed when a POST request is sent to a RESTlet.
* @param {string | Object} requestBody - The HTTP request body; request body is passed as a string when request
* Content-Type is 'text/plain' or parsed into an Object when request Content-Type is 'application/json' (in which case
* the body must be a valid JSON)
* @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
* Object when request Content-Type is 'application/json' or 'application/xml'
* @since 2015.2
*/
const post = (requestBody) => {
}
/**
* Defines the function that is executed when a DELETE request is sent to a RESTlet.
* @param {Object} requestParams - Parameters from HTTP request URL; parameters are passed as an Object (for all supported
* content types)
* @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
* Object when request Content-Type is 'application/json' or 'application/xml'
* @since 2015.2
*/
const doDelete = (requestParams) => {
}
return {get, put, post, delete: doDelete}
});
Entry Points
There are a few different entry points for restlets. Each entry point corresponds to a different http verb.
GET
The get
entry point is used for retrieving data. It is called when a GET
request is made to the restlet.
The requestParams
parameter is an object containing the query parameters from the request url. If the header Content-Type
is set to application/json
, the requestParams
parameter will be a JSON object. If the header Content-Type is not set or is set to
text/plain, the
requestParams` parameter will be a string.
The return value of the get
function is the response body.
post
The post
entry point is used for creating new records. It is called when a POST
request is made to the restlet.
The requestBody
parameter is the request body. Similar to the requestParams
parameter, if the header Content-Type
is set to application/json
, the requestBody
parameter will be a JSON object.
The return value of the post
function is the response body.
put
This is similar to the post
entry point, but is used for updating existing records.
The requestBody
parameter is as described above for a post
request. For more information about the difference between post
and put
, see this stack overflow question.
delete
This is used for deleting records.
Example
Here is a simple restlet that will return the work order variance report detiailed in this post for given a work order id.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* @NApiVersion 2.1
* @NScriptType Restlet
*/
define(['N/query'], (query) => {
const get = (requestParams) => {
const workorderid = requestParams.workorderid
const report = getReport(workorderid)
return report
}
const getReport = (workorderid) = {
var theQuery = `
WITH allitems AS (
SELECT
WorkorderLines.item AS item
FROM TransactionLine AS WorkorderLines
WHERE WorkorderLines.transaction = ${workorderid}
and WorkorderLines.mainline = 'F'
UNION
SELECT
BomRevisionComponentMember.item AS item,
FROM BomRevisionComponentMember
INNER JOIN Transaction ON Transaction.id = ${workorderid}
AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
), WorkorderLines AS (
SELECT
WorkorderLines.item AS item,
WorkorderLines.quantity * -1 AS quantity
FROM TransactionLine AS WorkorderLines
WHERE WorkorderLines.transaction = ${workorderid}
and WorkorderLines.mainline = 'F'
), AssemblyBuildLines AS (
SELECT
AssemblyBuildLines.item AS item,
AssemblyBuildLines.quantity * -1 AS quantity
FROM TransactionLine AS AssemblyBuildLines
WHERE AssemblyBuildLines.createdFROM = ${workorderid}
and AssemblyBuildLines.mainline = 'F'
), BomRevisionLines AS (
SELECT
BomRevisionComponentMember.item AS item,
BomRevisionComponentMember.quantity * TransactionLine.quantity * UnitsTypeUom.conversionrate AS quantity
FROM BomRevisionComponentMember
INNER JOIN Transaction ON Transaction.id = 1234
AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
INNER JOIN UnitsTypeUom ON UnitsTypeUom.internalid = BomRevisionComponentMember.units
INNER JOIN TransactionLine ON TransactionLine.transaction = transaction.id and TransactionLine.mainline = 'T'
)
SELECT
item.itemid AS itemid,
(COALESCE(BomRevisionLines.quantity, 0))/ConsumptionUom.conversionrate AS bomquantity,
(COALESCE(BomRevisionLines.quantity, 0) - COALESCE(WorkorderLines.quantity, 0))/ConsumptionUom.conversionrate AS workordervariance,
(COALESCE(BomRevisionLines.quantity, 0) - COALESCE(AssemblyBuildLines.quantity, 0))/ConsumptionUom.conversionrate AS assemblyvariance,
ConsumptionUom.pluralAbbreviation AS consumptionunit,
(COALESCE(AssemblyBuildLines.quantity, 0) * item.averagecost)/ConsumptionUom.conversionrate AS assemblycost
FROM allitems
INNER JOIN Item ON Item.id = allitems.item
INNER JOIN UnitsTypeUom AS ConsumptionUom ON ConsumptionUom.internalid = Item.consumptionunit
LEFT JOIN WorkorderLines ON workorderlines.item = allitems.item
LEFT JOIN AssemblyBuildLines ON AssemblyBuildLines.item = allitems.item
LEFT JOIN BomRevisionLines ON bomrevisionlines.item = allitems.item
`
return query.runSuiteQL({
query: theQuery
}).asMappedResults()
}
return {
get
}
})
And the request (using fetch) would look like this:
1
2
3
4
5
6
7
8
9
fetch('https://tstdrv123456.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=1234&deploy=1&workorderid=1234', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
'Accept': 'application/json'
}
})
Conclusion
Restlets are invaluable for building external applications that interact with NetSuite. While SuiteTalk is a full featured API, for custom data you want to pull from NetSuite, restlets are a great option.