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.
| Status | Meaning | Retry? |
|---|---|---|
400 | Malformed request — bad timestamp, bad cursor | No, fix the call |
401 | Token missing, revoked, or expired | No — the user must reconnect |
403 | Valid token, missing scope | No |
404 | Doesn't exist, or isn't visible to you | No |
409 | A cap was reached (rules per connection, places per user) | No |
422 | Value out of range — e.g. radius_m below 100 | No, fix the call |
429 | Rate or budget limit | Yes, with backoff |
503 | A dependency is unavailable | Yes, 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.