Skip to main content

List orders and follow their progress

There is no event push at v1, so following an order means reading it. This page covers listing orders, reading progress.eta, and how often to come back.

List a page of orders

GET /orders returns your orders newest first, 50 per page by default and at most 50 per page.

curl "https://api.spoke.com/connect/v1/orders?maxPageSize=50" \
-H "Authorization: Bearer <api-key>"

Each entry is a full Order. This one is abridged to the fields that matter for tracking:

{
"orders": [
{
"id": "orders/W3kJpzQm9q4LV2BdH7sNuT",
"createdAt": 1778508502,
"courier": "couriers/QyZ8mp34RFlIfwREp1js",
"submitted": true,
"submittedAt": 1778511103,
"readOnly": true,
"source": "retailer",
"progress": {
"trackingLink": "https://track.spoke.com/t/abc123",
"eta": {
"status": "available",
"value": {
"estimatedArrivalAt": 1779013800,
"estimatedEarliestArrivalAt": 1779012600,
"estimatedLatestArrivalAt": 1779015000
}
},
"attempted": false,
"attemptedAt": null,
"state": "unattempted",
"routeStartDate": { "year": 2026, "month": 5, "day": 16 },
"routeStarted": true,
"succeeded": false,
"photoUrls": [],
"signatureUrl": null,
"signeeName": null,
"driverProvidedRecipientNotes": null
}
}
],
"nextPageToken": "MTc3ODUwODUwMnxXM2tKcHpRbTlxNExWMkJkSDdzTnVU"
}

To walk past the first page, pass nextPageToken back as pageToken and keep every other query parameter the same. Stop when nextPageToken comes back null, and only then. A page can be short while more results exist, so a short page is not the end. See On List endpoints for the full rules.

To find one order you already have your own reference for, filter on the external id you set at creation:

curl "https://api.spoke.com/connect/v1/orders?filter.externalId=PO-2026-04-21-1138" \
-H "Authorization: Bearer <api-key>"

External ids are not unique-constrained, so if you have reused one this returns every order carrying it.

Read the progress

progress is null until the courier puts the order on a route. Once it is there, the whole delivery story is in that object.

progress.eta is a tagged union, so read status before value:

statusWhat it means
availablevalue holds the arrival estimate and its earliest and latest bounds.
pendingThe order is on a route but the route is not optimized yet. Come back later.
restrictedThe courier's subscription does not include ETAs. It will never become available for this order.
{ "status": "pending" }
{ "status": "restricted", "restrictionCode": "subscription_not_supported" }

Treat restricted as final. Retrying does not change it, and a client that loops waiting for available will loop forever.

ETAs are recomputed on every read from the route's current state, so they move as the driver does. They are not stored on the order, which is why you get a fresh number each time rather than a stale one.

The rest of progress reads in this order:

  1. routeStarted turns true when the driver starts the route. Before that an ETA is a plan, not a live estimate.
  2. attempted turns true once the driver has been to the address, successful or not, with attemptedAt holding when.
  3. succeeded says whether the delivery worked, and state says what actually happened, for example delivered_to_recipient or failed_not_home.
  4. photoUrls, signatureUrl, and signeeName carry the proof the driver captured. A photo URL can answer not-found for a short while after the attempt, because the driver's app is still uploading it. Retry rather than treating it as missing.

trackingLink is the page the recipient can follow. It is safe to pass on, unlike the shipping label link.

How often to poll

Reads are limited to 10 requests per second per IP (see On Rate-Limiting), which is generous enough that good sense, not the limit, is what should set your cadence. Every read recomputes ETAs from the live route, so a tight loop is expensive for both sides and buys you nothing: a driver's arrival estimate does not meaningfully change second to second.

What works:

  • Poll the list endpoint, not each order. One request covers 50 orders. A loop of GET /orders/{id} spends 50 requests against your read budget for the same information.
  • Once a minute per page is plenty while you have orders on a started route, and it stays far under the read limit.
  • Poll only the orders you are actually watching. An order with progress still null has nothing new to report until the courier routes it, and an order with attempted: true has already told you how it ended.
  • Back off outside delivery hours. Nothing moves overnight.

Handle a 429 with backoff and jitter even though a once-a-minute poll stays well under the limit, so an unexpected burst on your side does not lock you out. See On Rate-Limiting.