Inventory planning walkthrough
This page walks through the planning collections of the REST API with real requests: a commitment that turns into purchase orders, a product launch at two locations, a sales plan, a promotional event and the planning options. Responses are shortened to the fields that matter; the interactive explorer has every field.
Every write below needs a write-scope key; see Authentication.
On this page
Section titled “On this page”- Before you start
- Commitment: create, confirm, order
- New product plan: launch at several locations
- Sales plan: seed, page, grow
- Promotional event from the catalogue
- Planning options
- Units and formats
- What isn’t exposed over REST
- See also
Before you start
Section titled “Before you start”The examples use two shell variables:
API=https://prod.logistified.app/api/v1AUTH="Authorization: Bearer $LSTK_TOKEN"About ids:
- Variants and locations use Shopify’s numeric ids as strings, such as
"44912345678901"and"68123456789". Planning responses always return them in this form. - Plans have ids like
ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f11and their itemsipi-…. Purchase orders, transfer orders, suppliers, events and options use opaque ids. - The REST API has no endpoints to list suppliers, locations or variants yet. Take those ids from the app or from AI / Agent access.
Send an Idempotency-Key on every create, every status change and every order action. It makes a retry after a timeout
or a lost response safe; see Conventions.
Commitment: create, confirm, order
Section titled “Commitment: create, confirm, order”An inventory commitment records demand you expect, such as a wholesale order you’ve quoted.
1. Create the commitment
Section titled “1. Create the commitment”The header carries the probability (0 to 1) and the targetDate; lineItems add the products,
each with its plannedUnits and the locationId whose stock the demand draws on.
curl -s -X POST "$API/inventory-commitments" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Hotel Aurora spring order", "probability": 0.8, "targetDate": "2026-12-04", "lineItems": [ { "variantId": "44912345678901", "plannedUnits": 120, "locationId": "68123456789" }, { "variantId": "44912345678902", "plannedUnits": 80, "locationId": "68123456789" } ] }'201 Created, with Location: /api/v1/inventory-commitments/ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f11:
{ "data": { "inventoryPlanId": "ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f11", "planNumber": "IC-00012" }, "meta": { "requestId": "5d0c…", "apiVersion": "1.5" }}The plan starts as Draft and the number is generated for you. A variant listed twice comes back
in skipped (variantId, reason); an unknown locationId refuses the whole create with
UNKNOWN_LOCATIONS before anything is saved. Use startDate + endDate instead of targetDate
for demand spread over a period; a commitment takes one or the other.
IC=ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f112. Confirm it
Section titled “2. Confirm it”curl -s -X POST "$API/inventory-commitments/$IC/status" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "status": "Confirmed" }'{ "data": { "inventoryPlan": { "inventoryPlanId": "ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f11", "planNumber": "IC-00012", "status": "Confirmed", "itemCount": 2, "totalPlannedUnits": 200, "totalExpectedUnits": 160, "probability": 0.8, "targetDate": "2026-12-04", "includedInForecast": true }, "transition": { "from": "Draft", "to": "Confirmed" } }, "meta": { "requestId": "a71e…", "apiVersion": "1.5" }}Confirmed and Locked commitments feed the forecast. Confirming needs a header date
(PS_HEADER_DATE_REQUIRED) and at least one item (EMPTY_PLAN). Sending the same status again
returns 400 with data.code: "INVALID_STATUS_TRANSITION", so a retried status change that had
already gone through is easy to recognize. To keep a confirmed commitment out of the forecast
for a while, POST …/forecast-feed with { "includedInForecast": false }.
3. Add an item and give it its own date
Section titled “3. Add an item and give it its own date”Items can be added and edited while the plan is Draft or Confirmed.
curl -s -X POST "$API/inventory-commitments/$IC/items" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "items": [{ "variantId": "44912345678903", "plannedUnits": 40, "locationId": "68123456789" }] }'201 Created:
{ "data": { "addedCount": 1 }, "meta": { "requestId": "c3f9…", "apiVersion": "1.5" } }A variant already in the plan is not added twice; it is reported in skipped. To edit the new row,
look up its inventoryPlanItemId:
curl -s "$API/inventory-commitments/$IC/items?variantId=44912345678903" -H "$AUTH"{ "data": { "inventoryPlanId": "ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f11", "items": [ { "inventoryPlanItemId": "ipi-0b7e2c55-61a4-4f0e-8d3a-9e1f7c2b4a60", "variantId": "44912345678903", "locationId": "68123456789", "plannedUnits": 40, "expectedUnits": 32 } ], "totalCount": 1, "cursor": null }, "meta": { "requestId": "e802…", "apiVersion": "1.5" }}Then give that item a later date and a lower probability than the header’s:
curl -s -X PATCH "$API/inventory-commitments/$IC/items/ipi-0b7e2c55-61a4-4f0e-8d3a-9e1f7c2b4a60" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "targetDate": "2026-12-11", "probability": 0.5 }'{ "data": { "currencyCode": "EUR", "item": { "inventoryPlanItemId": "ipi-0b7e2c55-61a4-4f0e-8d3a-9e1f7c2b4a60", "plannedUnits": 40, "probability": 0.5, "expectedUnits": 20, "targetDate": "2026-12-11" } }, "meta": { "requestId": "19bd…", "apiVersion": "1.5" }}Sending null for targetDate or probability later makes the item follow the header again. An item
takes one date shape, like the header: a targetDate, or a startDate + endDate range. Sending a new
targetDate clears the item’s range, sending a new range clears its targetDate, and a request with both is
refused with INVALID_INPUT.
4. Create the purchase orders
Section titled “4. Create the purchase orders”The order action creates one draft purchase order per supplier, using each variant’s primary
supplier. quantityBasis: "expected" orders each item’s plannedUnits × its own probability (the
commitment’s when the item has none), rounded per item; "planned" orders plannedUnits. All orders are received at locationId. Keep the key: you’ll need it to replay.
KEY=$(uuidgen)curl -s -X POST "$API/inventory-commitments/$IC/purchase-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $KEY" \ -d '{ "locationId": "68123456789", "quantityBasis": "expected" }'This time one of two suppliers failed. The answer is still 200:
{ "data": { "purchaseOrders": [ { "purchaseOrderId": "0d9b6f2a-4c1e-4b7a-9e58-3a2f1c7d8e90", "purchaseOrderNumber": "PO-1042", "supplierId": "7d2b5c1e-90a4-4f6b-8c3d-2e1f0a9b8c7d" } ], "failed": [ { "supplierId": "b41e8a7c-1f3d-4e29-a6b0-5c8d9e2f1a34", "code": "ORDER_CREATE_FAILED", "message": "…" } ], "linkFailures": [], "unassignedVariantIds": [] }, "meta": { "requestId": "7f40…", "apiVersion": "1.5" }}purchaseOrders[]lists the orders that exist now, already linked to the plan. Keep their ids.failed[]lists the suppliers that got no order, with acodeandmessage.linkFailures[]lists orders that exist but couldn’t be linked to the plan.unassignedVariantIdslists planned variants without a supplier; they were left out.
Order actions work in every status of a commitment, and they only create drafts: nothing is sent to a supplier.
5. Recover the answer, or retry only what failed
Section titled “5. Recover the answer, or retry only what failed”If the very first order fails, the call answers with an error and nothing was created: fix the cause
and send it again. Once at least one order exists, the call answers 200 and lists what failed.
Replay the identical request with the same key to get the stored answer again, for example
after a timeout. Nothing new is created, and the response carries Idempotent-Replayed: true. If
the first attempt is still running, the replay answers 400 with Retry-After: 1: wait and replay
again.
curl -s -i -X POST "$API/inventory-commitments/$IC/purchase-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $KEY" \ -d '{ "locationId": "68123456789", "quantityBasis": "expected" }'Retry the failed supplier with a new key and supplierIds naming only that supplier:
curl -s -X POST "$API/inventory-commitments/$IC/purchase-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "locationId": "68123456789", "quantityBasis": "expected", "supplierIds": ["b41e8a7c-1f3d-4e29-a6b0-5c8d9e2f1a34"] }'{ "data": { "purchaseOrders": [ { "purchaseOrderId": "5a7c1e93-08d2-4b6f-a1e4-c9b3d27f6e15", "purchaseOrderNumber": "PO-1043", "supplierId": "b41e8a7c-1f3d-4e29-a6b0-5c8d9e2f1a34" } ], "failed": [], "linkFailures": [], "unassignedVariantIds": [] }, "meta": { "requestId": "2be6…", "apiVersion": "1.5" }}Sending the first request again with a new key and no supplierIds would create a second order for
the supplier that already has one.
A commitment can also create one linked draft transfer order:
curl -s -X POST "$API/inventory-commitments/$IC/transfer-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "fromLocationId": "68123456790", "toLocationId": "68123456789", "quantityBasis": "planned" }'It returns transferOrderId, transferOrderNumber and, if linking failed, linkFailure. While
that transfer order exists, a second call is refused with TRANSFER_ORDER_ALREADY_LINKED.
New product plan: launch at several locations
Section titled “New product plan: launch at several locations”A new product plan estimates launch demand from the sales of similar products you already sell.
1. Create the plan
Section titled “1. Create the plan”locationIds makes it a plan for those locations: every item gets one row per location, each built
from that location’s own history. Each item names its similarProducts (up to 10, with an
optional weight). volume scales their sales: 1.2 plans 20% more than they sold. The header
volume applies to every item without its own.
curl -s -X POST "$API/new-product-plans" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Trail runner launch", "launchDate": "2026-11-16", "horizonDays": 90, "locationIds": ["68123456789", "68123456790"], "volume": 1.2, "items": [ { "variantId": "44987650000011", "similarProducts": [ { "variantId": "44912345678901", "weight": 2 }, { "variantId": "44912345678902", "weight": 1 } ] }, { "variantId": "44987650000012", "similarProducts": [{ "variantId": "44912345678901" }], "volume": 0.8 } ] }'201 Created, with Location: /api/v1/new-product-plans/ip-8a41d0c2-5e7b-4b93-a1f6-0c3e9d27b584:
{ "data": { "inventoryPlanId": "ip-8a41d0c2-5e7b-4b93-a1f6-0c3e9d27b584", "planNumber": "NP-00004" }, "meta": { "requestId": "0e1d…", "apiVersion": "1.5" }}Two items at two locations make four rows, each with a daily curve of up to horizonDays (at most
180) values. warnings and skipped appear when there is something to report; an item locationId that the
locationIds fan-out ignored is reported in warnings. Use "locationIds": "all" to plan for
every active location; POST …/locations/sync adds locations opened later.
NPP=ip-8a41d0c2-5e7b-4b93-a1f6-0c3e9d27b5842. Let it feed the forecast, then confirm
Section titled “2. Let it feed the forecast, then confirm”Launch demand reaches the forecast only when both switches are on and the plan is Confirmed or
Locked. feedsForecast is off on a new plan unless the create sent "feedsForecast": true; turn it on
in the header:
curl -s -X PATCH "$API/new-product-plans/$NPP" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "feedsForecast": true }'{ "data": { "inventoryPlan": { "inventoryPlanId": "ip-8a41d0c2-5e7b-4b93-a1f6-0c3e9d27b584", "status": "Draft", "launchDate": "2026-11-16", "horizonDays": 90, "volume": 1.2, "feedsForecast": true, "includedInForecast": true, "allLocations": false } }, "meta": { "requestId": "61ac…", "apiVersion": "1.5" }}includedInForecast is on by default. It is the pause switch, set through /forecast-feed, and
unlike the header it can be changed while the plan is Locked:
curl -s -X POST "$API/new-product-plans/$NPP/forecast-feed" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "includedInForecast": true }'{ "data": { "inventoryPlanId": "ip-8a41d0c2-5e7b-4b93-a1f6-0c3e9d27b584", "includedInForecast": true }, "meta": { "requestId": "93d2…", "apiVersion": "1.5" }}Then confirm, exactly as for a commitment:
curl -s -X POST "$API/new-product-plans/$NPP/status" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "status": "Confirmed" }'3. Preview the order quantity
Section titled “3. Preview the order quantity”The preview creates nothing. It returns one line per item and location:
curl -s "$API/new-product-plans/$NPP/order-quantity?coverageDays=60&safetyFactor=0.1&packSize=12" -H "$AUTH"{ "data": { "totalQuantity": 612, "lines": [ { "inventoryPlanItemId": "ipi-6c2f81d4-9a3e-4b07-8f15-d2e0a7b94c31", "variantId": "44987650000011", "locationId": "68123456789", "demandOverWindow": 131.4, "safetyBuffer": 13.14, "rawQuantity": 144.54, "suggestedQuantity": 156, "coverageDays": 60 } /* … one line per item and location */ ] }, "meta": { "requestId": "b5e0…", "apiVersion": "1.5" }}demandOverWindow sums the curve over coverageDays, safetyBuffer adds safetyFactor on top,
and suggestedQuantity rounds the result up to packSize and moq. With coverFrom=today (after
launch), the window counts from today: a line already selling covers from today’s curve day, reported
as coverageFromDay, a line that launches later covers only the window’s days after its launch, and
each line subtracts its own stockOnHand and stockIncoming.
4. Create the launch orders
Section titled “4. Create the launch orders”On a plan with locations, the order action creates one draft purchase order per location by
default. The body takes the same coverage settings as the preview, plus an optional supplierId
and poName. Optional onHand and incoming are the plan’s stock, subtracted once and shared
across the lines by need (with coverFrom=today they replace the lines’ own stock):
KEY=$(uuidgen)curl -s -X POST "$API/new-product-plans/$NPP/purchase-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $KEY" \ -d '{ "supplierId": "7d2b5c1e-90a4-4f6b-8c3d-2e1f0a9b8c7d", "coverageDays": 60, "safetyFactor": 0.1, "packSize": 12 }'{ "data": { "purchaseOrders": [ { "purchaseOrderId": "c81f5d2e-7b40-4e9a-b3c6-1d9e8f2a5b07", "purchaseOrderNumber": "PO-1044", "locationId": "68123456789", "lineCount": 2, "suggestedQuantity": 312 } ], "failed": [ { "locationId": "68123456790", "code": "ORDER_CREATE_FAILED", "message": "…" } ], "linkFailures": [] }, "meta": { "requestId": "d4a8…", "apiVersion": "1.5" }}The same rules as for commitments apply: replay with $KEY to get this answer again, or retry
only the failed location with a new key and locationIds:
curl -s -X POST "$API/new-product-plans/$NPP/purchase-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "supplierId": "7d2b5c1e-90a4-4f6b-8c3d-2e1f0a9b8c7d", "coverageDays": 60, "safetyFactor": 0.1, "packSize": 12, "locationIds": ["68123456790"] }'An entry with warnings leaves out lineCount and suggestedQuantity, because how many lines
were saved isn’t known; check that order instead of retrying it.
One combined order instead. To buy for all locations on one purchase order, send
"splitByLocation": false and the receiving locationId (instead of the per-location request
above, not after it):
curl -s -X POST "$API/new-product-plans/$NPP/purchase-orders" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "supplierId": "7d2b5c1e-90a4-4f6b-8c3d-2e1f0a9b8c7d", "coverageDays": 60, "safetyFactor": 0.1, "packSize": 12, "splitByLocation": false, "locationId": "68123456789" }'A combined order applies moq, packSize and rounding once to each variant’s total over all
locations, so it can order less than the per-location orders together. Launch orders can be
created while the plan is Draft, Confirmed or Locked.
Sales plan: seed, page, grow
Section titled “Sales plan: seed, page, grow”A sales plan holds unit and revenue targets per location. It never adds demand to the forecast.
1. Create and seed it
Section titled “1. Create and seed it”seed fills the plan in the same call: each variant at each location starts from that location’s
sales on the same days last year, times growthFactor.
curl -s -X POST "$API/sales-plans" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Spring 2027 targets", "locationIds": ["68123456789", "68123456790"], "startDate": "2027-03-01", "endDate": "2027-05-31", "seed": { "variantIds": ["44912345678901", "44912345678902", "44912345678903"], "growthFactor": 1.1 } }'201 Created, with Location: /api/v1/sales-plans/ip-c27e95b1-3d48-4f0a-b6e2-71a9f4d0c835:
{ "data": { "inventoryPlanId": "ip-c27e95b1-3d48-4f0a-b6e2-71a9f4d0c835", "planNumber": "SP-00003", "locationIds": ["68123456789", "68123456790"], "itemsCreated": 6, "warnings": [], "historyWarnings": [ { "locationId": "68123456790", "locationName": "Outlet Hamburg", "variantIds": ["44912345678903"] } ] }, "meta": { "requestId": "8c17…", "apiVersion": "1.5" }}historyWarnings names the variants that had no sales at a location last year. Their rows start at
zero; a location never borrows another location’s history. If any variant id is unknown, the whole
create is refused with UNKNOWN_VARIANTS.
SP=ip-c27e95b1-3d48-4f0a-b6e2-71a9f4d0c8352. Read the items page by page
Section titled “2. Read the items page by page”Item pages are separate from the plan. Ask for a page size with limit and pass back the cursor
until it is null. The cursor is opaque and can contain +, / and =, so URL-encode it:
curl -s -G "$API/sales-plans/$SP/items" -H "$AUTH" --data-urlencode "limit=4"{ "data": { "inventoryPlanId": "ip-c27e95b1-3d48-4f0a-b6e2-71a9f4d0c835", "currencyCode": "EUR", "items": [ { "inventoryPlanItemId": "ipi-1f8e3a6c-27b9-4d05-9c4e-b80a5f1d2e73", "variantId": "44912345678901", "locationId": "68123456789", "locationName": "Flagship Berlin", "plannedUnits": 462, "price": 49.9, "plannedRevenue": 23053.8, "growthFactor": 1.1 } /* … three more */ ], "totalCount": 6, "cursor": "WzEsImlwLWMyN2U5NWIxLTNkNDgtNGYwYS1iNmUyLTcxYTlmNGQwYzgzNSIs…" }, "meta": { "requestId": "f1e9…", "apiVersion": "1.5" }}CURSOR=$(curl -s -G "$API/sales-plans/$SP/items" -H "$AUTH" --data-urlencode "limit=4" | jq -r '.data.cursor')curl -s -G "$API/sales-plans/$SP/items" -H "$AUTH" \ --data-urlencode "limit=4" --data-urlencode "cursor=$CURSOR"The second page holds the last two items and "cursor": null. A page holds up to 250 items, or 50
with includeCurves=true, which adds each row’s daily curve and revenueCurve. A cursor only
works with the plan and the locationId / variantId filters it came from. An empty cursor is
ignored and returns the first page, so stop when cursor is null.
3. Set the growth for one location
Section titled “3. Set the growth for one location”curl -s -X POST "$API/sales-plans/$SP/growth" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "growthFactor": 1.25, "locationId": "68123456789" }'{ "data": { "updated": 3, "reseeded": 0, "warnings": [], "historyWarnings": [] }, "meta": { "requestId": "4d6b…", "apiVersion": "1.5" }}Each matching row’s curve is rescaled from 1.1 to 1.25, so hand edits keep their proportions. A row
at factor 0, or whose curve adds up to 0, is re-seeded from history instead and counted in
reseeded. Leave out locationId for every location, or add variantIds for some products only.
4. Confirm it
Section titled “4. Confirm it”curl -s -X POST "$API/sales-plans/$SP/status" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "status": "Confirmed" }'Once the window has started, GET /sales-plans/{id}/plan-vs-actual compares the targets with
actual units and revenue per day (locationId narrows it to one location; collapse=variant combines each variant’s locations).
Promotional event from the catalogue
Section titled “Promotional event from the catalogue”A promotional event lifts the forecast on its dates once it is switched on.
1. Find the holiday
Section titled “1. Find the holiday”curl -s -G "$API/promotional-event-catalogue" -H "$AUTH" --data-urlencode "query=black friday"{ "data": { "hits": [ { "group": "nth_weekday", "matchedBy": "name", "entry": { "catalogueHolidayId": "black-friday", "name": "Black Friday", "category": "sales_event", "durationDays": 1, "aliases": ["bf", "black week"], "regions": ["US", "CA", "GB", "IE", "DE" /* … */], "dateRule": { "anchor": { "kind": "holiday", "holidayId": "thanksgiving-us" }, "shifts": [{ "kind": "days", "days": 1 }] } } } /* … more hits */ ] }, "meta": { "requestId": "a0c4…", "apiVersion": "1.5" }}2. Create the event
Section titled “2. Create the event”Send the catalogueHolidayId; the catalogue supplies the category and the date rule. Here the event
runs four days, through Cyber Monday:
curl -s -X POST "$API/promotional-events" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "catalogueHolidayId": "black-friday", "durationDays": 4 }'201 Created, with Location: /api/v1/promotional-events/2e9d4b7a-6f13-4c85-a0b2-9d7e1c3f5a68:
{ "data": { "today": "2026-10-27", "event": { "eventId": "2e9d4b7a-6f13-4c85-a0b2-9d7e1c3f5a68", "name": "Black Friday", "category": "sales_event", "catalogueHolidayId": "black-friday", "durationDays": 4, "feedsForecast": false, "archived": false }, "occurrences": [], "nextOccurrence": { "source": "projected", "occurrenceId": null, "startDate": "2026-11-27", "endDate": "2026-11-30", "uplift": null }, "window": { "fromKey": "2023-10-27", "toKey": "2028-10-27" } }, "meta": { "requestId": "37b1…", "apiVersion": "1.5" }}Every new event has feedsForecast: false, so creating it changes no forecast yet.
projectedOccurrences[] (left out above) lists the dates the rule gives for each year in window.
A catalogue create refuses category, dateRule and occurrences, and an eventId in the body is
refused too.
EV=2e9d4b7a-6f13-4c85-a0b2-9d7e1c3f5a683. Set the uplift and switch it on
Section titled “3. Set the uplift and switch it on”Uplifts are fractions: 0.4 means +40%. defaultUplift applies to every year of the event:
curl -s -X PATCH "$API/promotional-events/$EV" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "defaultUplift": 0.4, "upliftSource": "manual" }'Then switch the event on:
curl -s -X PATCH "$API/promotional-events/$EV" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "feedsForecast": true }'{ "data": { "event": { "eventId": "2e9d4b7a-6f13-4c85-a0b2-9d7e1c3f5a68", "defaultUplift": 0.4, "upliftSource": "manual", "feedsForecast": true, "archived": false }, "nextOccurrence": { "source": "projected", "startDate": "2026-11-27", "endDate": "2026-11-30", "uplift": null, "effectiveUplift": 0.4 } }, "meta": { "requestId": "c9e2…", "apiVersion": "1.5" }}The event now lifts every forecast view that hasn’t opted out of event effects. For a different
uplift in one year, store that year’s dates with
POST /promotional-events/{id}/occurrences and an uplift; null falls back to defaultUplift.
Once a year has passed, POST …/analyze-past-years measures the real uplift from your sales.
Planning options
Section titled “Planning options”Demand types, reasons and sources are lists you maintain; plans store the chosen name.
curl -s "$API/inventory-plan-options?type=demandType" -H "$AUTH"{ "data": { "options": [ { "optionId": "a3d81f60-4b2c-4e97-8a15-0f6c2d9e7b14", "type": "demandType", "name": "Wholesale", "sortOrder": 1, "active": true } ] }, "meta": { "requestId": "6e3f…", "apiVersion": "1.5" }}Create one:
curl -s -X POST "$API/inventory-plan-options" \ -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "type": "demandType", "name": "Trade show" }'201 Created (no Location):
{ "data": { "option": { "optionId": "5b0f9c7e-2d4a-4e11-9c3b-8f6a1d2e7c40", "type": "demandType", "name": "Trade show", "sortOrder": 2, "active": true } }, "meta": { "requestId": "0a9d…", "apiVersion": "1.5" }}The same name again in the same type, sent with a new key or none, is refused, and the error names the existing option:
{ "error": { "code": "INVALID_INPUT", "message": "An option named \"Trade show\" already exists for this type", "retryable": false, "data": { "code": "DUPLICATE_NAME", "optionId": "5b0f9c7e-2d4a-4e11-9c3b-8f6a1d2e7c40", "error": "An option named \"Trade show\" already exists for this type" }, "requestId": "77c2…" }}Use data.optionId instead of creating another option. A replay with the original key returns the
original 201 instead. Names are compared without regard to case.
To tag a plan, send the name in demandType, reasonCode or source:
curl -s -X PATCH "$API/inventory-commitments/$IC" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{ "demandType": "Trade show" }'PATCH /inventory-plan-options/{optionId} renames an option or deactivates it with
"active": false (not isActive). Plans keep the name they were saved with.
Units and formats
Section titled “Units and formats”| What | Format |
|---|---|
Planning dates (targetDate, launchDate, startDate, endDate, curve days, event dates, asOf, today) | YYYY-MM-DD, a UTC day |
Timestamps (createdAt, confirmedAt, …) | ISO 8601 instants in UTC |
Item plannedUnits | Units. A sales-plan item’s is its curve’s total to 2 decimals, as the plan page shows it; a new-product item’s is whole units |
price, plannedRevenue, revenueCurve | Shop currency, named by currencyCode on the same response. A sales-plan item’s plannedRevenue is null when it has neither a price nor a revenue override |
probability | Fraction from 0 to 1 |
sharePercent, prelaunchSharePercent | 0 to 100 |
attainmentPercent, revenueAttainmentPercent | Actual ÷ planned × 100, one decimal: above 100 when sales beat the plan, null when nothing is planned yet |
defaultUplift, uplift, effectiveUplift, analyzedUplift | Fraction from 0 to 10: 0.35 = +35%. In an analysis result, the raw measured uplift can be negative |
growthFactor | Multiplier on last year’s sales, 0 or 0.01 to 10: 1 = the same, 1.2 = +20% |
volume | Multiplier on the similar products’ sales, 0 to 10 (higher values are capped at 10): 1 = the same |
New-product curve | 1 to 180 daily units from the row’s launch date, rounded to 2 decimals |
Sales-plan curve, revenueCurve | One value per day of the plan window |
coverageFromDay | Zero-based day index into the curve where coverage starts, not a date |
Planning days are not shifted to the shop’s time zone: “today” in planning results, and the day a launch counts as started, is the current day in UTC.
What isn’t exposed over REST
Section titled “What isn’t exposed over REST”- Previews and calculators that save nothing: generating a curve from similar products,
previewing a size mix, reading a product’s reference history, previewing sales-plan seeding.
Available are the order-quantity preview and
apply-mixwith"dryRun": true. - Plan documents, PDFs and exports, and the plan activity log.
- Restoring products at a plan location.
- A Shopify draft (sales) order from a commitment.
- Single-item delete and curve routes for new product plans: use
DELETE …/items?itemIds=…and the itemPATCHwithcurve. - Plan-vs-actual for commitments, and forecast-feed or order routes for sales plans.
- Relinking an order whose link to the plan failed.
- Choosing plan numbers or ids: they are always generated.
See also
Section titled “See also”