API

Order lifecycle

How a real order moves from created to completed, when you can still cancel it, and how to rehearse all of it with a test order.

How a real order moves

Every order you create starts in one of two states.

  • NEW is the normal start. The order sits in an acceptance window of about two hours from creation. During that window you can still cancel it with POST /v1/orders/{orderId}/cancel: the merchant is refunded and you receive order.canceled. When the window closes, the order is accepted automatically and becomes ACCEPTED. It cannot be canceled through the API after that.
  • PROCESSING is a rare start for an order that requires additional review. It moves to ACCEPTED once the review is done, and the API cannot cancel it.

From ACCEPTED a warehouse takes over: shipments move the order to IN_PROGRESS and then COMPLETED, and every step arrives as a webhook. A hold (onHold) can land at any point, and it doesn't change the status. Read onHoldReasons to see what has to change before the order can proceed.

If you might need to cancel, do it early. The window is measured from when the order was created, and a cancel that arrives late gets order-not-cancelable.

Rehearse it with a test order

A real order moves when a warehouse packs it, a carrier scans it or a payment clears — none of which you can trigger from an API call. So a test order moves because you say so: POST /v1/test/orders/{orderId}/transitions puts it in the next state and publishes the event a real transition would have published.

Only test_ orders can be transitioned. A real order id answers 404, because its lifecycle is not yours to drive.

A test order starts where a real one would: if a real order would require additional review, the test order starts in PROCESSING, and cancelling it gets the same 409 it would in production.

The states

NEW ──process──▶ PROCESSING ──accept──▶ ACCEPTED ──ship──▶ IN_PROGRESS ──complete──▶ COMPLETED
                                                                │
                                                     returnShipment ──▶ RETURNED
 │
 └─ hold ⇄ releaseHold                                cancel ──▶ CANCELED
TransitionWhat it doesEvent
processStatus to PROCESSING, as if the order had needed review. A real order only ever starts in PROCESSING, so rehearse your handling of it here—
acceptStatus to ACCEPTED—
holdBlocks the order. reasons defaults to ["out_of_stock"]order.onHold
releaseHoldClears the blockorder.onHoldReleased
shipCreates a shipment over the lines in lineIds, or everything unshippedorder.shipmentCreated
returnShipmentBrings a parcel back. shipmentId defaults to the most recent one still outorder.shipmentReturned
completeStatus to COMPLETED. Refused while a line has no shipmentorder.completed
cancelStatus to CANCELED from any unsettled state, as Supliful canceling would. To rehearse your own cancel, call POST /v1/orders/{orderId}/cancel instead: it applies the real NEW-only ruleorder.canceled

order.created arrives when you create the order, before any of this.

You do not have to walk the chain. Jump straight to the transition you want to exercise — only the rules below are enforced.

The rules are the ones the real system enforces, and the sandbox says no for the same reasons: a held order will not ship, an order with an unshipped line will not complete, and a settled order takes nothing further except a parcel coming back — which is exactly when returns happen.

Try it

Create a throwaway order against this connection and push it through. Every event is delivered to your endpoints for real: signed, retried and recorded in your delivery log, with testMode: true in the payload so nothing downstream acts on it.

Sent straight to the API from your browser and never stored by this page.

By hand

# 1. Create it. The id comes back prefixed `test_`.
curl -X POST $API/v1/orders -H "Authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d '{"testMode":true,"externalOrderId":"demo-1",
       "shippingAddress":{"firstName":"Jane","lastName":"Doe","address1":"1 Main St",
                          "city":"Austin","stateCode":"TX","zip":"78701","countryCode":"US"},
       "lines":[{"productId":"<from GET /v1/products>","quantity":1}]}'

# 2. Move it. Repeat with the transitions you want to exercise.
curl -X POST $API/v1/test/orders/test_…/transitions -H "Authorization: Bearer $KEY" \
  -H 'content-type: application/json' -d '{"transition":"ship"}'

# 3. Read it back. This is what your consumer should reconcile against.
curl $API/v1/orders/test_… -H "Authorization: Bearer $KEY"

What it will not teach you

  • Timing. Real orders sit in a state for hours or days, and a NEW order is accepted by itself after about two hours. A test order stays NEW until you move it.
  • Money. A test order is never charged and never fulfilled, so nothing about pricing, invoicing or stock is exercised.
  • Expiry. The order is erased two hours after it was created, and a transition does not extend that. A 404 after two hours is the design, not a bug.

On this page