How to Generate Tiered B2B Price Lists from NetSuite and Publish Them via FTP

Build a Spojit workflow that runs on a weekly schedule, queries NetSuite item and customer-tier data with SuiteQL, computes per-tier discount pricing with math tools, builds a price-list file with CSV tools, and drops it onto a partner FTP folder.

What This Integration Does

Wholesale and manufacturing businesses sell the same catalog at different prices to different customer tiers: a Bronze reseller pays list price, Silver gets 10 percent off, Gold gets 20 percent off, and so on. Maintaining those tiered B2B price lists by hand in a spreadsheet is slow and error-prone, and your trading partners expect a fresh file in their drop folder on a predictable cadence. This workflow generates each tier's price list straight from your NetSuite catalog, applies the correct discount math, and publishes a clean CSV that partners or an EDI system can pick up.

The workflow is started by a Schedule trigger on a weekly cron. On each run it reads the live base prices and active item set from NetSuite with run-suiteql, calculates the discounted price for every item in a tier using the math connector, assembles the rows into a CSV with the csv connector's from-json tool, and uploads the result to a partner folder with the ftp connector's upload-file tool. It leaves a dated price-list file on the FTP server and writes nothing back to NetSuite, so it is safe to re-run: a second run for the same week simply overwrites the file with the latest catalog prices.

Prerequisites

  • A NetSuite connection in Spojit with permission to run SuiteQL queries against your item and pricing records. See the NetSuite connector article for setup.
  • An ftp connection pointing at your partner's drop server, with write access to the target directory. See the FTP/SFTP connector article.
  • A clear definition of your tiers and their discount percentages (for example Bronze 0 percent, Silver 10 percent, Gold 20 percent), and the SuiteQL field names for your base price (often the baseprice price level on the item table).
  • The remote folder path partners read from, and the file naming convention they expect (for example /pricelists/gold-2026-W25.csv).

Step 1: Add the Schedule trigger

Open the Workflow Designer and add a Trigger node, then set Trigger Type to Schedule. Enter a 5-field Unix cron expression and an IANA timezone. For a Monday 6am weekly publish in eastern Australia, use:

Cron:     0 6 * * 1
Timezone: Australia/Sydney

The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }} to stamp the file name. A single Schedule trigger can hold multiple schedules if you want to publish on more than one day.

Step 2: Query item and base-price data from NetSuite

Add a Connector node on the netsuite connector in Direct mode and choose the run-suiteql tool. This reads exactly the columns you need in one pass instead of fetching whole records. Map the query field to a SuiteQL statement that returns the active sellable items and their base price:

SELECT i.itemid AS sku,
       i.displayname AS name,
       p.price AS base_price
FROM item i
JOIN pricing p ON p.item = i.id
JOIN pricelevel pl ON pl.id = p.pricelevel
WHERE pl.name = 'Base Price'
  AND i.isinactive = 'F'
  AND i.isonline = 'T'
FETCH FIRST 1000 ROWS ONLY

Set the limit field (max 1000 rows per page) and use offset for pagination if your catalog is larger. Bind the output to a variable such as catalog; the rows are available as {{ catalog.data.items }} (adjust to match the shape your connection returns).

Step 3: Loop over the catalog and compute each tier price

Add a Loop node in ForEach mode over the catalog rows so each item is priced individually. Inside the loop body, add a Connector node on the math connector in Direct mode using the calculate tool to apply the tier discount. For a Gold tier at 20 percent off, set expression to base * (1 - discount / 100) and pass the variables object:

{
  "base": {{ item.base_price }},
  "discount": 20
}

The tool returns { result }, the discounted price, available as {{ tier_price.result }}. If you would rather express the discount as a share of list price for a report column, the math percentage tool computes value / total * 100 and returns both result and a formatted string. To present a human-readable amount, add a math currency node with number set to {{ tier_price.result }} and currency set to your code (for example AUD); it returns a formatted result string like $84.00.

Step 4: Collect the priced rows

Inside the loop, build the row object you want in the file (SKU, name, raw price, and formatted price) and accumulate it so the loop produces an array of priced rows. Use a Transform node to shape each row, for example:

