Fulfillment states
Fulfillment lifecycles are shared across all 5 fulfillment types (shipment, digital, access,
service, in_person). The terminal definition varies by type.
State machine
stateDiagram-v2 [*] --> pending: POST /fulfillments pending --> in_progress: shipment label created / digital prepared / access pending in_progress --> fulfilled: shipped+delivered / file delivered / access granted / service complete / in-person handoff pending --> cancelled: merchant cancels before in_progress in_progress --> cancelled: merchant cancels mid-fulfillment cancelled --> [*] fulfilled --> [*]
States
-
pending— fulfillment row created, no provider activity yet. -
in_progress— provider has acknowledged the work (label printed, digital file prepared, access pending). Firesfulfillment.shipment.shippedfor shipment type — on the first tracking number, whether it arrives withPOST /orders/{orderId}/fulfillments(shipment.trackingNumber) or a laterPATCH .../fulfillments/{id}/shipment. -
fulfilled— terminal. Per type:-
shipment— carrier marks delivered (fulfillment.delivered). -
digital— file generated and a presigned URL was returned at least once. -
access— access grant row inserted and active. -
service/in_person— merchant marks complete via API.
-
-
cancelled— terminal. Reachable frompendingorin_progress. For shipment, cancelling after label creation requires merchant void via the carrier.
Type-specific details
Each fulfillment carries exactly one detail record, decided by its type, and each detail endpoint only
accepts a fulfillment of its own type:
-
shipment→PATCH /api/v1/orders/{orderId}/fulfillments/{id}/shipment -
digital→…/digital -
access_grant→…/access -
service→…/service
Pointing one at a fulfillment of another type returns 409 fulfillment_type_mismatch and writes
nothing. digital and access_grant line items are fulfilled
automatically when the payment captures, so their fulfillment already
exists — creating a second one returns 400 over_fulfillment. Patch the existing
one instead.
When the order is considered fulfilled
Per-item, not per-row
The parent order reaches
fulfilled when every deliverable line
item's quantity is covered by completed fulfillments — not when every
fulfillment row happens to be terminal. Line items with nothing to
deliver (a donation, a fee) are excluded from the count, so an order made
entirely of them is fulfilled as soon as
it is paid. Progress is readable on the order as fulfillmentSummary.