Errors
External API errors return a small JSON envelope:
{ "error": "no_online_device", "message": "No user device is currently online."}error is a stable machine-readable code; message (when present) is
human-readable. Some errors carry extra fields — insufficient_scope
includes a required array of the missing scopes.
Error codes
Section titled “Error codes”| HTTP | error | Meaning / what to do |
|---|---|---|
| 400 | invalid_request, unknown_scope, no_scopes, invalid_url, no_events | Malformed input — fix before retrying. Schema validation failures return code FST_ERR_VALIDATION. |
| 401 | missing_token | No Authorization: Bearer header. |
| 401 | invalid_token | Expired, revoked, malformed, or the app/grant was disabled. |
| 403 | account_unavailable | The account is suspended. |
| 403 | insufficient_scope | Token lacks a required scope (see required). |
| 403 | pat_only_scope | machines:invoke was called with an OAuth token — it requires a PAT. |
| 403 | rejected | The target device declined the relay request (e.g. per-app policy). |
| 403 | no_content_grant | Content endpoint called without a key grant for the resource. |
| 404 | not_found | Resource doesn’t exist or isn’t yours. |
| 409 | session_inactive | Target session is not active. |
| 409 | machine_offline | Target machine is not online. |
| 429 | rate_limited | Quota exceeded — honor Retry-After and the X-RateLimit-* headers. |
| 502 | error | The device reported an internal failure handling the relay. |
| 503 | no_online_device | No device online to serve the relay. Comes with Retry-After: 30. |
| 504 | timeout | Device didn’t reply within 30 s. The operation may still have applied — check before blindly retrying. |
OAuth endpoint errors
Section titled “OAuth endpoint errors”The OAuth protocol endpoints (/oauth/token, /oauth/revoke,
/oauth/introspect) use the RFC 6749 shape instead:
{ "error": "invalid_grant", "error_description": "…" }Codes: invalid_request, invalid_client, invalid_grant,
unauthorized_client, unsupported_grant_type, invalid_scope,
unsupported_response_type, server_error. Note that refresh-token reuse
surfaces as invalid_grant and revokes the whole grant — the user must
re-consent.
Unhandled errors
Section titled “Unhandled errors”Anything unexpected returns the global shape with details masked:
{ "error": "Internal Server Error", "message": "An unexpected error occurred", "code": "internal_error", "statusCode": 500}Retry guidance
Section titled “Retry guidance”- 429 — back off exponentially; respect
Retry-After. - 503
no_online_device— retry after theRetry-Afterwindow once a device is up; this is the normal “laptop is asleep” case. - 504
timeout— verify state before retrying non-idempotent relays. - 500 — retry once with backoff; if it persists, report the
requestId. - Everything else is terminal — fix the request instead of retrying.