API

/api/oauth (device flow + OAuth 2.1)

RFC 8628 device authorization and the OAuth 2.1 connector flow, for CLI and MCP login.

Two distinct flows issue the bearer JWTs used everywhere else in this API, one for a human at a terminal, one for a client that drives a normal browser OAuth redirect. Both are documented in full, with sequence detail, on MCP → Authentication; this page is the endpoint reference.

Device authorization (RFC 8628), spec'd in openapi.yaml

Used by handoff-app login, mcp-init, and mcp-token.

EndpointPurpose
POST /api/oauth/deviceStarts a device-authorization request. Returns { device_code, user_code, verification_uri, verification_uri_complete }.
POST /api/oauth/tokenPolled with grant_type=urn:ietf:params:oauth:grant-type:device_code + device_code until the user approves in a browser. Returns { access_token, token_type, expires_in } on success, or an authorization_pending/expired_token OAuth error while waiting.

The user approves the request at /cli/device in a browser; the token that comes back is what gets saved to .handoff/cli-auth.json, see Workspace → .handoff/ runtime state.

OAuth 2.1 connector flow, not yet in openapi.yaml

Used by clients that register themselves dynamically and drive a standard authorization-code + PKCE redirect, e.g. a claude.ai Connector. These endpoints exist in code (src/app/app/api/oauth/*) but aren't yet documented in public/openapi.yaml:

EndpointPurpose
GET /.well-known/oauth-authorization-server (authorization-server-metadata)RFC 8414 metadata a client uses to discover the other endpoints below.
GET /.well-known/oauth-protected-resource (protected-resource-metadata)RFC 9728 metadata; the WWW-Authenticate header on a 401 from /api/mcp points here.
POST /api/oauth/registerRFC 7591 Dynamic Client Registration — no manual client setup on Handoff's side.
GET/POST /api/oauth/authorizeThe authorization redirect (response_type=code, code_challenge_method=S256); user approves here.
POST /api/oauth/device/approveServer-side approval step backing the human-facing /cli/device page.

POST /api/oauth/token is shared by both flows, it also accepts grant_type=authorization_code (with code_verifier) and grant_type=refresh_token for this flow, alongside the device-code grant type above.

Both flows issue the same kind of JWT (scp scope claim, audience handoff-api or the legacy handoff-cli-sync), see Auth & scopes for the scope vocabulary shared across everything that accepts one.

On this page