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:
addresscame back rewritten. Spoke geocodes the address you send and stores the resolved one, along withlatitude,longitude,placeId, andplaceTypes. Those four are response-only and are not accepted on the way in.barcodesis already populated, one barcode per package inpackageInfo.packageCount. Those are the barcodes that end up on the shipping labels.courierandprogressarenullandsubmittedisfalse, because nothing has been submitted to a courier yet.sourceisretailer, meaning you created it. Orders a courier adds on your behalf come back assource: "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
| Status | code | What happened |
|---|---|---|
400 | validation | The body does not match the schema, for example an address with no usable field. |
401 | The API key is missing, invalid, or expired. | |
422 | order_address_unresolvable | Spoke could not geocode the address. Nothing was created, so fix the address and send it again. |
422 | order_invalid_fields | A 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.