Ctrl K

PCE AP Write API — Integration Guide (External Vendors)

Audience: External system / vendor developers integrating with NetSuite
Purpose: Write Taiwan AP e-invoice data and related AP transactions via RESTlet
Internal details: Technical & testing guide · 中文版 · Postman collection

1. Quick start

  1. Obtain from the customer’s NetSuite administrator:
    • Account ID (e.g. TD1234567)
    • RESTlet External URL
    • OAuth 2.0 Integration: Client ID, and for M2M a Certificate ID / private key (or Client Secret / Redirect URI for Authorization Code)
  2. Obtain an OAuth 2.0 access token (Bearer).
  3. Dry-run with POST + options.validateOnly: true (GET is not supported).
  4. Call POST with JSON and the appropriate action.

1.1 Connectivity

ItemValue
ProtocolHTTPS
AuthOAuth 2.0 (Authorization: Bearer {access_token})
Content-Typeapplication/json (writes); application/x-www-form-urlencoded (token)
MethodsPOST only (GET returns PCE_METHOD_NOT_ALLOWED)
Do not use OAuth 1.0a / TBA per-request signing.

External URL pattern (numeric IDs are environment-specific):

https://{accountId}.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=xxx&deploy=yyy

Token endpoint:

https://{accountId}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token
Do not hard-code client secrets, private keys, or access tokens in source control.

Client Credentials (Machine-to-Machine) — server-to-server, no user login:

  1. Integration: enable Client Credentials (Machine to Machine) with scope RESTlets.
  2. Generate a certificate (e.g. ES256), upload the public key under Setup → Integration → OAuth 2.0 Client Credentials (M2M) Setup, bind Entity / Role / Application, note the Certificate ID.
  3. Sign a JWT (iss = Client ID, aud = token URL, scope = restlets, header kid = Certificate ID).
  4. POST to the token URL:
grant_type=client_credentials
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion={signed_jwt}
  1. Send subsequent RESTlet calls with Authorization: Bearer {access_token}.

Helper: docs/postman/generate-client-assertion.js.

Authorization Code Grant — interactive apps / manual testing; still use Bearer on RESTlet calls.

Postman: Import docs/postman/PCE-AP-Write.postman_collection.json → run 0. Auth first → then RESTlet requests (collection auth = Bearer {{accessToken}}).

2. Business scenarios (action)

Every POST must include exactly one action:

actionPurposeRequiredMust not include
apInvoiceOnlyCreate AP invoice(s) only (no parent required)apInvoices (≥ 1)Transaction create payload (type / fields / lines)
transactionOnlyCreate or reference transactionstransaction (object or array; each item needs id or type, max 10)apInvoices
transactionWithApInvoicesResolve/create transaction(s) then create linked AP invoices (1:N)Each transaction (object or array, max 10) must nest non-empty apInvoices[]Top-level apInvoices / invoices / apInvoicesByTransaction

This package accepts only the three actions above. API intake review and purchase requests are not included. Those actions return INVALID_ACTION.

2.1 Supported transaction types

When creating a new transaction, transaction.type must be one of (case-insensitive):

typeNetSuite record
vendorbillVendor Bill
expensereportExpense Report
vendorcreditVendor Credit
vendorprepaymentVendor Prepayment

To link to an existing transaction, send only transaction.id (internal ID). type / fields may be omitted.

3. GET — Not allowed

This API does not support GET. A GET to the External URL should return:

{
  "success": false,
  "returnCode": "1014",
  "returnMessage": "PCE_METHOD_NOT_ALLOWED",
  "error": {
    "code": "PCE_METHOD_NOT_ALLOWED",
    "message": "GET is not allowed on PCE AP Write. Use POST with action=apInvoiceOnly|transactionOnly|transactionWithApInvoices.",
    "details": null
  }
}

See this guide and the Postman collection for the contract; use POST with options.validateOnly: true for dry-runs.

4. POST — Write

4.1 Common structure

