Order Lifecycle
Every status and sub-status an order can move through, who triggers each transition, and what your integration should do at each stage
Order Lifecycle
An order carries two orthogonal status fields: a primary status (coarse — what bucket is this in?) and a sub-status (fine-grained — what exactly is happening inside that bucket?). Your integration should branch primarily on primary status and use sub-status for observability and reconciliation.
Primary Status — at a glance
| Primary | Vouchers? | Who can trigger transitions out | Terminal? |
|---|---|---|---|
PLACED | No | Starship (auto) | No |
PENDING | No | Admin (usually), inventory pump, auto-retry cron (if enabled) | No |
DELIVERED | Yes | — | Yes (can still be refunded) |
PARTIALLY_DELIVERED | Partial | Admin, retry on remaining items | No (eventually → DELIVERED or CANCELLED) |
CANCELLED | No | — | Yes |
FAILED | No | — | Yes (rare — usually becomes CANCELLED on admin action) |
See the Orders Overview page for the full UPPERCASE enum tables.
The Three Common Paths
Most orders in production take one of three shapes. Your retry and reconciliation logic should be designed around these, not around the full state-machine graph.
Path 1 — Instant Fulfillment (most common)
Typical turnaround: 200–800 ms end-to-end. The HTTP response carries status: DELIVERED.
Path 2 — Pending → Admin-Driven Vendor Fulfillment
Turnaround: minutes to hours, depending on admin response time and vendor latency.
Path 3 — Partial Delivery
When a single order covers multiple units and only some fulfill, the order is marked PARTIALLY_DELIVERED with sub-status PARTIAL. The remaining items stay in their item-level pending state. Your integration must read GET /orders/:id and iterate order.items[] to see which units are delivered vs. pending.
A PARTIALLY_DELIVERED order eventually converges to DELIVERED (all items fulfilled) or CANCELLED (remaining items refunded). It does not stay partial indefinitely.
Sub-Status Decoder Ring
When you see an unexpected sub-status on a GET /orders/:id, use this table to figure out where in the pipeline the order is. Sub-statuses are additive over time — new ones may appear on existing primary statuses. Treat any unrecognized sub-status as informational, never as a reason to error out.
| Sub-status | Typical primary | What it means | Your action |
|---|---|---|---|
INITIAL | PLACED / PENDING | Order just created, nothing has run yet | Wait. |
PROCESSING | PENDING | Starship is mid-pipeline (validation, allocation) | Wait — brief. |
INVENTORY_FULFILLED | DELIVERED | Filled from cache, no vendor involved | Retrieve vouchers. |
VENDOR_ORDER_PENDING | PENDING | Awaiting admin to escalate or awaiting vendor response | Wait or poll. |
VENDOR_ORDER_CREATED | PENDING | Vendor accepted the order | Wait — seconds to minutes. |
VENDOR_VOUCHER_FETCHED | PENDING → DELIVERED | Vouchers returned by vendor, about to finalize | Almost done. |
VENDOR_ORDER_FAILED | PENDING / CANCELLED | Vendor rejected or errored | Await admin action. |
PENDING_REPROCESS | PENDING | Queued for retry after transient failure | Wait. |
REPROCESS_EXPIRED | PENDING → CANCELLED | Retry window closed without success | Admin must intervene; wallet will refund. |
PARTIAL | PARTIALLY_DELIVERED | Some items delivered, others pending | Iterate order.items[]. |
COMPLETED | DELIVERED | All items fulfilled and reconciled | Done. |
GV_LINK_PENDING | PENDING | Gift-voucher link creation queued | Wait. |
GV_LINK_CLAIMED | DELIVERED | End-user claimed the link | Nothing — informational. |
GV_LINK_UNCLAIMED | DELIVERED | Link generated but not yet claimed | Nothing — informational. |
BULK_LIMIT_EXCEEDED | CANCELLED | Your request exceeded the bulk cap | Split the order; resubmit. |
REFUNDED | CANCELLED | Wallet refunded | Reconcile wallet. |
NOT_REFUNDED | CANCELLED / FAILED | Wallet refund still owed | Flag for ops. |
RECONCILED | DELIVERED | Post-delivery reconciliation passed | Nothing — informational. |
Refunds & Reversals
DELIVERED is not strictly terminal. If a voucher is found invalid (vendor recall, fraud flag), an admin can mark the order CANCELLED after delivery, which:
- Moves the primary status:
DELIVERED→CANCELLED - Sets sub-status to
REFUNDED(orNOT_REFUNDEDif the refund fails) - Fires an
order.cancelledwebhook - Credits the wallet back
Your integration should handle a late order.cancelled webhook even for an order you already marked "done" in your system.
What You Should Build
Branch on primary status, observe sub-status
Customer-facing logic (show/hide voucher UI, send email) keys off primary status. Reconciliation and ops dashboards should surface sub-status so humans know where stuck orders are parked.
Treat unknown sub-statuses as informational
Sub-statuses evolve. Don't let your client error out on an unrecognized value. Log it and carry on using primary status as truth.
Expect late cancellations
Build idempotent "cancel this order and refund the customer" handlers that don't blow up if the order is already marked delivered in your system.
Poll PENDING orders on a schedule
An order that's been PENDING for more than 24 hours is stuck. Flag it for human review in your ops tool, not in the customer UI.
Next
- Delivery Semantics — poll vs. webhook decision
- Webhooks —
order.*event payloads - Get Order Details — how to read items and their per-item status
Fulfillment Model
How Starship fulfills orders — inventory-first, admin-gated vendor fallback — and what that means for your integration's polling strategy
Delivery Semantics
When to poll, when to wait on a webhook, and how to handle retries — the correctness rules for finding out "is this order done yet?"