Skip to content

REST API ​

Cloud Only

The Cloud REST API is available at https://api.adhf.dev. Self-hosted users can access a local API — see Self-hosted API.

This page documents the hosted cloud automation surface.

It does not try to cover:

  • local standalone endpoints such as /api/v1/status
  • self-hosted runtime operator flows such as session-host recovery
  • local terminal mux endpoints such as /api/v1/mux/*

For those, use the OSS self-hosted docs instead.

This page is an intentional public summary, not a trimmed copy that drifted. A fuller internal reference covering admin-gated and internal endpoints is maintained privately; where the two disagree, the internal reference is authoritative.

Authentication ​

All API requests require an API key in the Authorization header:

bash
curl -H "Authorization: Bearer adk_..." \
     https://api.adhf.dev/api/v1/daemons

Note: API key issuance via the dashboard is not available in the current release. Existing keys continue to work. Contact support if you need access.

For normal cloud automation, API keys are the main auth surface.

Some sensitive cloud-account mutations now require a recent browser login instead of API-key auth. Those routes expect a fresh dashboard JWT session and return 403 RECENT_LOGIN_REQUIRED when called with an API key or a stale session.

Current recent-login protected routes:

  • POST /api/v1/webhooks
  • PATCH /api/v1/webhooks/{webhookId}
  • DELETE /api/v1/webhooks/{webhookId}
  • POST /api/v1/webhooks/{webhookId}/test
  • POST /api/v1/daemons/{daemonId}/disconnect
  • POST /api/v1/daemons/{daemonId}/revoke
  • POST /api/v1/api-keys (create)
  • DELETE /api/v1/api-keys/{keyId} (revoke)

Scopes ​

ScopeDescription
ide:readList daemons, read IDE state
ide:controlLaunch/stop IDEs, send daemon commands
agent:readRead chat messages, agent status
agent:controlSend messages, approve/reject actions
terminal:execExecute terminal commands on machines
webhook:manageInspect deliveries and, with a recent dashboard login, manage webhooks

Public API Shape ​

For most cloud integrations, think in these layers:

  • GET /api/v1/daemons to find connected machines
  • GET /api/v1/daemons/{daemonId}/status to inspect machine and session state
  • /api/v1/shortcuts/* for the common agent actions you actually want
  • /api/v1/webhooks/* for event delivery to your own systems

Prefer Shortcuts unless you specifically need the raw daemon command router.

All daemon and shortcut routes are scoped to machines and sessions owned by the authenticated cloud account. Supplying some other account's daemon or synthetic session id returns 404 instead of crossing account boundaries.


Daemons ​

Manage connected machines (daemons) and their IDEs.

List Daemons ​

http
GET /api/v1/daemons

Scope: ide:read

Returns all connected machines with their managed IDEs and CLIs.

Response:

json
{
  "daemons": [
    {
      "id": "c79baa8f...",
      "hostname": "M1-Server",
      "nickname": "작업용",
      "platform": "darwin",
      "cdpConnected": true,
      "ides": [
        { "id": "c79baa8f:ide:cursor_myproject", "type": "cursor", "cdpConnected": true }
      ],
      "clis": [
        { "id": "c79baa8f:cli:claude-cli", "type": "claude-cli", "name": "Claude Code" }
      ]
    }
  ]
}

Get Daemon Status ​

http
GET /api/v1/daemons/{daemonId}/status

Scope: ide:read

Returns detailed daemon status including IDE/CLI state, workspace info, and agent activity.

Send Daemon Command ​

http
POST /api/v1/daemons/{daemonId}/command

Scope: ide:control

Send a direct command to the target machine.

For most integrations, prefer the Shortcuts API below. POST /daemons/:id/command is the lower-level escape hatch.

Body:

json
{
  "type": "send_chat",
  "payload": { "message": "Hello!" }
}

Available Command Types ​

CommandTargetPayloadDescription
send_chatIDE/CLImessageSend a chat message
read_chatIDE/CLI—Read current chat contents
resolve_actionIDE/CLIactionApprove/reject agent action
screenshotIDEwidth?Take a screenshot
launch_ideDaemonideType, enableCdp?Launch an IDE
launch_cliDaemoncliType, dir?, model?Start CLI session
stop_cliDaemoncliTypeStop CLI session

Not every command in the internal router belongs in public automation material. If you are building a normal integration, stay on the documented command set or the shortcut endpoints.

Execute Terminal Command ​

http
POST /api/v1/daemons/{daemonId}/terminal

Scope: terminal:exec

Execute a terminal command on the daemon machine.

Body:

json
{
  "command": "ls -la",
  "name": "my-terminal",
  "cwd": "/home/user"
}

Disconnect Daemon ​

http
POST /api/v1/daemons/{daemonId}/disconnect

Scope: ide:control

Force disconnect the daemon. The daemon can reconnect automatically.

This is a recent-login protected cloud action. API keys and stale browser sessions receive 403 RECENT_LOGIN_REQUIRED.

Revoke Daemon Token ​

http
POST /api/v1/daemons/{daemonId}/revoke

Scope: ide:control

Revoke the daemon's connection token permanently. Requires running adhdev setup again on the machine.

This is a recent-login protected cloud action. API keys and stale browser sessions receive 403 RECENT_LOGIN_REQUIRED.


Shortcuts ​

Convenience endpoints for the most common agent actions.

Launch IDE / CLI ​

http
POST /api/v1/shortcuts/{daemonId}/launch

Scope: ide:control

Body:

json
{ "type": "cursor" }
json
{ "type": "claude-cli", "dir": "/path/to/project" }

Stop CLI ​

http
POST /api/v1/shortcuts/{daemonId}/stop

Scope: ide:control

Body:

json
{ "type": "claude-cli" }

Send Chat Message ​

http
POST /api/v1/shortcuts/{ideId}/chat

Scope: agent:control

Send a message to an AI agent. The ideId is the machine-scoped target ID returned by the daemon/session status APIs, including synthetic session routes like {daemonId}:session:{sessionId}.

This is the default send path for external automation.

Body (legacy text form):

json
{ "message": "Fix the bug in auth.ts" }

Body (canonical envelope form):

json
{
  "input": {
    "parts": [
      { "type": "text", "text": "Fix the bug in auth.ts" }
    ],
    "textFallback": "Fix the bug in auth.ts"
  }
}

Optional — specify agent type for multi-agent IDEs:

json
{ "message": "Fix the bug", "agentType": "cursor" }

You must provide either message or input.

Read Chat ​

http
GET /api/v1/shortcuts/{ideId}/chat

Scope: agent:read

Read the current chat contents from an agent. Optionally filter by ?agentType=cursor.

Chat Debug Bundle ​

http
GET /api/v1/shortcuts/{ideId}/chat/debug
POST /api/v1/shortcuts/{ideId}/chat/debug

Scope: agent:read

Build a bounded, sanitized debug bundle for the current chat/session. The daemon generates the authoritative provider/parser/session/terminal evidence; POST may include a dashboard frontendSnapshot to supplement it with currently rendered frontend state.

Optional body for POST:

json
{ "agentType": "codex-cli", "frontendSnapshot": { "activeConversation": { "sessionId": "session_..." } } }

The response includes bundle and copy-ready text. Secrets and credentials are redacted by default.

Approve / Reject Action ​

http
POST /api/v1/shortcuts/{ideId}/approve

Scope: agent:control

Approve or reject a pending agent action.

Body:

json
{ "action": "approve" }

or

json
{ "action": "reject", "agentType": "cursor" }

Get Agent Status ​

http
GET /api/v1/shortcuts/{ideId}/status

Scope: agent:read

Returns current status (idle, generating, waiting_approval, error), provider summary metadata/controls when present, and workspace info.

Webhooks ​

Manage webhook subscriptions for real-time event notifications. See Webhooks feature guide for setup instructions and signature verification.

List Webhooks ​

http
GET /api/v1/webhooks

Scope: webhook:manage

Create Webhook ​

http
POST /api/v1/webhooks

Scope: webhook:manage

Body:

json
{
  "url": "https://example.com/hook",
  "events": ["agent:status", "agent:approve_request"]
}

events is an allow-list — see Events below for which names can be subscribed to individually versus only via "*". Use ["*"] to subscribe to all events. The response includes a secret (shown only once) for signature verification.

This is a recent-login protected cloud action. API keys and stale browser sessions receive 403 RECENT_LOGIN_REQUIRED.

Toggle Webhook ​

http
PATCH /api/v1/webhooks/{webhookId}

Scope: webhook:manage

Body:

json
{ "active": false }

This is a recent-login protected cloud action. API keys and stale browser sessions receive 403 RECENT_LOGIN_REQUIRED.

Delete Webhook ​

http
DELETE /api/v1/webhooks/{webhookId}

Scope: webhook:manage

This is a recent-login protected cloud action. API keys and stale browser sessions receive 403 RECENT_LOGIN_REQUIRED.

List Deliveries ​

http
GET /api/v1/webhooks/{webhookId}/deliveries?limit=20

Scope: webhook:manage

Returns recent delivery history with status codes, response bodies, and timing.

Test Webhook ​

http
POST /api/v1/webhooks/{webhookId}/test

Scope: webhook:manage

Sends a test webhook:test event to the webhook URL.

This is a recent-login protected cloud action. API keys and stale browser sessions receive 403 RECENT_LOGIN_REQUIRED.


TURN Credentials ​

http
GET /api/v1/turn/credentials

Issues short-lived TURN/STUN credentials for the dashboard's own P2P connection (a daemon receives its credentials separately, over the WebSocket auth_ok message — this REST endpoint is dashboard-only). Requires an authenticated request (access token); an expired token returns 401 and the caller should fall back to STUN only.

TURN is enabled on every selling plan, including Free — this is a deliberate product decision, not a Free-tier restriction. The response falls back to STUN-only (turn: false) only when a plan has been given turnenabled: false via an admin override, or when the TURN service itself is unconfigured or returns an error.

Response:

json
{
  "iceServers": [ { "urls": "stun:stun.cloudflare.com:3478" } /* + TURN servers when enabled */ ],
  "turn": true,
  "plan": "pro"
}