{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "apInvoiceOnly | transactionOnly | transactionWithApInvoices",
  "apInvoices": [ { } ],
  "transaction": { },
  "options": {
    "validateOnly": false
  }
}
FieldRequiredDescription
actionRequiredOne of: apInvoiceOnly / transactionOnly / transactionWithApInvoices (see §2)
requestIdRequiredIdempotency key. Prefer UUID v4; 8–64 chars of A–Z a–z 0–9 - _. New key per logical write; retries must reuse the same key.
apInvoicesPer actionRequired only for apInvoiceOnly (≥ 1); alias invoices accepted. Must not be top-level for other actions
transactionPer actionRequired for transactionOnly / transactionWithApInvoices (object or array, max 10). Latter must nest non-empty apInvoices[] on each item
options.validateOnlyOptionalWhen true, validates without persisting (see per-action notes). Failure and validateOnly responses include requestSummary (slim: index / clientRef / invoiceNo / type / id)
options.echoRequestOptionalDefault off. When true, every response includes the full request (for debugging; keep off in production)
clientRefOptionalOn each transaction and/or each apInvoices[] item (string 1–64); echoed in the success response for correlation
Idempotency: If the same requestId already succeeded (not validateOnly), the API returns the previous result with idempotentReplay: true and does not create again. Same key with a different payload → REQUEST_ID_PAYLOAD_MISMATCH.
Breaking change: For transactionWithApInvoices, invoices must live under transaction.apInvoices (or each item when transaction is an array). Top-level apInvoices / apInvoicesByTransaction are rejected.

4.2 Success / failure response

Every response (including rejected GET) includes top-level returnCode (4-digit string) and returnMessage (fixed English phrase) for programmatic routing. success and error.code remain unchanged.

Success codes (000x)

returnCodeWhenreturnMessage
0000Normal write succeeded (all items)OK
0001validateOnly: true and all passedVALIDATED
0002idempotentReplay: true (prior full success)IDEMPOTENT_REPLAY
0003All succeeded with warnings (e.g. tax tolerance)OK_WITH_WARNINGS
0004Partial success: at least one committed/passed and at least one failedPARTIAL_SUCCESS
Partial success: success is false, partialSuccess is true, returnCode is 0004, but full results[] is still returned (successful items have id / transactionId; failed items have error). Retrying the same requestId + payload returns the prior partial result (idempotent; no duplicate creates). Resubmit only failed items with a new requestId.

Success (map via results[]; each item includes returnCode; optional clientRef echoed):

{
  "success": true,
  "returnCode": "0000",
  "returnMessage": "OK",
  "action": "apInvoiceOnly",
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "results": [
    {
      "index": 0,
      "returnCode": "0000",
      "returnMessage": "OK",
      "id": "1001",
      "invoiceNo": "CA12345678",
      "clientRef": "inv-001"
    }
  ]
}

Partial success (transactionWithApInvoices multi invoices):

{
  "success": false,
  "partialSuccess": true,
  "returnCode": "0004",
  "returnMessage": "PARTIAL_SUCCESS",
  "action": "transactionWithApInvoices",
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "error": {
    "code": "PARTIAL_SUCCESS",
    "message": "Partial success: 1 succeeded, 1 failed. Succeeded: [transaction[0].apInvoices[0] clientRef=… invoiceNo=CA12345678 id=1001 returnCode=0000]. Failed: [transaction[0].apInvoices[1] clientRef=… invoiceNo=AA99999999 returnCode=2001 error=PCE_AP_INVOICE_VALIDATION_ERROR].",
    "details": {
      "succeeded": 1,
      "failed": 1,
      "succeededItems": [{ "path": "transaction[0].apInvoices[0]", "clientRef": "…", "invoiceNo": "CA12345678", "id": "1001", "returnCode": "0000" }],
      "failedItems": [{ "path": "transaction[0].apInvoices[1]", "clientRef": "…", "invoiceNo": "AA99999999", "returnCode": "2001", "errorCode": "PCE_AP_INVOICE_VALIDATION_ERROR" }]
    }
  },
  "results": [
    {
      "index": 0,
      "returnCode": "0004",
      "returnMessage": "PARTIAL_SUCCESS",
      "transactionId": "789",
      "transactionType": "vendorbill",
      "transactionCreated": true,
      "apInvoices": [
        {
          "index": 0,
          "returnCode": "0000",
          "returnMessage": "OK",
          "id": "1001",
          "invoiceNo": "CA12345678"
        },
        {
          "index": 1,
          "returnCode": "2001",
          "returnMessage": "AP_INVOICE_VALIDATION_FAILED",
          "invoiceNo": "AA99999999",
          "error": {
            "code": "PCE_AP_INVOICE_VALIDATION_ERROR",
            "message": "…",
            "details": {}
          }
        }
      ]
    }
  ],
  "requestSummary": { }
}

