Device Authorization

OAuth 2.0 device authorization flow (RFC 8628) for signing in from CLIs, IDEs, and other devices without a browser on the same machine.

The belt and infsh CLIs use this flow by default when you run belt login or infsh login in a terminal. You can also call the endpoints directly — or point any off-the-shelf OAuth 2.0 client library at our standard endpoints (see Standard OAuth endpoints).

No API key is required to start or poll a device authorization. Approving a request requires a logged-in workspace session in the browser.

Authentication · Magic link sign-in · CLI setup


Credential kinds

When you start device authorization, an optional token_kind field selects what the first successful poll returns after browser approval:

token_kindReturned fieldBehavior
sessionsession_tokenRevocable CLI session (30-day expiry by default; configurable at approval). Acts as the signed-in user, supports X-Team-ID team switching, optional API key scopes at approval, and appears in Settings → Sessions. Send as Authorization: Bearer … on API calls.
(omit or api_key)api_keyLegacy 30-day device-scoped API key (CLI, source: device-auth). Still returned for older CLIs that send no request body.

New CLIs should request token_kind: "session". The API key path remains for backward compatibility until older CLIs are retired.


Flow overview

mermaid
1sequenceDiagram2    participant CLI as CLI or client3    participant API as api.inference.sh4    participant Browser as Browser (logged in)56    CLI->>API: POST /device/auth {"token_kind":"session"}7    API-->>CLI: user_code, device_code, approve_url, interval8    CLI->>Browser: Open approve_url (optional)9    Browser->>API: POST /device/auth/approve (session cookie)10    loop Every interval seconds11        CLI->>API: GET /device/auth/poll/{device_code}12        API-->>CLI: status pending | approved | denied | expired13    end14    Note over API,CLI: First approved poll mints and returns credential (one-shot)15    API-->>CLI: session_token or api_key
  1. Your client calls POST /device/auth and receives a short user code and a secret device code.
  2. The user opens approve_url in a browser (or enters the user code at the workspace device-auth page) and approves while signed in. Approval records the authorization only — no credential is created or stored yet.
  3. Your client polls GET /device/auth/poll/{device_code} every interval seconds until the status is approved, denied, or expired.
  4. On the first poll where status is approved, the API mints and returns a credential (one-shot delivery):
    • session_token when the flow was started with token_kind: "session" — a revocable session you send as Authorization: Bearer …. Supports X-Team-ID like a web session. The session records the polling device's IP and user agent (not the approving browser's).
    • api_key when no token_kind was sent (legacy) — a 30-day CLI key named CLI with source: device-auth.

Browser approval only records authorization (user, team, scopes). The credential is minted at poll time and is not stored in the challenge record. A second poll for the same device_code returns status: expired even after a successful delivery.

Codes expire after 300 seconds (5 minutes), including approved-but-never-collected authorizations. The recommended poll interval is 3 seconds.


Standard OAuth endpoints (RFC 8628)

For third-party clients we recommend the standards-compliant endpoints — any OAuth 2.0 library that supports the device authorization grant works unmodified. Endpoints are discoverable via /.well-known/oauth-authorization-server (RFC 8414).

Start: POST /oauth/device_authorization

Authentication: None. Always mints a session token.

bash
1curl -X POST https://api.inference.sh/oauth/device_authorization
json
1{2  "device_code": "368bcb…",3  "user_code": "4XQA-HTM9",4  "verification_uri": "https://app.inference.sh/auth/device",5  "verification_uri_complete": "https://app.inference.sh/auth/device?code=4XQA-HTM9",6  "expires_in": 300,7  "interval": 38}

Exchange: POST /oauth/token

Poll the token endpoint with the device code grant:

bash
1curl -X POST https://api.inference.sh/oauth/token \2  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \3  -d "device_code=DEVICE_CODE_HERE"

While pending, the endpoint returns standard RFC 8628 errors:

ErrorMeaning
authorization_pendingUser has not approved yet — keep polling
access_deniedUser denied the request
expired_tokenCode expired, or the credential was already delivered (one-shot)
invalid_grantUnknown device code

On approval:

json
1{2  "access_token": "0zdzfx…",3  "token_type": "Bearer",4  "team_id": "team_abc123"5}

team_id is an extension member identifying the team the user selected at approval. Send the token as Authorization: Bearer … on subsequent requests.

Revoke a credential (RFC 7009)

POST /oauth/revoke — possession of a token is the authority to revoke it. Works for session tokens and API keys. Unknown tokens still return 200 (per the RFC, so validity can't be probed).

bash
1curl -X POST https://api.inference.sh/oauth/revoke -d "token=TOKEN_HERE"

Start device authorization

POST /device/auth

Authentication: None.

Request (optional body)

FieldTypeRequiredDescription
token_kindstringNosession for a revocable CLI session; omit for a legacy API key
json
1{2  "token_kind": "session"3}

An empty body or omitted token_kind keeps legacy behavior (API key on the first approved poll).

Response

FieldTypeDescription
user_codestringShort code shown to the user (format XXXX-XXXX)
device_codestringSecret code used only for polling (do not display to the user)
approve_urlstringWorkspace URL to open for one-click approval
poll_urlstringConvenience URL (workspace host); clients should poll the API path below
expires_innumberSeconds until the code expires (300)
intervalnumberRecommended seconds between poll requests (3)

Example:

bash
1curl -X POST https://api.inference.sh/device/auth \2  -H "X-API-Version: 2" \3  -H "Content-Type: application/json" \4  -d '{"token_kind":"session"}'
json
1{2  "user_code": "ABCD-2345",3  "device_code": "01HXYZ…",4  "approve_url": "https://app.inference.sh/auth/device?code=ABCD-2345&utm_source=cli&utm_medium=app",5  "poll_url": "https://app.inference.sh/device/auth/poll/01HXYZ…",6  "expires_in": 300,7  "interval": 38}

Poll for approval

GET /device/auth/poll/{device_code}

Authentication: None.

Poll until status is terminal (approved, denied, or expired). Wait at least interval seconds between requests.

The credential is minted by — and delivered only to — the first poll that sees approved. Subsequent polls return expired with no credential. If you miss the response, restart the flow.

Response

FieldTypeDescription
statusstringpending, approved, denied, or expired
session_tokenstringPresent when status is approved and the flow used token_kind: "session"
api_keystringPresent when status is approved and the flow used the legacy API-key path (starts with 1nfsh-)
team_idstringTeam context for the login (when approved)

Only one credential field is set per flow. While status is pending, both are omitted.

One-shot delivery: The first poll that observes status: approved atomically mints the credential and returns it. Later polls for the same device_code return status: expired. If the user approves in the browser but the client stops polling before the code expires, the authorization is lost when the 300-second window ends.

Example:

bash
1curl "https://api.inference.sh/device/auth/poll/DEVICE_CODE_HERE" \2  -H "X-API-Version: 2"
json
1{2  "status": "approved",3  "session_token": "0zdzfx…",4  "team_id": "team_abc123"5}

Using a session token

After approval with token_kind: "session", store the session_token securely and send it on API requests:

bash
1curl https://api.inference.sh/apps/my \2  -H "Authorization: Bearer SESSION_TOKEN_HERE" \3  -H "X-API-Version: 2"

Session tokens are neither API keys (1nfsh-…) nor JWTs. The API resolves them as user sessions via the Authorization header.

Team switching: Send X-Team-ID to act as another team you belong to — same rules as a browser session. See Authentication — Team context.

Revocation: CLI sessions appear in Settings → Sessions (auth_method: device-auth) with the polling device's IP and user agent. The belt and infsh CLIs send an RFC 7231 product token (belt/x.y.z or legacy inference-cli/x.y.z), which the API parses into client name and version for the session record. Revoke sessions from the workspace or via the sign-in sessions API below.


Sign-in sessions

Manage browser and CLI authentication sessions (distinct from app sessions used for stateful POST /run).

Routes are under /auth/ and require an authenticated session or bearer token.

List sessions

GET /auth/sessions

Returns active sign-in sessions for the current user. The session making the request is marked current: true.

FieldTypeDescription
idstringSession ID
created_atstringISO timestamp
expires_atstringISO timestamp
ipstringClient IP
browserstringBrowser or client name
auth_methodstringmagic-link, device-auth, api_key, etc.
scopesstring[]Active API key scopes when auth_method is api_key
currentbooleanWhether this is the requesting session
bash
1curl https://api.inference.sh/auth/sessions \2  -H "Authorization: Bearer inf_your_session_token" \3  -H "X-API-Version: 2"

Revoke one session

DELETE /auth/sessions/{id}

Deletes a specific session. Use another valid session or token to call this endpoint.

Revoke all other sessions

POST /auth/sessions/revoke-all

Deletes every session except the one making the request.

You can also revoke bearer tokens with POST /oauth/revoke (RFC 7009).


Check user code (approval page)

GET /device/auth/code/{code}

Authentication: None.

Used by the workspace device-auth UI to validate a user code before approval. Accepts codes with or without the dash (ABCD2345 or ABCD-2345).

Response

FieldTypeDescription
user_codestringNormalized code
validbooleanWhether the code can still be approved
statusstringpending, approved, or expired
expires_atstringISO timestamp (when known)

Approve device authorization

POST /device/auth/approve

Authentication: Workspace session (browser cookie after SSO or magic link). Guests cannot approve.

Approval records the authorization only (signed-in user, team, optional scopes, and optional credential TTL). It does not return a credential — the client must keep polling. The token_kind from initiation determines what the first successful poll mints:

  • token_kind: "session" — revocable CLI session (30-day expiry by default, or expires_in from approval).
  • Legacy (no token_kind)CLI API key (30-day expiry by default, or expires_in from approval).

For both credential kinds, omit scopes for full access or pass an explicit scope list to restrict what the minted credential can do (same enforcement as API keys). Scoped session tokens are checked on every request the same way as scoped API keys.

Nothing secret is stored server-side at approval; a minted session records the polling device's own IP and user agent.

Request

FieldTypeRequiredDescription
codestringYesUser code from POST /device/auth
team_idstringNoTeam to issue the credential for (defaults to active team)
scopesstring[]NoPermission scopes for the minted credential (session or API key). Omit for full access.
expires_innumberNoCredential lifetime in seconds (default 2592000 — 30 days). Capped at one year.
json
1{2  "code": "ABCD-2345",3  "scopes": ["apps:execute", "billing:read"],4  "expires_in": 72005}

Response

json
1{2  "success": true,3  "message": "Device authorized successfully"4}

Errors include invalid_code, expired, and already_processed when the code is no longer pending.

Workspace approval UI

When approving from app.inference.sh/auth/device, the consent form lets you pick permissions (scopes) and session expiration before authorizing. The expiration picker defaults to 1 year and offers 1 hour through 1 year (sessions cannot be set to never expire). The selected TTL is sent as expires_in on this endpoint. Programmatic callers that omit expires_in still get the 30-day API default.


CLI usage

bash
1belt login                # device flow → session token (supports belt auth switch)2belt login --key 1nfsh-  # API key login — team-pinned, for CI / automation3belt auth token           # print stored credential to stdout (no newline) for scripts4belt me                   # shows auth method: session vs api key (team-pinned)

belt login stores a session token: it acts as you, works with belt auth switch, and shows up in the workspace sessions list where it can be revoked. belt login --key (or INFSH_API_KEY) stores an API key: team-pinned by design — belt auth switch has no effect and will say so.

In non-interactive environments (no TTY, CI set, or BELT_NON_INTERACTIVE=1), device authorization is skipped; pass --key or set INFSH_API_KEY. After login, use belt auth token in subshells: curl -H "Authorization: Bearer $(belt auth token)" ...

Session-start hook: When the belt plugin is installed for Claude Code or Codex, the SessionStart hook calls belt plugin hook session-start. If no credential is configured (including anonymous guest sessions), the hook opens a browser for device authorization, prints the approval URL and user code into session context, and polls for up to 45 seconds. A 6-hour cooldown prevents reopening the browser on every session (~/.inferencesh/hook-auth-cooldown). When already logged in, the hook prints your account email instead.

CLI setup — Login · Coding agents — SessionStart hook


we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.