Successful TURN credentials are cached server-side for 1 hour, so repeated calls within that window return cached: true with the same servers.


Provider Registry ​

http
GET /api/v1/registry/providers
GET /api/v1/registry/providers/{type}
GET /api/v1/registry/providers/{type}/{version}
GET /api/v1/registry/providers/{type}/{version}/download

Public, unauthenticated catalog of published provider packages — daemons use this to sync provider bundles. ?channel=preview opts into the preview namespace; omitting it (or any other value) returns stable. This registry delivery mechanism is a distinct, non-frozen capability, separate from the frozen dashboard UI for browsing providers.

Write endpoints are gated to admins or an API key carrying the narrower registry:publish scope:

http
POST   /api/v1/registry/providers
POST   /api/v1/registry/providers/{type}/{version}/promote
DELETE /api/v1/registry/providers/{type}/{version}

POST /registry/providers publishes a new package and always requires an explicit channel: "preview" in the body — publishing directly to stable is rejected; a stable release only ever happens through the promote endpoint, which references the existing preview version's artifact rather than accepting new bytes. DELETE yanks a version (defaults to the stable channel unless ?channel=preview is given).


Events ​

These events are delivered to webhooks and also drive live cloud notification flows. Webhook events subscriptions are an allow-list, not a free-form list of names you'll receive (normalizeWebhookEvents in packages/server/src/utils/webhook-events.ts):