Failure (single-item validation still returns results[]; all-failed multi often uses top-level 2100):

{
  "success": false,
  "returnCode": "2001",
  "returnMessage": "AP_INVOICE_VALIDATION_FAILED",
  "error": {
    "code": "PCE_AP_INVOICE_VALIDATION_ERROR",
    "message": "Human-readable description",
    "details": null
  },
  "results": [
    {
      "index": 0,
      "returnCode": "2001",
      "returnMessage": "AP_INVOICE_VALIDATION_FAILED",
      "invoiceNo": "CA12345678",
      "error": {
        "code": "PCE_AP_INVOICE_VALIDATION_ERROR",
        "message": "Human-readable description",
        "details": null
      }
    }
  ]
}
HTTP vs returnCode: NetSuite RESTlets often return HTTP 200 even on business failure; use body returnCode / success. OAuth 401/403 are token/role issues and may have no JSON body.

For AP validation failures, error.code is typically PCE_AP_INVOICE_VALIDATION_ERROR and details contains per-field errors.

warnings: request still succeeds, but soft notices exist (e.g. tax amount within ±5 tolerance). Log and review as needed.

5. AP invoice fields (apInvoices[])

Prefer friendly aliases (camelCase). You may also send NetSuite field IDs (custrecord_pce_ap_*).

AliasMeaningRequiredNotes
docFormatCodeDocument format codeRequired"21"–"29"; also docType / formatCode
invCatCodeInvoice category codeCond. requiredFormat 21: 01/05; format 25: 06/07; alias invoiceCategoryCode
docFormatNameDocument format nameOptionalFull lookup Name (e.g. 21-三聯式統一發票); disambiguates when code repeats
docDateDocument issue dateRequiredYYYY/MM/DD or YYYY-MM-DD, e.g. 2026/08/01
docPeriodData periodRequiredROC 5-digit YYYMM, e.g. 11508 (may be derived from docDate)
filingPeriodFiling periodRequiredROC 5-digit YYYMM
deductionCodeDeduction codeRequired1 deductible purchases/expenses / 2 deductible fixed assets / 3 non-deductible purchases/expenses / 4 non-deductible fixed assets
invoiceNoInvoice numberConditionalDepends on format / path; also invoiceNumber
otherDocNoOther document numberConditionalDepends on format / summary path
customsAuthCodeCustoms VAT payment auth codeConditionalformats 28 / 29
utilityCarrierSnUtility carrier serialConditionalformat 25
buyerTaxIdBuyer tax IDRequired8 digits with checksum
sellerTaxIdSeller tax IDConditionalRequired or forbidden depending on format / summary
taxTypeTax typeRequired1 taxable / 2 zero-rated / 3 exempt / F void / D blank unused
salesAmountSales amountConditionalalso salesAmt; required under some rules
taxAmountTax amountConditionalalso taxAmt; checked by tax type / format
totalAmountTotalOptionalalso totalAmt
summaryMarkSummary / allocation markConditionalA summary / B allocation; required for some formats
summaryQtySummary quantityConditionale.g. summary scenarios
fileBizTaxFile business taxOptionaltrue/false or T/F
clientRefClient correlation keyOptionalstring 1–64; echoed in response
transactionParent transactionOptionalExisting transaction internal id (CSV also accepts transactionId)

Lookup fields may be sent as business codes (recommended) or NetSuite internal ids.

5.1.1 Formats 21 / 25 — ERP field mapping (invCatCode)

When the ERP sends only docFormatCode: "21" or "25", add invCatCode (or docFormatName / lookup internal id) to disambiguate:

