Skip to main content
POST
/
api
/
v1
/
orders
/
{order_id}
/
fulfillments.json
Create Fulfillment
curl --request POST \
  --url https://api.launchmystore.io/api/v1/orders/{order_id}/fulfillments.json \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "status": "<string>",
  "tracking_number": "<string>",
  "tracking_company": "<string>",
  "tracking_url": "<string>",
  "service_id": "<string>",
  "line_items": [
    {
      "id": "<string>",
      "quantity": 123
    }
  ],
  "notify_customer": true
}
'
{
  "status": 201,
  "state": "success",
  "data": {
    "fulfillment": {
      "fulfillmentId": "ful_new456",
      "orderId": "ord_xyz789",
      "status": "success",
      "trackingNumber": "1Z999AA10123456784",
      "trackingCompany": "UPS",
      "trackingUrl": "https://ups.com/track?num=1Z999AA10123456784",
      "serviceId": "fs_abc123",
      "createdAt": "2024-01-20T14:30:00Z"
    }
  }
}
Creates a fulfillment record on the order and, when status is a terminal success state, automatically bumps order.status to shipped (or delivered). Shipping apps don’t need a second call to update the order — one POST writes the tracking number, courier, and status in a single request.
This endpoint is the write-back half of the live-rate flow. For the full lifecycle (quote → checkout pick → orders/create webhook → push to carrier → call this endpoint with the AWB), see Live Rate Providers and Build a shipping app.

When the order status changes

Each Fulfillment row represents one shipment. After every write, the platform recomputes coverage by summing lineItems[].quantity across all success-state Fulfillment rows for the order and updates order.status accordingly:
Coverageorder.status becomes
0% — no success fulfillments yetunchanged
> 0% but < 100% — some packages outpartial
100% — every ordered quantity shippedshipped (or delivered if the fulfillment status is delivered)
The order lifecycle is:
pending → confirmed → paid → partial → shipped → delivered
                                              (canceled / abandoned as terminal off-ramps)
order.status is only mutated from pending, confirmed, paid, or partial. Manual overrides (shipped set by the merchant) and terminal states (canceled/delivered) are never trampled.

Multi-package orders

For orders that ship in multiple packages, POST one fulfillment per shipment and include only the items in that package in line_items. The platform tracks coverage per line_item.id (the orderProductId):
# Shipment 1 — 2 of the 5 items go out today
curl -X POST '.../orders/ord_xyz789/fulfillments.json' \
  -d '{
    "status": "success",
    "tracking_number": "AWB-PKG-A",
    "tracking_company": "DHL",
    "tracking_url": "https://dhl.com/track/AWB-PKG-A",
    "line_items": [
      { "id": "li_001", "quantity": 1 },
      { "id": "li_002", "quantity": 1 }
    ]
  }'
# → order.status flips from "paid" → "partial"

# Shipment 2 — remaining 3 items ship a day later
curl -X POST '.../orders/ord_xyz789/fulfillments.json' \
  -d '{
    "status": "success",
    "tracking_number": "AWB-PKG-B",
    "tracking_company": "FedEx",
    "tracking_url": "https://fedex.com/track/AWB-PKG-B",
    "line_items": [
      { "id": "li_003", "quantity": 1 },
      { "id": "li_004", "quantity": 1 },
      { "id": "li_005", "quantity": 1 }
    ]
  }'
# → order.status flips from "partial" → "shipped"
Omitting line_items is the back-compat shortcut for single-shipment orders: the fulfillment is treated as covering everything, so a single POST flips order.status straight to shipped (skipping partial).

Path Parameters

order_id
string
required
The order ID

Body Parameters

status
string
default:"pending"
Initial fulfillment status. Set to success to immediately mark the shipment as out the door — this is what bumps order.status to shipped. Allowed: pending, open, success, cancelled, error, failure.
tracking_number
string
Carrier tracking number (e.g. AWB code).
tracking_company
string
Carrier name (e.g. "UPS", "FedEx", "USPS", "DHL", "Shiprocket").
tracking_url
string
Public, shopper-facing tracking URL. Must be a valid URL.
service_id
string
Optional. The ID of the fulfillment service that produced this shipment, returned by Create Fulfillment Service. Caching this on a shop metafield after registration avoids a list-call on every push.
line_items
array
Optional. Items being fulfilled in this shipment. Omit for single-shipment orders where every line item ships together.
notify_customer
boolean
default:"true"
Send shipment notification email to the customer.

Side effects

When the call succeeds:
  1. A row is inserted into the Fulfillments table.
  2. If status triggers a status flip (see table above), order.status is updated in the same request.
  3. A fulfillments/create webhook is dispatched to every subscriber on the store (3-retry exponential backoff: 1m / 5m / 15m).

Required scope

write_orders

Request example

curl -X POST 'https://api.launchmystore.io/api/v1/orders/ord_xyz789/fulfillments.json' \
  -H 'Authorization: Bearer <oauth_access_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "tracking_number": "1Z999AA10123456784",
    "tracking_company": "UPS",
    "tracking_url": "https://ups.com/track?num=1Z999AA10123456784",
    "status": "success",
    "service_id": "fs_abc123"
  }'

Response

{
  "status": 201,
  "state": "success",
  "data": {
    "fulfillment": {
      "fulfillmentId": "ful_new456",
      "orderId": "ord_xyz789",
      "status": "success",
      "trackingNumber": "1Z999AA10123456784",
      "trackingCompany": "UPS",
      "trackingUrl": "https://ups.com/track?num=1Z999AA10123456784",
      "serviceId": "fs_abc123",
      "createdAt": "2024-01-20T14:30:00Z"
    }
  }
}
order.status is not returned in this response — the order row is updated as a side effect. Re-fetch the order via Get Order if you need the new status in the same flow.