# NetSuite REST API Sales Order and Journal Entry Request Generator

> Source: https://netray.co/tools/netsuite-rest-api-sales-order-journal-entry-request-generator
> Publisher: Netray (netray.co), AI and integration engineering for ERP and manufacturing systems.
> Section: Free Tools

## Short answer

Fill in IDs and amounts to get correct record/v1 JSON for a sales order, a two-line journal entry, a sales order to invoice transform and a C# call.

## Category

developer

## Erp

NetSuite

## Audience

- NetSuite developer
- Integration developer
- NetSuite administrator

## Tool

**Kind:** template

**Output:** code

**Language:** http

**Fields:**

#### Account ID as used in the hostname

**Key:** account_host

**Type:** text

**Default:** 1234567

**Help:** Lowercase with hyphen for sandbox, for example 1234567-sb1.

#### External ID for the sales order

**Key:** external_id

**Type:** text

**Default:** SO-EXT-1001

#### Customer internal ID

**Key:** customer_id

**Type:** number

**Default:** 1234

#### Subsidiary internal ID

**Key:** subsidiary_id

**Type:** number

**Default:** 1

**Help:** OneWorld only. Remove the subsidiary line on non-OneWorld accounts.

#### Location internal ID

**Key:** location_id

**Type:** number

**Default:** 2

#### Item internal ID

**Key:** item_id

**Type:** number

**Default:** 456

#### Quantity

**Key:** quantity

**Type:** number

**Default:** 10

#### Rate

**Key:** rate

**Type:** number

**Default:** 25.00

#### Journal debit account internal ID

**Key:** debit_account_id

**Type:** number

**Default:** 112

#### Journal credit account internal ID

**Key:** credit_account_id

**Type:** number

**Default:** 54

#### Journal amount

**Key:** je_amount

**Type:** number

**Default:** 1500.00

#### Memo

**Key:** memo

**Type:** text

**Default:** Created via REST API

**Blocks:**

#### 1. Create a sales order

**Body:** POST https://{{account_host}}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder
Authorization: Bearer <access_token>
Content-Type: application/json
X-NetSuite-PropertyNameValidation: Error

{
  "externalId": "{{external_id}}",
  "entity": { "id": "{{customer_id}}" },
  "subsidiary": { "id": "{{subsidiary_id}}" },
  "location": { "id": "{{location_id}}" },
  "memo": "{{memo}}",
  "item": {
    "items": [
      {
        "item": { "id": "{{item_id}}" },
        "quantity": {{quantity}},
        "rate": {{rate}}
      }
    ]
  }
}

Success: HTTP 204 No Content
Location: https://{{account_host}}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder/<new internal id>

#### 2. Create a journal entry (sublist is line, not item)

**Body:** POST https://{{account_host}}.suitetalk.api.netsuite.com/services/rest/record/v1/journalEntry
Authorization: Bearer <access_token>
Content-Type: application/json
X-NetSuite-PropertyNameValidation: Error

{
  "subsidiary": { "id": "{{subsidiary_id}}" },
  "memo": "{{memo}}",
  "line": {
    "items": [
      {
        "account": { "id": "{{debit_account_id}}" },
        "debit": {{je_amount}},
        "memo": "{{memo}}"
      },
      {
        "account": { "id": "{{credit_account_id}}" },
        "credit": {{je_amount}},
        "memo": "{{memo}}"
      }
    ]
  }
}

#### 3. Transform the sales order into an invoice, or upsert by external ID

**Body:** POST https://{{account_host}}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder/<sales order internal id>/!transform/invoice
Authorization: Bearer <access_token>
Content-Type: application/json

{}

Upsert by external ID instead of create (same body without externalId):
PUT https://{{account_host}}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder/eid:{{external_id}}

#### 4. C# HttpClient: send request 1

**Body:** // .NET 6 or later console app (top-level statements)
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var url = "https://{{account_host}}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder";
var accessToken = Environment.GetEnvironmentVariable("NS_ACCESS_TOKEN");

var body = new
{
    externalId = "{{external_id}}",
    entity = new { id = "{{customer_id}}" },
    subsidiary = new { id = "{{subsidiary_id}}" },
    location = new { id = "{{location_id}}" },
    memo = "{{memo}}",
    item = new
    {
        items = new[]
        {
            new { item = new { id = "{{item_id}}" }, quantity = {{quantity}}, rate = {{rate}} }
        }
    }
};

using var http = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Headers.Add("X-NetSuite-PropertyNameValidation", "Error");
request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");

using var response = await http.SendAsync(request);
if ((int)response.StatusCode == 204)
{
    Console.WriteLine("Created: " + response.Headers.Location);
}
else
{
    Console.WriteLine((int)response.StatusCode + " " + await response.Content.ReadAsStringAsync());
}