docFormatCodeinvCatCodeNetSuite Name (docFormatName)Typical ERP category keywords
210121-三聯式統一發票triplicate, paper triplicate
210521-電子計算機統一發票electronic computer
250625-進項三聯式收銀機統一發票 (…)triplicate POS, register triplicate
250725-進項一般稅額計算之電子發票 (…)general tax e-invoice, B2B e-invoice

Full mapping CSV: docs/samples/ap-invCatCode-erp-mapping.en.csv

Pseudocode (ERP mapping):

if docFormatCode = "21":
  if category contains "electronic computer" → invCatCode = "05"
  else if category contains "triplicate" → invCatCode = "01"
if docFormatCode = "25":
  if category contains "triplicate POS" / "register triplicate" → invCatCode = "06"
  else if category contains "e-invoice" / "general tax" → invCatCode = "07"
else → use docFormatName or lookup internal id

JSON example (format 25):

{
  "docFormatCode": "25",
  "invCatCode": "07",
  "docDate": "2026/08/01",
  "docPeriod": "11508",
  "filingPeriod": "11508",
  "deductionCode": "1",
  "invoiceNo": "CA12345680",
  "buyerTaxId": "24536806",
  "sellerTaxId": "24536806",
  "taxType": "1",
  "salesAmount": 1000,
  "taxAmount": 50,
  "totalAmount": 1050,
  "fileBizTax": true
}
Conditional: Which fields are required, forbidden, or one-of — and the tax, track, and period checks — are in AP validation rules. Still dry-run real samples with options.validateOnly: true before go-live.

5.2 Example: AP invoices only

{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "apInvoiceOnly",
  "apInvoices": [
    {
      "docFormatCode": "21",
      "invCatCode": "01",
      "docDate": "2026/08/01",
      "docPeriod": "11508",
      "filingPeriod": "11508",
      "deductionCode": "1",
      "invoiceNo": "CA12345678",
      "buyerTaxId": "24536806",
      "sellerTaxId": "24536806",
      "taxType": "1",
      "salesAmount": 1000,
      "taxAmount": 50,
      "totalAmount": 1050,
      "fileBizTax": true
    }
  ]
}

Success example:

{
  "success": true,
  "action": "apInvoiceOnly",
  "validateOnly": false,
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "results": [
    {
      "index": 0,
      "id": "1001",
      "invoiceNo": "CA12345678",
      "clientRef": "inv-001"
    }
  ]
}

6. Transaction object

{
  "id": "456",
  "type": "vendorbill",
  "fields": {
    "entity": "123",
    "subsidiary": "1",
    "account": "220",
    "memo": "from external system"
  },
  "expenseLines": [
    { "account": "610", "amount": 1050 }
  ],
  "itemLines": []
}
FieldRequiredDescription
idOptionalExisting transaction internal id; if set, no create (mutually exclusive with purchaseOrderId)
typeConditionalRequired when creating (see §2.1); may omit when only referencing an existing id
purchaseOrderIdOptionalvendorbill only: create VB by transforming an existing Purchase Order (see §6.2); must not be combined with id
fieldsOptionalBody fields → NetSuite field id → value (no whitelist; see §6.0). NetSuite form mandatories depend on the customer’s setup
expenseLinesOptionalexpense sublist lines (same pass-through)
itemLinesConditionalOptional in general; required non-empty when purchaseOrderId is set (see §6.2)
apInvoicesConditionalRequired non-empty on each transaction for transactionWithApInvoices only
clientRefOptionalString 1–64; echoed in the response

Mandatory fields / lines on the NetSuite side depend on the customer’s setup. Request a field mapping from the customer.

6.0 Custom fields (pass-through allowed)

The current design allows writing custom fields on Vendor Bill / Expense Report / Vendor Credit / Vendor Prepayment via this API.

  • Keys under transaction.fields are passed as body fieldId to setValue (e.g. custbody_xxx).
  • Keys under expenseLines / itemLines are passed as sublist fieldId to setCurrentSublistValue (e.g. custcol_yyy).
  • There is no field whitelist in the payload contract. PCE does not validate transaction / custom field values; success depends on NetSuite field setup, form, permissions, and value types.
{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "transactionOnly",
  "transaction": {
    "type": "vendorbill",
    "fields": {
      "entity": "123",
      "subsidiary": "1",
      "memo": "AP Write",
      "custbody_xxx": "custom body value"
    },
    "expenseLines": [
      {
        "account": "610",
        "amount": 1050,
        "custcol_yyy": "custom column value"
      }
    ]
  }
}

