Skip to main content

Create an order

POST /orders creates one draft order. Only address is required, and it only has to carry a single field that can locate the delivery. Everything else is optional and falls back to your account defaults.

A new order is a draft: no courier owns it yet, so you can still edit it, delete it, or add it to a batch you submit later.

Create the order

curl "https://api.spoke.com/connect/v1/orders" \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"recipient": {
"name": "Oliver Smith",
"email": "oliver.smith@example.com",
"phone": "+447700900123"
},
"packageInfo": {
"products": ["Wireless headphones"],
"packageCount": 1,
"packageLoadAmount": 1.2,
"externalId": "PO-2026-04-21-1138"
},
"address": {
"addressLineOne": "12 High Street",
"addressLineTwo": "London",
"zip": "SW1A 1AA",
"country": "United Kingdom"
},
"deliveryInstructions": {
"notes": "Leave with the neighbour",
"expectedDeliveryDate": { "year": 2026, "month": 5, "day": 15 },
"proofOfAttemptRequirements": { "enabled": true },
"handOffInfo": { "handOffType": "ship_to_courier" }
}
}'

The response is 201 Created with the full order:

{
"id": "orders/W3kJpzQm9q4LV2BdH7sNuT",
"createdAt": 1778508502,
"recipient": {
"name": "Oliver Smith",
"email": "oliver.smith@example.com",
"phone": "+447700900123",
"externalId": null
},
"packageInfo": {
"products": ["Wireless headphones"],
"packageCount": 1,
"packageLoadAmount": 1.2,
"externalId": "PO-2026-04-21-1138"
},
"address": {
"address": "12 High Street, London SW1A 1AA, United Kingdom",
"addressLineOne": "12 High Street",
"addressLineTwo": "London SW1A 1AA, United Kingdom",
"latitude": 51.5074,
"longitude": -0.1278,
"placeId": "ChIJdd4hrwug2EcRmSrV3Vo6llI",
"placeTypes": ["street_address"]
},
"deliveryInstructions": {
"notes": "Leave with the neighbour",
"expectedDeliveryDate": { "year": 2026, "month": 5, "day": 15 },
"proofOfAttemptRequirements": { "enabled": true },
"handOffInfo": {
"handOffType": "ship_to_courier",
"pickupLocationId": null
}
},
"courier": null,
"submitted": false,
"submittedAt": null,
"progress": null,
"readOnly": false,
"source": "retailer",
"barcodes": ["4Z7X9K2L1M0N8B-1"]
}

Four things in that response are worth pointing out:

  • address came back rewritten. Spoke geocodes the address you send and stores the resolved one, along with latitude, longitude, placeId, and placeTypes. Those four are response-only and are not accepted on the way in.
  • barcodes is already populated, one barcode per package in packageInfo.packageCount. Those are the barcodes that end up on the shipping labels.
  • courier and progress are null and submitted is false, because nothing has been submitted to a courier yet.
  • source is retailer, meaning you created it. Orders a courier adds on your behalf come back as source: "courier" and are always read-only.

Have the courier collect instead

The order above ships to the courier. To have the courier collect from one of your own locations, set the hand-off type and name the pickup location. List them with GET /pickupLocations first:

curl "https://api.spoke.com/connect/v1/pickupLocations" \
-H "Authorization: Bearer <api-key>"
{
"pickupLocations": [
{ "id": "pickupLocations/8Qm3RtBnZ9wXcLmPd2Fh", "name": "Camden warehouse" }
],
"nextPageToken": null
}

Then pass the id straight through, prefix included:

{
"address": { "addressLineOne": "12 High Street", "addressLineTwo": "London" },
"deliveryInstructions": {
"handOffInfo": {
"handOffType": "pickup_by_courier",
"pickupLocationId": "pickupLocations/8Qm3RtBnZ9wXcLmPd2Fh"
}
}
}

Omit pickupLocationId and the courier collects from your main location.

When it fails

StatuscodeWhat happened
400validationThe body does not match the schema, for example an address with no usable field.
401The API key is missing, invalid, or expired.
422order_address_unresolvableSpoke could not geocode the address. Nothing was created, so fix the address and send it again.
422order_invalid_fieldsA field was rejected. param names it, most often pickupLocationId.

An unresolvable address looks like this:

{
"message": "The order address could not be resolved.",
"code": "order_address_unresolvable"
}

Geocoding runs before anything is written, so a 422 here always means no order was created. Retrying with a corrected address is safe.

Edit it before it ships

PATCH /orders/{id} changes only what you send. The order's id goes straight onto the base address:

curl -X PATCH "https://api.spoke.com/connect/v1/orders/W3kJpzQm9q4LV2BdH7sNuT" \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"deliveryInstructions": {"notes": "Ring the bell twice"}}'

The response is 200 OK with the updated order. Read On Update endpoints before you rely on the edit semantics, especially for address, which is replaced rather than merged.