SKU lifecycle
Every SKU carries a LifecycleState that controls whether the SKU is
operational — i.e. whether items, tags, and order lines can be created
against it. The state is a string on the SKU ("Draft", "Active",
"Clearance", "EOL", "Discontinued").
States and transitions
| From | Allowed transitions | Notes |
|---|---|---|
| (create) | → Active | Default when LifecycleState is omitted. Pass "Draft" explicitly for staged setup. |
Draft | → Active, Discontinued | Draft SKUs can be fully configured (including prices) but are not operational. |
Active | → Clearance, EOL, Discontinued | The normal trading state. |
Clearance | → Active (rollback), EOL, Discontinued | Fully operational; the one state with a rollback path. |
EOL | → Discontinued | Terminal markdown — no rollback to Active/Clearance. |
Discontinued | → Draft | Admin only, via POST api/skus/relaunch/{id}. |
A same-state "transition" is an allowed no-op (the SKU's EntityVersionNo is
not bumped for it).
How transitions are requested — and how rejections behave
- Single update (
updatesku): setLifecycleStateon the update DTO. A disallowed transition still returns HTTP 200 — the response carries aLifecycleTransitionRejectedcascading-update marker and the state is unchanged, but every other field change in the same request commits. Check the response, not just the status code (see Errors & response conventions). - Bulk upsert (
BulkUpsert): a disallowed transition fails that row (per-rowFailedstatus); the rest of the batch proceeds.
The operational lock
A SKU is operational when its state is not Draft and not
Discontinued. Non-operational SKUs block:
| Operation | Behaviour when the SKU is non-operational |
|---|---|
| Create item (single) | 400 validation failure |
| Bulk item create | Per-row failure |
| Auto-create on tag scan | Silent skip — the unknown tag is simply not turned into an item |
| Tag generation / reservation | 400 |
| New order lines | Order create → 400. Existing order lines referencing the SKU keep working. |
Per-state extras:
Draft— price edits are allowed (stage your pricing before activation); items/tags/order lines are blocked.Discontinued— price edits are blocked too (reverted with anInvalidOperationerror).Clearance/EOL— fully operational at the API level; the states are informational for downstream processes.- Deleting a SKU is not lifecycle-gated — only blocked while items still reference it.
A SKU whose stored state is null or unparseable (pre-lifecycle data) is treated
as Draft by the transition guard but as operational by the lock — two
deliberate defensive defaults so legacy SKUs keep working while their first
explicit transition is validated as if from Draft.
Price changes are audited
Price updates through updatesku stamp PriceEffectiveFrom and append a
SkuPriceHistory row automatically — see the price-history reference endpoints
for the audit trail.
SKU pricing (UnitCost = supplier cost, RetailPrice = RRP, SalePrice =
promotional price) is the retail pricing model. Asset-management tenants
track per-item cost on the item itself instead — see the Item lifecycle page.
Related
- Errors & response conventions — the 200-despite-failure family this lifecycle participates in.
GET api/skus/getdeletedskus/{ticks}— deleted-SKU sync for offline devices.