This article goes over how to connect Ironclad and NetSuite. The NetSuite integration pulls data from NetSuite into Ironclad so you can securely authenticate, pull NetSuite records into Ironclad workflows, and sync contract data between systems.
Use Case
A procurement team manages vendors and purchasing in NetSuite but runs contract requests through Ironclad. An admin completes this setup once so the two systems can exchange data, removing the need to copy vendor details into workflows by hand or re-upload signed contracts into NetSuite.
Prerequisites
| Features |
Integrations |
| Connected Systems |
NetSuite |
| Permissions |
Admins or users with Integrations Management permissions. |
Step 1: Get Client ID and Client Secret in NetSuite
To generate your Client ID:
- In NetSuite, navigate to Setup > Integration > Manage Integrations > New.
- Enter a name in the Name field.
- Set State to Enabled.
- Under OAuth 2.0, select the follow options:
- CLIENT CREDENTIALS (MACHINE-TO-MACHINE) GRANT
- RESTLETS
- REST WEB SERVICES
- Under OAuth 2.0, deselect the following options:
- AUTHORIZATION CODE GRANT
- Click Save.
- Under Client Credentials, copy and paste the CLIENT ID and CLIENT SECRET keys somewhere safe. You'll need these keys to configure the integration in Ironclad.
View Screenshot: Client Credentials
Step 2: Configure NetSuite in Ironclad
Required Permissions: Ironclad Admin or user with integrations group permissions.
Now that you’ve created the integration in NetSuite, you can add the credentials to Ironclad:
- In Ironclad, navigate to Company settings > Integrations > NetSuite.
- Select the Settings tab.
- In Host, enter the subdomain of your NetSuite URL.
- Example: If your NetSuite URL is https://1234567.app.netsuite.com, your subdomain is 1234567.
- In NetSuite Client ID, enter the Customer Key / Client ID from NetSuite.
- In NetSuite Client Secret, enter the Customer Secret / Client Secret from NetSuite.
- Click Save.
- Click Generate Certificate and download the generated certificate file.
Step 3: Create the Integration Role in NetSuite
Ironclad authenticates as a non-administrator role, so you must create that role before setting up OAuth in Step 4.
Note:
NetSuite restricts admin access via API. You perform this setup as a NetSuite Administrator, but the role you create here, the one Ironclad actually runs as, must not be an administrator. Learn more about creating roles in NetSuite.
To create a non-administrator role:
In NetSuite, navigate to Setup > Users/Roles > Manage Roles.
-
Click New to create a role, or click Edit next to an existing non-administrator role.
Note: Starting from New creates a role with no inherited permissions, so the role has only the access listed below. On the Manage Roles page, Customize appears next to standard roles and copies that role's full permission set. Use New instead so the integration role isn't granted access it doesn't need.
In the Name field, enter a name you'll recognize when assigning it, such as Ironclad Integration Role.
(Optional) If you use scripting, enter an ID for the role.
Select a Center Type to base the role on. The center type sets the default permissions and access levels you'll customize in the next step.
-
Open the Permissions tab and assign the following:
Reports: SuiteAnalytics Workbook (to verify configuration via SuiteQL) (edit).
-
Lists:
Documents and Files (Full). This enables File Cabinet uploads.
Vendors (Full). Full is only required for populating the workflow link on the vendor if the user has that configured for Workflow Sync.
-
Setup: Log in using OAuth 2.0 Access Tokens (Full).
REST Web Services (Full).
SuiteScript (edit).
-
Custom Record: Ironclad Contract (Full).
Note:
If you plan to set up contract sync, this role needs one additional permission: Custom Record: Ironclad Contract (Full). You add it after creating the contract record type in Set Up NetSuite Contract Sync.
Click Save.
Warning: Don't check the Web Services Only or Require Single Sign On checkboxes.
View Screenshot: Reports
View Screenshot: Lists
View Screenshot: Setup
Step 4: Set up OAuth in NetSuite
Warning: For file uploads to work, you must assign the required permissions to a non-administrator role.
Now that you’ve configured NetSuite in Ironclad, you’ll need to configure OAuth in NetSuite using the certificate generated in Ironclad.
- In NetSuite, navigate to Setup > Integration > OAuth 2.0 Client Credentials (M2M) Setup.
- Click Create New.
- Complete the following fields:
- Entity: Select a System Administrator generic user
-
Role: Non-Administrator.
- Note: This should be the same role you created when configuring the integration role.
- Application: Ironclad
- Choose File: Upload the certificate downloaded from Ironclad
- Click Save. After saving, Ironclad will appear in your list of OAuth applications.
- Copy the Certificate ID. You will need this value in Ironclad.
Step 5: Finish Configuring NetSuite in Ironclad
Required Permissions: Ironclad Admin or user with integrations group permissions.
Now that you’ve set up OAuth in NetSuite, you’ll need to complete the authentication process by entering the NetSuite Certificate ID into Ironclad.
- In Ironclad, navigate to Company settings > Integrations > NetSuite.
- Click Settings.
- Paste the NetSuite Certificate ID into the field labeled Enter NetSuite Certificate ID.
- Click Link Integration.
Once linked, the NetSuite integration is enabled and ready for workflow and contract sync configuration.
Step 6: Deploy the RESTlet in NetSuite
In NetSuite:
- Create a Script Record and Deployment for the RESTlet.
- Upload the provided JavaScript code, name it, and save it with the .js file extension: [Example Name].js
- Ensure the Deployment status is set to Released.
View NetSuite RESTlet JavaScript Code
/**
* @NApiVersion 2.1
* @NScriptType Restlet
* @description Handles file uploads with strict record attachment and atomic rollback.
*/
define(['N/file', 'N/record', 'N/search', 'N/error', 'N/log'], (file, record, search, error, log) => { // Fixed =>
const getNSFileType = (fileName) => { // Fixed =>
if (!fileName || fileName.indexOf('.') === -1) {
throw error.create({
name: 'INVALID_FILE_NAME',
message: 'File name must include a valid extension.',
notifyOff: true,
});
}
const ext = fileName.split('.').pop().toLowerCase();
const typeMap = {
pdf: file.Type.PDF,
doc: file.Type.WORD,
docx: file.Type.WORD,
rtf: file.Type.RTF,
txt: file.Type.PLAINTEXT,
xls: file.Type.EXCEL,
xlsx: file.Type.EXCEL,
xlsm: file.Type.EXCEL,
csv: file.Type.CSV,
ppt: file.Type.POWERPOINT,
pptx: file.Type.POWERPOINT,
jpg: file.Type.JPGIMAGE,
jpeg: file.Type.JPGIMAGE,
png: file.Type.PNGIMAGE,
gif: file.Type.GIFIMAGE,
tif: file.Type.TIFFIMAGE,
tiff: file.Type.TIFFIMAGE,
xml: file.Type.XMLDOC,
htm: file.Type.HTMLDOC,
html: file.Type.HTMLDOC,
eml: file.Type.MESSAGERFC,
msg: file.Type.AUTODETECT,
asice: file.Type.AUTODETECT,
adoc: file.Type.AUTODETECT,
bdoc: file.Type.AUTODETECT,
ddoc: file.Type.AUTODETECT,
edoc: file.Type.AUTODETECT,
};
if (!typeMap[ext]) {
throw error.create({
name: 'INVALID_FILE_TYPE',
message: `File type .${ext} is not supported.`,
notifyOff: true,
});
}
return typeMap[ext];
};
const checkFileExists = (fileName, folderId) => { // Fixed =>
const fileSearch = search.create({
type: 'file',
filters: [['name', 'is', fileName], 'AND', ['folder', 'anyof', folderId]],
});
return fileSearch.runPaged().count > 0; // Fixed: Added '>' operator
};
const isValidRecord = (recType, recId) => { // Fixed =>
try {
if (!recType || !recId) return false;
const lookup = search.lookupFields({
type: recType,
id: recId,
columns: ['internalid'],
});
return !!lookup.internalid;
} catch (e) {
return false;
}
};
return {
post: (request) => { // Fixed =>
log.debug({ title: 'Incoming Request', details: request });
// 1. Input Validation
if (!request.name || !request.folderId || !request.content || !request.recordType || !request.recordId) {
throw error.create({
name: 'MISSING_REQUIRED_FIELDS',
message: 'Payload must contain: name, folderId, content, recordType, and recordId',
notifyOff: true,
});
}
const fileName = request.name.trim();
const folderId = parseInt(request.folderId);
const { recordType, recordId } = request;
// 2. Record Validation
if (!isValidRecord(recordType, recordId)) {
throw error.create({
name: 'INVALID_RECORD_TARGET',
message: `Target record (${recordType}:${recordId}) does not exist.`,
notifyOff: true,
});
}
// 3. Duplicate Prevention
if (checkFileExists(fileName, folderId)) {
throw error.create({
name: 'FILE_ALREADY_EXISTS',
message: `File ${fileName} already exists in folder ${folderId}.`,
notifyOff: true,
});
}
// TRANSACTIONAL OPERATIONS (Rollback Active)
let fileId = null;
try {
// 4. File Creation
const fileObj = file.create({
name: fileName,
fileType: getNSFileType(fileName),
contents: request.content,
folder: folderId,
encoding: file.Encoding.BASE64,
isOnline: true,
});
fileId = fileObj.save();
log.audit({ title: 'File Saved', details: `File ID: ${fileId} created.` });
// 5. Attach to Record
record.attach({
record: { type: 'file', id: fileId },
to: { type: recordType, id: recordId },
});
return {
status: 'success',
fileId: fileId,
fileName: fileName,
attachedToRecordType: recordType,
attachedToRecordId: recordId,
};
} catch (e) {
log.error({ title: 'Transaction Failed', details: e });
// ROLLBACK: Delete the file if it was created but attachment failed
if (fileId) {
try {
file.delete({ id: fileId });
log.audit({ title: 'Rollback Executed', details: `Deleted orphan file ${fileId}` });
} catch (delErr) {
log.emergency({ title: 'Rollback Failed', details: delErr });
}
}
throw error.create({
name: e.name || 'UPLOAD_FAILED',
message: e.message || 'An unexpected error occurred.',
notifyOff: true,
});
}
},
};
});View Screenshot: Script Deployment
Step 7: Configure File Uploads in Ironclad
In the Ironclad NetSuite settings page:
- Input the Script ID, Deployment ID, and the Destination Folder ID from your File Cabinet.
-
Note: The Script ID and Destination Folder ID are NetSuite internal IDs, found on the script record and the File Cabinet respectively. The Deployment ID works differently. You'll need to retrieve it from the deployment URL shown on the Deployment record in Step 6. The URL follows this format: https://[accountID].restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=[Script ID]&deploy=[Deployment ID]
- Example: In the URL https://2906109.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=2&deploy=2, the Deployment ID is 2.
-
Note: The Script ID and Destination Folder ID are NetSuite internal IDs, found on the script record and the File Cabinet respectively. The Deployment ID works differently. You'll need to retrieve it from the deployment URL shown on the Deployment record in Step 6. The URL follows this format: https://[accountID].restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=[Script ID]&deploy=[Deployment ID]
- Ensure the record sync setting is set to Sync Document.
Once a document is signed, Ironclad automatically:
- Uploads files to their designated NetSuite folder.
- Attaches the file to their corresponding Ironclad contract record in NetSuite via the RESTlet.
Now, you can start setting up workflow syncs and record syncs.