# Reading Shopify purchase orders in n8n (new 2026-10 API): a morning list of late supplier stock

**URL:** <https://community.n8n.io/t/reading-shopify-purchase-orders-in-n8n-new-2026-10-api-a-morning-list-of-late-supplier-stock/326520>\
**Category:** Built with n8n\
**Created:** [November 4, 2026, 8:17am UTC](https://community.n8n.io/t/reading-shopify-purchase-orders-in-n8n-new-2026-10-api-a-morning-list-of-late-supplier-stock/326520 "2026-11-04T08:17:36Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![TrenoxaHQ](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/trenoxahq/32/194852_2.png) [@TrenoxaHQ](https://community.n8n.io/u/TrenoxaHQ)\
**Post date:** [November 4, 2026, 8:17am UTC](https://community.n8n.io/t/reading-shopify-purchase-orders-in-n8n-new-2026-10-api-a-morning-list-of-late-supplier-stock/326520/1 "2026-11-04T08:17:36Z")

</div>

Shopify’s purchase orders have been readable from the Admin API since version **2026-10** (`inventoryPurchaseOrders`). Before that they were preview-only. I built a small workflow on it that emails one morning list: shipments that are **overdue** , **arriving this week** , **partly received** , and POs the supplier **hasn’t shipped** yet.

**How it works (11 nodes + an error alert)**  
Schedule → Settings → HTTP Request (Shopify OAuth2 credential, GraphQL) → Code (collect page) → IF (more pages? loops back through a Wait that only pauses when Shopify’s cost bucket runs low) → Code (sort) → Switch (email / optional Google Sheet).

The query, with status filter and everything needed for the four lists:

```graphql
query($after: String) {
  inventoryPurchaseOrders(first: 50, after: $after, query: "status:ORDERED") {
    pageInfo { hasNextPage endCursor }
    nodes {
      id name orderedAt origin { supplierName } destination { name }
      lineItems(first: 50) { nodes { title variantTitle totalQuantity } }
      transfers(first: 10) { nodes {
        totalQuantity receivedQuantity
        shipments(first: 10) { nodes {
          status dateReceived
          tracking { arrivesAt company trackingNumber trackingUrl }
        } }
      } }
    }
  }
}

```

The expected date lives on the **shipment** (`tracking.arrivesAt`), not on the PO. Overdue = a shipment `IN_TRANSIT` or `PARTIALLY_RECEIVED` whose `arrivesAt` is before today; “not shipped” = no shipment in those states or `RECEIVED`. Fully received POs (received ≥ ordered) are skipped.

**Things that cost me time**

1. **Three scopes, not one:** `read_inventory_purchase_orders`, `read_inventory_transfers` and `read_inventory_shipments`. Without the last one the query fails at `shipments` with “Access denied for shipments field.”
2. **“Oauth error failed\_grant\_with\_invalid\_scopes”** when installing the app, although all scope names are valid. The cause was **“Use legacy install flow”** being on in the app version (Dev Dashboard). It rejected these newer scopes; with it off, the install went through.
3. `InventoryPurchaseOrderLineItem` has no `productVariant` field. Use `inventoryItem { variant { id } }` if you need to match PO lines to orders.
4. The admin link to a PO is `https://admin.shopify.com/store/<shop>/purchase_orders/<numeric id from the gid>`.

A replay of real runs on a test store (report email, tracking, partly received): [Incoming Stock Tracker Pro live demo · TrenoxaHQ](https://trenoxahq.com/demo/incoming-stock-tracker/)

Happy to share the workflow JSON or answer questions on the purchase order / transfer / shipment model.

---

<div class="post-metadata">

**Author:** ![JayglePromo](https://avatars.discourse-cdn.com/v4/letter/j/7bcc69/32.png) [@JayglePromo](https://community.n8n.io/u/JayglePromo)\
**Post date:** [November 6, 2026, 2:11am UTC](https://community.n8n.io/t/reading-shopify-purchase-orders-in-n8n-new-2026-10-api-a-morning-list-of-late-supplier-stock/326520/2 "2026-11-06T02:11:29Z")

</div>

Useful write-up — the three-scope trap is the one I see people hit most often with the 2026-10 PO API.

Two extra gotchas that bit me on a multi-location store:

1. `arrivesAt` timezone. Shopify returns `tracking.arrivesAt` in UTC (sometimes date-only at midnight). If you compare that to `new Date()` in the shop’s local day without normalizing, you get false “overdue” on the morning of arrival, or you miss it entirely. I normalize both sides to the shop timezone’s date string before comparing.

2. Multi-destination noise. `query: "status:ORDERED"` pulls every location. For a morning ops email I also filter on `destination` (name or location id) in the Code node, otherwise one late PO for a secondary warehouse drowns the list that actually needs action today.

On the cost-bucket Wait: if the loop ever spins without the page cursor moving, log `extensions.cost.throttleStatus` from the GraphQL response. A short Wait only helps when `currentlyAvailable` is low; if you’re already under the restore rate, paging 50 with a fixed ~1s Wait is enough and avoids looking hung.

Happy to dig into line-item → variant matching via `inventoryItem { variant { id } }` if you hit that next.

---

<div class="post-metadata">

**Author:** ![TrenoxaHQ](https://dub1.discourse-cdn.com/n8n/user_avatar/community.n8n.io/trenoxahq/32/194852_2.png) [@TrenoxaHQ](https://community.n8n.io/u/TrenoxaHQ)\
**Post date:** [November 6, 2026, 6:40pm UTC](https://community.n8n.io/t/reading-shopify-purchase-orders-in-n8n-new-2026-10-api-a-morning-list-of-late-supplier-stock/326520/3 "2026-11-06T18:40:41Z")

</div>

Thanks, these are useful additions.

**Timezone:** you caught a real bug in my version. I was taking the date with `slice(0, 10)` on `arrivesAt`, which gives the UTC date, while “today” came from `$now` in the workflow’s timezone. On a store well east or west of UTC, an arrival close to midnight could land on the wrong day. Fixed now: timestamps are converted to the workflow timezone before comparing, and date-only values stay as they are:

```js
const day = v => !v ? '' : String(v).length <= 10 ? String(v)
  : DateTime.fromISO(String(v), { setZone: true }).setZone($now.zone).toISODate();

```

**Multiple destinations:** agreed. The email already shows a Destination column, but on a store with several warehouses that doesn’t stop the list getting long. A `destinations` setting that filters in the Code node is an easy addition.

**Throttling:** good point about logging `throttleStatus`. I’ll log it when the cursor doesn’t move instead of always waiting.

Matching line items to variants is probably next, so I may take you up on that.
