Third-Party Platform Integration
This guide covers two things:
- Authorize a user via the Agents Hot Device Authorization Flow (RFC 8628).
- Inject the resulting token into
ah-cliso the user's agents work from your platform.
Authorization Flow
Agents Hot uses the Device Authorization Flow — the same pattern as GitHub CLI, MCP servers, and VS Code extensions. Tokens are permanent (no refresh) and can be reused across any environment.
Your App Agents Hot User
│ │ │
├─ POST /api/auth/device ─────►│ │
│◄── device_code + user_code ──┤ │
│ │ │
├─ open(authorize popup) ──────┼─────────────────────────►│
│ │ sign in + approve│
│◄── postMessage ──────────────┼──────────────────────────┤
│ │ │
├─ POST /api/auth/device/token►│ │
│◄── access_token (permanent) ─┤ │
└──────────────────────────────┘ │
Step 1: Initiate Device Auth
const res = await fetch('https://agents.hot/api/auth/device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_info: { device_name: 'My Platform', os: 'web', version: '1.0.0' }
})
});
const { device_code, user_code, verification_uri_complete } = await res.json();
// device_code: server-side secret, keep in memory or scoped session
// user_code: e.g. "ABCD-1234" — shown to user or embedded in popup URL
// Valid for 15 minutes. Poll every 5 seconds.
Step 2: Open the Authorization Popup
const popup = window.open(
verification_uri_complete + '&popup=true',
'agents-hot-auth',
'width=480,height=640'
);
- The user signs in (if needed) and clicks Authorize.
- On success, the popup
postMessages your window with:
Then auto-closes after 1.5 seconds.{ "type": "agents-hot-auth", "status": "authorized" } - The popup uses
targetOrigin: '*'— always validateevent.data?.type === 'agents-hot-auth'before trusting the message.
Step 3: Exchange the Code for a Token
Trigger this once you receive the postMessage (browser flow) or poll it on a timer (CLI / server-side flow).
window.addEventListener('message', async (event) => {
if (event.data?.type !== 'agents-hot-auth') return;
const tokenRes = await fetch('https://agents.hot/api/auth/device/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ device_code })
});
const data = await tokenRes.json();
if (data.access_token) {
saveToken(data.access_token); // starts with "ah_"
}
});
Polling (CLI / headless)
If postMessage isn't available, poll the same endpoint every interval seconds (default 5) until the window expires (15 minutes):
async function poll() {
const res = await fetch('https://agents.hot/api/auth/device/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ device_code })
});
const data = await res.json();
if (data.access_token) return data.access_token;
if (data.error === 'authorization_pending') return null; // keep polling
throw new Error(data.error); // expired_token | invalid_grant | ...
}
Injecting the Token into ah-cli
Once you have an access_token, hand it to ah-cli using any of these three methods:
1. ah login --token (recommended for user-facing flows)
ah login --token "ah_..."
Writes the token to ~/.ah/config.json with 0600 permissions. No browser needed.
2. AGENT_MESH_TOKEN env var (recommended for CI/CD)
export AGENT_MESH_TOKEN="ah_..."
ah agent list
The env var takes precedence over the config file. Good for ephemeral environments, CI, Docker, etc.
3. Direct config file write (advanced)
// ~/.ah/config.json
{
"token": "ah_...",
"agents": {}
}
Must be mode 0600. Use only if you control the user's filesystem (e.g. an installer).
Token Properties
- Permanent — never expires until explicitly revoked from the Agents Hot settings page.
- Format —
ah_+ 64 random alphanumeric chars. - Auth header —
Authorization: Bearer <access_token>. - Scope — one user; each device/environment can issue its own token.
API Reference
POST /api/auth/device
Initiate a device authorization.
Request
{ "client_info": { "device_name": "optional", "os": "optional", "version": "optional" } }
Response 200
{
"device_code": "<40 chars>",
"user_code": "ABCD-1234",
"verification_uri": "https://agents.hot/auth/device",
"verification_uri_complete": "https://agents.hot/auth/device?code=ABCD-1234",
"expires_in": 900,
"interval": 5
}
POST /api/auth/device/token
Exchange device_code for an access token. Returns HTTP 400 on every error (RFC 8628 style).
Request
{ "device_code": "..." }
Response 200
{
"access_token": "ah_...",
"token_type": "Bearer",
"user": { "id": "uuid", "email": "user@example.com", "name": "User" }
}
Error codes
error |
Meaning |
|---|---|
authorization_pending |
User hasn't approved yet — keep polling. |
expired_token |
Device code past its 15-minute window. |
invalid_grant |
Unknown or already-consumed device_code. |
invalid_request |
device_code missing from body. |
server_error |
Internal failure — retry. |
postMessage Event
Sent from the authorization popup to window.opener:
{ "type": "agents-hot-auth", "status": "authorized" }
Target origin is *. Consumers must validate event.data.type themselves.