buildd
Features

Device Authentication

Headless login flow for CLI tools and MCP servers using human-readable codes

Device Authentication

Device authentication lets CLI tools and MCP servers obtain API keys without browser-based OAuth redirects. It follows the OAuth 2.0 Device Authorization Grant pattern with a human-readable code.

How It Works

CLI / MCP Server                    Buildd Server                    Browser
      │                                  │                              │
      ├── POST /api/auth/device/code ──→ │                              │
      │   { clientName: "CLI" }          │                              │
      │                                  │                              │
      │ ←── { user_code: "ABCD-1234",    │                              │
      │       device_token: "abc...",    │                              │
      │       verification_url: "..." }  │                              │
      │                                  │                              │
      │   Display code to user           │                              │
      │   "Enter ABCD-1234 at url"       │                              │
      │                                  │                              │
      │                                  │ ←── User visits URL ─────── │
      │                                  │     enters code, approves    │
      │                                  │                              │
      │── POST /api/auth/device/token ─→ │                              │
      │   (polling every 5s)             │                              │
      │                                  │                              │
      │ ←── 428 (pending)                │                              │
      │ ←── 428 (pending)                │                              │
      │ ←── 200 { api_key: "bld_..." }   │                              │
      │                                  │                              │
      │   Store key, authenticated ✓     │                              │
  1. CLI requests a code — POST /api/auth/device/code returns a user code (ABCD-1234 format) and a device token
  2. User approves in browser — visits the verification URL, enters the code while logged in
  3. CLI polls for completion — POST /api/auth/device/token with the device token, receives 428 until approved
  4. API key returned — once approved, the CLI receives a bld_xxx API key (one-time retrieval, then cleared from DB)

Account Level

There are three levels — see Task Access Model:

  • admin — full access: create tasks, manage workspaces and schedules, send agent instructions
  • worker — claim tasks, report progress, open and merge PRs, read/write memory
  • trigger — file and read only; cannot claim or execute
POST /api/auth/device/code
{ "clientName": "MCP Server", "level": "worker" }

Omitting level produces an admin key. The endpoint accepts only the literal strings "worker" and "trigger"; every other input — the field missing, null, "Worker", "ADMIN", a typo — falls through to admin. The device_codes.level column defaults to admin as well. Always pass level explicitly, and pass it lowercase.

The approval flow creates or updates a named account for the user's team with the requested level, matching on (team, account name).

Approval overwrites the level of an existing account with the same name. If a worker-level account named Runner already exists in the team and someone completes a device flow with clientName: "Runner" and no level, that account's API key is rotated and it is promoted to admin. Use a distinct clientName per credential, and always set level.

Security

  • 15-minute expiry — codes expire after 15 minutes if not approved
  • One-time key retrieval — the plaintext API key is cleared from the database after the CLI retrieves it
  • Human-readable codes — ABCD-1234 format excludes ambiguous characters (I, O) to prevent typos
  • Session-gated approval — only authenticated users can approve codes
  • Unauthenticated code request — POST /api/auth/device/code requires no auth; the security boundary is the browser approval step, not the code request

API Reference

MethodEndpointAuthDescription
POST/api/auth/device/codeNoneGenerate user code + device token
POST/api/auth/device/tokenNonePoll for approval (returns 428 if pending)
POST/api/auth/device/approveSessionApprove a code (browser-side)

On this page