Skip to main content

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:

codeWhat happened
order_address_unresolvableSpoke could not geocode the address on that row.
order_invalid_fieldsA field on that row was rejected, most often an unusable pickup location.

A sound import loop looks like this:

  1. Send at most 100 orders.
  2. Record every id in success against your own orders.
  3. For every entry in failed, use error.code to 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:

StatusWhat happened
400The body does not match the schema, or orders holds more than 100 entries.
401The 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.