PCE AP Write API — Integration Guide (External Vendors)
Purpose: Write Taiwan AP e-invoice data and related AP transactions via RESTlet
Internal details: Technical & testing guide · 中文版 · Postman collection
1. Quick start
- 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)
- Account ID (e.g.
- Obtain an OAuth 2.0 access token (Bearer).
- Dry-run with POST +
options.validateOnly: true(GET is not supported). - Call POST with JSON and the appropriate
action.
1.1 Connectivity
| Item | Value |
|---|---|
| Protocol | HTTPS |
| Auth | OAuth 2.0 (Authorization: Bearer {access_token}) |
| Content-Type | application/json (writes); application/x-www-form-urlencoded (token) |
| Methods | POST only (GET returns PCE_METHOD_NOT_ALLOWED) |
External URL pattern (numeric IDs are environment-specific):
https://{accountId}.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=xxx&deploy=yyyToken endpoint:
https://{accountId}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token1.2 Obtaining a token (recommended for vendors: M2M)
Client Credentials (Machine-to-Machine) — server-to-server, no user login:
- Integration: enable Client Credentials (Machine to Machine) with scope RESTlets.
- 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.
- Sign a JWT (
iss= Client ID,aud= token URL,scope=restlets, headerkid= Certificate ID). - 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}- 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:
| action | Purpose | Required | Must not include |
|---|---|---|---|
apInvoiceOnly | Create AP invoice(s) only (no parent required) | apInvoices (≥ 1) | Transaction create payload (type / fields / lines) |
transactionOnly | Create or reference transactions | transaction (object or array; each item needs id or type, max 10) | apInvoices |
transactionWithApInvoices | Resolve/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):
| type | NetSuite record |
|---|---|
vendorbill | Vendor Bill |
expensereport | Expense Report |
vendorcredit | Vendor Credit |
vendorprepayment | Vendor 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
}
}| Field | Required | Description |
|---|---|---|
action | Required | One of: apInvoiceOnly / transactionOnly / transactionWithApInvoices (see §2) |
requestId | Required | Idempotency 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. |
apInvoices | Per action | Required only for apInvoiceOnly (≥ 1); alias invoices accepted. Must not be top-level for other actions |
transaction | Per action | Required for transactionOnly / transactionWithApInvoices (object or array, max 10). Latter must nest non-empty apInvoices[] on each item |
options.validateOnly | Optional | When true, validates without persisting (see per-action notes). Failure and validateOnly responses include requestSummary (slim: index / clientRef / invoiceNo / type / id) |
options.echoRequest | Optional | Default off. When true, every response includes the full request (for debugging; keep off in production) |
clientRef | Optional | On each transaction and/or each apInvoices[] item (string 1–64); echoed in the success response for correlation |
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)
| returnCode | When | returnMessage |
|---|---|---|
0000 | Normal write succeeded (all items) | OK |
0001 | validateOnly: true and all passed | VALIDATED |
0002 | idempotentReplay: true (prior full success) | IDEMPOTENT_REPLAY |
0003 | All succeeded with warnings (e.g. tax tolerance) | OK_WITH_WARNINGS |
0004 | Partial success: at least one committed/passed and at least one failed | 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
}
}
]
}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_*).
5.1 Recommended aliases
| Alias | Meaning | Required | Notes |
|---|---|---|---|
docFormatCode | Document format code | Required | "21"–"29"; also docType / formatCode |
invCatCode | Invoice category code | Cond. required | Format 21: 01/05; format 25: 06/07; alias invoiceCategoryCode |
docFormatName | Document format name | Optional | Full lookup Name (e.g. 21-三聯式統一發票); disambiguates when code repeats |
docDate | Document issue date | Required | YYYY/MM/DD or YYYY-MM-DD, e.g. 2026/08/01 |
docPeriod | Data period | Required | ROC 5-digit YYYMM, e.g. 11508 (may be derived from docDate) |
filingPeriod | Filing period | Required | ROC 5-digit YYYMM |
deductionCode | Deduction code | Required | 1 deductible purchases/expenses / 2 deductible fixed assets / 3 non-deductible purchases/expenses / 4 non-deductible fixed assets |
invoiceNo | Invoice number | Conditional | Depends on format / path; also invoiceNumber |
otherDocNo | Other document number | Conditional | Depends on format / summary path |
customsAuthCode | Customs VAT payment auth code | Conditional | formats 28 / 29 |
utilityCarrierSn | Utility carrier serial | Conditional | format 25 |
buyerTaxId | Buyer tax ID | Required | 8 digits with checksum |
sellerTaxId | Seller tax ID | Conditional | Required or forbidden depending on format / summary |
taxType | Tax type | Required | 1 taxable / 2 zero-rated / 3 exempt / F void / D blank unused |
salesAmount | Sales amount | Conditional | also salesAmt; required under some rules |
taxAmount | Tax amount | Conditional | also taxAmt; checked by tax type / format |
totalAmount | Total | Optional | also totalAmt |
summaryMark | Summary / allocation mark | Conditional | A summary / B allocation; required for some formats |
summaryQty | Summary quantity | Conditional | e.g. summary scenarios |
fileBizTax | File business tax | Optional | true/false or T/F |
clientRef | Client correlation key | Optional | string 1–64; echoed in response |
transaction | Parent transaction | Optional | Existing 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:
| docFormatCode | invCatCode | NetSuite Name (docFormatName) | Typical ERP category keywords |
|---|---|---|---|
| 21 | 01 | 21-三聯式統一發票 | triplicate, paper triplicate |
| 21 | 05 | 21-電子計算機統一發票 | electronic computer |
| 25 | 06 | 25-進項三聯式收銀機統一發票 (…) | triplicate POS, register triplicate |
| 25 | 07 | 25-進項一般稅額計算之電子發票 (…) | 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
}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": []
}| Field | Required | Description |
|---|---|---|
id | Optional | Existing transaction internal id; if set, no create (mutually exclusive with purchaseOrderId) |
type | Conditional | Required when creating (see §2.1); may omit when only referencing an existing id |
purchaseOrderId | Optional | vendorbill only: create VB by transforming an existing Purchase Order (see §6.2); must not be combined with id |
fields | Optional | Body fields → NetSuite field id → value (no whitelist; see §6.0). NetSuite form mandatories depend on the customer’s setup |
expenseLines | Optional | expense sublist lines (same pass-through) |
itemLines | Conditional | Optional in general; required non-empty when purchaseOrderId is set (see §6.2) |
apInvoices | Conditional | Required non-empty on each transaction for transactionWithApInvoices only |
clientRef | Optional | String 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.fieldsare passed as bodyfieldIdtosetValue(e.g.custbody_xxx). - Keys under
expenseLines/itemLinesare passed as sublistfieldIdtosetCurrentSublistValue(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:
- Field IDs must be correct and present on that transaction type / form.
- Value types must match the field (selects need internal ids, dates per account preference, etc.).
- The integration role must have write access to the field.
- Standard fields use a preferred write order (
entity/subsidiaryearlier;account/paymentlast). 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:
| Item | Behavior |
|---|---|
| Scope | vendorbill only; other types with purchaseOrderId → PURCHASE_ORDER_NOT_ALLOWED |
With id | Not allowed together → PURCHASE_ORDER_WITH_EXISTING_ID |
itemLines | Required non-empty array; each line needs orderline (or alias line) and quantity (≥ 0 number) |
| Quantity | Request quantity wins; PO lines not listed in the request are set to quantity=0 (not billed) |
| Missing line | Request orderline not found on the transformed VB → PO_ORDERLINE_NOT_FOUND |
| Other fields | fields / remaining itemLines keys still pass through (e.g. rate, custcol_*); optional expenseLines may be appended |
itemLines[] (when purchaseOrderId is set):
| Field | Required | Description |
|---|---|---|
orderline (or line) | Required | Matches the PO item sublist line number |
quantity | Required | Number ≥ 0; overwrites billed quantity |
Other keys (e.g. rate, custcol_*) | Optional | Pass-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).
| Item | Notes |
|---|---|
| Script | customscript_pce_sl_ap_csv_import / customdeploy_pce_sl_ap_csv_import |
| Open | Customization → Scripting → Script Deployments → PCE SL AP CSV Import |
| Encoding | Save as CSV from Excel (prefer CSV UTF-8); use UTF-8 if characters appear garbled |
| Limit | 100 data rows per file (excluding header) |
| Headers | Same as §5.1 API aliases (case-insensitive); or custrecord_pce_ap_* |
| Link TX | Optional transaction / transactionId = existing transaction internal id |
| Validate | Check “validate only” = options.validateOnly: true |
| Audit | Each import gets a requestId (csv-…) and a PCE AP Write call log row |
| POST redirect | PRG: after POST, https.redirect to GET ?action=result&writeLogId={id}; refresh does not resubmit CSV/API |
| Result replay | GET 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 UI | On-page Bootstrap UI: Chinese summary (success/fail counts); failed rows first with reason; success rows link to AP invoice |
| Retry | When any row fails, download failed-rows CSV (same headers) to fix and re-upload |
| Templates | Page 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
returnCode, full JSON) are under “Advanced”.8. Validate only
{
"requestId": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
"action": "apInvoiceOnly",
"options": { "validateOnly": true },
"apInvoices": [ { "...": "..." } ]
}| action | validateOnly behavior |
|---|---|
apInvoiceOnly | Runs AP validation; no create |
transactionWithApInvoices | Validates invoices (uses transaction.id if present); no create |
transactionOnly | Checks 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)
8.1 returnCode (recommended for routing)
| returnCode | Meaning | success |
|---|---|---|
0000–0099 | Success / partial (see §4.2; 0004=partial) | true (false when 0004) |
1000–1999 | Request / payload shape | false |
2000–2999 | AP business validation | false |
3000–3999 | Lookup / master data | false |
4000–4999 | NetSuite runtime (permissions, save, etc.) | false |
9000–9999 | Unexpected error | false |
8.2 error.code mapping
| code | returnCode | Meaning | Suggested action |
|---|---|---|---|
INVALID_BODY | 1001 | Body is not a JSON object | Fix serialization |
INVALID_ACTION | 1002 | Unknown action | Use one of the three values |
REQUEST_ID_REQUIRED | 1003 | Missing requestId | Add idempotency key (prefer UUID v4) |
REQUEST_ID_INVALID | 1004 | Invalid requestId format | 8–64 chars of A–Z a–z 0–9 - _ |
REQUEST_ID_PAYLOAD_MISMATCH | 1005 | Same requestId, different payload | Use a new requestId, or resend the exact same body |
AP_INVOICES_REQUIRED | 1006 | Missing invoice array | For apInvoiceOnly add top-level apInvoices; for transactionWithApInvoices nest under each transaction.apInvoices |
AP_INVOICES_TOP_LEVEL_NOT_ALLOWED | 1008 | Top-level invoice keys not allowed for transactionWithApInvoices | Nest under each transaction |
CLIENT_REF_INVALID | 1013 | Invalid clientRef | Use a string of 1–64 characters |
AP_INVOICES_NOT_ALLOWED | 1007 | Invoices not allowed for transactionOnly | Change action or remove the array |
TRANSACTION_REQUIRED | 1009 | Missing transaction | Add id or type |
TRANSACTION_NOT_ALLOWED | 1010 | Cannot create transaction under this action | Use transactionWithApInvoices |
INVALID_TRANSACTION_TYPE | 1011 | Unsupported type | Use §2.1 list |
TRANSACTION_LIMIT_EXCEEDED | 1012 | More than 10 transactions per request | Split into batches |
PCE_METHOD_NOT_ALLOWED | 1014 | Unsupported HTTP method (e.g. GET) | Use POST |
PURCHASE_ORDER_NOT_ALLOWED | 1015 | purchaseOrderId on a non-vendorbill type | Change type or remove the field |
PURCHASE_ORDER_WITH_EXISTING_ID | 1016 | Both id and purchaseOrderId present | Choose either an existing VB or create from PO |
PO_ITEM_LINES_REQUIRED | 1017 | Missing itemLines when billing from PO | Provide lines with orderline / quantity |
PO_ORDERLINE_REQUIRED | 1018 | Item line missing orderline / line | Use the PO line number |
PO_QUANTITY_REQUIRED | 1019 | Item line missing a valid quantity | Use a number ≥ 0 |
PCE_AP_INVOICE_VALIDATION_ERROR | 2001 | AP business rule failure | Fix using results[i].error / message / details |
BATCH_FAILED | 2100 | All items in the batch failed | Inspect each results[] entry |
PARTIAL_SUCCESS | 0004 | Partial success | Keep successful items; resubmit failures with a new requestId |
PCE_LOOKUP_RESOLVE_ERROR | 3001 | Code not in master data | Ask customer to verify lookup tables |
PCE_INVALID_TRANSACTION_TYPE | 3002 | Runtime type not mapped to record | Use §2.1 list |
SKIPPED_PARENT_FAILED | 4001 | Nested invoices skipped after parent TX failed | Fix the transaction first |
PO_ORDERLINE_NOT_FOUND | 4002 | Request orderline not on PO / transform result | Verify PO line numbers |
PO_TRANSFORM_FAILED | 4003 | PO→VB transform failed | Check PO status / permissions / billable qty |
| Other NetSuite errors | 4000 | Permissions, mandatories, system errors | Contact customer NetSuite admin |
UNEXPECTED_ERROR | 9999 | Unexpected exception | Contact 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
- One transaction may own many AP invoices — send them together via
transactionWithApInvoices, or create the transaction first and attach later withtransaction.id. - Every logical write needs a unique
requestId(prefer UUID v4) — on network timeout retries, reuse the same key to avoid duplicate creates. - 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.
- Create only for AP invoices — no update/delete via this API.
- 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.
- Persist
apInvoiceIds/transactionIdfrom success responses for reconciliation. - 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
errorJSON, and timestamp (with timezone).
Document version aligned with SuiteApp com.netsuite.pceguitw — PCE RL AP Write (customscript_pce_rl_ap_write).