Saved Search Job¶
Mitto’s Saved Search Job calls Zuar’s NetSuite Saved Search RESTlet which, in turn, executes a previously defined NetSuite saved search. The RESTlet returns the results of the saved search as JSON data so that the data can be further processed by Mitto. This note describes how to configure a Mitto Saved Search job to load data into Mitto.
Zuar’s Mitto Bundle installs the Mitto User Role and the Mitto Saved Search RESTlet in the host NetSuite account. The Mitto Bundle must be installed prior to following these instructions.
Unlike other NetSuite jobs, a Saved Search job must be created by hand. At a high-level, the steps to create and use a Saved Search job are:
Install Zuar’s Mitto Bundle in the host NetSuite account.
Create a NetSuite saved search.
Create a job configuration, either “load” or “upsert”.
Create a Mitto job with the configuration.
Run the job
The remainder of this note walks through setting up Mitto and configuring NetSuite for a Saved Search Upsert Job that will collect transactions affecting revenue in 2021.
Install the Mitto Bundle¶
Each version of the mitto-plugin-netsuite
is matched to a specific
version of the Mitto Bundle, as is shown in this table:
Mitto Bundle / RESTlet |
mitto-plugin-netsuite |
---|---|
2.0 / 2021.5.25 |
2021.6.4 |
2.0 / 2021.5.25 |
2021.5.25 |
2.0 / 2021.5.25 |
2021.5.13 |
1.5 / 2021.5.25 |
2021.5.13 |
1.4 / 2021.2.25 |
2021.3.1, 2021.2.25 |
Warning
When Zuar publishes updated bundles to NetSuite, you may receive email from NetSuite about the update. Additionally, NetSuite will display alerts regarding available updates when viewing installed bundles. Do not update the Mitto bundle without first confirming that it is compatible with the current plugin version. It may be necessary to first upgrade the plugin to ensure compatibility. Mismatched versions are likely to cause job failures.
These instructions describe how to install the Mitto Bundle. Among other things, the bundle will install Mitto’s Saved Search RESTlet and the Mitto User Role; both are necessary for Mitto to be able to collect data from a saved search.
Create a Saved Search¶
If you have an existing saved search, feel free to use that, but be sure to skim this section as it contains important information. In this example, we’ll create a saved transaction search. Refer to the Figure: jdss_saved_search_criteria.


The criteria shown select income affecting transactions; your criteria
can differ. The box next to Public
must be checked for Mitto to be
able to use the saved search.
When the criteria are complete, click the Results
tab and specify
the output fields. Refer to Figure:
Saved Search Results Fields.