{
  "sku": "{{ item.sku }}",
  "name": "{{ item.name }}",
  "tier": "Gold",
  "unit_price": {{ tier_price.result }},
  "unit_price_display": "{{ tier_price_display.result }}"
}

After the loop completes, you have a single list of priced rows (for example {{ priced_rows }}) ready to serialise. To publish several tiers in one run, wrap Steps 3 and 4 in a Parallel node with one branch per tier, each branch using its own discount value and producing its own row list.

Step 5: Build the price-list CSV

Add a Connector node on the csv connector in Direct mode and choose the from-json tool. Map the data field to your priced rows array, and use the columns field to pin column order so the partner's parser stays happy:

data:    {{ priced_rows }}
columns: ["sku", "name", "tier", "unit_price", "unit_price_display"]
header:  true

The tool returns { csv, rows, encoding }. The csv string is your finished file body, referenced downstream as {{ pricelist.csv }}, and rows gives you a count you can log or validate.

Step 6: Upload the file to the partner FTP folder

Add a Connector node on the ftp connector in Direct mode and choose the upload-file tool. Set path to a dated remote path so each weekly file is distinct, set content to the CSV body, and leave encoding as utf8 for text:

path:     /pricelists/gold/{{ trigger.scheduledAt }}-gold-pricelist.csv
content:  {{ pricelist.csv }}
encoding: utf8

The tool overwrites the file if it already exists and returns { success, data: { path, size, message } }, where size is the bytes written. If the target directory might not exist yet, add an earlier ftp create-directory node, or call verify-connection once during setup to confirm credentials.

Step 7: Confirm and notify (optional)

Add a Send Email node so the team knows the file shipped. Set Recipients to your operations alias, a templated Subject such as Gold price list published ({{ pricelist.rows }} items), and a body that includes the remote path from {{ upload.data.path }} and byte count from {{ upload.data.size }}. Remember external recipients must be on the org allowlist under Settings > General > Email recipients. For high-value partner files you can place a Human node before Step 6 so a manager approves the run before it reaches the partner; approval halts the workflow on reject, so use it only where a sign-off gate is genuinely required.

Tips

  • Keep SuiteQL reads narrow: select only the columns you need and filter inactive items in the query. This is faster and cheaper than fetching full records and discarding fields.
  • Use Direct mode for every connector node here. The whole pipeline is deterministic single-tool calls, so there is no need to spend AI credits on Agent mode.
  • If your tier table lives in NetSuite rather than as fixed numbers, query the customer-tier discounts with a second run-suiteql call and feed each tier's percentage into the math calculate variables, instead of hard-coding the discount.
  • Need to ask Spojit to scaffold this? Tell Miraxa, the intelligent layer across your automation: "Add a Loop over {{ catalog.data.items }} with a math calculate node that computes base * (1 - discount / 100), then a csv from-json node, then an ftp upload-file node."

Common Pitfalls

  • Pagination: run-suiteql caps at 1000 rows per page. A catalog larger than that needs a paging loop using offset, or the last items silently drop off the price list.
  • Floating-point money: raw discount math can produce values like 83.99999. Round to two decimals (use the math round tool or the decimals option) before writing the file so partners do not see ragged prices.
  • Column drift: if you omit the columns field in from-json, the CSV uses the keys of the first object in whatever order they appear. Pin columns so a partner's fixed-position parser never breaks when row shape changes.
  • Timezone and file naming: the cron fires in the timezone you set, and {{ trigger.scheduledAt }} reflects that instant. Confirm the date stamp in your file name lands on the day partners expect, especially across a Sunday-to-Monday boundary.

Testing

Before turning on the weekly schedule, narrow the SuiteQL query with a tight filter (for example a single product category or FETCH FIRST 5 ROWS ONLY) and point the ftp upload-file path at a temporary test folder. Run the workflow with the Run button and inspect the execution log to confirm the discounted prices, the row count from from-json, and the bytes written reported by upload-file. Download the test file from the FTP server and open it to verify column order and rounding. Once the small-scope file looks right, widen the query, switch the path to the real partner folder, and enable the Schedule trigger.

Learn More

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