Notes:

  1. Field IDs must be correct and present on that transaction type / form.
  2. Value types must match the field (selects need internal ids, dates per account preference, etc.).
  3. The integration role must have write access to the field.
  4. Standard fields use a preferred write order (entity / subsidiary earlier; account / payment last). Custom fields usually follow most standard fields; if a custom field depends on sourcing, validate with the customer.

6.1 Example: transaction only

{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "transactionOnly",
  "transaction": {
    "type": "vendorbill",
    "fields": {
      "entity": "123",
      "subsidiary": "1",
      "account": "220",
      "memo": "AP Write"
    },
    "expenseLines": [
      { "account": "610", "amount": 1050 }
    ]
  }
}

6.2 Create Vendor Bill from Purchase Order

When type is vendorbill and purchaseOrderId (PO internal id) is set, the API creates the bill via NetSuite transform (PO → Vendor Bill) and overwrites quantities from request itemLines (partial billing supported).

Rules:

ItemBehavior
Scopevendorbill only; other types with purchaseOrderId → PURCHASE_ORDER_NOT_ALLOWED
With idNot allowed together → PURCHASE_ORDER_WITH_EXISTING_ID
itemLinesRequired non-empty array; each line needs orderline (or alias line) and quantity (≥ 0 number)
QuantityRequest quantity wins; PO lines not listed in the request are set to quantity=0 (not billed)
Missing lineRequest orderline not found on the transformed VB → PO_ORDERLINE_NOT_FOUND
Other fieldsfields / remaining itemLines keys still pass through (e.g. rate, custcol_*); optional expenseLines may be appended

itemLines[] (when purchaseOrderId is set):

FieldRequiredDescription
orderline (or line)RequiredMatches the PO item sublist line number
quantityRequiredNumber ≥ 0; overwrites billed quantity
Other keys (e.g. rate, custcol_*)OptionalPass-through overrides; quantity still comes from the request
{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "transactionOnly",
  "transaction": {
    "type": "vendorbill",
    "purchaseOrderId": "12345",
    "fields": {
      "memo": "bill from PO"
    },
    "itemLines": [
      { "orderline": "1", "quantity": 2 },
      { "orderline": "3", "quantity": 1, "rate": 100 }
    ]
  }
}

On success, results[] includes transactionId, transactionCreated: true, and echoed purchaseOrderId.

6.3 Example: transaction + multiple AP invoices

{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "transactionWithApInvoices",
  "transaction": {
    "clientRef": "vb-001",
    "type": "vendorbill",
    "fields": {
      "entity": "123",
      "subsidiary": "1",
      "account": "220"
    },
    "expenseLines": [
      { "account": "610", "amount": 2100 }
    ],
    "apInvoices": [
      {
        "clientRef": "inv-001",
        "docFormatCode": "21",
        "invCatCode": "01",
        "docDate": "2026/08/01",
        "docPeriod": "11508",
        "filingPeriod": "11508",
        "deductionCode": "1",
        "invoiceNo": "CA12345678",
        "buyerTaxId": "24536806",
        "sellerTaxId": "24536806",
        "taxType": "1",
        "salesAmount": 1000,
        "taxAmount": 50,
        "totalAmount": 1050,
        "fileBizTax": true
      },
      {
        "clientRef": "inv-002",
        "docFormatCode": "21",
        "invCatCode": "05",
        "docDate": "2026/08/02",
        "docPeriod": "11508",
        "filingPeriod": "11508",
        "deductionCode": "1",
        "invoiceNo": "CA12345679",
        "buyerTaxId": "24536806",
        "sellerTaxId": "24536806",
        "taxType": "1",
        "salesAmount": 1000,
        "taxAmount": 50,
        "totalAmount": 1050,
        "fileBizTax": true
      }
    ]
  },
  "options": {
    "validateOnly": false
  }
}

Success example:

