Bulk-import orders
POST /orders:import creates up to
100 orders in one request. Each entry is the same body
POST /orders takes, so anything valid
there is valid here.
Use it for any batch. One import of 100 orders is a single geocoding pass and a single write, where 100 individual creates are 100 of each.
Import a batch
curl "https://api.spoke.com/connect/v1/orders:import" \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"orders": [
{
"recipient": { "name": "Oliver Smith" },
"packageInfo": { "externalId": "PO-2026-04-21-1138" },
"address": {
"addressLineOne": "12 High Street",
"addressLineTwo": "London",
"zip": "SW1A 1AA"
}
},
{
"recipient": { "name": "Amara Okafor" },
"packageInfo": { "externalId": "PO-2026-04-21-1139" },
"address": {
"addressLineOne": "48 Bold Street",
"addressLineTwo": "Liverpool",
"zip": "L1 4EA"
}
}
]
}'
When everything lands, the response is 200 OK:
{
"success": ["orders/W3kJpzQm9q4LV2BdH7sNuT", "orders/Rb8YnFq2LxJ5wPvM6TdKe"],
"failed": []
}
Partial failure is the normal case
The import is not all-or-nothing, and it does not fail with an error status
when some rows are rejected. As long as the request itself is valid you get
200 OK, even when every single order failed. Orders that were created are in
success, and orders that were not are in failed. This is the part
integrations get wrong: a client that only checks the status code will report a
clean import while half the batch is missing.
Send the same batch with a bad address in the second row:
{
"orders": [
{
"recipient": { "name": "Oliver Smith" },
"packageInfo": { "externalId": "PO-2026-04-21-1138" },
"address": {
"addressLineOne": "12 High Street",
"addressLineTwo": "London",
"zip": "SW1A 1AA"
}
},
{
"recipient": { "name": "Amara Okafor" },
"packageInfo": { "externalId": "PO-2026-04-21-1139" },
"address": { "addressLineOne": "asdfgh" }
}
]
}
and the response is still 200 OK:
{
"success": ["orders/W3kJpzQm9q4LV2BdH7sNuT"],
"failed": [
{
"order": {
"recipient": { "name": "Amara Okafor" },
"packageInfo": { "externalId": "PO-2026-04-21-1139" },
"address": { "addressLineOne": "asdfgh" },
"index": 1
},
"error": {
"code": "order_address_unresolvable",
"message": "The order address could not be resolved."
}
}
]
}
Every failed entry echoes back the order you sent, plus an index field
holding its position in the request array. Use index to line the failure up
with your own record, since two rows can otherwise be identical. success is
not index-aligned, so do not try to match entries by position across the two
lists.
The codes a row can fail with are the same 422 codes
POST /orders returns:
code | What happened |
|---|---|
order_address_unresolvable | Spoke could not geocode the address on that row. |
order_invalid_fields | A field on that row was rejected, most often an unusable pickup location. |
A sound import loop looks like this:
- Send at most 100 orders.
- Record every id in
successagainst your own orders. - For every entry in
failed, useerror.codeto decide whether to fix and resend the row or to surface it to a human. Nothing about a failed row was written, so resending a corrected one is safe.
When the whole request fails
Only the request itself can fail outright:
| Status | What happened |
|---|---|
400 | The body does not match the schema, or orders holds more than 100 entries. |
401 | The API key is missing, invalid, or expired. |
The 100-order cap is checked before any order is processed, so an oversized batch creates nothing. Split your work into chunks of 100 rather than relying on partial acceptance.