How to Translate Inbound EDI 850 Files from FTP into NetSuite Sales Orders

Build a scheduled Spojit workflow that lists new X12 EDI 850 purchase-order files on your trading partner's FTP, downloads each one, parses the raw segment-delimited EDI into structured JSON with the code connector, and creates a NetSuite sales order for every document.

What This Integration Does

Wholesale and manufacturing trading partners still exchange purchase orders as ANSI X12 EDI 850 documents dropped onto an FTP folder. Those files are not CSV: they are flat, segment-delimited text where each segment starts with a tag (ISA, GS, ST, BEG, N1, PO1) and ends with a segment terminator, with elements separated by a fixed delimiter. This workflow turns that raw EDI into NetSuite sales orders automatically, so your team stops rekeying purchase orders and new orders land in your ERP within minutes of the partner posting them.

A Schedule trigger runs the workflow on a cron interval. Each run lists the partner's inbound FTP directory, downloads any files present, and the code connector parses each X12 document into a structured object (header plus line items). A Loop then iterates over the parsed documents and calls NetSuite once per order. The workflow is stateless between runs, so you should move or delete each file after processing (or filter by filename) to avoid creating the same sales order twice on the next scheduled run.

Prerequisites

  • An ftp connection pointing at your trading partner's server, with read access to the inbound directory (and, ideally, write access to an archive or processed folder).
  • A netsuite connection authorized to create sales order records. Confirm it with the verify-connection tool before you build.
  • The partner's EDI spec: the element and segment delimiters they use, and the qualifier values in their PO1 lines (so you know whether to match items by your SKU, their SKU, or UPC).
  • A mapping from the partner's item identifiers to NetSuite internal item ids, and the NetSuite entity (customer) internal id that should own these orders.

Step 1: Add the Schedule trigger

Add a Trigger node and set its type to Schedule. Enter a 5-field Unix cron expression and an IANA timezone. For example, poll the partner folder every 15 minutes during business hours with */15 8-18 * * 1-5 and timezone Australia/Sydney. A single Schedule trigger can hold multiple schedules if your partner posts at fixed windows. The trigger output is { scheduledAt }, which you do not need downstream here.

Step 2: List new files on the FTP directory

Add a Connector node on the ftp connector in Direct mode and choose the list-directory tool. Set path to the inbound EDI folder, for example /edi/inbound/850. The tool returns data.entries, an array where each entry has name, type, size, and modifiedAt. Name the output variable listing.

Add a Transform node to keep only the files you want to process, dropping any directories and any name that does not look like an EDI 850 drop. For instance, filter {{ listing.data.entries }} down to entries where type equals file and name ends with .edi or .x12. Output the trimmed array as files.

Step 3: Loop over each file and download it

Add a Loop node in ForEach mode over {{ files }}. Inside the loop body, add a Connector node on the ftp connector in Direct mode with the download-file tool. Set path to the full remote path of the current file, built from the inbound folder and the loop item name, for example /edi/inbound/850/{{ item.name }}. Leave encoding as utf8 because EDI 850 files are plain text. The tool returns data.content (the file text), data.encoding, and data.size. Name this output download.

Step 4: Parse the X12 EDI 850 with the code connector

Still inside the loop, add a Connector node on the code connector in Direct mode and choose execute-javascript. Pass {{ download.data.content }} as input and split the raw text into segments and elements, then read the header and PO1 line items into a clean object. A starting point:

// input.raw is the EDI 850 file text
const raw = input.raw;
const segTerm = raw.includes("~") ? "~" : "\n";
const elemSep = raw[3] || "*"; // ISA defines the element separator at position 3

const segments = raw
  .split(segTerm)
  .map(s => s.trim())
  .filter(Boolean)
  .map(s => s.split(elemSep));

const get = tag => segments.find(s => s[0] === tag) || [];
const beg = get("BEG");