{
  "success": true,
  "action": "transactionWithApInvoices",
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "results": [
    {
      "index": 0,
      "clientRef": "vb-001",
      "transactionId": "789",
      "transactionType": "vendorbill",
      "transactionCreated": true,
      "apInvoices": [
        {
          "index": 0,
          "id": "1001",
          "invoiceNo": "CA12345678",
          "clientRef": "inv-001"
        },
        {
          "index": 1,
          "id": "1002",
          "invoiceNo": "CA12345679",
          "clientRef": "inv-002"
        }
      ]
    }
  ]
}

Attach to an existing transaction:

{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "transactionWithApInvoices",
  "transaction": {
    "id": "789",
    "apInvoices": [ { "...": "..." } ]
  }
}

7. CSV import (Suitelet)

Besides the RESTlet, you can upload a CSV in NetSuite via Suitelet to create AP invoices (internally calls the same AP Write with action=apInvoiceOnly).

ItemNotes
Scriptcustomscript_pce_sl_ap_csv_import / customdeploy_pce_sl_ap_csv_import
OpenCustomization → Scripting → Script Deployments → PCE SL AP CSV Import
EncodingSave as CSV from Excel (prefer CSV UTF-8); use UTF-8 if characters appear garbled
Limit100 data rows per file (excluding header)
HeadersSame as §5.1 API aliases (case-insensitive); or custrecord_pce_ap_*
Link TXOptional transaction / transactionId = existing transaction internal id
ValidateCheck “validate only” = options.validateOnly: true
AuditEach import gets a requestId (csv-…) and a PCE AP Write call log row
POST redirectPRG: after POST, https.redirect to GET ?action=result&writeLogId={id}; refresh does not resubmit CSV/API
Result replayGET result page loads response JSON from PCE AP Write call log and restores headers / validateOnly / row count from _csvUi snapshot (PCE_LIB_AP_CsvImportReplay.js)
Results UIOn-page Bootstrap UI: Chinese summary (success/fail counts); failed rows first with reason; success rows link to AP invoice
RetryWhen any row fails, download failed-rows CSV (same headers) to fix and re-upload
TemplatesPage offers Download CSV template (action=template) and Download field guide (action=guide)

Sample: docs/samples/ap-invoice-import-sample.csv (includes 21/25 invCatCode rows) / field guide: docs/samples/ap-invoice-import-field-guide.csv / ERP mapping: docs/samples/ap-invCatCode-erp-mapping.en.csv

Unlike native NetSuite CSV Import, this Suitelet supports API aliases / business codes, per-row results, call logs, and partial success — same contract as the RESTlet. Technical fields (returnCode, full JSON) are under “Advanced”.

8. Validate only

{
  "requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
  "action": "apInvoiceOnly",
  "options": { "validateOnly": true },
  "apInvoices": [ { "...": "..." } ]
}
actionvalidateOnly behavior
apInvoiceOnlyRuns AP validation; no create
transactionWithApInvoicesValidates invoices (uses transaction.id if present); no create
transactionOnlyChecks JSON shape only; does not validate transaction content or save

Always dry-run real sample data with validateOnly: true before go-live.

9. Error codes (integrator view)

returnCodeMeaningsuccess
0000–0099Success / partial (see §4.2; 0004=partial)true (false when 0004)
1000–1999Request / payload shapefalse
2000–2999AP business validationfalse
3000–3999Lookup / master datafalse
4000–4999NetSuite runtime (permissions, save, etc.)false
9000–9999Unexpected errorfalse

8.2 error.code mapping

