Skip to content

Errors

Roost uses one shape, the OAuth endpoints use another. Both are stable in the code and neither is stable in the message.

Two shapes, because the OAuth endpoints follow a specification that predates the rest.

Roost#

{
  "error": "not_found",
  "message": "That program does not exist."
}
errorstringRequired

A stable machine-readable code. Match on this.

messagestringRequired

One sentence for a person. Written to be shown as-is, and not stable across releases.

fieldsarray

Present only when a specific input was rejected. Each entry names the parameter and says what is wrong with it.

StatusCodeMeans
400bad_requestA parameter could not be parsed, or a required one is missing.
404not_foundNothing is published at that address, or it is hidden or suspended.
429too_many_requestsOver a rate-limit budget.
500internal_errorSomething went wrong inside Roost. Carries no detail on purpose.
503database_unavailableA 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"
}
StatusCodeMeans
400invalid_requestSomething is missing or malformed. Usually redirect_uri on the exchange, or code_verifier.
400invalid_grantThe code or refresh token is used, expired, or was issued to a different client.
400unauthorized_clientThe application is disabled, or its project is archived.
400invalid_scopeA scope that does not exist, or one this application was not granted.
400unsupported_grant_typeA grant type this provider does not offer.
400access_deniedThe person refused. Do not retry.
401invalid_clientClient authentication failed.
500server_errorSomething went wrong. Retry with backoff.

The device flow adds three#

These are not failures. They are how polling works.

StatusCodeMeans
400authorization_pendingNot yet approved. Wait and poll again.
400slow_downIncrease your polling interval and continue.
400expired_tokenThe attempt timed out. Start again with a new code.
The device flow

How to poll without getting rate-limited.

Read on

What to retry#

Retry, with backoff429, 500, 503

Transient. Respect any Retry-After, back off exponentially, and add jitter.

Do not retry400, 401, 404

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.

CEagle documentation