Skip to content

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.

The examples use two shell variables:

Terminal window
API=https://prod.logistified.app/api/v1
AUTH="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-2d5e6b8c0f11 and their items ipi-…. 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.

An inventory commitment records demand you expect, such as a wholesale order you’ve quoted.

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.

Terminal window
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.

Terminal window
IC=ip-3f6c1a2e-8b4d-4c1e-9a7f-2d5e6b8c0f11
Terminal window
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 }.

Items can be added and edited while the plan is Draft or Confirmed.

Terminal window
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:

Terminal window
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:

Terminal window
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.

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.

Terminal window
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 a code and message.
  • linkFailures[] lists orders that exist but couldn’t be linked to the plan.
  • unassignedVariantIds lists 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.

Terminal window
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:

Terminal window
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:

Terminal window
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.

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.

Terminal window
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.

Terminal window
NPP=ip-8a41d0c2-5e7b-4b93-a1f6-0c3e9d27b584

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:

Terminal window
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:

Terminal window
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:

Terminal window
curl -s -X POST "$API/new-product-plans/$NPP/status" \
-H "$AUTH" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "status": "Confirmed" }'

The preview creates nothing. It returns one line per item and location:

Terminal window
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.

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):

Terminal window
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:

Terminal window
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):

Terminal window
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.

A sales plan holds unit and revenue targets per location. It never adds demand to the forecast.

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.

Terminal window
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.

Terminal window
SP=ip-c27e95b1-3d48-4f0a-b6e2-71a9f4d0c835

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:

Terminal window
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" }
}
Terminal window
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.

Terminal window
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.

Terminal window
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).

A promotional event lifts the forecast on its dates once it is switched on.

Terminal window
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" }
}

Send the catalogueHolidayId; the catalogue supplies the category and the date rule. Here the event runs four days, through Cyber Monday:

Terminal window
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.

Terminal window
EV=2e9d4b7a-6f13-4c85-a0b2-9d7e1c3f5a68

Uplifts are fractions: 0.4 means +40%. defaultUplift applies to every year of the event:

Terminal window
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:

Terminal window
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.

Demand types, reasons and sources are lists you maintain; plans store the chosen name.

Terminal window
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:

Terminal window
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:

Terminal window
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.

WhatFormat
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 plannedUnitsUnits. 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, revenueCurveShop 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
probabilityFraction from 0 to 1
sharePercent, prelaunchSharePercent0 to 100
attainmentPercent, revenueAttainmentPercentActual ÷ planned × 100, one decimal: above 100 when sales beat the plan, null when nothing is planned yet
defaultUplift, uplift, effectiveUplift, analyzedUpliftFraction from 0 to 10: 0.35 = +35%. In an analysis result, the raw measured uplift can be negative
growthFactorMultiplier on last year’s sales, 0 or 0.01 to 10: 1 = the same, 1.2 = +20%
volumeMultiplier on the similar products’ sales, 0 to 10 (higher values are capped at 10): 1 = the same
New-product curve1 to 180 daily units from the row’s launch date, rounded to 2 decimals
Sales-plan curve, revenueCurveOne value per day of the plan window
coverageFromDayZero-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.

  • 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-mix with "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 item PATCH with curve.
  • 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.