Docs
Device login

Sign in from a CLI.

The OAuth 2.0 Device Authorization Grant, for terminals and headless devices with no browser of their own. The device shows a short code, the human approves it somewhere comfortable, and the device receives a token.

A device or CLI with no browser cannot run the normal sign-in card. Instead it uses the OAuth 2.0 Device Authorization Grant (RFC 8628). The tool requests a short code, the user approves it in their browser on elvix, and the tool polls until it receives an eak_ Personal Access Token bound to the approving user. This is the path elvix login and plm login use.

Both endpoints are unauthenticated (no eak_ key needed) and return flat OAuth-shaped JSON, not the Spine ResponseDto<T> envelope. They are not exposed as MCP / agent tools precisely because they take no key. Drive them directly over HTTP, or let the SDK do it for you.

The flow, end to end

  1. Request a code. POST /api/v1/device/code with { client_id } returns device_code, user_code, verification_uri, verification_uri_complete, expires_in, and interval.
  2. Show the user the code. Print user_code and the verification_uri. Tell the user to open it and enter the code, or open verification_uri_complete (the code prefilled) for them.
  3. User approves in the browser. The /device approval card renders <ElvixSignInForm> with the app's Console-configured sign-in methods and branding, so the user signs in (or is already signed in) and approves with one tap.
  4. Poll for the token. POST /api/v1/device/token with { device_code, client_id } every interval seconds until it returns an access_token.

1. Request a code

POST /api/v1/device/code with the app's public clientId. No key required.

bash
curl -X POST https://elvix.is/api/v1/device/code \
-H "Content-Type: application/json" \
-d '{ "client_id": "<your app clientId>" }'
Response
{
"device_code": "<secret the device polls with>",
"user_code": "WDJB-MJHT",
"verification_uri": "https://elvix.is/device",
"verification_uri_complete": "https://elvix.is/device?code=WDJB-MJHT",
"expires_in": 900,
"interval": 5
}

verification_uri is https://elvix.is/device. verification_uri_complete is the same URL with ?code=<user_code> appended so a tool can open the prefilled page for the user. expires_in is the seconds the code stays valid; interval is the minimum seconds between polls.

2. Show the user the code

Display user_code and tell the user to visit verification_uri. The /device page renders <ElvixSignInForm>, so the user signs in with the same methods and branding your app configured in Console (Google, email OTP, passkey), then approves the request for the named device.

3. Poll for the token

POST /api/v1/device/token with the device_code from step 1, every interval seconds. While the user has not finished, it returns HTTP 400 with an OAuth error. Keep polling until you get an access_token.

bash
curl -X POST https://elvix.is/api/v1/device/token \
-H "Content-Type: application/json" \
-d '{ "device_code": "<from step 1>", "client_id": "<your app clientId>" }'
Still pending
// HTTP 400 while the user has not finished:
{ "error": "authorization_pending" }
Approved
// HTTP 200 once the user approves:
{
"access_token": "eak_...", // a Personal Access Token, source=device
"token_type": "Bearer",
"scope": null
}
  • authorization_pending: the user has not approved yet. Keep polling at interval.
  • slow_down: you are polling too fast. Increase your interval and keep going.
  • expired_token: the code's expires_in elapsed, or it was already claimed (codes are single use). Start over from step 1.
  • access_denied: the user declined the request. Stop.

The minted access_token is an eak_ Personal Access Token with source=device, bound to the approving user. The user can see and revoke every device-granted key from their Account at /account/devices (Connected devices). A key minted in Console instead carries source=console.

Let the SDK do it

@elvix.is/sdk ships this flow end to end. The elvix login CLI runs the whole grant (request, prompt, poll, persist the key). If you build your own CLI, the server helpers requestDeviceCode() and pollDeviceToken() from @elvix.is/sdk/server wrap the two endpoints with the correct polling and slow_down back-off. Device-login support shipped in @elvix.is/[email protected].

Once a CLI holds the eak_ key it behaves like any other Application API key: pass it as Authorization: Bearer eak_... to the REST API or the MCP server. See the Developers guide for the REST surface and the MCP guide for agent tooling.