Shipments API
Manage the shipments for an order. A shipment records which items were shipped together, along with carrier and tracking details. An order can have several shipments, which is useful when items ship separately.
A shipment counts as sent once it has a tracking number, and the buyer is
emailed then. When sent shipments hold every item, the order’s status becomes
fulfilled. Canceling or reverting a shipment moves the order back to paid.
Stock is taken when the order is paid, so shipments never change inventory.
A store can also buy a shipment’s label in the dashboard through its own
ShipStation account. The label sets the shipment’s carrier and tracking, and
the status then follows the carrier’s scans, from label_created through
in_transit to delivered (or exception). Labels are only bought and voided
in the dashboard. Until its label is voided there, a labeled shipment can’t be
canceled or reverted, and its carrier and tracking can’t be changed: those
requests return 409 Conflict.
See Webhooks to be told when a shipment is created.
Endpoints
- List Shipments -
GET /api/v1/orders/{order_id}/shipments/ - Create Shipment -
POST /api/v1/orders/{order_id}/shipments/ - Get Shipment -
GET /api/v1/orders/{order_id}/shipments/{shipment_id}/ - Update Shipment -
PUT /api/v1/orders/{order_id}/shipments/{shipment_id}/ - Cancel Shipment -
POST /api/v1/orders/{order_id}/shipments/{shipment_id}/cancel/ - Revert Shipment -
POST /api/v1/orders/{order_id}/shipments/{shipment_id}/revert/
List Shipments
Retrieve the shipments for an order. This endpoint is not paginated and does not include line items. Fetch a single shipment to see its items.
Endpoint: GET /api/v1/orders/{order_id}/shipments/
Scope Required: shipments:read
Request
curl -X GET "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/shipments/" \
-H "Authorization: Bearer your_api_key_here"
Response
Status: 200 OK
{
"data": [
{
"id": "shp_d0b6fmv28q6vn14peun0",
"number": 1,
"status": "label_created",
"tracking_number": "1Z999AA10123456784",
"tracking_url": "https://example.com/track/1Z999AA10123456784",
"carrier_id": 1,
"items": [],
"created_at": "2023-01-02T09:00:00Z",
"updated_at": "2023-01-02T09:30:00Z"
}
]
}
Create Shipment
Create a shipment for one or more ordered items. Supply the
order_variant_id from the order’s items array (see
Orders). Quantities are applied against the
order. The new shipment is unshipped until you add its tracking with
Update Shipment.
Endpoint: POST /api/v1/orders/{order_id}/shipments/
Scope Required: shipments:write
Request Body
{
"items": [
{
"order_variant_id": "orv_d0b6fmv28q6vn14peun0",
"quantity": 1
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
items |
array | Yes | Line items to ship, with at least one item and a positive total quantity |
items[].order_variant_id |
string | Yes | Line item public ID from the order’s items array |
items[].quantity |
integer | Yes | Quantity to ship (non-negative) |
Request
curl -X POST "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/shipments/" \
-H "Authorization: Bearer your_api_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-12345" \
-d '{
"items": [
{ "order_variant_id": "orv_d0b6fmv28q6vn14peun0", "quantity": 1 }
]
}'
Response
Status: 201 Created
{
"id": "shp_d0b6fmv28q6vn14peun0"
}
Error Responses
No items supplied
Status: 400 Bad Request
{
"error": "A shipment requires at least one item",
"code": "INVALID_FIELD"
}
Negative quantity
Status: 400 Bad Request
{
"error": "Quantity must be non-negative",
"code": "INVALID_FIELD"
}
Order canceled by the customer
Status: 409 Conflict
{
"error": "The customer canceled this order",
"code": "ORDER_CANCELED"
}
Order not found
Status: 404 Not Found
{
"error": "Resource not found",
"code": "NOT_FOUND"
}
Get Shipment
Retrieve a single shipment, including its line items.
Endpoint: GET /api/v1/orders/{order_id}/shipments/{shipment_id}/
Scope Required: shipments:read
Request
curl -X GET "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/shipments/shp_d0b6fmv28q6vn14peun0/" \
-H "Authorization: Bearer your_api_key_here"
Response
Status: 200 OK
{
"id": "shp_d0b6fmv28q6vn14peun0",
"number": 1,
"status": "label_created",
"tracking_number": "1Z999AA10123456784",
"tracking_url": "https://example.com/track/1Z999AA10123456784",
"carrier_id": 1,
"items": [
{
"order_variant_id": "orv_d0b6fmv28q6vn14peun0",
"product_id": "pro_d0b6fmv28q6vn14peun0",
"product_name": "Premium T-Shirt",
"variant_name": "Small Red",
"quantity": 1,
"price": 1999
}
],
"created_at": "2023-01-02T09:00:00Z",
"updated_at": "2023-01-02T09:30:00Z"
}
Update Shipment
Update a shipment’s carrier, tracking details, or item quantities. A canceled
shipment can’t be updated and returns 409 Conflict with code
SHIPMENT_CANCELED.
Endpoint: PUT /api/v1/orders/{order_id}/shipments/{shipment_id}/
Scope Required: shipments:write
Request Body
{
"carrier_id": 1,
"tracking_number": "1Z999AA10123456784",
"tracking_url": "https://example.com/track/1Z999AA10123456784",
"items": [
{
"order_variant_id": "orv_d0b6fmv28q6vn14peun0",
"quantity": 1
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
carrier_id |
integer|null | No | Carrier ID. See Carriers for the list of IDs |
tracking_number |
string|null | No | Tracking number |
tracking_url |
string|null | No | Tracking URL |
items |
array | No | New line item quantities (non-negative) |
carrier_id and the tracking fields are optional. Set tracking_number and
tracking_url directly if you do not have a carrier ID. Adding a tracking
number marks an unshipped shipment as sent and emails the buyer; changing it
later emails them the new one.
A shipment with a label bought in the dashboard keeps that label’s carrier
and tracking. Changing either returns 409 Conflict;
updating only its items is allowed.
Request
curl -X PUT "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/shipments/shp_d0b6fmv28q6vn14peun0/" \
-H "Authorization: Bearer your_api_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-67890" \
-d '{
"carrier_id": 1,
"tracking_number": "1Z999AA10123456784"
}'
Response
Status: 204 No Content
Error Responses
Negative quantity
Status: 400 Bad Request
{
"error": "Quantity must be non-negative",
"code": "INVALID_FIELD"
}
Cancel Shipment
Cancel a shipment, such as one recorded by mistake or a parcel lost before
it reached the carrier. Shipments are never deleted, so the order keeps a
record of every parcel. A canceled shipment keeps its items and tracking for
reference, but they count as unshipped again, so the order moves back to
paid until they ship in another shipment. A canceled shipment can’t be
updated, reverted, or canceled again. The cancellation is recorded in the
order’s events.
A shipment with a label returns 409 Conflict until
the label is voided in the dashboard.
The buyer is not emailed. If they were already sent the shipping email, the next shipment you record emails them its own tracking.
Endpoint: POST /api/v1/orders/{order_id}/shipments/{shipment_id}/cancel/
Scope Required: shipments:write
Request Body
{
"reason": "Recorded by mistake"
}
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string|null | No | Why the shipment was canceled, up to 256 characters |
Request
curl -X POST "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/shipments/shp_d0b6fmv28q6vn14peun0/cancel/" \
-H "Authorization: Bearer your_api_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-13579" \
-d '{ "reason": "Recorded by mistake" }'
Response
Status: 204 No Content
Error Responses
Already canceled
Status: 409 Conflict
{
"error": "Shipment is canceled",
"code": "SHIPMENT_CANCELED"
}
Reason too long
Status: 400 Bad Request
{
"error": "Reason must be at most 256 characters",
"code": "INVALID_FIELD"
}
Revert Shipment
Revert a shipment to unshipped and clear its carrier and tracking. The
shipment keeps its items.
A shipment with a label returns 409 Conflict until
the label is voided in the dashboard.
Endpoint: POST /api/v1/orders/{order_id}/shipments/{shipment_id}/revert/
Scope Required: shipments:write
Request
curl -X POST "https://studio.easel.engineering/api/v1/orders/ord_d0b6fmv28q6vn14peun0/shipments/shp_d0b6fmv28q6vn14peun0/revert/" \
-H "Authorization: Bearer your_api_key_here" \
-H "Idempotency-Key: unique-request-id-24680"
Response
Status: 204 No Content
Labeled Shipments
A change that would leave a bought label behind returns 409 Conflict with
the CONFLICT error code:
{
"error": "This shipment has a shipping label. Void it in the dashboard first.",
"code": "CONFLICT"
}
Void the label in the dashboard, on the shipment’s Shipping label page,
then retry. Voiding returns the shipment to unshipped and clears its carrier
and tracking.
Shipment Object
Fields
| Field | Type | Description |
|---|---|---|
id |
string | Shipment public ID |
number |
integer | Shipment number within the order |
status |
string | Shipment status (see below) |
tracking_number |
string|null | Tracking number |
tracking_url |
string|null | Tracking URL |
carrier_id |
integer|null | Carrier ID |
items |
array | Line items (single shipment only) |
canceled_at |
string|null | ISO 8601 timestamp when the shipment was canceled |
cancel_reason |
string|null | Why it was canceled, when a reason was given |
created_at |
string | ISO 8601 timestamp |
updated_at |
string | ISO 8601 timestamp |
Shipment Item Object
Fields
| Field | Type | Description |
|---|---|---|
order_variant_id |
string | Ordered line item public ID |
product_id |
string | Product public ID |
product_name |
string | Product name |
variant_name |
string | Variant name |
quantity |
integer | Quantity in this shipment |
price |
integer | Unit price in the store’s smallest currency unit |
Shipment Status Values
unshipped- recorded but not yet sentprelabelon_holdlabel_created- has tracking; for a label bought through ShipStation, not yet scanned by the carrierin_transit- accepted or moving, from the carrier’s scansout_for_deliveryfailed_attempt- the carrier tried to deliverdeliveredavailable_for_pickup- waiting at a pickup pointexception- the carrier reported a delivery problem
Statuses after label_created are set from tracking, which Easel checks every
few hours for labels bought through ShipStation. The buyer is emailed once
when a shipment reaches delivered, and once if it reaches exception.
- shipped - sent without tracking, such as a letter by regular post, recorded from the dashboard
- canceled - canceled; its items count as unshipped (see Cancel Shipment)