Individually subscribable:

EventDescription
daemon:connectMachine came online
daemon:disconnectMachine went offline
agent:statusAgent status changed (throttled, max 1 per 10s per daemon)
agent:approve_requestReserved — allow-listed but not currently dispatched by the server

Dispatched but only delivered to webhooks subscribed with "*":

EventDescription
agent:generating_startedAgent started generating a response
agent:generating_completedAgent finished generating (carries duration in whole seconds when the daemon saw the turn start)
agent:waiting_approvalAgent is waiting for user approval
agent:waiting_choiceAgent is waiting on a multiple-choice prompt
agent:stoppedAgent session stopped
monitor:no_progressAgent appears stalled with no progress (legacy alias: monitor:long_generating)
webhook:testExplicit test delivery via POST /webhooks/{id}/test — delivered only to the one webhook being tested, bypasses subscription filtering

Event Payload ​

Every delivery is a JSON body with three top-level fields: event, payload, and timestamp (when the server sent the delivery, epoch milliseconds). For the agent and monitor events (agent:* except agent:status, and monitor:no_progress) the payload looks like this:

json
{
  "event": "agent:generating_completed",
  "payload": {
    "daemonId": "8e1f4c2a9b7d3e5f6a0c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f",
    "daemonType": "adhdev-daemon",
    "event": "agent:generating_completed",
    "timestamp": 1714000000000,
    "targetSessionId": "9b2d7c4e-1f3a-4b5c-8d6e-0a1b2c3d4e5f",
    "providerType": "claude-cli",
    "providerSessionId": "3f2a9c1e-5b7d-4e8a-9c0f-1a2b3c4d5e6f",
    "workspaceName": "/home/dev/projects/myproject",
    "duration": 42,
    "surfaceHidden": false,
    "muted": false
  },
  "timestamp": 1714000000215
}
FieldWhen present
daemonIdAlways — the daemon connection that reported the event
event, timestampAlways — the event name and when the daemon observed it (epoch milliseconds)
targetSessionIdWhen the event concerns one agent session — always for agent:generating_completed and agent:stopped
providerTypeWhen the daemon can resolve the session. The agent provider, for example claude-cli or cursor. Absent otherwise — it never carries the daemon kind
daemonTypeAlways. The kind of daemon that sent the event, currently adhdev-daemon
providerSessionIdWhen the provider exposes its own conversation id
workspaceNameWhen the session has a workspace — the working directory of a CLI agent, or the IDE workspace
durationagent:generating_completed only, in whole seconds, when the daemon saw the turn start
elapsedSecmonitor:no_progress — seconds without visible progress
modalMessage, modalButtonsagent:waiting_approval / agent:waiting_choice — the approval prompt text and its button labels
surfaceHidden, mutedWhen the daemon hosts the session — whether it is hidden or muted on the dashboard

