LoopFour
IntegrationsAccounting

NetSuite

ERP integration for customers, invoices, bills, and GL entries

NetSuite

Connect to NetSuite for comprehensive ERP automation including customers, invoices, vendor bills, journal entries, and SuiteQL queries.

Overview

NetSuite is Oracle's cloud ERP platform. The integration supports:

  • Customers - Customer master records
  • Invoices - Customer invoices (AR)
  • Vendor Bills - Vendor bills (AP)
  • Journal Entries - GL journal entries
  • Vendors - Vendor master records
  • Generic Records - Any NetSuite record type
  • SuiteQL - SQL-like queries

Prerequisites

  • NetSuite account with SuiteCloud enabled
  • Token-Based Authentication (TBA) configured
  • REST Web Services enabled
  • Appropriate role permissions

Authentication

NetSuite uses OAuth 1.0a Token-Based Authentication via Nango.

curl "http://localhost:3000/api/v1/connections/netsuite/auth-url" \
  -H "x-api-key: YOUR_API_KEY"

Required Permissions

PermissionDescription
REST Web ServicesAccess REST API
Record-level permissionsBased on records accessed
SuiteAnalytics WorkbookFor SuiteQL queries

Available Actions

Customer Actions

createCustomer

Create a new customer.

{
  "id": "create-customer",
  "type": "action",
  "action": "netsuite.createCustomer",
  "config": {
    "entityId": "CUST-{{input.customerNumber}}",
    "companyName": "{{input.companyName}}",
    "subsidiaryId": "1",
    "email": "{{input.email}}",
    "phone": "{{input.phone}}",
    "url": "{{input.website}}",
    "isPerson": false,
    "termsId": "2",
    "creditLimit": 50000,
    "currencyId": "1",
    "taxable": true,
    "billingAddress": {
      "line1": "{{input.address.street}}",
      "city": "{{input.address.city}}",
      "state": "{{input.address.state}}",
      "postalCode": "{{input.address.zip}}",
      "country": "US"
    },
    "externalId": "{{input.externalId}}",
    "customFields": {
      "custentity_source": "Workflow"
    }
  }
}

Parameters:

FieldTypeRequiredDescription
entityIdstringNoCustomer ID/number
companyNamestringYes*Company name (*or firstName/lastName)
subsidiaryIdstringYesSubsidiary internal ID
emailstringNoEmail address
phonestringNoPhone number
isPersonbooleanNoIndividual vs company
firstNamestringNoFirst name (if isPerson)
lastNamestringNoLast name (if isPerson)
termsIdstringNoPayment terms internal ID
creditLimitnumberNoCredit limit
currencyIdstringNoCurrency internal ID
taxablebooleanNoIs taxable
billingAddressobjectNoBilling address
externalIdstringNoExternal system ID
customFieldsobjectNoCustom field values

getCustomer

Get a customer by internal ID.

{
  "action": "netsuite.getCustomer",
  "config": {
    "customerId": "{{input.customerId}}",
    "expandSubResources": true
  }
}

updateCustomer

Update an existing customer.

{
  "action": "netsuite.updateCustomer",
  "config": {
    "customerId": "{{input.customerId}}",
    "email": "{{input.newEmail}}",
    "creditLimit": 75000,
    "customFields": {
      "custentity_last_order": "{{now | date: 'YYYY-MM-DD'}}"
    }
  }
}

listCustomers

Search customers using SuiteQL.

{
  "action": "netsuite.listCustomers",
  "config": {
    "companyName": "Acme",
    "email": "contact@acme.com",
    "subsidiaryId": "1",
    "isInactive": false,
    "limit": 100,
    "offset": 0
  }
}

Invoice Actions

createInvoice

Create a customer invoice.

