Two shapes, because the OAuth endpoints follow a specification that predates the rest.
Roost#
{
"error": "not_found",
"message": "That program does not exist."
}errorstringRequiredA stable machine-readable code. Match on this.
messagestringRequiredOne sentence for a person. Written to be shown as-is, and not stable across releases.
fieldsarrayPresent only when a specific input was rejected. Each entry names the parameter and says what is wrong with it.
| Status | Code | Means |
|---|---|---|
| 400 | bad_request | A parameter could not be parsed, or a required one is missing. |
| 404 | not_found | Nothing is published at that address, or it is hidden or suspended. |
| 429 | too_many_requests | Over a rate-limit budget. |
| 500 | internal_error | Something went wrong inside Roost. Carries no detail on purpose. |
| 503 | database_unavailable | A dependency is down. |
404 does not distinguish#
A 404 from Roost means the registry will not serve that address. It covers a program that never
existed, one that is hidden, and one whose project has been archived, and the response deliberately
does not say which.
A client should treat all three the same way: keep whatever local copy it has, stop offering updates, and say so if the person asks.
The OAuth endpoints#
{
"error": "invalid_grant",
"error_description": "the authorization code is invalid or has expired"
}| Status | Code | Means |
|---|---|---|
| 400 | invalid_request | Something is missing or malformed. Usually redirect_uri on the exchange, or code_verifier. |
| 400 | invalid_grant | The code or refresh token is used, expired, or was issued to a different client. |
| 400 | unauthorized_client | The application is disabled, or its project is archived. |
| 400 | invalid_scope | A scope that does not exist, or one this application was not granted. |
| 400 | unsupported_grant_type | A grant type this provider does not offer. |
| 400 | access_denied | The person refused. Do not retry. |
| 401 | invalid_client | Client authentication failed. |
| 500 | server_error | Something went wrong. Retry with backoff. |
The device flow adds three#
These are not failures. They are how polling works.
| Status | Code | Means |
|---|---|---|
| 400 | authorization_pending | Not yet approved. Wait and poll again. |
| 400 | slow_down | Increase your polling interval and continue. |
| 400 | expired_token | The attempt timed out. Start again with a new code. |
How to poll without getting rate-limited.
What to retry#
Transient. Respect any Retry-After, back off exponentially, and add jitter.
Nothing about the request will be different next time. Fix it, or start the flow again.
An expired access token is the one case worth automating: refresh once, retry once, and if that fails, treat the session as over rather than looping.
Never branch on the message#
message and error_description are prose. They are rewritten when they read badly, and they are
not part of the contract. error is.
Request ids#
Every response carries a request id header. Quote it if you report a problem: it is what makes a
server-side log line findable, and a 500 deliberately tells you nothing else.