API reference
API v2 — items
The v2 surface cleans up the box vocabulary: every box of an order is an item, addressed only by your own references. Everything else — authentication, scopes, errors, the polling model — is unchanged from v1.
v1 and v2 are separate, parallel contracts. v1 keeps serving exactly as documented on the other pages of this portal; v2 is where new vocabulary lands. Pick one version per integration and migrate deliberately — never mix versions inside one workflow.
What changes on v2, completely:
- The box array on create is
items[](v1:parcels[]), and your per-box reference isitem_id(v1:item_id). - An item has no Starmile tracking number of its own. v1's
items[].parcel_idis gone — you address a item by youritem_idor itsmerchant_tracking, and Starmile's only tracking reference is the order'stracking_number. - The create response returns the Starmile reference as
tracking_numberand echoes your ownorder_idback (v1 overloadedorder_idwith the Starmile number). - Status-pool rows name your order reference
order_id(v1:external_parent_id) and the item referenceitem_id(v1:external_id). The status vocabulary itself — every status string, including the customs ones — is identical to v1.
Endpoints that only read — GET /api/v2/services, GET /api/v2/rates, GET /api/v2/orders/label — and POST /api/v2/partner/events behave exactly as their v1 twins; only the path is versioned.
Migrating the status pool: the cursor space is new
cursorcomes from a different id space than v1's. A cursor you stored while polling v1 is meaningless on v2 — never carry it over. When you switch, drain v2 from since=0 (optionally per order via external_parent_id) and de-duplicate against what you already processed on v1, then continue with v2 cursors only.Create an order (v2)
/api/v2/ordersIdentical behavior to v1 — same required fields, same service_id resolution, idempotent on your order_id, same region handling — with the body's parcels[] spelled items[] and item_id spelled item_id. All other fields (recipient, delivery, products, gov_id, shipping_cost, …) are unchanged from Create an order.
A replayed order_id answers 200 with duplicate: true and the same body shape.
Body — what differs from v1
itemsarray, requiredoptional- One entry per physical box (v1: parcels[]). Same per-box fields: merchant_tracking, package_type, weight_grams, length_mm, width_mm, height_mm, products[].
items[].item_idstring, optionaloptional- Your own per-box reference (same key as v1). Must be unique across your orders; it is how you address the item afterwards.
curl -X POST 'https://api.starmile.io/api/v2/orders' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{
"service_id": 12,
"order_id": "PO-10294",
"customer_name": "Jane Doe",
"customer_phone": "+994500000000",
"items": [
{
"item_id": "BOX-1",
"merchant_tracking": "MT-0001",
"weight_grams": 1200,
"products": [
{ "name": "Running shoes", "declared_value": 50, "currency": "USD", "quantity": 1 }
]
}
]
}'Add an item
/api/v2/orders/{order_id}/itemsAdd a new item to an order that already has more than one item. The body is a single item in the same shape as one entry of the create items[]; the item carries no Starmile tracking of its own, so the 201 returns the order's tracking_number, your order_id, and the new item by its own references. A reused item_id, or a merchant_tracking held by a real item, is 422; an unidentified package already at the hub under that barcode is matched instead. Scope: orders:update.
An order created with a single item carries that box on the order itself and has no separate items to add to — adding one is refused 409. Create a new order for the additional package instead.
Body
item_idstringoptional- Optional. Your own reference for the item. Must be unique across your orders.
merchant_trackingstringoptional- Optional. The physical sticker code.
package_typestringoptional- Optional. One of: fragile, breakable, liquid.
weight_gramsintegeroptional- Optional. Declared gross weight (records-only).
length_mm / width_mm / height_mmintegeroptional- Optional. Declared dimensions in mm.
productsarrayoptional- Required. The item's contents (same shape as create).
curl -X POST 'https://api.starmile.io/api/v2/orders/PO-10294/items' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{
"item_id": "BOX-3",
"merchant_tracking": "CN773300012345",
"weight_grams": 1200,
"products": [
{ "name": "Ceramic mug", "hs_code": "691200", "declared_value": 12.5, "currency": "USD", "quantity": 1 }
]
}'Update or cancel an item
/api/v2/orders/{order_id}/items/{item_id}Both path segments are your references — the order_id and item_idyou sent on create. Same rules as v1's parcel update: only while the order is at its flow's first step, products replaces the full list, and a late merchant_tracking that matches a package already waiting at the hub is adopted, not rejected.
POST …/items/{item_id}/cancel cancels one item (and the order, when it was the last active one); POST /api/v2/orders/{order_id}/cancel cancels the whole order. Scopes: orders:update / orders:cancel.
curl -X PATCH 'https://api.starmile.io/api/v2/orders/PO-10294/items/BOX-1' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{ "weight_grams": 1500 }'Split items onto a new order
/api/v2/orders/{order_id}/splitThe v2 twin of v1's parcel split: peel one or more items out of a multi-item order and move them onto one fresh clone of the order (same service, flow, customer and destination). The body names the items with item_ids — an array of your own references (one or more, no duplicates) — and the new order with new_order_id, your own reference, so you can track and manage it like any other order. The 201follows the v2 wire: the new order's Starmile reference is tracking_number, order_id echoes your new_order_id, and each moved item carries no tracking of its own under items (item_id + merchant_tracking). Scope: orders:update.
Move a single item and the new order is a single-item order, stored the way v2 stores any single-item order — the item lives on the order itself, with no separate item to address (fetch its label and track it by its order_id, and you cannot add an item to it). Move two or more and the new order is a genuine multi-item order that keeps its items.
An ordinary order is split only while the items are at their flow's first step (else 409); a consolidation order can be split any time before its boxes are packed (an item already received or shelved can still be pulled out), and only a packed consolidation is refused 409. A folded single-item source order has no item to split off, and naming every item would leave the source empty — both 409; an item_id that names no active item is 404; a new_order_id already used, or equal to the source order_id, is 422.
Body
item_idsstring[]optional- Required. Your own item_id references for the items to move onto the new order — one or more, distinct. All move onto the one new order; the source keeps the rest.
new_order_idstringoptional- Required. Your own reference for the new order the items are moved onto. Must be unused across your orders and different from the source order_id.
reasonstringoptional- Optional free-text reason, kept on the new order's history.
curl -X POST 'https://api.starmile.io/api/v2/orders/PO-10294/split' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{
"item_ids": ["BOX-2", "BOX-3"],
"new_order_id": "PO-10294-B",
"reason": "ships separately"
}'Poll the status pool (v2)
/api/v2/partner/changesSame polling model as Status updates: page with since, persist next_cursor, poll again immediately while has_more is true. Same optional filters (tracking_number, external_parent_id), same reason / reason_detail codes, and the feed still opens with the status the order was created in.
Row differences from v1: order_id is your own order reference, and item_id (set only on item-scoped rows, e.g. a single box reaching received_at_hub) replaces external_id.
# Poll from your last v2 cursor (NOT a v1 cursor — see the migration note)
curl 'https://api.starmile.io/api/v2/partner/changes?since=48210' \
-H 'Authorization: Bearer <access_token>'
# One order's history, by YOUR own reference
curl 'https://api.starmile.io/api/v2/partner/changes?since=0&order_id=PO-10294' \
-H 'Authorization: Bearer <access_token>'