daemon:connect, daemon:disconnect, agent:status, and webhook:test carry their own, different payloads. Apart from the approval prompt text in modalMessage, the payload never includes chat messages, chat titles, or agent responses.


Typical Flow ​

The most common API flow is:

  1. List machines with GET /api/v1/daemons
  2. Pick a target agent from the daemon status payload
  3. Send a message with POST /api/v1/shortcuts/{ideId}/chat
  4. Poll GET /api/v1/shortcuts/{ideId}/status or use webhooks
  5. Read the transcript with GET /api/v1/shortcuts/{ideId}/chat

What This Page Excludes ​

If you are looking for any of these, this is the wrong API page:

  • standalone GET /api/v1/status
  • standalone POST /api/v1/command
  • runtime snapshot/events endpoints for hosted local CLI sessions
  • terminal mux workspace endpoints

Those belong to Self-hosted API.


Rate Limits ​

Per-Request Limits ​

LimitValue
API Key120 requests / minute
IP address60 requests / minute
Auth login/callback endpoints100 requests / 5 minutes
Command timeout60 seconds

Monthly Call Limits ​

PlanMonthly API Calls
Free1,000
Pro50,000
Ultra500,000
EnterpriseUnlimited

Error Codes ​

StatusCodeDescription
400—Bad request (validation failed)
401AUTH_REQUIREDMissing Authorization header
401AUTH_INVALIDInvalid or expired API key
403AUTH_FORBIDDENAPI key lacks required scope
403RECENT_LOGIN_REQUIREDSensitive cloud action requires a fresh dashboard login instead of API-key auth or a stale session
404—Daemon/resource not found, expired invite/share, or target not owned by the authenticated account
429RATE_LIMITEDPer-minute rate limit exceeded
429API_LIMIT_EXCEEDEDMonthly API call limit reached
500—Command send failed (daemon offline)
503—Daemon connected but WebSocket disconnected
504—Command response timeout (exceeded 60s)

Hosted cloud docs live here. Open-source and self-hosted docs live in the OSS repository.