Errors and limits

Error shape

{
  "statusCode": 400,
  "message": "at must be an ISO timestamp",
  "error": "Bad Request"
}

message is a string, or an array of strings when several validation errors apply at once. Log it — the messages are written to be actionable, not generic.

StatusMeaningRetry?
400Malformed request — bad timestamp, bad cursorNo, fix the call
401Token missing, revoked, or expiredNo — the user must reconnect
403Valid token, missing scopeNo
404Doesn't exist, or isn't visible to youNo
409A cap was reached (rules per connection, places per user)No
422Value out of range — e.g. radius_m below 100No, fix the call
429Rate or budget limitYes, with backoff
503A dependency is unavailableYes, with backoff

The rule of thumb: only 429 and 503 are worth retrying. Everything else means the request itself needs to change.

The three limits

Per-connection rate limit — 600 requests/hour. The ordinary ceiling across every endpoint. Ordinary use won't reach it.

Verify budget — 60/hour, 300/24 hours. Shared across verify-zone, current-place and presence combined, so you can't dodge it by alternating between them. This is roughly one question every minute, sustained.

Distinct-area budget — 12 areas/24 hours. verify-zone only. Zone centres are quantised to roughly 1.1 km cells, and asking about more than twelve different cells in a day is refused.

Why the distinct-area budget exists

Volume alone is a poor signal. An app that polls one delivery zone every minute is behaving normally. An app that asks about a dozen scattered areas is doing something else: narrowing down where someone is by elimination.

So the limit is on variety, not frequency. Re-asking about the same zone is free — the cell is already counted. Only genuinely new territory advances it.

Tripping it isn't just a 429. It also:

  • flags the connection,
  • writes a line the user reads: "blocked: asked about more than 12 separate areas in 24h",
  • shows a banner on their Connections page suggesting they review you,
  • and emails them once (at most weekly per app).

If your product legitimately needs many zones, don't work around this — a courier app watching twenty depots is a reasonable thing to be, and the honest fix is to talk to us rather than to spread queries across connections. Working around it looks identical to abuse from where we're standing.

Handling 429 properly

There's no Retry-After header today. Back off exponentially, starting around a minute, and cap your retries.

Two things not to do. Don't retry a distinct-area 429 with a different zone: that advances the budget further and reads as exactly the sweep the limit exists to stop. And don't fan out across users — a limit hit for one connection says something about your query pattern, which probably applies to the others too.

If you're hitting the verify budget, the usual fix isn't more requests but fewer: register a rule and be told, instead of asking repeatedly.

What we do on our side

Both budgets fail open. If our rate-limit store has a problem, requests are allowed through rather than refused — a limiter should never become the outage. The privacy guarantees don't depend on it: the geometry margin is enforced in the answer itself.

That means you can't infer anything from a limit not firing.