{
  "id": "create-invoice",
  "type": "action",
  "action": "netsuite.createInvoice",
  "config": {
    "customerId": "{{steps.customer.output.id}}",
    "subsidiaryId": "1",
    "invoiceDate": "{{now | date: 'YYYY-MM-DD'}}",
    "dueDate": "{{now | dateAdd: 30, 'day' | date: 'YYYY-MM-DD'}}",
    "invoiceNumber": "INV-{{input.invoiceNumber}}",
    "termsId": "2",
    "memo": "Services for {{input.period}}",
    "currencyId": "1",
    "lines": [
      {
        "itemId": "100",
        "quantity": 10,
        "rate": 150.00,
        "description": "Professional Services",
        "department": "1",
        "class": "1"
      },
      {
        "itemId": "101",
        "quantity": 1,
        "amount": 500.00,
        "description": "Software License"
      }
    ],
    "departmentId": "1",
    "classId": "1",
    "poNumber": "{{input.poNumber}}",
    "externalId": "{{input.externalId}}",
    "customFields": {
      "custbody_source": "Workflow"
    }
  }
}

Parameters:

FieldTypeRequiredDescription
customerIdstringYesCustomer internal ID
subsidiaryIdstringNoSubsidiary internal ID
invoiceDatestringNoTransaction date (YYYY-MM-DD)
dueDatestringNoDue date
invoiceNumberstringNoDocument number
termsIdstringNoPayment terms ID
memostringNoInternal memo
currencyIdstringNoCurrency ID
linesarrayYesLine items
lines[].itemIdstringYesItem internal ID
lines[].quantitynumberNoQuantity (default: 1)
lines[].ratenumberNoUnit rate
lines[].amountnumberNoLine amount
lines[].descriptionstringNoLine description
lines[].taxCodestringNoTax code ID
lines[].departmentstringNoDepartment ID
lines[].classstringNoClass ID
lines[].locationstringNoLocation ID
departmentIdstringNoHeader department
classIdstringNoHeader class
locationIdstringNoHeader location
poNumberstringNoCustomer PO number
salesRepIdstringNoSales rep ID
externalIdstringNoExternal ID
customFieldsobjectNoCustom field values

getInvoice

Get an invoice by internal ID.

{
  "action": "netsuite.getInvoice",
  "config": {
    "invoiceId": "{{input.invoiceId}}",
    "expandSubResources": true
  }
}

updateInvoice

Update an invoice.

{
  "action": "netsuite.updateInvoice",
  "config": {
    "invoiceId": "{{input.invoiceId}}",
    "dueDate": "{{input.newDueDate}}",
    "memo": "{{input.newMemo}}"
  }
}

listInvoices

Search invoices using SuiteQL.

{
  "action": "netsuite.listInvoices",
  "config": {
    "customerId": "{{input.customerId}}",
    "status": "open",
    "tranDateAfter": "2024-01-01",
    "tranDateBefore": "2024-12-31",
    "subsidiaryId": "1",
    "limit": 100,
    "offset": 0
  }
}

Vendor Bill Actions

createVendorBill

Create a vendor bill (accounts payable).

{
  "id": "create-bill",
  "type": "action",
  "action": "netsuite.createVendorBill",
  "config": {
    "vendorId": "{{input.vendorId}}",
    "subsidiaryId": "1",
    "billDate": "{{now | date: 'YYYY-MM-DD'}}",
    "dueDate": "{{now | dateAdd: 30, 'day' | date: 'YYYY-MM-DD'}}",
    "billNumber": "{{input.vendorInvoiceNumber}}",
    "termsId": "2",
    "memo": "{{input.description}}",
    "lines": [
      {
        "accountId": "54",
        "amount": 1500.00,
        "description": "Office Supplies",
        "department": "1",
        "class": "1"
      },
      {
        "itemId": "200",
        "quantity": 5,
        "rate": 100.00,
        "description": "Inventory Item",
        "customerId": "123",
        "isBillable": true
      }
    ],
    "externalId": "{{input.externalId}}"
  }
}

Line Types:

  • Expense lines: Use accountId for expense account
  • Item lines: Use itemId for inventory/service items
  • Billable expenses: Set customerId and isBillable: true

getVendorBill

Get a vendor bill by internal ID.

{
  "action": "netsuite.getVendorBill",
  "config": {
    "billId": "{{input.billId}}",
    "expandSubResources": true
  }
}

