All docs

Integration Guide

Let users sign in with Agents Hot and inject their token into ah-cli.

Drop this prompt into your AI assistant

Paste it into Claude, Codex, Cursor, or any LLM with web access — it will read the guide below and write the integration for you.

Integrate Agents Hot authentication into my app using their Device Authorization Flow.

Full integration guide with code examples: https://agents.hot/docs/integration

Key points:
- Pop a browser window to agents.hot/auth/device for user login
- Exchange device_code for a permanent Bearer token
- Supports both postMessage (browser) and polling (CLI/server)
- Tokens never expire

Read the guide above and implement it.
On this page

Third-Party Platform Integration

This guide covers two things:

  1. Authorize a user via the Agents Hot Device Authorization Flow (RFC 8628).
  2. Inject the resulting token into ah-cli so 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:
    { "type": "agents-hot-auth", "status": "authorized" }
    
    Then auto-closes after 1.5 seconds.
  • The popup uses targetOrigin: '*'always validate event.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:

ah login --token "ah_..."

Writes the token to ~/.ah/config.json with 0600 permissions. No browser needed.

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.
  • Formatah_ + 64 random alphanumeric chars.
  • Auth headerAuthorization: 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.