Skip to main content

Submit an order, and withdraw it

Submitting hands a draft order to a courier. Withdrawing pulls it back, but only while the courier has not yet put it on a route. This page walks both calls as one story, because the interesting part is where the window closes.

Both endpoints are batch endpoints even for a single order, and both take at most 100 ids per request.

Find the courier

GET /couriers lists the couriers your account is connected to. Connections are made in the Spoke Connect app, so this endpoint is read-only.

curl "https://api.spoke.com/connect/v1/couriers" \
-H "Authorization: Bearer <api-key>"
{
"couriers": [
{ "id": "couriers/QyZ8mp34RFlIfwREp1js", "name": "Northside Logistics" }
],
"nextPageToken": null
}

Submit

POST /orders:submit takes the order ids and one courier id, both with their collection prefix:

curl "https://api.spoke.com/connect/v1/orders:submit" \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"orderIds": ["orders/W3kJpzQm9q4LV2BdH7sNuT"],
"courierId": "couriers/QyZ8mp34RFlIfwREp1js"
}'
{
"success": ["orders/W3kJpzQm9q4LV2BdH7sNuT"],
"failed": []
}

Like the import endpoint, this answers 200 OK whenever the request itself is valid, including when every order failed. Check failed, not just the status code.

Read the order back and it now carries the courier (abridged to the fields that changed):

{
"id": "orders/W3kJpzQm9q4LV2BdH7sNuT",
"courier": "couriers/QyZ8mp34RFlIfwREp1js",
"submitted": true,
"submittedAt": 1778511103,
"progress": null,
"readOnly": false
}

progress is still null and readOnly is still false. That combination is what tells you the courier has accepted the order but has not routed it yet, which is exactly the window in which a withdraw succeeds.

Per-order failures

codeWhat happened
order_not_foundNo such order for your account, or it was deleted.
order_already_submittedThe order is already with a courier. Withdraw it before resubmitting.
order_invalid_fieldsThe order is missing a field your account requires, or its address is beyond a maximum distance from the pickup your account has set.
order_concurrent_modificationThe order changed mid-request. Read it back and retry.

Request-level failures

A courier problem fails the whole request with 422, because a batch submitted to the wrong courier has no partially correct outcome:

{
"message": "No connected courier matches `courierId`.",
"code": "order_invalid_fields",
"param": "courierId"
}
{
"message": "This courier charges for deliveries, so orders cannot be submitted to it through the API. Submit them from the Spoke Connect app, which handles the payment.",
"code": "order_courier_requires_payment",
"param": "courierId"
}

The second one is a real constraint rather than a misconfiguration. The API only serves the free path, so orders going to a courier that charges per delivery have to be submitted from the Spoke Connect app, which handles payment.

Withdraw

POST /orders:withdraw takes only order ids. There is no courier to name, since the order already knows which courier holds it.

curl "https://api.spoke.com/connect/v1/orders:withdraw" \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"orderIds": ["orders/W3kJpzQm9q4LV2BdH7sNuT"]}'
{
"success": ["orders/W3kJpzQm9q4LV2BdH7sNuT"],
"failed": []
}

The order is a draft again: courier is back to null, submitted is false, and you can edit or delete it.

When the window closes

Once the courier puts the order on a route, it is out of your hands. The same withdraw now fails per-order, still inside a 200 OK:

{
"success": [],
"failed": [
{
"orderId": "orders/W3kJpzQm9q4LV2BdH7sNuT",
"error": {
"code": "order_already_scheduled",
"message": "The order is already on a route and can no longer be withdrawn."
}
}
]
}

Withdraw takes no courier, so there is no request-level rejection beyond schema validation. Every ineligible order is a per-order outcome:

codeWhat happened
order_already_scheduledThe courier already routed it. This is the closed window.
order_not_submittedThe order was never submitted, so there is nothing to withdraw.
order_read_onlyThe order can no longer be changed through the API.
order_courier_managedThe courier added this order on your behalf, so the courier owns it.
order_no_courier_linkThe order is not linked to a courier.
order_not_foundNo such order for your account, or it was deleted.
order_concurrent_modificationThe order changed mid-request. Read it back and retry.

If you need to cancel an order after it has been routed, contact the courier. The API cannot take it back.