CLI Authentication
How the bot CLI authenticates humans (OAuth device flow), mints agent bootstrap tokens, and signs a local bot-agent into the control plane — without the dashboard.
The bot CLI is a Go client for the control plane. It lets you manage
projects and agents, install and run a local bot-agent binary, and send
messages — all from the terminal after a one-time device login.
This page covers authentication only. Install and everyday usage: CLI. For the OAuth wire protocol see OAuth 2.0 API. For runtime JWT details see Security.
Three trust boundaries
Keep these separate — they use different credentials and different servers:
| # | Boundary | Actor | Credential | Used for |
|---|---|---|---|---|
| 1 | CLI → control plane | You (human) | OAuth access + refresh JWTs | /api/cli/**, mint bootstrap tokens |
| 2 | bot-agent → control plane | Agent record | Runtime access + refresh JWTs | /api/runtime/** (LLM proxy, workspace) |
| 3 | Caller → local bot-agent | You / your app | Local API key (BOT_AGENT_API_KEY) | http://127.0.0.1:4484/** |
Passwords never enter the CLI. Layer 1 uses the OAuth 2.0 Device Authorization Grant (RFC 8628). Layer 2 starts from a short-lived bootstrap token. Layer 3 never leaves your machine.
you ──bot login──► control plane /api/oauth/*
(device flow)
tokens.json
you ──bot agents / projects──► control plane /api/cli/*
(Bearer access JWT)
you ──bot runtime auth──► local bot-agent /auth/session
(bootstrap token + local API key)
│
└── exchanges ──► control plane /api/runtime/session/exchange
(agent cloud session → cloud.json)
1. Human login (device flow)
Commands
bot login # print code + URL; open browser
bot login --no-open # print only (SSH, CI, remote desktops)
bot auth status # who am I, expiry, server
bot auth status -o json
bot logout # delete stored tokens
Step-by-step
- Authorize — CLI
POST /api/oauth/device/authorizewith{ "client_id": "bot-platform-cli" }. - Display — CLI shows a user code (e.g.
WDJB-MQKG) and a verification URL. The URL is always built from the CLI’s configured server base (https://169.63.180.31.sslip.io/auth/device?code=…), not from the server’s rawverification_uri*fields (those can point at a wrong host ifBETTER_AUTH_URLis mis-set). - Approve — You open the URL, sign in with IBM W3 ID if needed, and approve the device. The device / user codes last 15 minutes.
- Poll — CLI polls
POST /api/oauth/tokenabout every 5 seconds until approved, denied, or expired. - Store — Access + refresh JWTs are written to disk; subsequent
/api/cli/*calls sendAuthorization: Bearer <access_token>.
$ bot login
AIOps Bot Platform — Login
Server: https://169.63.180.31.sslip.io
Open this URL in your browser:
https://169.63.180.31.sslip.io/auth/device?code=WDJB-MQKG
Then enter the code:
WDJB-MQKG
✓ Logged in
Signed in as you@example.com
Role: whitelisted_user
Where state lives
| Path | Purpose | Mode |
|---|---|---|
| Config directory | macOS: ~/Library/Application Support/bot/ · Linux / others: ~/.config/bot/ (or $XDG_CONFIG_HOME/bot) | 0700 |
tokens.json | access_token, refresh_token, token_type, expires_in | 0600 |
config.yaml | server_url, output, insecure, binary_repo | 0600 |
runtime.json | Local bot-agent pid, port, API key, home (after bot runtime start) | 0600 |
Override the directory with --config /path/to/dir (directory, not a file).
Token lifetimes
| Token | Lifetime | Storage / behavior |
|---|---|---|
| Device + user code | 15 minutes | Server device_authorization table |
| CLI access JWT | 7 days | Stateless JWT; Bearer on /api/cli/** |
| CLI refresh JWT | 30 days | JWT + SHA-256 hash in cli_refresh_token; rotated on every refresh |
| Agent bootstrap | 5 minutes | Single-use; response-only |
| Agent access JWT | ~30 minutes | In bot-agent cloud.json |
| Agent refresh JWT | ~30 days | Rotated; hashed at rest on the server |
All JWTs are HS256 with BETTER_AUTH_SECRET. The CLI never verifies
signatures locally — it only decodes claims for bot auth status display. The
control plane always verifies.
Automatic refresh
On HTTP 401 from /api/cli/*, the CLI:
- Calls
POST /api/oauth/refreshwith the stored refresh token. - Persists the new access + refresh pair (old refresh is revoked).
- Retries the original request once.
bot auth status will also attempt a refresh if the access token’s exp is in
the past, so the printed state stays honest.
Choosing the control-plane URL
bot config set server https://your-host.example.com
bot --server https://your-host.example.com login
bot config set insecure true # self-signed lab TLS only
Registered OAuth client_id is always bot-platform-cli.
2. Agent bootstrap tokens (CLI → control plane → agent)
Once you are logged in as a human, you can create agents and mint bootstrap tokens without opening the dashboard:
bot projects create "my-app" -d "…"
bot agents create PROJECT_ID worker -d "…"
# create response already includes bootstrapToken + expiresIn
bot agents bootstrap-token PROJECT_ID AGENT_ID # mint a fresh one later
Requirements:
- Project role write or admin (or global admin) to create agents / mint tokens.
- Bootstrap tokens are single-use, expire in 5 minutes, and cannot be retrieved again. Store them securely or exchange immediately.
3. Signing the local bot-agent into the cloud
The local binary does not use your human OAuth tokens. It needs its own cloud session:
bot runtime install # zip / git release repo / --from-server
bot runtime start # background process; generates local API key
bot runtime auth --project PROJECT_ID --agent AGENT_ID
# or: bot runtime auth --token "$BOOTSTRAP_TOKEN"
bot runtime status # expect health.signedIn=true
What bot runtime auth does:
- (If
--project+--agent) mints a fresh bootstrap token via/api/cli/…. POST http://127.0.0.1:<port>/auth/sessionwith{ "cloudBaseUrl", "bootstrapToken" }, using the local API key.- bot-agent calls
POST /api/runtime/session/exchangeand writes the result to<agent-home>/cloud.json. - Workspace content is synced from the control plane (local state files such as
cloud.json/runtime.jsonare never overwritten by that zip).
After this, bot runtime send "…" can reach the LLM proxy through the agent’s
cloud session.
Local API key (layer 3)
bot runtime start --api-key my-secret # optional; otherwise auto-generated
Callers (including the CLI) must send the key as:
Authorization: Bearer <key>, orX-API-Key: <key>, or?api_key=<key>(WebSockets)
The key is stored in runtime.json next to the CLI config and never sent to
the control plane.
End-to-end without the browser UI
bot login
bot projects create "demo"
bot agents create PROJECT_ID worker
bot runtime install --zip ./bot-agent-macos-universal.zip # or --repo / --from-server
bot runtime start
bot runtime auth --project PROJECT_ID --agent AGENT_ID
bot runtime send "Hello" --wait 2m
bot runtime status
bot logout # when finished on a shared machine
Interactive equivalent: bot tui → project → agent → u (runtime screen), or
dashboard t for the global runtime screen.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
not logged in: run bot login first | No / expired CLI tokens | bot login; confirm with bot auth status |
| Login URL shows an unexpected host | Old CLI or wrong server_url | bot config get server; upgrade CLI (URL is always derived from config) |
| Stuck on “Waiting for approval” | Browser never approved | Open the printed URL; finish W3 sign-in within 15 minutes |
Refresh / invalid_grant | Refresh rotated elsewhere or >30 days old | bot login again |
runtime auth OK but later not signed in | Stale bot-agent overwrote cloud.json from workspace zip | Use a current bot-agent build; re-run bot runtime auth |
| Local HTTP 401 from bot-agent | API key mismatch | Align --api-key with runtime.json; restart with bot runtime start |
| TLS errors to the control plane | Private CA / self-signed | Lab only: bot config set insecure true |
Security checklist
- Treat
tokens.jsonlike a password (permissions0600, never commit). - Bootstrap tokens are as powerful as a first agent login for five minutes — do not paste them into tickets or logs.
--insecuredisables TLS verification to the control plane — not for production.- The CLI does not log access, refresh, or bootstrap token values.
- Revoke access:
bot logout(CLI), regenerate bootstrap + restart agent (runtime), or sign the agent out / delete it in the project.
Related docs
- CLI — install, quick start, command overview
- OAuth 2.0 API — authorize / token / refresh endpoints
- Security — full platform auth matrix
- Getting Started — first conversation (dashboard path)
- Agent API: Getting Started — HTTP API against a running bot-agent
- Runtime API — bot-agent → control plane