listVendorBills

Search vendor bills using SuiteQL.

{
  "action": "netsuite.listVendorBills",
  "config": {
    "vendorId": "{{input.vendorId}}",
    "status": "open",
    "tranDateAfter": "2024-01-01",
    "limit": 100
  }
}

Journal Entry Actions

createJournalEntry

Create a GL journal entry.

{
  "id": "create-journal",
  "type": "action",
  "action": "netsuite.createJournalEntry",
  "config": {
    "subsidiaryId": "1",
    "tranDate": "{{now | date: 'YYYY-MM-DD'}}",
    "tranId": "JE-{{input.journalNumber}}",
    "memo": "Accrual adjustment - {{input.period}}",
    "currencyId": "1",
    "approved": true,
    "lines": [
      {
        "accountId": "54",
        "debit": 1000.00,
        "memo": "Accrued expense",
        "department": "1",
        "class": "1"
      },
      {
        "accountId": "120",
        "credit": 1000.00,
        "memo": "Accrual liability",
        "department": "1",
        "class": "1"
      }
    ],
    "externalId": "{{input.externalId}}"
  }
}

Journal entries must balance. Total debits must equal total credits.

Parameters:

FieldTypeRequiredDescription
subsidiaryIdstringYesSubsidiary ID
tranDatestringNoTransaction date
tranIdstringNoDocument number
memostringNoHeader memo
currencyIdstringNoCurrency ID
approvedbooleanNoAuto-approve (default: true)
linesarrayYesJournal lines (min 2)
lines[].accountIdstringYesGL account ID
lines[].debitnumberConditionalDebit amount
lines[].creditnumberConditionalCredit amount
lines[].memostringNoLine memo
lines[].entityIdstringNoCustomer/vendor ID
lines[].departmentstringNoDepartment ID
lines[].classstringNoClass ID
lines[].locationstringNoLocation ID

getJournalEntry

Get a journal entry by internal ID.

{
  "action": "netsuite.getJournalEntry",
  "config": {
    "journalId": "{{input.journalId}}",
    "expandSubResources": true
  }
}

Vendor Actions

createVendor

Create a new vendor.

{
  "action": "netsuite.createVendor",
  "config": {
    "entityId": "VEND-{{input.vendorNumber}}",
    "companyName": "{{input.companyName}}",
    "subsidiaryId": "1",
    "email": "{{input.email}}",
    "phone": "{{input.phone}}",
    "termsId": "2",
    "currencyId": "1",
    "taxIdNum": "{{input.taxId}}",
    "is1099Eligible": true,
    "externalId": "{{input.externalId}}"
  }
}

getVendor

Get a vendor by internal ID.

{
  "action": "netsuite.getVendor",
  "config": {
    "vendorId": "{{input.vendorId}}",
    "expandSubResources": true
  }
}

listVendors

Search vendors using SuiteQL.

{
  "action": "netsuite.listVendors",
  "config": {
    "companyName": "{{input.searchTerm}}",
    "email": "{{input.email}}",
    "isInactive": false,
    "limit": 100
  }
}

Generic Record Actions

Work with any NetSuite record type.

getRecord

Get any record by type and ID.

{
  "action": "netsuite.getRecord",
  "config": {
    "recordType": "salesOrder",
    "internalId": "{{input.salesOrderId}}",
    "expandSubResources": true
  }
}

Common Record Types:

TypeDescription
customerCustomer
vendorVendor
invoiceCustomer invoice
vendorBillVendor bill
salesOrderSales order
purchaseOrderPurchase order
journalEntryJournal entry
paymentCustomer payment
itemInventory item
accountGL account

createRecord

Create any record type.

{
  "action": "netsuite.createRecord",
  "config": {
    "recordType": "task",
    "data": {
      "title": "Follow up with customer",
      "assigned": { "id": "123" },
      "duedate": "2024-02-01",
      "company": { "id": "{{input.customerId}}" }
    }
  }
}

updateRecord

Update any record type.