**Notes:** Check every field ID against the REST API Browser for your account version (record v1, salesOrder and journalEntry), because mandatory custom fields, forms and features (OneWorld, multi-location, approvals) change what a create needs. A successful create or transform returns 204 No Content with the new record URL in the Location header. Keep X-NetSuite-PropertyNameValidation: Error while testing so misspelled fields fail instead of being ignored.

## Content

**Intro:** This generator produces correct NetSuite REST record API requests for the three transactions integrations create most: a sales order with item lines, a balanced journal entry, and an invoice transformed from a sales order. It also gives the sales order call in C# with HttpClient for .NET teams.

**Sections:**

#### How to use it

**Body:** Look up the internal IDs you need first: customer, subsidiary, location, item and the two GL accounts. Enable Show Internal IDs under Home > Set Preferences to see them on lists. Enter them with quantity, rate and amount, then copy the block you need. Block 1 creates the sales order, block 2 a two-line journal, block 3 bills an existing order, and block 4 sends block 1 from C#. Reference fields are objects with an id, and sublists are objects with an items array, which is the shape the REST record service expects for both reads and writes.

**Bullets:**

- Sales order lines live under item.items.
- Journal entry lines live under line.items with debit or credit per line.
- Totals must balance or the journal is rejected.

#### Where this lives in NetSuite

**Body:** All calls go to /services/rest/record/v1/<recordType> on the suitetalk.api.netsuite.com host for your account. The record type IDs are camel case in REST: salesOrder, journalEntry, invoice. The REST API Browser (linked from Oracle help, filtered by version) lists every field and sublist for each record, and it is the quickest way to confirm whether a custom body field such as custbody_ship_priority is writable. Transforms use POST /record/v1/salesOrder/{id}/!transform/invoice and accept a body that overrides fields on the new invoice; an empty {} bills everything that is billable. The role needs REST Web Services permission plus create rights on each transaction type.

#### Worked example and reading the response

**Body:** With the defaults, block 1 creates an order for customer 1234 at location 2 with 10 units of item 456 at 25.00, so the line amount is 250.00 before tax. NetSuite answers 204 No Content and puts the new URL in the Location header, for example .../salesOrder/98765. Read the ID from that header, not from the body, because there is no body. Block 3 then bills order 98765 and again returns 204 with the invoice URL. If you send block 1 twice with the same externalId, the second create fails with a duplicate external ID error, which is why the PUT .../salesOrder/eid:SO-EXT-1001 upsert form is safer for retries.

**Bullets:**

- 204: created, read Location.
- 400: field or value problem, read o:errorDetails.
- 403: role or permission problem.

#### Common mistakes

**Body:** Sending journal lines under item instead of line is the most common journal failure. Others: sending a reference as a bare number instead of {"id": "..."}, using a non-posting or inactive account, omitting subsidiary on OneWorld, and leaving a mandatory custom field empty because the form used by REST differs from the UI form. Amounts are numbers, not strings. Dates go in YYYY-MM-DD form. If an order lands in Pending Approval, that is your sales order approval preference, not the API. For high volume, reuse the token and respect the account concurrency limit rather than retrying in tight loops.

**Cta:** If you want an assistant that drafts these transactions from emails or spreadsheets and posts them only after a named user approves, we can build it on your own infrastructure.

**Faq:**

#### How do I create a sales order with the NetSuite REST API?

**Answer:** POST to /services/rest/record/v1/salesOrder with entity as {"id": "<customer id>"} and lines under item.items, each with item, quantity and rate. Add subsidiary and location where your account requires them. A 204 response with a Location header containing the new internal ID means it worked.

#### What is the JSON for a journal entry in the NetSuite REST API?

**Answer:** POST to /record/v1/journalEntry with subsidiary and a line sublist: line.items holds one object per line with account as a reference and either debit or credit as a number. Debits and credits must balance. Add memo, department, class or location per line where your segments require them.

#### How do I upsert a record by external ID in NetSuite REST?

**Answer:** Use PUT on /record/v1/<recordType>/eid:<externalId> with the record body. If a record with that external ID exists it is updated, otherwise it is created with that external ID. This makes retries after a timeout safe, which a plain POST create is not.

#### Is there a C# example for the NetSuite REST API?

**Answer:** Block 4 is one: it builds the body as an anonymous object, serializes it with System.Text.Json, sends it with HttpClient and a Bearer token, and reads the Location header on a 204. Get the token with the OAuth 2.0 client credentials flow or sign each request with TBA if you are on an older integration.

## Search terms

netsuite rest api sales order, netsuite rest api journal entry, netsuite rest api example, netsuite rest api c# example, netsuite rest api tutorial, netsuite rest transform sales order to invoice, netsuite rest api upsert external id

---
Talk to an engineer: https://netray.co/contact?intent=erp_ai | Machine catalog: https://netray.co/agent/catalog.json