Saved Search Results Fields¶
Important points about results:
NetSuite automatically includes the
Internal Id
in results fields; it is unnecessary, but not incorrect, to add it as a results field.If
Line Id
appears in the list of available fields, it must be added to the results columns. This is necessary to prevent data loss in certain situations. IfLine Id
does not appear in the list of available fields, it can safely be omitted.If the saved search will be used with an upsert job,
Last Modified
must be included in the results columns.Generally, you should not provide
Sort By
criteria. The RESTlet automatically sorts results in increasing order ofInternal Id
and, if present,Line Id
.Do not include groups, summaries, or formulas in the results. NetSuite does not return all data to the RESTlet when these are present, causing data loss.
You can provide an Internal Id
for the saved search or you can allow
NetSuite to automatically create one for you. In the above example,
NetSuite assigned an Internal Id
of customsearch799
to the
search. Record the Internal Id
of your search; it will be needed
later when configuring the saved search job.
Important: Once the saved search has been saved, it should be
tested to ensure that it executes properly and returns the expected
records. This is done by viewing the saved search in the UI or
downloading the results as a CSV. The viewing/export must be done
using the NetSuite role under which the saved search job will
ultimately run (e.g., the Mitto User Role
).
Create a Job Configuration¶
Begin by collecting the information necessary to create the job configuration
NetSuite Credentials¶
As of release 2020.6.1
, the connector supports use of Token-Based Authentication (TBA)
for calling RESTlets to invoke saved searches. Earlier versions of the connector
supported only Basic Authentication (username/password). All new saved search jobs
should be configured to use Token-Based Authentication.
TBA Credentials¶
Credentials used for Token-Based Authentication are the same as used in all other jobs requiring credentials. E.g.:
"credentials": {
"account": "TSTDRV1762243",
"consumer_key": "...consumer-key...",
"consumer_secret": "...consumer-secret...",
"token_id": "...token-id...",
"token_secret": "...token-secret..."
}
Reference: TBACredentials
Basic Credentials¶
When Basic Credentials are used, the Internal ID of the Mitto User Role must be provided. If NetSuite has been provisioned such that the connector is using a different role, that role’s Internal ID must be provided.
Note that a role
is not used with TBA.
"credentials": {
"account": "TSTDRV1762243",
"email": "zed@zuar.com",
"password": "...password...",
"role": 1078
},
Reference: BasicCredentials
RESTlet Information¶
The restlet
section contains the information necessary to identify the Saved
Search RESTlet installed when the Mitto Bundle is installed in the NetSuite
account. The configuration values are taken from from the Script Deployment
page for the Mitto Saved Search RESTlet.
Customization -> Scripting -> Script Deployments
If necessary, filter results by setting
Filters:Type
toRESTlet
.Locate the deployment record for “Mitto Saved Search RESTlet”.
Click
View
.Locate the
URL
field. It will look something like:/app/site/hosting/restlet.nl?script=641&deploy=8
Record the script id and deploy id. For the above URL:script
is641
anddeploy
is8
.If the
Status
of the record isTesting
, change it toReleased
.
These will go in the restlet
section of the configuration:
"restlet": {
"script": 641,
"deploy": 8
},
Configuration <–> NetSuite Relationships¶
The following diagram depicts the relationship between sections of the job configuration and the customer’s NetSuite account.
Example NetSuite Account Configuration & Matching Mitto Job Configuration¶
The Job Configuration¶
Now it is time to take the information collected thus far and combine it into a single job configuration file that can be executed by Mitto. The saved search job supports both “load” jobs as well as “upsert” jobs.
Note
These configurations are in HJSON format, which is supported in Mitto 2.9 and later. When using these with earlier Mitto versions, it will be necessary to remove trailing commas, comments, and other features not supported by plain JSON.
Record Type¶
The record type associated with the saved search must be included in the job configuration.
When creating a saved seach in the NetSuite UI, the first choice one
makes is “Search Type”. The search type specifies the record type to
be searched. However, it isn’t always obvious what the internal id of
the chosen record type is. While an Account
search searches records
with a record type of Account
, a Project
search searches records
with a record type of job
.
For standard NetSuite records, the Records Browser is the best source to consult when divining the record type of a saved search. The “Internal ID” field contains the the associated record type.