{
  "action": "netsuite.updateRecord",
  "config": {
    "recordType": "salesOrder",
    "internalId": "{{input.salesOrderId}}",
    "data": {
      "memo": "Updated via workflow"
    }
  }
}

deleteRecord

Delete any record.

{
  "action": "netsuite.deleteRecord",
  "config": {
    "recordType": "task",
    "internalId": "{{input.taskId}}"
  }
}

SuiteQL Actions

searchRecords

Search records with filters.

{
  "action": "netsuite.searchRecords",
  "config": {
    "recordType": "transaction",
    "fields": ["id", "tranid", "trandate", "entity", "total"],
    "filters": {
      "type": "SalesOrd",
      "status": "open"
    },
    "orderBy": "trandate DESC",
    "limit": 100,
    "offset": 0
  }
}

suiteql

Execute a raw SuiteQL query.

{
  "id": "custom-query",
  "type": "action",
  "action": "netsuite.suiteql",
  "config": {
    "query": "SELECT t.id, t.tranid, t.trandate, c.companyname, t.foreigntotal FROM transaction t INNER JOIN customer c ON t.entity = c.id WHERE t.type = 'CustInvc' AND t.status = 'open' AND t.trandate >= TO_DATE('2024-01-01', 'YYYY-MM-DD') ORDER BY t.trandate DESC",
    "limit": 1000,
    "offset": 0
  }
}

SuiteQL Notes:

  • SQL-like syntax with NetSuite-specific functions
  • Use TO_DATE() for date comparisons
  • Boolean fields use 'T' and 'F'
  • Join tables using internal field names
  • Max 1000 results per query

Webhook Triggers

NetSuite can trigger workflows via RESTlets or scheduled scripts.

{
  "trigger": {
    "type": "webhook",
    "provider": "netsuite",
    "events": ["record.create", "record.update"]
  }
}

Example Workflow

Stripe to NetSuite invoice sync:

{
  "name": "Sync Stripe Invoice to NetSuite",
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["invoice.paid"]
  },
  "steps": [
    {
      "id": "find-customer",
      "type": "action",
      "action": "netsuite.listCustomers",
      "config": {
        "email": "{{input.data.object.customer_email}}"
      }
    },
    {
      "id": "check-customer",
      "type": "condition",
      "config": {
        "conditions": {
          "left": "{{steps.find-customer.output.items.length}}",
          "operator": "gt",
          "right": 0
        },
        "then": ["create-invoice"],
        "else": ["create-customer"]
      }
    },
    {
      "id": "create-customer",
      "type": "action",
      "action": "netsuite.createCustomer",
      "config": {
        "companyName": "{{input.data.object.customer_name}}",
        "email": "{{input.data.object.customer_email}}",
        "subsidiaryId": "1",
        "externalId": "stripe_{{input.data.object.customer}}"
      }
    },
    {
      "id": "create-invoice",
      "type": "action",
      "action": "netsuite.createInvoice",
      "config": {
        "customerId": "{{steps.find-customer.output.items[0].id || steps.create-customer.output.id}}",
        "subsidiaryId": "1",
        "lines": [
          {
            "itemId": "100",
            "amount": "{{input.data.object.amount_paid / 100}}",
            "description": "{{input.data.object.lines.data[0].description}}"
          }
        ],
        "externalId": "stripe_{{input.data.object.id}}"
      }
    }
  ]
}

Rate Limits

LimitValue
REST API10 concurrent requests
SuiteQL1000 results per query
Batch operationsVaries by record type

Troubleshooting

Common Errors

ErrorCauseSolution
INVALID_LOGIN_ATTEMPTAuth failedCheck TBA credentials
RCRD_DSNT_EXISTRecord not foundVerify internal ID
INVALID_FLD_VALUEInvalid field valueCheck field constraints
INSUFFICIENT_PERMISSIONMissing role permissionUpdate role access
UNEXPECTED_ERRORServer errorRetry with backoff

Field Names

NetSuite uses camelCase field names in REST API. Custom fields use custentity_, custbody_, custcol_ prefixes.

Multi-Subsidiary

For multi-subsidiary accounts, always specify subsidiaryId on transactions.