codereturnCodeMeaningSuggested action
INVALID_BODY1001Body is not a JSON objectFix serialization
INVALID_ACTION1002Unknown actionUse one of the three values
REQUEST_ID_REQUIRED1003Missing requestIdAdd idempotency key (prefer UUID v4)
REQUEST_ID_INVALID1004Invalid requestId format8–64 chars of A–Z a–z 0–9 - _
REQUEST_ID_PAYLOAD_MISMATCH1005Same requestId, different payloadUse a new requestId, or resend the exact same body
AP_INVOICES_REQUIRED1006Missing invoice arrayFor apInvoiceOnly add top-level apInvoices; for transactionWithApInvoices nest under each transaction.apInvoices
AP_INVOICES_TOP_LEVEL_NOT_ALLOWED1008Top-level invoice keys not allowed for transactionWithApInvoicesNest under each transaction
CLIENT_REF_INVALID1013Invalid clientRefUse a string of 1–64 characters
AP_INVOICES_NOT_ALLOWED1007Invoices not allowed for transactionOnlyChange action or remove the array
TRANSACTION_REQUIRED1009Missing transactionAdd id or type
TRANSACTION_NOT_ALLOWED1010Cannot create transaction under this actionUse transactionWithApInvoices
INVALID_TRANSACTION_TYPE1011Unsupported typeUse §2.1 list
TRANSACTION_LIMIT_EXCEEDED1012More than 10 transactions per requestSplit into batches
PCE_METHOD_NOT_ALLOWED1014Unsupported HTTP method (e.g. GET)Use POST
PURCHASE_ORDER_NOT_ALLOWED1015purchaseOrderId on a non-vendorbill typeChange type or remove the field
PURCHASE_ORDER_WITH_EXISTING_ID1016Both id and purchaseOrderId presentChoose either an existing VB or create from PO
PO_ITEM_LINES_REQUIRED1017Missing itemLines when billing from POProvide lines with orderline / quantity
PO_ORDERLINE_REQUIRED1018Item line missing orderline / lineUse the PO line number
PO_QUANTITY_REQUIRED1019Item line missing a valid quantityUse a number ≥ 0
PCE_AP_INVOICE_VALIDATION_ERROR2001AP business rule failureFix using results[i].error / message / details
BATCH_FAILED2100All items in the batch failedInspect each results[] entry
PARTIAL_SUCCESS0004Partial successKeep successful items; resubmit failures with a new requestId
PCE_LOOKUP_RESOLVE_ERROR3001Code not in master dataAsk customer to verify lookup tables
PCE_INVALID_TRANSACTION_TYPE3002Runtime type not mapped to recordUse §2.1 list
SKIPPED_PARENT_FAILED4001Nested invoices skipped after parent TX failedFix the transaction first
PO_ORDERLINE_NOT_FOUND4002Request orderline not on PO / transform resultVerify PO line numbers
PO_TRANSFORM_FAILED4003PO→VB transform failedCheck PO status / permissions / billable qty
Other NetSuite errors4000Permissions, mandatories, system errorsContact customer NetSuite admin
UNEXPECTED_ERROR9999Unexpected exceptionContact technical support with full response

HTTP 401/403 are OAuth 2.0 token / role issues, separate from JSON returnCode / success: false.

10. Call examples (conceptual)

# 1) Access token (M2M; client_assertion is a signed JWT)
curl -X POST "$TOKEN_URL" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  -d "client_assertion=$CLIENT_ASSERTION"

# 2) GET is not allowed (expect PCE_METHOD_NOT_ALLOWED)
curl -X GET "$RESTLET_URL" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json"

# 3) POST write
curl -X POST "$RESTLET_URL" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json

JWT helper: docs/postman/generate-client-assertion.js. Access tokens expire; request a new one from the token endpoint when needed.

11. Integration notes

  1. One transaction may own many AP invoices — send them together via transactionWithApInvoices, or create the transaction first and attach later with transaction.id.
  2. Every logical write needs a unique requestId (prefer UUID v4) — on network timeout retries, reuse the same key to avoid duplicate creates.
  3. Duplicate invoice numbers are rejected. The check covers official PCE AP invoices for the same document format. If the account has API intake rows, pending and failed rows are included; rejected and posted intake rows are not. The public package does not include API intake records.
  4. Create only for AP invoices — no update/delete via this API.
  5. Tax ID, track, filing period, and tax amount rules are in AP validation rules. Tracks come from the account MOF track master, so sandbox and production can differ.
  6. Persist apInvoiceIds / transactionId from success responses for reconciliation.
  7. UI and API share the same validation library, so behavior should be consistent.

12. Support & changes

  • Breaking API changes (actions, mandatories, error codes) should be announced in writing by the customer project team.
  • For disputes, contact the designated consultant/technical contact with: Account ID, request payload (redact secrets), full error JSON, and timestamp (with timezone).

Document version aligned with SuiteApp com.netsuite.pceguitw — PCE RL AP Write (customscript_pce_rl_ap_write).