Errors

One body shape for every failure: a JSON object with a single detail key. The status code says what class of problem it is; the string says which instance.

Any error
{ "detail": "Over the per-minute limit of 60 requests." }

detail is written for a developer reading a log, and it names the fix wherever there is one. It is not a stable identifier — treat the status code as the thing to branch on and the string as the thing to print.

Status codes

401Unauthorized
No key, an unknown key, a revoked key or an expired one. Carries WWW-Authenticate: Bearer. Not retryable — the credential has to change. Which is which.
403Forbidden
The key is valid and the request is not allowed. Three causes: the account is pending, rejected or suspended; the key lacks the scope; or a trial key named a location outside its ZIP. All three are decisions about access, so none is retryable.
404Not Found
No such event, or one that is not public. Also what a trial key gets for a real event outside its ZIP — deliberately indistinguishable, because a 403 there would confirm the id was real.
422Unprocessable Content
A parameter is wrong and detail names which: a lone lat without lng, two conflicting centres, an unknown category, a ZIP with no centroid on file, or a cursor that is not one of ours. Fix the request; retrying it unchanged returns the same thing.
429Too Many Requests
Over the per-minute or the daily window, and detail says which. Back off on Retry-After rather than on a fixed interval.
5xxServer error
Ours. Retry with backoff; nothing about the request needs to change.

What to retry

Two of the six are worth retrying and four are not, which is the whole decision:

Retry429, 5xx
429 on the Retry-After it gives you; 5xx on exponential backoff. Cap both — a daily 429 can be hours out, and sleeping through it is worse than failing the job.
Do not retry401, 403, 404, 422
Nothing on loso's side will change between attempts. Retrying these is how a rate limit gets spent discovering that a parameter is still wrong.

Not an error

An empty page is a 200, not a 404: { "data": [], "next_cursor": null } means the filters matched nothing. Common and legitimate — a quiet ZIP on a Tuesday, a category with nothing upcoming, a starts_before in the past.

One case deliberately does not degrade to an empty page: a trial key querying outside its ZIP gets a 403. Answering it with zero rows would be a false statement about loso’s coverage made to the one reader deciding whether to pay for it.

Errors · loso API