NetSuite Records Browser¶
In the case of Project
:
Click
Records Browser
Click
Project
Record type is
job
Custom record types are a little different. Custom records naturally
won’t appear in the Records Browser. You can view all of the custom
records configured in your account by going to: Customization ~>
Lists, Records & Fields~ > Record Types
. The name used when creating
the saved search appears in the Edit
column; the record type to use
in the Mitto job configuration appears in the Id
column.
Load Job¶
Let’s begin by presenting an example “load” job configuration. Each time it is run, this job will load all records returned by the saved search.
“load” Job Configuration
1/* file: mitto-plugin-netsuite/docs/src/examples/load.json
2 *
3 * Run a job using this config from the commandline in a dev environment via:
4 * $ CREDS=~/run/ns EX=~/mitto-plugin-netsuite/docs/src/examples
5 * $ job_io.py `mmc.py --env $CREDS/ns-zuardev.env $EX/load.json $CREDS/ns-creds.py`
6 */
7{
8 "title": "Saved Search - Load Example",
9 "name": "ss_load",
10 "type": "io",
11 "input": {
12 "use": "netsuite.io#NetsuiteSavedSearchInput",
13 "credentials": { /*credentials*/ },
14 "restlet": {
15 "script": 641,
16 "deploy": 8,
17 },
18 "saved_search": {
19 "saved_search_id": "customsearch799",
20 "record_type": "Transaction",
21 // must be present if "Line Id" is in saved search output columns
22 "row_line_id": 0,
23 },
24 },
25 "output": {
26 "use": "call:mitto.iov2.db#todb",
27 "dbo": "postgresql://localhost/analytics",
28 "schema": "netsuite",
29 "tablename": "ss_load",
30 },
31 "steps": [
32 {
33 "use": "mitto.iov2.steps#Input",
34 "transforms": [
35 {
36 "use": "mitto.iov2.transform#ExtraColumnsTransform",
37 "jpath": "$",
38 "ignores": ["$.columns"]
39 },
40 {
41 "use": "mitto.iov2.transform#ExtraColumnsTransform",
42 "jpath": "$.columns"
43 },
44 {
45 "use": "mitto.iov2.transform#ColumnsTransform"
46 }
47 ]
48 },
49 {
50 "use": "mitto.iov2.steps#CreateTable"
51 },
52 {
53 "use": "mitto.iov2.steps#Output",
54 "transforms": [
55 {
56 "use": "mitto.iov2.transform#FlattenTransform"
57 }
58 ],
59 },
60 {
61 "use": "mitto.iov2.steps#CollectMeta"
62 }
63 ],
64}
Upsert Job¶
In many cases, it’s only necessary to get the records that have changed since the last time ran a job. Jobs configured to do this are called upsert jobs.
For an upsert job to work properly, Last Modified
must be included
in the Results
columns. Generally, this places the last modified
date of each row in a column named lastmodifieddate
. Knowledge of
the column name is important to crafting the job configuration.
Upsert Job Configuration
1/* file: mitto-plugin-netsuite/docs/src/examples/upsert-default.json
2 *
3 * Run a job using this config from the commandline in a dev environment via:
4 * $ CREDS=~/run/ns EX=~/mitto-plugin-netsuite/docs/src/examples
5 * $ job_io.py `mmc.py --env $CREDS/ns-zuardev.env $EX/upsert.json $CREDS/ns-creds.py`
6 */
7{
8 "title": "Saved Search - Upsert Default Example",
9 "name": "ss_upsert",
10 "type": "io",
11 "input": {
12 "use": "netsuite.io#NetsuiteSavedSearchInput",
13 "credentials": { /*credentials*/ },
14 "restlet": {
15 "script": 641,
16 "deploy": 8,
17 },
18 "saved_search": {
19 "saved_search_id": "customsearch799",
20 "record_type": "Transaction",
21 // must match the Last Modified column name in saved search output
22 "last_modified_column": "lastmodifieddate",
23 // must be present if "Line Id" is in saved search output columns
24 "row_line_id": 0,
25 },
26 },
27 "output": {
28 "use": "call:mitto.iov2.db#todb",
29 "dbo": "postgresql://localhost/analytics",
30 "schema": "netsuite",
31 "tablename": "ss_upsert",
32 },
33 "steps": [
34 {
35 "use": "mitto.iov2.steps#MaxTimestamp"
36 "schema": "netsuite",
37 "tablename": "ss_upsert",
38 // Database column containing last modified date
39 "column": "last_modified",
40 },
41 {
42 "use": "mitto.iov2.steps.upsert#SetUpdatedAt"
43 },
44 {
45 "use": "mitto.iov2.steps#Input",
46 "transforms": [
47 {
48 "use": "mitto.iov2.transform#ExtraColumnsTransform",
49 "jpath": "$",
50 "ignores": ["$.columns"]
51 },
52 {
53 "use": "mitto.iov2.transform#ExtraColumnsTransform",
54 "jpath": "$.columns"
55 },
56 {
57 "use": "mitto.iov2.transform#ColumnsTransform"
58 }
59 ]
60 },
61 {
62 "use": "mitto.iov2.steps#CreateTable"
63 },
64 {
65 "use": "mitto.iov2.steps.upsert#CreateTempTable"
66 },
67 {
68 "use": "mitto.iov2.steps#Output",
69 "transforms": [
70 {
71 "use": "mitto.iov2.transform#FlattenTransform"
72 }
73 ],
74 },
75 {
76 "use": "mitto.iov2.steps.upsert#SyncTempTable",
77 "key": [
78 "id",
79 "line",
80 ],
81 },
82 {
83 "use": "mitto.iov2.steps#CollectMeta"
84 }
85 ],
86 "store": {
87 "key": [
88 "$.id",
89 "$.columns.line",
90 ],
91 // Location of last modified date in the store
92 "updated_at": "$.last_modified",
93 },
94}
Highlights from the job configuration:
Line 22 tells Mitto that output column named
lastmodifieddate
should be considered the most recent date on which the record was modified by NetSuite. Mitto will automatically save these values in thelast_modified
column of the output database table.Line 24 tells Mitto that the
Line Id
column is present in the output of the saved search. This line should be omitted ifLine Id
is not in the output.Line 39 tells Mitto that values from the
last_modified_column
in the saved search output are store in thelast_modified
column of the output database table..Lines 78-79 tell Mitto that, when syncing new data with an existing output table, the key is
id
+line
.Lines 88-89 specify the key to use when saving incoming data to the store. These are the
jpath
locations of the keys in the JSON data received from NetSuite.Line 92 specifies the
jpath
location of the last modified value in Mitto’s store.
Data Modified On or After a Date¶
An upsert job requests data from NetSuite that has been modified on or
after the date that the job last got data. The saved search plugin does this
using the SuiteScript nlapiSearchRecord
function. NetSuite’s handling of the
date associated with the request is somewhat unusual, An understanding of it
is important; it is described in the
SavedSearch section
of the job configuration documentation.
Date/Time Formats¶
Every NetSuite account has a preferences setting controlling the formatting of date and
time values. The default appears to be m/d/yyyy hh:mm am
(at least in the US):
m
- month without leading zerod
- day without leading zeroyyyy
- four digit yearhh
- hour with leading zeromm
- minute with leading zeroam
- AM or PM, any of:am
,AM
,pm
,PM
The date format used by NetsuiteSavedSearchInput
must exactly match the date/time
format in the account’s NetSuite preferences. By default, NetsuiteSavedSearchInput
expects the format above.
If an account uses a date/time format different from the above, the connector must be
informed of the account’s date/time format. This is done via the
saved_search.date_time_format
value in the job’s config. The value is a string
containing a date/time format specification. The value of date_time_format
is
ultimately used as an argument to ` datetime.strftime()
<http://strftime.org/>`_.
date_time_format
can contain all strftime formatting options except for %m
and %d
.
You may ask yourself, “That’s strange, why is that?”
NetSuite does not allow leading zeros in month and day. strftime
only formats month
and day with leading zeros. Therefore, %m
and %d
are not allowed in a
date_time_format
value. Instead, the connector support {month}
and {day}
; they
provide month and day without a leading zero.
The default value for date_time_format
used by the connector is
"{month}/{day}/%Y %I:%M %p"
.
As an example, assume that a NetSuite account has its date/time format set to
something like this: yyyy-m-d HH:MM
, where HH:MM
is 24 hour time. The configuration
described earlier would need to be modified like this:
"saved_search": {
"id": "customsearch761",
"record_type": "Contact",
"date_time_format": "%Y-{month}-{day} %H:%M"
},
Common Challenges¶
I’m not getting all the data I’m expecting.
This is often due to NetSuite roles and permissions. It’s likely that the Mitto User Role does not have sufficient permissions to access some or all of the data.
This situation can be easily diagnosed by:
Login to the NetSuite account.
Assume the Mitto User Role (or whatever role is assigned to Mitto). This step is critically important.
Go to
Lists -> Search -> Saved Searches
.Locate the row with the desired saved search and click
Export (CSV)
.This file contains exactly the data that should be retrieved by Mitto.
Remove any existing table and store associated with the Mitto job.
Run the associated Mitto saved search job.
Compare the results of the CSV with the contents of the table created by the job.
Permission Violation
If you get an error similar to the following :
Permission Violation: You need a higher permission for custom record type customrecord484 to access this page. Please contact your account administrator.
Ensure the the
Run Unrestricted
box is check in theResults
tab of the saved search definition.HTTP 200 with Errors
Although the request completes with a 200, no data is returned and
result
in the response looks like this:{ "params": { "record_type": "Contact", "search_id": "notAnExistingSearchId", }, "result": { "message": "That search or mass update does not exist.", "trace": ["<anonymous>(netsuite_toolkit.js:209)", "<anonymous>(mitto_saved_search.js$6594:131)", "<anonymous>(mitto_saved_search.js$6594:117)", "<anonymous>(mitto_saved_search.js$6594:99)", "<anonymous>(mitto_saved_search.js$6594:219)", "restletwrapper(null:4)", "<anonymous>(mitto_saved_search.js$6594:223)"] }, "success": true }
Possible causes:
Ensure that
Public
underneath theOwner
field in the Saved Search configuration is checked.Ensure that the
record_type
for a custom record search is correct. A common error is to use the name of the custom record; theId
of the custom record obtained from theRecord Types
page is the correct value.
Saved Search “does not exist”
If you get errors about a saved search “does not exist”, ensure that “Public” is checked.
Mitto Upsert Job Errors
If an
upsert
configuration was used for the job, be sure to include Last Modified in the data that is returned by the saved search. If Last Modified is not available, it will be necessary to use aload
configuration.Errors Returned by NetSuite and Logged by Mitto
'{"error" : {"code" : "SSS_INVALID_SCRIPTLET_ID", "message" : "That Suitelet is invalid, disabled, or no longer exists."}}'
means that either therestlet
configuration is incorrect, or that theStatus
of the deployment record isTesting
, making it invisible to the Mitto User Role. In the latter case, change theStatus
toReleased
.INVALID_LOGIN_ATTEMPT
- This error indicates a problem in an OAuth header.INVALID_LOGIN_CREDENTIALS
- This error indicates a problem in an NLAuth header.INVALID_REQUEST
- The request could not be understood by the server due to malformed syntax.TWO_FA_REQD
- Two-Factor Authentication required.
Debugging in the Browser¶
Your browser can be an effective tool in tracking down problems with saved searches and the Saved Search RESTlet. This document describes the process.
Appendix¶
This appendix contains additional in-depth information about the saved search job and RESTlets. It is worth skimming at least once to familiarize oneself with edge-cases that may affect job behavior.
Timezones and NetSuite DateTime Objects¶
NetSuite uses three different timezones:
- NetSuite’s internal / datacenter timezone
This is always Pacific Time (e.g.:
America/Los_Angeles
,GMT-08:00
). This value never changes, regardless of the physical location of the server. NetSuite never uses GMT/UTC.- The company’s timezone
The timezone set for the company in the
Setup
section of the NetSuite user interface.- The user’s timezone
Each user’s NetSuite account has a timezone setting in their
Preferences
page. For a user’s timezone change to go into effect, the user must log out and log back in. It can take some time for a user’s timezone change to propagate / go into effect.
To date, we have not found NetSuite documentation discussing how and when each timezone is used. The documentation for nlapiStringToDate states that server-side scripts receive a datetime object in the Pacific Time Zone. Neither have we found NetSuite documentation describing the timezone of data returned by saved search via RESTlet.
Our testing to date leads us to strongly believe that data returned by saved
search via RESTlet is in Pacific Time (GMT-08:00
) as well.
This is important because it influences how the saved search job saves last modified date values and how it uses those values when requesting data modified after a certain date.
Last Modified Dates and Mitto¶
A DateTime
represents a date and/or time in a specific timezone. This
is of particular importance for upsert jobs that make use of
last_modified
modified database column (or whatever is specified by
last_modified_column
) and the MaxTimestamp
step.
The plugin saves last modified date values in the store exactly as the are
received from NetSuite. The plugin saves last modified date values in the
in the database as a Postgres timestamp with timezone
. In the latter case,
the value used is the original value received from NetSuite with a timezone of
GMT-08:00
.
For example:
The plugin receives a
lastmodifieddate
value of1/12/2021 7:00:00 am
.That record is saved in the store with a
$.last_modified
value of2021-01-12T07:00:00-08:00
.lastmodifieddate
is saved in the store with a value of1/12/2021 7:00:00 am
. Note that in the former case, the value has a timezone offset of-08:00
; it has been placed in the Pacific Timezone.The same data will be saved in the database as
2021-01-12 15:00:00.000000
. This value is identical to2021-01-12T07:00:00-08:00
; the original value in Pacific Time was been converted to UTC.
RESTlet Limitations¶
RESTlets use NetSuite’s SuiteScript. Because of that, any limitations of SuiteScript are also limitations of RESTlets and using Saved Search via RESTlets. SuiteScript does not support all NetSuite record types. Additionally NetSuite has “client-side” and “server-side” flavors of SuiteScript, each with their own set of supported record types.
Among other records, DeletedRecords are not supported by SuiteScript and, therefore, saved searches for them do not work via RESTlet.
The following records are apparently unsupported by server-side SuiteScript, which means that is not possible to obtain them from Mitto’s saved search job:
DeletedRecord
Transaction Numbering Audit Log
The following references may be helpful in confirming record availability from a RESTlet:
Saved Search Best Practices¶
When defining the output columns of a saved search, if
Line Id
is available, include it and use it provide therow_line_id
parameter in the saved search section of the job configuration.The columns (
Results
) included in a saved search can have a material effect on the amount of data returned by the search. For example, in the Zuar NetSuite account, creating a saved search for Contact records using only the default columns returns 99,742 records. If the search is modified by adding all possible columns to the results, 545,265 records are returned, many with the same internal id. This is apparently due to implicit join(s) that take place as a result of some of the non-default fields that were added.Limiting output columns to only those needed can significantly reduce runtimes.
The NetSuite API does not return all data for saved searches that contain formulas or grouping/summaries; formulas and grouping should not be used in saved searches that will be called by the RESTlet.
Set
Audience: Roles
toSelect All
.Always test saved searches using the NetSuite role that will be used by Mitto jobs.
saved_search.page_size
¶
The default page_size
used by the connector is 1,000.
Saved Searches allow Mitto to work with larger page sizes than record searches.
In some situations, increasing the page size over the default
can provide significant performance improvements. This is
done using the page_size
parameter of saved_search
.
saved_search.lower_bound
¶
When the connector calls the RESTlet, it provides an internal id. When the RESTlet
calls the saved search, it adds a search filter requesting only records with an internal
id greater than the one provided. This, coupled with page_size
, is how the RESTlet
implements paging. The internal_id
used by the RESTlet in its first call to the saved
search is defined by lower_bound
, whose default is 1
.
If desired, a value for lower_bound
can be specified in the saved_search
section of
the configuration. Negative values are allowed (some builtin NetSuite records have
negative internal ids).
NetSuite Logging¶
SuiteLets (JavaScript code) installed by the Mitto Bundle in newer
versions of the plugin log helpful information on the NetSuite
backend. By default, all logging done by the RESTlet is at the
DEBUG
level.
To view RESTlet logging output:
View the
Deployment Record
for theMitto Saved Search RESTlet
.Click
Execution Log
in the sub-menu.
Information that can be logged by the RESTlet includes:
The user name of the user
The internal id of the role used.
The roleId of the role used.
The scriptId of the script called by the job.
The version of NetSuite in use.
Other parameters such as the current
internal_id
andline_id
.
In a typical NetSuite account, the RESTlet will be set to Released
with a logging level of ERROR
. Because all RESTlet logging is
DEBUG
, it will not appear in the execution log for the RESTlet.
RESTlet log messages can be made to appear in the execution log by
either of two means:
Edit the
Deployment Record
for theMitto Saved Search RESTlet
.Ensure the
Status
is set toReleased
.Change
Log Level
toDebug
.Click
Save
After doing this, RESTlet log messages will appear in the execution log. Note that this requires privileges sufficient to edit the deployment record.
An alternative to the above is to use the
restlet_log_level
parameter ofsaved_search
. By setting this toERROR
, all RESTlet log messages will be at theERROR
level, causing them to appear in the execution log. This is primarily intended for use with accounts in which it is not possible to change the log level of the deployment record.
In either case, enabling logging for searches returning large amount of data can result in thousands of messages being logged in NetSuite. One must keep NetSuite Governance rules regarding the number of allowed log messages when using this feature.