How to Retrieve Sales Order Fulfillment Details Through REST

Find item fulfillment IDs with SuiteQL, retrieve shipped dates and tracking, and keep package rows separate from fulfilled quantities.

The quick answer

Use SuiteQL to find distinct item fulfillment IDs through the sales order's line-level Created From relationship. GET each fulfillment with expanded subresources, then report shipment headers, item quantities and package tracking as separate row sets.

This walkthrough is a local API exercise with fictional records and illustrative response subsets. It makes no production NetSuite calls. Its final native record view cross-checks the local business data; it does not verify a real integration.

Before you start

Use an account with REST Web Services and an authorized integration role. The role needs access to REST queries, the relevant transaction data and item fulfillment records, subject to subsidiary and location restrictions. Verify field availability in the account's Records Catalog and REST metadata. Shipping features, integrations and permissions affect what a GET returns.

The curl examples assume REST_BASE is the account's /services/rest URL and TOKEN is a valid OAuth 2.0 access token. Token-Based Authentication uses a signed OAuth 1.0a header instead. Keep credentials out of files and screenshots.

The example order is 74000, for Cedar Ridge Industrial. It has two shipped fulfillments of the same Field-service kit order line, measured in Ea. Those units are known from the local native record. Normalize units before adding quantities in a real report. All IDs and tracking values below are examples.

01. Discover the fulfillment IDs

Prepare query.json:

{
  "q": "SELECT DISTINCT f.id AS fulfillment_id FROM transaction f INNER JOIN transactionLine l ON l.transaction = f.id WHERE f.type = 'ItemShip' AND l.createdFrom = ? ORDER BY f.id",
  "params": ["74000"]
}

TransactionLine.createdFrom supplies the source-order relationship. DISTINCT keeps a fulfillment from appearing once for every matching line. An item-table join is unnecessary for finding IDs. Avoid SELECT * with GROUP BY only the transaction ID: selected nonaggregate columns must meet SQL grouping rules.

curl -X POST "$REST_BASE/query/v1/suiteql?limit=1000&offset=0" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Prefer: transient" \
  --data-binary @query.json

This POST is read-only: it submits a query rather than creating a record. Prefer: transient is required. The local response contains 74001 and 74002, with hasMore:false. In production, follow pagination links and metadata until every page is collected. Keep a stable order; observe current endpoint page and result limits. A single page is not proof that you retrieved every fulfillment.

Local example: two distinct IDs, with hasMore:false on this complete page.

02. Read the first fulfillment

curl "$REST_BASE/record/v1/itemFulfillment/74001?expandSubResources=true" \
  -H "Authorization: Bearer $TOKEN"

Verify createdFrom.id 74000 before using the response. Read the status reference and confirm that it represents Shipped, rather than comparing an entire reference object to a string. Status identifiers and available properties must match current metadata.

The verified 2026.1 REST schema uses shippedDate, labeled Shipped Date. It does not expose shipDate on this record. The first local fulfillment shows 2026-10-13. This is a date-only field, not a timezone-qualified event timestamp. A missing date does not justify substituting the transaction date silently.

Local example: fulfillment 74001 has shippedDate 2026-10-13. Enlarge for the complete response.

Read shipMethod as a shipping-item reference and shipCarrier separately. In the local example these display Regional Ground and More. The returned More/non-UPS category does not establish the actual shipping company. A report that needs the actual carrier must obtain it from the account's shipping configuration or an explicit, verified integration mapping. Do not infer it from a tracking-number pattern or treat a shipping-method label as the carrier automatically.

The response's item.items contains the Field-service kit line with orderLine 1 and quantity 2. Its package.items contains DEMO-PKG-A and DEMO-PKG-B. These are deliberately fictional tracking strings. Two packages do not turn the line quantity into four, and this response does not allocate item quantities to individual packages.

03. Read the second fulfillment

GET the second returned ID with the same expansion option:

curl "$REST_BASE/record/v1/itemFulfillment/74002?expandSubResources=true" \
  -H "Authorization: Bearer $TOKEN"

This local response points to the same source order. It shows shippedDate 2026-10-14, item-line quantity 3 and generic tracking DEMO-PKG-C. Preserve the fulfillment ID alongside the order line and item ID, so split shipments remain separate. Follow any subresource pagination or links needed to retrieve all lines and packages; expansion alone is not a completeness check.

Local example: fulfillment 74002 has shippedDate 2026-10-14. Enlarge to compare quantity and package data.

Generic packages use package.items[].packageTrackingNumber. Integrated UPS, FedEx and USPS shipping can use packageUps, packageFedex or packageUsps, with carrier-specific field names. Choose the applicable collection from current metadata and shipping configuration. Do not assume generic packages contain every tracking number or blindly combine all variants.

04. Keep report rows at the right level

Choose Build local report. The report derives three tables from the two retrieved objects:

  • Fulfillments: one row per fulfillment, with shipped date, method and the returned carrier category.
  • Item lines: fulfillment ID, order line, item and quantity.
  • Generic packages: fulfillment ID and tracking number, with no repeated item quantity.

The illustrative native fixture uses one item in Ea: 2 + 3 = 5 Ea shipped. The GET examples omit units; a production integration must verify and normalize them before aggregation. The package count is 3. Enlarge the image to compare all three tables and the calculated total.

Local example: item quantities 2 and 3 total 5 Ea. Enlarge for both shipment dates and three separate package rows.

Do not sum item quantity after joining lines to every package. Two lines and three packages can create six joined rows. Keep separate child datasets, or aggregate each at its intended level before joining. Lot, serial, bin and inventory-status allocations need their own inventory-detail reconciliation.

05. Cross-check the first native record

Open IF74001 from the report. The local record is Shipped, with Created From Sales Order #SO74000 and Field-service kit quantity 2. The focused image shows the source-order reference; enlarge it to compare the complete header and item row.

Local example: IF74001 links to Sales Order SO74000. Enlarge to verify Shipped status and quantity 2.

The native record corroborates this local fixture's business relationship. It does not prove OAuth permissions, response serialization, package access or carrier mappings in another account.

Common questions

Can I filter the item fulfillment collection by createdFrom.id?

Only if the current account's REST collection metadata marks the field and operator as filterable. A nested reference in the record does not automatically make it a collection filter. The SuiteQL line relationship is a separate query model.

Why does the sales order's fulfilled quantity not show each shipment?

Ordered quantity describes the order; fulfilled quantity is an order-line total; each fulfillment line describes one shipment. Use the related item fulfillments for shipment-level dates, quantities and package information. A sales order can have multiple partial fulfillments.

Is shippedDate the same as tranDate?

They are different fields. The verified REST record schema labels shippedDate as Shipped Date. Preserve a missing shipped date and investigate the record's status and account configuration instead of silently copying the transaction date.

Why is there no tracking number in package.items?

The fulfillment may have no recorded package tracking, the role may lack access, or shipping may use a carrier-specific collection. Inspect the applicable schema, shipping configuration and response links. An empty generic collection alone does not prove that no tracking exists.

Can I total the quantities for every returned item?

Keep item, order line and unit context. Add comparable item quantities in compatible units, excluding technical or non-item lines where applicable. The local total of five is for one item measured in Ea; it is not a useful total across unrelated products or units.

Does a SuiteQL POST update the fulfillment?

No. This POST executes a read-only query. The subsequent record GET requests also read data. This walkthrough does not create or update any production transactions.

Supporting sources