const items = segments
  .filter(s => s[0] === "PO1")
  .map(s => ({
    quantity: Number(s[2]),
    unitPrice: Number(s[4]),
    partnerSku: s[7],   // qualifier in s[6], value in s[7]
  }));

return {
  poNumber: beg[3] || null,
  poDate: beg[5] || null,
  lineCount: items.length,
  items,
};

Name the output parsed. Map your partner's partnerSku values to NetSuite item internal ids here too, either with a lookup object embedded in the code or by adding a downstream Transform node. Adjust the element positions to match your partner's exact EDI spec.

Step 5: Create the NetSuite sales order

Still inside the loop, add a Connector node on the netsuite connector in Direct mode and choose the create-record tool. Set recordType to salesOrder and build the body from the parsed document. Reference the partner's entity internal id, carry their PO number into a reference field, and expand the parsed line items into the item sublist:

{
  "entity": { "id": "1234" },
  "otherRefNum": "{{ parsed.poNumber }}",
  "memo": "EDI 850 from {{ item.name }}",
  "item": {
    "items": [
      {
        "item": { "id": "5678" },
        "quantity": 2,
        "rate": 19.95
      }
    ]
  }
}

In practice you build the item.items array dynamically from {{ parsed.items }} after mapping each partnerSku to its NetSuite item id. Name the output salesOrder. If you want a second-pass enrichment, you can follow with the add-sublist-item tool, but a complete create-record body is the cleanest single call.

Step 6: Archive the processed file and confirm

Still inside the loop, add a Connector node on the ftp connector in Direct mode with the rename tool to move the file out of the inbound folder so the next scheduled run does not reprocess it. Set from to /edi/inbound/850/{{ item.name }} and to to /edi/archive/{{ item.name }} (create the archive folder once with create-directory if needed). Then add a Send Email node after the loop to email yourself a run summary listing each created sales order internal id, so you have a daily paper trail. Set the recipient and a templated subject such as EDI 850 import: {{ files.length }} orders processed.

Tips

  • Detect the delimiters at runtime instead of hardcoding them. The X12 ISA segment is fixed-width: the element separator is the 4th character and the segment terminator is the last character of the ISA line. Reading them from the file makes the parser robust across partners.
  • If you prefer Python for the parse, execute-python is available on the same code connector. Keep the parse deterministic in Direct mode; you do not need Agent mode or AI credits for fixed-format EDI.
  • One EDI file can contain multiple ST/SE transaction sets (multiple POs). Split on ST boundaries in the code step and return an array of documents, then add an inner Loop over them so each becomes its own sales order.
  • Ask Miraxa, the intelligent layer across your automation, to scaffold this for you with a prompt like "Add a Loop over {{ files }} that downloads each file with the ftp download-file tool, parses it with code execute-javascript, and creates a NetSuite sales order," then fine-tune the field mapping in the properties panel.

Common Pitfalls

  • Duplicate orders. Because the workflow is stateless between scheduled runs, a file left in the inbound folder is reprocessed and creates a duplicate sales order. Always move or delete each file after a successful NetSuite create, as in Step 6.
  • Binary vs text encoding. EDI 850 is text, so download with utf8. If you accidentally use base64, the parser receives base64 instead of segments and fails to find any PO1 lines.
  • Item mapping gaps. A PO1 line whose partnerSku has no NetSuite item id will make create-record reject the whole order. Validate every line maps before the NetSuite call and route unmapped POs to an exceptions email instead of failing silently.
  • Partial reads. If the partner is still uploading when the schedule fires, you may download a truncated file. Filter Step 2 by modifiedAt to skip files modified in the last minute, or have the partner upload to a temp name and rename on completion.

Testing

Place a single known-good EDI 850 file in the inbound folder and run the workflow with the Run button rather than waiting for the schedule. Inspect the execution log: confirm list-directory found the file, the code step returned the expected poNumber and items count, and the NetSuite create-record response carries a sales order internal id. Verify the order in NetSuite, confirm the file moved to your archive folder, then enable the Schedule trigger for live traffic once a few sample partners parse cleanly.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.