English | 繁體中文
Use AgentAPI to control Claude Code, AmazonQ, Opencode, Goose, Aider, Gemini, GitHub Copilot, Sourcegraph Amp, Codex, Kimi Code, Pi, Auggie, and Cursor CLI with an HTTP API.
The screenshots show AgentAPI v0.28.0 running the built-in native-demo agent. Its replies, tool
results, models and usage are simulated. See the chat UI guide for features and
animations, or the screenshot guide to reproduce the captures.
Use AgentAPI to:
- Build a unified chat interface for coding agents.
- Provide an MCP server backend that lets one agent control another coding agent.
- Create tools that delegate pull request reviews to an agent.
- Watch many agents from one dashboard: each AgentAPI pushes its tasks, the requests that need you, its messages and heartbeats there with webhooks, and a client that connects to many follows each through its summary.
This fork builds on upstream v0.12.2. It drives Claude Code, Codex and Pi through their own protocols with the native transport by default, and keeps terminal emulation (the PTY transport) for every other agent. Session management and chat features support long-running tasks across transports, and webhooks report them to a central platform. CHANGELOG.md lists what changed, docs/architecture.md describes how it is built, and the full comparison with upstream has the complete commit history.
- Features at a glance
- Quickstart
- Agents and transports
- Chat UI
- Native transport
- PTY transport
- Conversation API
- Integrations
- Security
- Resource usage
- CLI reference
- HTTP API reference
- Architecture
- Development
- Runtime reliability
- Roadmap and long-term vision
Drive coding agents over HTTP
- Control 13 coding agents through their terminal UI with the PTY transport, which turns the screen into messages.
- Drive Claude Code, Codex and Pi through their own protocols with the native transport, which they use by default; agents with an ACP server can use the experimental ACP transport (see Transports).
- Read structured activity (thinking, tool calls and their results, token
usage) from
GET /rich-messagesandGET /timeline. - Messages sent while the agent works wait in a queue that you can edit and reorder; with the native transport they can also steer or interrupt the running turn.
- Each message and the agent's turn on it is a task the server tracks: running, completed, failed, or stopped by a usage limit.
- Follow everything live over server-sent events, with heartbeats and an incremental replay after a reconnect.
Native agents
- Tool approvals, questions, and plans arrive as requests, answered over HTTP or in the chat.
- Switch the permission mode, model and reasoning effort while the agent runs: bypass (the default), auto, ask or plan.
- See how full the context and the rate limits are, and compact the context; the chat suggests it once Claude Code's prompt cache expired.
- A turn a usage limit stops holds the queue until the limit resets, and can go on by itself then.
- A missing or expired login is reported with the command to run, and the agent starts by itself once it is logged in.
- A shared terminal in the agent's working directory, for logins and quick commands.
- Persistent sessions: with
--state-db, the conversation survives an AgentAPI restart, and a native agent resumes its own session. - Open one of the agent's earlier sessions in its working directory, or start a copy of one.
- See what each session and task cost at the model providers' list prices (the API-equivalent cost), subagents included, with totals for today and the last 7 and 30 days.
Chat UI (/chat)
- Tasks read as a transcript, with tool calls as rows, thinking as margin notes and what each task cost, and can be exported as Markdown; the Session Explorer has links, files, a task index, MCP servers, the agent's earlier sessions, Restart and the webhook.
- Lineage: the subagents the agent ran, and what each did, step by step, also while it works.
- Decision cards for terminal dialogs and native requests, menus for the agent's settings, a ring for its context, and slash commands.
- A file viewer with the file, its git diff and a preview of HTML, Markdown and images, which finds a file the agent named relative to a repository below its working directory; TTY mode and the native terminal, also on phones.
- Background alerts, browser notifications, a transcript cache, and recovery from dropped connections.
Operations and integrations
- MCP management for Claude Code, Codex and Pi, with reusable profiles; restarting the agent updates Codex and Pi first.
- Webhooks push tasks, requests, messages and heartbeats to a dashboard that watches many agents, signed with HMAC, and a client that connects to many follows each through one summary stream; with a human-reply API let a bot tell you when the agent stops and pass your answer back.
- Self-update, verified against the release's checksums.
- Security: an API token (the chat signs in with a cookie), the listen address, and the allowed hosts and origins.
-
Install
agentapi:OS=$(uname -s | tr "[:upper:]" "[:lower:]"); ARCH=$(uname -m | sed "s/x86_64/amd64/;s/aarch64/arm64/"); curl -fsSL "https://github.com/k1dav-c/agentapi/releases/latest/download/agentapi-${OS}-${ARCH}" -o agentapi && chmod +x agentapi
Alternatively, you can download this fork's latest binary from the releases page. Upstream
coder/agentapirelease binaries do not include this fork's features. -
Verify the installation:
agentapi --help
On macOS, if you're prompted that the system was unable to verify the binary, go to
System Settings -> Privacy & Security, click "Open Anyway", and run the command again. -
Run a Claude Code server (assumes
claudeis installed on your system and in thePATH):agentapi server -- claude
If you're getting an error that
claudeis not in thePATHbut you can run it from your shell, trywhich claudeto get the full path and use that instead.This drives Claude Code through its own protocol, the native transport: approvals arrive as structured requests, its settings can be switched and the chat has a terminal. To drive its terminal UI instead (the PTY transport, with TTY mode in the chat), add
--transport pty:agentapi server --transport pty -- claude
-
Send a message to the agent:
curl -X POST localhost:3284/message \ -H "Content-Type: application/json" \ -d '{"content": "Hello, agent!", "type": "user"}'
-
Get the conversation history:
curl localhost:3284/messages
-
Try the chat web interface at http://localhost:3284/chat.
The agent type determines screen formatting and available features. Set it with --type, or let
AgentAPI detect it from a command that matches a known type name, such as agentapi server -- claude.
Full paths such as -- /usr/local/bin/claude need an explicit type; unrecognized commands use custom.
| Agent | Type | PTY | Native | Structured activity | MCP management |
|---|---|---|---|---|---|
| Claude Code | claude |
✓ | ✓ | ✓ | ✓ |
| Codex | codex |
✓ | ✓ | ✓ | ✓ |
| Pi | pi |
✓ | ✓ | ✓ | ✓ |
| Kimi Code | kimi |
✓ | |||
| Goose | goose |
✓ | |||
| Aider | aider |
✓ | |||
| Gemini CLI | gemini |
✓ | |||
| GitHub Copilot | copilot |
✓ | |||
| Sourcegraph Amp | amp |
✓ | |||
| Auggie | auggie |
✓ | |||
| Cursor CLI | cursor (cursor-agent) |
✓ | |||
| Amazon Q | amazonq (q) |
✓ | |||
| Opencode | opencode |
✓ | |||
| Any other terminal program | custom |
✓ |
- Structured activity: thinking, tool calls and token usage, read from the agent's session log on the PTY transport (see Structured Claude, Codex and Pi activity) and from its protocol on the native transport.
- MCP management: the Explorer's MCP tab and the
/mcpendpoints (see MCP management). - Agents with an ACP server, such as Kimi Code (
kimi acp), can also use the experimental ACP transport.
Note
AgentAPI detects the agent type from the agent command only when the command is a type name, such as claude, codex, kimi or pi. When you run the agent through a full path or a wrapper script, specify the type explicitly (e.g., agentapi server --type=claude -- /usr/local/bin/claude), or message formatting may break.
--transport chooses how AgentAPI drives the agent. Without it, Claude Code, Codex and Pi use the
native transport, and every other agent the PTY transport, as does a server started with the
deprecated --state-file:
| PTY | Native | ACP (experimental) | |
|---|---|---|---|
| Flag | --transport pty |
--transport native |
--transport acp or --experimental-acp |
| Default for | every other agent, and with --state-file |
Claude Code, Codex, Pi | |
| Agents | all of the above | Claude Code, Codex, Pi | agents with an ACP server |
| How | runs the agent's terminal UI in a terminal emulator and reads its screen | speaks the agent's own protocol: claude -p stream-json, codex app-server, pi --mode rpc |
speaks the Agent Client Protocol over the agent's stdio |
| Tool calls and thinking | from the session log (Claude Code, Codex, Pi) | streamed from the protocol | tool calls as [Tool: …] lines in the reply; no thinking |
| Approvals, questions, plans | terminal dialogs, answered from decision cards or TTY mode | structured requests | approved automatically |
| Steer and interrupt a turn | ✓ | ||
| Settings, usage, compaction | ✓ | ||
| Survives an AgentAPI restart | --state-db: the conversation; the agent starts anew |
--state-db: the conversation and the agent's session |
|
| Terminal in the chat | TTY mode: the agent's own terminal | a shell in the working directory |
agentapi server -- claude # native
agentapi server --transport pty -- claude # Claude Code's terminal UI
agentapi server --state-db ~/.agentapi/myproject.db -- codex
agentapi server -- aider # PTY
agentapi server --type=kimi --experimental-acp -- kimi acp--state-db <file> (native and PTY transports) keeps the conversation in a SQLite file, and a native
agent resumes its session when AgentAPI starts again with the same file; see
Persistent sessions. --terminal (default true, native transport only)
offers a shell in the agent's working directory; see Terminal. --state-file <file> is
deprecated: its conversation is imported into a state database next to it, which keeps it from
then on.
The chat UI at /chat is embedded in the binary and works with every transport on desktops and
phones. It includes searchable tool activity, compact tool-call cards and connection indicators.
The header shows the AgentAPI version and the agent's mark, such as Claude's orange square or Pi's
logo. See the chat UI guide for screenshots and animations.
The chat groups each user request with its response, thinking, tool calls and background activity as a task. You can navigate between tasks in the transcript.
Each task has a timeline on the left: when it started, how long it took and, for Claude Code, Codex
and Pi, what it cost. Your prompt appears in a tinted block labelled "You", followed
by the agent's logo, name, and reply in Markdown. Each tool call occupies one row with its command
($ command), exit code, duration, and output preview. On wide screens, thinking appears as margin
notes.
The server's task determines the displayed state (Working, Done, Failed, Limit reached) and which tool calls and thinking belong to it. A strip above the composer shows the agent's current activity. While the agent waits for an answer, the header, tab title and status strip show "Needs you", and queued tasks are marked as held. Long tasks show their latest 40 steps first.
Each agent message has its own Markdown/raw toggle so you can switch display modes without affecting the rest of the conversation.
The Session Explorer provides:
- links and file paths discovered in the current conversation;
- an index for jumping directly to earlier tasks;
- MCP server configuration without leaving the chat UI;
- a Session tab with the agent's earlier sessions to open or copy, what they cost, and a two-step restart confirmation;
- a Webhook tab that sets up the webhook (open the Explorer with ⌘K / Ctrl+K).
Each task's ⋯ menu previews, copies or exports the task as Markdown.
When Claude Code or Codex runs subagents with the native transport, a header button shows their total and running counts. It opens the Lineage panel, which lists each subagent's task, kind, state and elapsed time. Select one to follow its thinking, tool calls, results and answer as it works. The Steps button on an Agent or spawn_agent tool call opens that subagent directly.
AgentAPI reads the steps from each subagent's log. Claude Code keeps these logs beside the parent
session's log. The panel includes subagents from earlier sessions you open;
a copy of a session starts without the original's subagents. GET /subagents lists them, and
GET /subagents/{id} returns a subagent's task and messages.
Claude Code reports a subagent's state through its Agent tool call. Codex's spawn call returns before the subagent finishes, so AgentAPI reads its state from its own log. Codex encrypts the task sent to a subagent; the panel uses its task name and nickname as the label.
Paste an image into the composer to upload it as an attachment. Pasted text remains text. Image attachments and images in sent messages appear as thumbnails; click one to view the full image. The agent receives the file path.
Items awaiting your response appear above the composer as decision cards: its terminal dialogs on the PTY transport (see Interactive prompt support), its requests on the native transport, and the login card while it waits for a login. Number keys pick options. On a phone a card takes at most 60% of the screen, and a title cut to two lines can be shown in full.
The agent's questions come one step at a time (here the package manager, then the checks and notes) and are sent together. The header, the status line and the tool row show that the agent is waiting for you.
File paths in the chat open the file the agent works on. The file name on a tool call's row (Claude
Code's Read, Write, Edit, MultiEdit and NotebookEdit; Codex's apply_patch and view_image; Pi's read,
write and edit), a path in inline code in the agent's reply (lib/foo.go:12 opens at line 12) and
the Explorer's Files tab open a viewer with two tabs: File, with line numbers and syntax
highlighting, and Diff, with the tool call's own change and the file's uncommitted changes against
HEAD (GET /files/content, GET /files/diff, cut at 1 MiB). Only files inside the agent's working
directory can be opened. An expanded tool call lists its files and shows its change as a diff, with
the raw input a click away. On a phone the viewer is full screen.
A path that isn't in the working directory is looked for below it (GET /files/find), ignoring
case: an agent working in a repository below its working directory (a home directory) names files
relative to the repository. One match opens, with a note naming the path the agent wrote; several
are listed to choose from. The search leaves out hidden directories, dependencies and Go's module
cache unless the path goes into them, and stops after 50,000 entries or 2 seconds.
HTML, Markdown and SVG files have a third tab, Preview, and an image file shows as the image:
- An HTML page renders in an iframe, with the stylesheets, scripts, images,
fonts and media it refers to by relative path inlined (
GET /files/preview; up to 4 MiB each, 16 MiB in all; a note lists what was left out). The iframe is sandboxed without an origin of its own, so the page's scripts run but can't reach the chat, its session or the API; localStorage, sessionStorage and cookies are kept in memory while the preview is open. Script modules that import other files, and requests the page makes for relative URLs, don't load. - A Markdown file opens on its preview: relative images load from the working
directory, and relative links open their file in the viewer (
#L12at its line). - PNG, JPEG, GIF, WebP, BMP, ICO and SVG images show as images
(
GET /files/image).
The chat UI guide also shows the File tab and a Markdown preview.
With the PTY transport, TTY mode is an escape hatch for when the chat view looks wrong: the terminal
button in the header swaps the conversation for the agent's terminal screen (xterm.js) and sends
every key straight to the agent, including arrows, Ctrl/Alt keys, pastes and IME input. The same key
bar as the terminal's gives a phone the keys its keyboard lacks. The font grows with
the window (11–20px); the mirror uses the agent's real terminal width, so start AgentAPI with
--term-width 120 if you want longer lines.
Native requests for tool approval, answers or plan approval appear as cards above the composer,
using at most 60% of a phone's screen. While the agent works, the send menu can steer a message into
the running turn or interrupt it. The toolbar controls permission mode, model and reasoning effort,
and shows context usage and rate limits. Slash commands include /model and /compact.
The terminal button opens a shell in the agent's working directory. TTY mode requires the agent's own terminal, so screen views stay hidden for native and ACP sessions. See Native transport.
While the tab is in the background, its icon shows an amber dot when the agent needs you, a blue dot while it works, and a green dot when a task finishes. Browser notifications can be turned on from the status menu, which also downloads the conversation as JSONL (so does the Explorer's Session tab), with the steps of the subagents a native Claude Code or Codex ran after the agent's own.
The agent's warnings and errors show as notifications that stay until you close them. The server sends its recent ones again on every connection, so the chat remembers the ones you closed, per server: a reload doesn't bring them back.
Long-running browser sessions are protected by SSE heartbeats, automatic reconnection, stale-connection detection, and local recovery of messages that failed before reaching the server. The page title and session header show the current task, agent status, and connection state.
Reopening a long conversation is fast: the chat caches the transcript in the browser (IndexedDB), shows it immediately, and then asks the server only for changes since the last sync.
Native transport uses the agent's structured protocol to receive replies, tool calls, and usage, and to handle approvals, questions, and plans in the chat UI.
Choosing the native transport
Claude Code, Codex and Pi use the native transport by default. --transport native asks for it
explicitly, and --transport pty drives their terminal UI instead:
agentapi server -- claude --model sonnet
agentapi server -- codex -a on-request
agentapi server -- pi --provider openai-codex --model gpt-5.6-luna --thinking low- Claude Code, Codex, and Pi are supported. AgentAPI selects their adapter from
the command name; specify
--typefor full executable paths or wrapper scripts (without it they run ascustomagents, with the PTY transport). - A server started with the deprecated
--state-fileand no--transportkeeps the PTY transport, so command lines written for it still start; see Persistent sessions. - ACP is a separate transport.
--transport acpis equivalent to--experimental-acpand cannot be enabled together with native mode. - For UI development,
--type custom -- native-demoruns a simulated agent with the native transport; see the development guide. With--type claude,codexorpiinstead, the demo poses as that agent: AgentAPI reads that agent's logs for it, for its earlier sessions and subagents.--transport nativewith any other agent type is rejected at startup.
What to expect
- Replies, tool results, and thinking update live as the turn runs.
- Approvals and questions appear as decision cards above the composer; see request handling.
- When the agent is stopped or restarted and doesn't exit within 3 seconds of its input closing, AgentAPI signals its whole process group, which also stops its MCP servers and child commands.
AgentAPI removes environment markers and secrets inherited from a parent agent session while preserving login and provider configuration. See child process environment for the complete list.
Technical details: protocols, streaming, status fields, and shutdown
Agent protocols and thinking
| Agent | Protocol | Thinking |
|---|---|---|
| Claude Code | stream-json and control protocol | Summarized adaptive thinking |
| Codex | codex app-server, using JSON-RPC over stdio |
Detailed reasoning summaries requested each turn |
| Pi | pi --mode rpc |
Streamed as Pi writes it |
Messages come directly from the protocol, without screen parsing or a session-log watcher. Each streaming update replaces the entire rich message, at most 10 snapshots per second per message. The latest state arrives within 100 ms after the stream pauses.
API status
GET /status and status_change events include these fields:
| Field | Meaning |
|---|---|
transport |
native in this mode |
capabilities |
Operations the agent supports |
pending_requests |
Number of requests waiting for a response |
settings |
Current session settings |
usage |
Context and usage information |
terminal |
Whether a separate shell terminal is available |
When the agent is only waiting for a request response, status is stable, as
it is when waiting for a terminal dialog response.
Shutdown
The agent runs in its own process group. Shutdown first interrupts a running turn and waits up to 3 seconds for it to end, so the session log doesn't end in a tool call without result. Then it closes the agent's input so it can exit; if it is still running 3 seconds later, AgentAPI sends SIGTERM to the group, then SIGKILL 2 seconds after that.
Native mode uses the agent CLI's stored login. Log in as the same operating-system user that runs
AgentAPI. You can use the chat's terminal, any terminal on the same machine, or a PTY
session. The login card's Run in terminal button and the /login command both run the login
command in the chat terminal.
- Claude Code:
claude auth loginuses a Claude subscription by default;--consoleselects Anthropic Console API billing. On a machine without a browser,claude setup-tokencreates a token forCLAUDE_CODE_OAUTH_TOKEN. Check the login withclaude auth status. - Codex:
codex login --device-authsigns in with a ChatGPT plan from any device: open the link it shows and enter its one-time code. The login card and/loginrun it, since AgentAPI usually runs on a server without a browser:codex login's browser sign-in redirects the browser tolocalhost:1455on the machine that runs Codex, which a browser elsewhere can't reach.codex loginstill works with a browser on the same machine, andprintenv OPENAI_API_KEY | codex login --with-api-keyuses an API key. Check the login withcodex login status. - Pi: run
/logininsidepi, or set a provider key such asANTHROPIC_API_KEYin AgentAPI's environment.
AgentAPI checks authentication when starting the agent:
- Claude Code uses
claude auth status --json. API keys,CLAUDE_CODE_OAUTH_TOKEN,apiKeyHelper, Bedrock, and Vertex count as authenticated. With--settings, the check is skipped becauseauth statuscannot see anapiKeyHelpersupplied that way. - Codex uses app-server's
account/read. - Pi checks its state: no selected model and no available models indicate a missing login.
If authentication is missing, AgentAPI keeps running and queues messages. GET /status and
status_change report login_required, including the agent, problem, login command, help, host,
and operating-system user. The chat shows these details in a card above the composer, with a button
to copy the command.
AgentAPI retries startup every 5 seconds and detects a completed login automatically. Check
again (POST /login/retry) retries immediately; if a startup check is already running, that check
is reused. Startup warnings appear once rather than on every retry. If the check itself fails, for
example with an older CLI or a wrapper, AgentAPI starts the agent as before.
Authentication can also expire during a session. A turn that fails with a login error reports
login_required in the same way. Recognized errors include Not logged in, HTTP 401,
Invalid API key, an unusable refresh token, Pi's No API key found, and
Authentication failed for "<provider>" after a subscription login expires.
After logging in, select Check again. AgentAPI restarts the agent on the same session and preserves the conversation. A completed model turn also clears the login error; interrupted turns and messages handled by the agent itself do not.
Tool approvals, questions (Claude's AskUserQuestion, Codex's request_user_input, dialogs from
Pi extensions) and plan approvals (Claude's ExitPlanMode) arrive as requests:
GET /requestslists the pending requests and the 50 most recently closed ones, andGET /requests/{id}returns one./eventssends arequest_updateevent whenever one opens or closes, andGET /statuscounts the pending ones inpending_requests. Other transports return an empty list.POST /requests/{id}/respondanswers one:option_idfor approvals and plans (with optionaltextas feedback when the request hasfeedback),answerskeyed by question ID for questions. A request is answered at most once: 409 when it is no longer pending (already answered, withdrawn or expired, also for a request restored from--state-db), 400 when the answer doesn't fit the request.- Each request has a
status(pending,resolved,withdrawn,expired) and aresponse_capability:live(the agent is waiting),message(the agent stopped waiting; answer with a new message) ornot_resumable(it can't be answered any more: the agent that asked exited or was restarted, or stopped waiting for an approval or plan). When the agent stops waiting for a request (it withdraws it, or the turn ends), questions becomemessageand approvals and plansnot_resumable. An answer already on its way still counts if the agent takes it. - Answers to secret questions are stored and reported as
[redacted]; only the agent gets them. - The human-reply API (
/handoff) offers the oldest pending request with numbered choices: a number picks an option, other text answers a single question or goes with the deny option as feedback. A reply answers exactly the request that was offered; if that one was answered or withdrawn meanwhile, the reply issuperseded, and one the request can't take isinvalid.
In the chat, requests appear above the composer as decision cards:
- Approvals show the command and distinct Allow, Always allow, and Deny options. A denial can include feedback.
- Plans render as Markdown and can be approved or returned with feedback.
- Questions appear one step at a time, with single-choice, multiple-choice, typed, or hidden answers. All answers are submitted together.
Number keys select options. They are disabled while you type or while a dialog, menu, or terminal has focus; holding a key does not repeat the action.
Tool rows show whether approval is pending, granted, or denied. Answer
AskUserQuestion, request_user_input, and ExitPlanMode calls on the card.
Requests answered elsewhere or no longer pending disappear from the card. If a
question expires with the turn or is withdrawn, the UI explains that you can
still answer it with a normal message.
While a request is pending, the header and tab title say "Needs you", the tab icon shows an amber dot, the embed status bar's icon turns amber, and a background tab with notifications turned on shows a notification that the agent needs you.
An approval shows what would run and why. Allow runs it once, Always allow stops asking for the rest of the session, and Deny refuses it, with an optional note on what to do instead.
Plans stay above the composer while the agent waits. Read the plan, approve it, or enter feedback
and select Keep planning to ask for changes. The plan shown here is generated by native-demo;
approving it does not change files.
POST /messagetakesdispatch_mode:queue(the default, as on every transport: send the message once the agent is idle),steer(add it to the turn the agent is working on; the reply sayssteered) orinterrupt(stop that turn and send the message next). They need the native transport. While a turn runs they also need the matching capability, otherwise the answer is 400; when no turn is running (also while the agent restarts or waits for a login), both send or queue the message as usual.POST /interruptstops the running turn (409 if there is none, or while no agent runs).POST /queue/{id}/steermoves a queued message into the running turn (409 if no turn is running; the message stays queued).PUT /queuewith{"ids": [...]}reorders the queue. It must list every queued message once (409 otherwise). This works on every transport.- A queued message the agent refuses is tried up to 5 times in all, then dropped with an error that quotes it. One the agent didn't answer in time is dropped at once: it may have reached the agent, and sending it again could run the task twice.
GET /status reports the supported capabilities: steer, interrupt, resume (the session
can be resumed when AgentAPI starts again, see Persistent sessions),
settings (see Settings), compact (see
Slash commands) and images (image attachments, @"<path>"
references to PNG, JPEG, GIF or WebP files up to 10 MB, are sent to the agent as images as well as
paths; to an agent without it, only the paths are sent). While no agent runs (a restart, a wait for
a login), it reports what the last one supported.
In the chat, while the agent works, the send button's menu adds Steer (⌘/Ctrl+Enter) and
Interrupt & send (⌘/Ctrl+Shift+Enter) next to queueing (Enter), and Stop interrupts the turn
(POST /interrupt). Queued tasks can be steered in right away, and moved up and down on every
transport.
You can change the agent's permission mode, model, and reasoning effort while it runs.
GET /settings returns the current settings and available choices, including models and their
supported effort levels.
PUT /settings updates only the fields in the request body, such as {"permission_mode": "ask"} or
{"model": "sonnet", "effort": "high"}. Unsupported values return 400. GET /status and
status_change also include the current settings.
The permission modes are bypass (tools run without asking; the default), auto (the agent's own review decides what may run), ask (tool calls that need approval come as requests) and plan (the agent proposes a plan, which comes as a plan request, before it changes anything):
| Mode | Claude Code | Codex | Pi |
|---|---|---|---|
| bypass | bypassPermissions |
no approvals, no sandbox | how Pi always runs |
| auto | auto |
workspace sandbox, escalations decided by Codex's auto review | — |
| ask | default |
untrusted: asks for every command it doesn't trust |
— |
| plan | plan |
plan collaboration mode | — |
If the agent uses a policy outside these four modes, such as Claude Code's acceptEdits after plan
approval or a custom Codex command-line policy, the UI does not label it as one of these modes.
Claude Code and Pi apply changes immediately. Codex applies them on the next turn, when app-server
accepts settings. AgentAPI reapplies your choices after an agent restart or a new login; with
--state-db, it also restores them after an AgentAPI restart. The UI follows changes the agent
makes itself, such as switching modes after plan approval.
In the chat, the composer's toolbar has a menu for the mode and one for the model and effort (on a phone in a row of their own above the buttons).
The permission control in the composer's toolbar changes how the agent handles approvals, and the
model control chooses a model and its reasoning effort. Swift, Deep, and Plain are demonstration
models supplied by native-demo.
GET /status and status_change report the agent's usage: how full its context window is after
the last turn (context: used and size in tokens) and the account's rate limits (rate_limits:
a label, how much is used, when it resets and, for Claude Code, whether it is close to or at the
limit). Claude Code reports its 5-hour and weekly limits and its context after every turn, Codex its
context and its usage windows, and Pi its context. A new session starts with an empty context; the
rate limits, which are the account's, stay. In the chat, a ring in the composer's toolbar shows how
full the context is, and its menu the limits with the time to each reset.
Claude Code also reports its prompt_cache: how long the model provider keeps the conversation's
cache after its last use (ttl_seconds: an hour on a subscription, five minutes with an API key, on
Bedrock or Vertex) and when it expires (expires_at). Once it expired, the next message has the
model read the whole context again, at the full price and against the usage limits. While the agent
is idle with an expired cache and at least 50k tokens of context, the chat says so above the
composer and offers to compact first; dismissed, the note stays away until the next turn.
When the account reaches a usage limit, the agent stops its turn: Claude Code says "You've hit your
limit", Codex reports usageLimitExceeded, Pi the provider's quota error. GET /status and
status_change then report limit_stop: the agent's message, when it stopped and, if the agent
says, when the limit resets (resets_at, else the reset of the limit rate_limits reports as
reached). Queued messages wait until then instead of failing one after another; a message you send
yourself goes out at once, past them, and ends the stop.
POST /limit/continuewith{"at": "now"}sends a message that asks the agent to go on with the task;{"at": "reset"}schedules it for a minute after the reset (409 without a reset time). The queued messages go after it.DELETE /limit/continuecancels a scheduled continuation; the queue still waits for the reset.DELETE /limitforgets the stop, so the queued messages go out as soon as the agent can take them.--auto-continueschedules the continuation for every stop with a reset time. A continuation the limit stops again is scheduled again.
The stop is kept with --state-db, so a scheduled continuation still goes out after AgentAPI
restarts. In the chat, the stop is a card above the composer with the reset time, Continue at
reset, Continue now, and Send queued tasks, and the queue says it waits for the limit.
AgentAPI provides a shell in the native agent's working directory for logins, git commands and other work. It supports colors, cursor movement and full-screen programs, and fits the window on desktops and phones. Everyone shares the same shell, like a tmux window. It keeps running with no viewers; if it exits, opening the terminal starts a new one. Its environment matches the agent's, with AgentAPI's own variables removed.
Web links are clickable even when wrapped across lines. The last link printed by a command, such as a login URL, also appears above the terminal for opening or copying. TTY mode behaves the same.
The key bar supplies arrows, Enter, Esc, Tab, Shift+Tab, Ctrl+C, Ctrl+R, Ctrl+A, Ctrl+E, Ctrl+U, Ctrl+W, Home, End, Page up, Page down, / - | ~, Alt+↑ and Ctrl+L. Ctrl+D and Ctrl+Z require a second press. The Ctrl and Alt buttons hold the modifier for the next key.
GET /terminalstreams its output as server-sent events (outputevents with base64 data, the first one the recent output to replay;exitwhen the shell exits) and starts the shell if it doesn't run.POST /terminal/input({"data": "..."}) types into it andPOST /terminal/resize({"cols": 120, "rows": 40}) sizes it.- It is on by default with the native transport;
--terminal=falseturns it off. It is as protected as the rest of the API, so use--api-tokenwhen the server is reachable by others.
Native agents do not run their interactive terminal UI. The chat handles these slash commands for them:
/model [model],/effort [effort](/think) and/mode [mode](/permissions) switch a setting by its id or name, or open its menu;/planswitches to the plan mode;/compactcompacts the context;/clear(/new) starts a new conversation;/terminalopens the terminal, and/loginruns the agent's login command in it;/helplists them.
The composer suggests commands as you type their names. Tab completes the first suggestion. Anything
else is sent to the agent as typed, so the agent's own commands (Claude Code's /code-review, Pi's
extension commands) still work. POST /compact compacts the context while the agent isn't working
on a turn: Codex (thread/compact/start) in a turn of its own, Pi (its compact command) before it
answers. Messages sent meanwhile wait in the queue, and the agent's refusal (Pi's "Nothing to
compact") is a 409 with its reason. Claude Code compacts with the /compact message, which the chat
sends it.
--state-db <file> keeps the conversation in a SQLite file: messages, rich messages (tool calls,
thinking), the timeline, requests and the queue, plus the agent's own session ID and the webhook
settings changed through the API. When AgentAPI starts again with the same file, the chat shows the
conversation as it was and queued messages are still queued. A native agent resumes its session, so
it remembers the conversation too; a PTY agent starts anew below it, and the last agent message
keeps its text until the next message. After a clean shutdown browser caches stay valid, because
the conversation keeps its epoch. Without --state-db, every start of AgentAPI is a new
conversation.
agentapi server --state-db ~/.agentapi/myproject.db -- claude- Requests that were open when AgentAPI stopped are marked expired: the agent that asked is gone.
- Uploads are kept next to the file (
<file>.uploads), because messages refer to them by path. - A file belongs to one agent type, transport and working directory; AgentAPI
refuses to start with another one's file. It is locked while AgentAPI runs,
so a second AgentAPI (or
sqlite3) can't open it. - A new file is created readable and writable by its owner only (0600), and missing directories are created (0700).
- Restart (Explorer → Restart or
DELETE /messages) clears the stored conversation and the queue and starts a new session.?restart=trueon the MCP endpoints restarts the agent on its session instead, so the conversation stays; an agent that can't resume its session (resumeis false) starts a new one, and the conversation is cleared. The settings chosen for the session are kept either way. - Writes are batched every 200 ms, so streaming doesn't wait for the disk. After a crash the last of them may be missing, so the conversation gets a new epoch and clients load it again in full. Writes that keep failing are reported as an error.
- A restored native conversation doesn't get
--initial-promptagain. A PTY conversation records the prompt it got and sends it again after a restart only if it changed. - Webhook settings changed with
PUT /webhookor in the Explorer apply after a restart, unless AgentAPI starts with other webhook flags. - The ACP transport doesn't keep its conversation.
--state-file <file>, the PTY transport's former persistence, is deprecated. The conversation it holds is imported the first time into a state database next to it (the same name with.db, or--state-db), which keeps the conversation from then on; the file isn't written anymore.--load-state=falseskips the import, and--save-statehas no effect. SIGUSR1 writes what the state database hasn't written yet.
AgentAPI reads Claude Code, Codex and Pi session logs to list and open earlier sessions in the
working directory. This works without --state-db:
GET /sessionslists them, the latest first, with the one the agent has open (current): each with itsid,title(the agent's or the user's name for it),first_prompt, number ofprompts, and when it started and last changed. Sessions in which the user sent nothing are left out. They are read from Claude Code's projects directory, Codex's sessions and Pi's session directory (CLAUDE_CONFIG_DIR,CODEX_HOME,PI_CODING_AGENT_DIR,PI_CODING_AGENT_SESSION_DIRand a--session-dirpassed to Pi are honored). A session belongs to the directory its log records, also when AgentAPI runs in it through a symlink; Claude Code names some directories alike (/w/a_band/w/a-b), and their sessions are told apart.POST /sessions/{id}/resumerestarts the agent on that session, and replaces the conversation with the session's, read from its log. With{"fork": true}the agent opens a copy of it under a new id (Claude Code's--fork-session, Codex'sthread/fork, Pi's--fork), and the session itself stays as it was. The queue and open requests are cleared, as on a restart; it returns 409 while the agent works on a turn, compacts its context or restarts, and a resume refused so leaves the conversation as it was.
In the chat, the Explorer's Session tab lists the sessions with Open and Start a copy, which ask for a second click.
AgentAPI reads model calls from the agent's session logs and prices their tokens in USD at the providers' list prices. This is an API-equivalent cost: Claude and ChatGPT subscriptions don't charge per token, so it estimates what the same work would cost with an API key.
- Claude Code: every call in the session's log and in its subagents' logs, with the cache writes priced as Anthropic's 5-minute or one-hour cache. Calls Claude Code counts in its own total but doesn't log one by one, such as compacting the context, are added from its cost records as unitemized calls.
- Codex: each turn's token counts, its subagents' included; input read from the cache is priced as a cache read, and the rest as input.
- Pi: the cost Pi reports for each call.
A call a fork copied from another session counts in the session it came from, so totals don't count
it twice. The built-in prices cover Anthropic's and OpenAI's models; --prices <file> adds models or
replaces their prices, in USD per million tokens:
{
"my-model": {"input": 3, "output": 15, "cache_read": 0.3, "cache_write": 3.75},
"gpt-6.1-sol": {"input": 2, "output": 10, "cache_read": 0.1, "cache_write": 2.5,
"tiers": [{"input_tokens_above": 272000, "input": 4, "output": 15, "cache_read": 0.2, "cache_write": 5}]}
}A tier applies to a call whose input (uncached, read from and written to the cache) is above
input_tokens_above. Model IDs are matched ignoring case, a provider prefix (anthropic/), a
context suffix ([1m]) and a release date, else by the longest known prefix
(claude-opus-5-5-x is priced as claude-opus-5-5). A call to a model without a price counts its
tokens but no cost (priced: false).
GET /sessionsgives each session'scost:usd,tokens,calls, the cost per model (models) and per hour (hourly, in UTC), andtotal_cost, the sum of all of them.GET /costgives the open session's cost and its modelcalls(time,model,tokens,usd,subagent_id), from which a task's cost is the calls made from its message until the next.
In the chat, each task shows what it cost under its start time and duration (in its header on narrow screens), with the tokens in the tooltip; the cost is read again when a turn ends, and every 15 seconds while one runs. The Explorer's Session tab shows what each session cost, and what all of them cost today, in the last 7 and 30 days and in all, by your time zone.
AgentAPI runs claude -p headless with stream-json on stdin and stdout. Tool calls carry their
input, result, status and images, and each assistant message has its model, stop reason and token
usage.
The permission mode, model and effort switch with Claude Code's control requests
(set_permission_mode, set_model, apply_flag_settings); its models and their efforts come
from its initialize response, and the model and effort it runs with from get_settings. AgentAPI
adds --allow-dangerously-skip-permissions, without which Claude Code ignores a switch to
bypassPermissions, except where Claude Code refuses that mode (as root outside a sandbox).
Approving a plan offers "Yes, and bypass permissions" whenever the session can switch to it.
- Approvals offer Allow, Deny (with optional feedback for Claude) and, when Claude Code suggests a permission rule, "Allow always", which applies the rule to this session only. Commands Claude Code considers read-only run without asking.
- Questions list Claude's options; any other answer is accepted too.
- Plans show the plan and offer "Yes, and bypass permissions" (when the session can switch to it), "Yes, and auto-accept edits", "Yes, and manually approve edits" and "No, keep planning". Feedback sent with No makes Claude revise the plan and ask again.
Claude Code supports steer (a steered message joins the running turn at Claude's next step),
interrupt (if Claude Code doesn't end the interrupted turn within 10 s, AgentAPI ends it),
resume and images. The session name is the title Claude Code generates after the first prompt;
after /clear the new session is titled too, and it is the one --state-db resumes. A turn ends
when Claude Code reports it is idle, so a background subagent and the turn it wakes up when it
finishes belong to the turn that started it; the subagent's own steps are left out of the
conversation, as in the session log, and its tool call shows as running until it reports.
Command line and environment:
- AgentAPI adds
-p --output-format stream-json --input-format stream-json --verbose --include-partial-messages --replay-user-messages --permission-prompt-tool stdio, and--session-idfor a new session or--resumefor a resumed one. - Unless you choose otherwise it adds
--permission-mode bypassPermissions, so tools run without asking; questions (AskUserQuestion) and plans (ExitPlanMode) still come as requests, and approving a plan offers to bypass permissions. Pass--permission-mode default(oracceptEdits,plan, …) to start in another mode, or switch it while Claude Code runs (see Settings); AgentAPI also adds--allow-dangerously-skip-permissions, so a session can switch to bypass later. Claude Code refusesbypassPermissionsas root outside a sandbox, so when AgentAPI runs as root it adds--permission-mode defaultinstead, leaves out--allow-dangerously-skip-permissionsand logs a warning; setIS_SANDBOX=1in a sandbox, or choose a mode yourself (--allow-dangerously-skip-permissionsalone still makes Claude Code exit as root). It also adds--thinking adaptive --thinking-display summarized(without it thinking is empty). Your--permission-mode,--dangerously-skip-permissions,--thinking,--max-thinking-tokensor--thinking-displaywins. - Flags that conflict with the protocol or the session AgentAPI manages are
rejected at startup:
-p/--print,--output-format,--input-format,-r/--resume,-c/--continue,--session-id,--fork-session,--from-pr,--teleport,--permission-prompt-tool,--permission-prompts,--bg/--background,--cloud/--remote,--desktop,--remote-control/--rc,--sdk-url,--resume-session-atand--resume-drops-turn, also inside a group of short flags such as-pc. Your own copies of--verbose,--include-partial-messagesand--replay-user-messagesare dropped. Everything else (--model,--allowedTools,--mcp-config,--append-system-prompt, ...) is passed through. - Besides removing a parent agent session's variables (see
Native transport), AgentAPI sets
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1andCLAUDE_CODE_ENTRYPOINT=sdk-cli.
Resuming a session keeps Claude's context; Claude Code doesn't replay the earlier messages, so the
chat shows them only from the --state-db file. A session can be resumed once it had a turn; with
--no-session-persistence it can't, and resume is false. Requests still open when Claude Code
exits or restarts can't be answered anymore. Slash commands that need the interactive UI don't work.
AgentAPI starts codex app-server and starts or resumes a thread. Messages, rich messages and the
timeline have the same shape the session-log parser produces, so the chat shows Codex's replies,
reasoning summaries, commands (the command itself, not the bash -lc wrapper), file edits, MCP tool
calls, plan updates and token usage as they stream. Every turn asks for detailed reasoning summaries
unless you choose model_reasoning_summary with -c; whether the model writes any depends on the
model and the reasoning effort.
Codex's command line after codex is handled like this:
-c/--config,--enable,--disable,--code-mode-host,--strict-configand--analytics-default-enabledare passed tocodex app-server.-m/--model,-s/--sandbox,-a/--ask-for-approval(untrusted,on-request,never) and-C/--cdbecome the thread's settings;--dangerously-bypass-approvals-and-sandboxmeans-a never -s danger-full-access. Without them (or-c approval_policy=...,sandbox_mode,approvals_reviewer), the thread runs without approvals or sandbox, the native transport's bypass mode; switch it from the chat or withPUT /settings(see Settings). Codex 0.160 refusesapproval_policy = "untrusted"in its config, so use-a untrustedfor that policy.--no-alt-screen,--no-daemonand--stdioare ignored.- Short flags take attached values as Codex does (
-mo3,-m=o3); a value on a switch (--dangerously-bypass-approvals-and-sandbox=false) is rejected. - A prompt, a subcommand (other than
app-server),-p/--profile,-i/--image,--oss,--local-provider,--search,--add-dir,--approve-for-me,--dangerously-bypass-hook-trust,--remote,--remote-auth-token-env,--worktree,--listen,--help,--versionand unknown flags are rejected at startup with an explanation. Use--initial-promptfor a first message and attach images to messages instead.
Codex supports steer, interrupt, resume and images. Command, file-edit and MCP tool
approvals, permission requests, MCP form elicitations and request_user_input questions arrive as
requests (Codex asks those questions in the plan mode; with codex-cli 0.160,
--enable default_mode_request_user_input lets Codex ask them in the default mode too). The plan
mode is Codex's plan collaboration mode, an experimental part of app-server's API that AgentAPI opts
into: Codex ends its turn with a plan, and AgentAPI asks "Implement this plan?" as the Codex TUI
does. The Yes options (implement_bypass, implement_auto and implement_ask, the mode before
the plan first) leave the plan mode for that permission mode and have Codex implement the plan in
the same turn; No (keep_planning) with feedback sends the feedback back to Codex, still planning;
No alone ends the turn. The other requests' option IDs are Codex's own decisions (accept,
acceptForSession, acceptWithExecpolicyAmendment, decline, cancel, …), nothing is
approved automatically, and requests AgentAPI can't show are declined. A command approval shows the
command that runs, without the shell wrapper; Codex's reading of it (files it reads, searches) is in
the detail. Requests still open when a turn ends are declined (MCP elicitations cancelled), so
neither Codex nor an MCP server keeps waiting. A steered message joins the running turn at Codex's
next model request (the current output keeps streaming); the reply to it is filed under the steering
message. An interrupt ends the turn within milliseconds, and tool calls it cut short show as failed.
The session reference is Codex's thread ID. Resuming it continues the thread with its context; a
turn that was running when the process died comes back as interrupted, approvals that were pending
are gone, and Codex's earlier turns are not loaded into AgentAPI's conversation (the chat shows them
only from the --state-db file). A thread that can't be resumed is replaced by a new one, with a
warning. Codex doesn't name threads itself; a name set elsewhere (thread/name/updated) becomes the
session name. MCP servers managed from the Explorer live in $CODEX_HOME/config.toml, which
app-server reads when it starts; saving them with restart restarts Codex on its thread.
Codex's own slash commands are TUI features; the chat runs /model, /compact and the others in
Slash commands itself, and /review isn't available. Command
output streams as a preview that Codex may leave incomplete, and the full output replaces it when
the command ends.
AgentAPI runs Pi in its RPC mode (pi --mode rpc, JSON over stdin and stdout).
- Flags. AgentAPI adds
--mode rpc --session-id <id>(a newagentapi-<hex>ID for every new session, including after a restart). It refuses options that pick the mode or the session or make Pi exit (--mode,--print/-p,--session,--session-id,--continue/-c,--resume/-r,--fork,--no-session,--export,--list-models,--help/-h,--version/-v),@filearguments, messages (after--or as bare arguments, which RPC mode would ignore) and subcommands (pi install,remove,uninstall,update,list,config,auth,mcp), with an error that names the option. It drops the TUI-only--tui-modeand--use-themewith their values. Everything else is passed on (--provider,--model,--thinking,--session-dir,-e,--approve, extension flags);--flag=valueis split for Pi's built-in options, which Pi only reads in the two-token form. Set the thinking level with--thinking. - Capabilities. Steer (
dispatch_mode: "steer"is sent as a prompt withstreamingBehavior: "steer"), interrupt (POST /interruptsendsabort; the turn ends as interrupted, without the "This operation was aborted" error Pi adds when it aborts during a tool, and if Pi doesn't end the turn within 10 seconds AgentAPI ends it), resume, and images (attached images go to Pi as base64images; reported unless Pi says its model doesn't take images). - Messages. Text and thinking stream into the rich messages as they arrive, bash output streams into the tool result, and the final messages have the same shape as the ones parsed from Pi's session log. Images in tool results (an image file Pi reads) are kept.
- Requests. Stock Pi has no tool approvals. Dialogs that extensions open
(
select,confirm,input,editor) become requests: aselectorconfirmwhile a tool runs is an approval of that tool call (option IDs are the extension's labels), anyconfirmtakesyes/no, and other dialogs are questions (choiceorvalue; optioncanceldismisses them). A dialog Pi times out is withdrawn. Dialogs still open when the turn ends expire with it, like other agents' requests (a question can then be answered with a message), and the extension gets a cancel; interrupting an extension command that waits on a dialog withdraws the dialog. An extension command that runs without a model turn ends with the stop reasonhandled. An extension'snotifywith level error shows as an error, and itssetTitlestands in for the session name until Pi has one; Pi's own name (--name, renames) is the session name. - Resume. Pi's session ID is the session reference. Resuming starts Pi with
the same
--session-id(keep the same directory and--session-dir), so Pi keeps the context; Pi doesn't replay earlier messages, so the chat shows them only from the--state-dbfile. - Shutdown. Stopping AgentAPI (or a restart) aborts a running turn first, so the session log doesn't end in a tool call without result, then stops Pi as described in Native transport.
- Settings. Pi has no permission modes. Its model (
set_model, from the models it has a provider for) and thinking level (set_thinking_level, with the levels each model supports) can change; Pi fits the level to a new model by itself. - Commands. Pi's TUI commands (
/settings,/loginand the like) are unavailable; the chat runs/model,/compactand the others in Slash commands, and extension slash commands work. Log in from the chat's terminal (pi, then/login).
- The agent has no terminal of its own. The chat hides TTY mode, the terminal
prompt card, the live screen tail and "Terminal output" (for ACP too, where
Stop and the answer buttons on tool calls aren't offered either),
rawmessages (keys for the terminal) are refused with 400, and the agent's slash commands that need its interactive UI don't work, apart from the ones the chat runs itself. The terminal is a separate shell. - Restart (Explorer → Restart or
DELETE /messages) replaces the agent with a new one in a new session and clears the conversation and the queue; requests still open becomenot_resumable. The--initial-promptis sent to the new session again, and Codex and Pi are updated first, as with PTY. - Without
--state-db, the next start of AgentAPI is a new session; the agent's earlier session isn't resumed. - Images reach the agent only as
@"<path>"attachments of a message, not as command-line flags (codex -iand Pi's@fileare refused).
The PTY transport works with every agent that has a terminal UI, and is the default for every agent
but Claude Code, Codex and Pi, which use it with --transport pty. For those three it also follows
their session logs for structured activity, and the chat answers their terminal dialogs.
AgentAPI runs an in-memory terminal emulator. It translates API calls into appropriate terminal keystrokes and parses the agent's outputs into individual messages.
There are two types of messages:
- User messages: sent by the user to the agent
- Agent messages: sent by the agent to the user
To parse individual messages from the terminal output, we take the following steps:
- The initial terminal output, before any user messages are sent, is treated as the agent's first message.
- When the user sends a message through the API, a snapshot of the terminal is taken before any keystrokes are sent.
- The user message is then submitted to the agent. From this point on, any time the terminal output changes, a new snapshot is taken. It's diffed against the initial snapshot, and any new text that appears below the initial content is treated as the agent's next message.
- If the terminal output changes again before a new user message is sent, the agent message is updated.
AgentAPI removes two common TUI elements from replies:
- Echoed user input: lines containing the text of the user's last message.
- The input box: trailing lines with common TUI elements such as
>or------.
Message splitting should still work because it doesn't depend on the TUI structure. Removing TUI elements may need an update; until then, replies may include extra interface text.
In addition to parsing terminal snapshots, this fork can watch Claude, Codex and Pi session logs. This provides structured thinking blocks, tool invocations, tool results, usage information, and stable message identifiers that cannot always be reconstructed reliably from terminal output alone.
GET /rich-messagesreturns the structured conversation.GET /timelinereturns normalized session events suitable for export, auditing, or building another UI. With?subagents=true(native Claude Code and Codex), the events of the subagents the agent ran follow, read from their own logs: each subagent's in turn, starting with a system event, and each event with itssubagent_idand, for Claude Code, theparent_tool_use_idof the Agent tool call that started it. The agent's own events only have the subagent's prompt and final answer.- Background and delegated tasks remain visible with their running, completed, or failed state and output details.
- Claude thinking blocks are rendered inline as collapsible sections in the task timeline.
- Codex is started with
-c model_reasoning_summary="auto"(unless you set it yourself) so its log contains readable reasoning summaries instead of only encrypted reasoning.
Terminal parsing remains the fallback for other agents and for environments where a session log is unavailable. With the native transport, the same endpoints are filled from the agent's protocol instead of its session log.
The chat UI detects interactive TUI prompts — such as Claude Code's plan approval dialog or
permission confirmation, Codex's approvals, and Pi's project trust prompt — and docks them above the
composer as a decision card. Number keys pick an option. Multi-select questions (Claude Code's
AskUserQuestion with multiSelect) show checkboxes and a Continue button that moves on to the next
question or the review step. A numbered list in the agent's answer is not mistaken for options.
GET /status reports the prompt as terminal_prompt. With the
native transport there are no terminal prompts; approvals, questions and plans
arrive as structured requests instead.
This fork adds the kimi agent type, automatic detection for the kimi executable, chat UI
labeling, terminal message formatting, readiness detection, and tests for its startup state.
Kimi can use the regular interactive PTY transport:
agentapi server -- kimiIt can also use Kimi's native ACP server after completing /login once:
agentapi server --type=kimi --experimental-acp -- kimi acpWith --transport pty, the pi agent type runs the Pi coding agent (npm install -g @earendil-works/pi-coding-agent) in its interactive terminal UI. It is auto-detected
when the executable name is pi:
agentapi server --transport pty -- piLog in once with /login in Pi (TTY mode in the chat UI works), or set a provider key such as
ANTHROPIC_API_KEY. AgentAPI follows Pi's session log (~/.pi/agent/sessions/, or
$PI_CODING_AGENT_DIR / $PI_CODING_AGENT_SESSION_DIR) for structured messages, so the chat shows
Pi's tool calls, thinking and token usage like it does for Claude Code and Codex.
Pi's dialogs (such as the project trust prompt) can be answered from the chat, and the header and
tab icon show Pi's logo. The Explorer's MCP tab manages Pi's user-level servers in
~/.pi/agent/mcp.json, and restarting the agent runs pi update --self first.
AgentAPI starts Pi with its own --session-id (a new one on every start or restart) so it follows
the right session log even when several Pi processes share a directory. If you pass --session,
--continue, --resume or --session-id yourself, AgentAPI uses your session settings; with
--continue/--resume the chat falls back to the terminal output.
Without --transport pty, Pi runs in its RPC mode; see Native transport.
These endpoints work with every transport, except where noted; the HTTP API reference lists them all.
POST /messagesends a message:{"content": "Hello, agent!", "type": "user"}. A 200 means AgentAPI saw the agent start on it, or queued it (queued) while the agent is busy;dispatch_modechooses what happens while it is busy (see Steer, interrupt and the queue)."type": "raw"types keys into the agent's terminal instead (PTY transport).GET /messagesreturns the conversation;GET /rich-messagesandGET /timelinereturn its structured form (see Structured Claude, Codex and Pi activity).- A message that went out but wasn't confirmed (the call timed out, or AgentAPI was stopping) may have reached the agent: it isn't sent again, but leaves the queue with an error.
DELETE /messagesclears the conversation (messages, rich messages, requests, tasks, timeline, errors and the queue) and restarts the agent (with the native transport, in a new session). With the ACP transport, which can't restart its agent, it returns 400. While the agent restarts, messages wait in the queue; raw keys, another restart and opening a session get 409, and so does a restart while AgentAPI stops.GET /statusreturns the backward-compatiblestable/runningstatus, the detailed lifecycle (starting,ready,running,restarting,exitedorfailed), the transport,server_id(a random identifier of this AgentAPI run, new at every start),turn(how many times the agent started working), the conversation'sepoch, the session name, the AgentAPI version, the agent's open dialog (terminal_prompt) and the terminal width (terminal_columns). With the native transport it also reportsagent_session_id(the agent's own session, whichGET /sessionslists),capabilities,pending_requests,settings,usage,terminalandlogin_required.GET /tasksreturns the conversation's tasks.GET /titlereturns the session's human-readable title.POST /uploadstores a file (up to 10 MB; a body over 11 MiB is refused before it is read) and returns its path, which a message references as@"<path>".
AgentAPI groups the conversation into tasks, for every transport: a task is a message of yours and the agent's turn on it. Messages you add while it runs (steering) belong to it; the next message after it ends starts the next task. A task ends when the agent is idle again with no request or terminal dialog open. When answering a request or dialog leaves the agent idle, AgentAPI waits 3 seconds before ending the task, since the agent usually works on.
GET /tasks lists them, the earliest first, each with:
id: the ID of the message that started it, as inGET /messages;status:running,completed,failed(the agent reported an error or exited;errorssays what) orlimited(a usage limit stopped it;limithas the stop);prompt(the start of the message),started_atandended_at;reply_id: the agent's last message of the task;steered: the IDs of the messages added while it ran.
Each rich message carries the task_id of the task it began in, so a client groups the tool calls
and thinking by task without guessing from timestamps; a resumed session's rich messages have none.
Messages restored or opened from before AgentAPI started are history: their tasks are completed. The
state database keeps the tasks, and a task that ran when AgentAPI stopped is
over after a restart. /events?sync=1 sends task_update when a task starts or changes, and the
chat and the webhook's task.* events use the same tasks.
Messages submitted while an agent is busy are placed in a FIFO queue instead of being rejected. Queued messages can be inspected, edited, or deleted before delivery through both the API and chat UI.
GET /queuelists the queued messages.PUT /queue/{id}edits a queued message andDELETE /queue/{id}removes it.PUT /queuewith{"ids": [...]}reorders the queue; it must list every queued message once.POST /queue/{id}/steermoves a queued message into the turn the agent is working on (native transport).- With
?sync=1,/eventssends aqueue_updatewith the whole queue whenever it changes, so a client sees what another queued without asking.
GET /events streams server-sent events: message_update, status_change,
rich_message_update, agent_error, request_update (native transport) and a heartbeat every
15 seconds, so clients can detect a lost connection. With ?sync=1 the stream starts with a
session_sync event, adds task_update and queue_update, and every update carries a seq;
reconnecting with &since=<seq>&epoch=<epoch> replays only what changed. Clients without sync
don't get those three events: some (such as agentapi-sdk-go) reject event types they don't know.
With ?sync=1, events also carry SSE IDs (the end of the replay has the replay's seq, later
changes their own), so a plain EventSource that reconnects by itself resumes from its
Last-Event-ID; put the epoch from GET /status in the URL. When a message is taken back (a
turn the agent refused to start), the conversation gets a new epoch and the server ends the event
streams (screen streams excepted): clients reconnect, and with their old epoch get the whole state
again. Opening a session with more than 256 rich messages ends them too, rather than streaming it.
Use webhooks to send tasks, requests, messages and heartbeats to a receiver, the summary to monitor several AgentAPIs, the human-reply API to answer the agent through a bot, and MCP management to configure the agent's tools.
To display several AgentAPIs in a sidebar or dashboard, read one summary response or stream per instance:
GET /summaryreturns theinstanceidentity (as in webhook events:id,name,server_id, host, port, directory, agent andurl),status,lifecycleand atitlefrom the session name or the start of the latest prompt. It also includes the latest task, task count,pending_requests,queued_messages, the open terminal dialog,login_required,limit_stopandusage(context window and account limits).attentionidentifies why the agent needs you, in priority order:login,request(approval, question or plan),dialog(in the terminal),limit(a usage limit stopped the turn),exited, ornone.GET /events?only=summarysendssummary_updateon connection and when the summary changes, plus heartbeats. It omits messages and screen updates, allowing a client to follow dozens of instances. Reply text growing doesn't change the summary; task starts and ends, requests, status changes and queue changes do. Other/eventsclients don't receivesummary_update.
Set --instance-id to identify the instance (default <hostname>:<port>), --name for its display
name (default the working directory's name), and --public-url for its address. PUT /webhook can
change the ID and URL at runtime. Webhook events carry the same instance identity, so clients
using webhooks or direct connections can identify instances consistently.
For a browser client on another origin, add that origin to
--allowed-origins. To send an API token, read the
stream with fetch and an Authorization header; EventSource can't set that header.
AgentAPI POSTs JSON events to --webhook-url, for example to a dashboard monitoring several agents.
It sends only run.status_changed by default. Select events with --webhook-events by type
(task.completed), group (task.*) or all events (*):
| Event | Sent when | data |
|---|---|---|
run.status_changed |
The agent changes between running and stable |
status, previous_status, server_id, agent_type, transport |
instance.started, instance.heartbeat, instance.stopped |
AgentAPI starts, every --webhook-heartbeat (60s), and shuts down |
What the agent does: status, lifecycle, session_name, pending_requests, queued_messages, and agent_session_id, terminal_prompt, login_required, limit_stop, usage, settings when set |
task.started |
A task starts: a message of yours reaches the agent | The task as GET /tasks has it: id (the message's), status, prompt, started_at |
task.completed, task.failed |
The task ends: completed, or failed and limited (task.failed) |
The task with status, ended_at, duration_seconds, the agent's last message (reply, reply_id), steered, errors, limit, session_name, and for native Claude Code, Codex and Pi its API-equivalent cost (see Session costs) |
request.opened, request.closed |
An approval, question or plan (native; the request as GET /requests has it), or a terminal dialog (PTY; kind terminal_prompt with the prompt), opens or closes |
The request |
login.required, login.completed |
The native agent needs a login, or no longer does | agent, message, command to run |
limit.stopped, limit.cleared |
A usage limit stopped a turn, or the stop is over | message, stopped_at, resets_at |
agent.error |
The agent reported an error or a warning | message, level, time |
message.created |
A message of yours or a final message of the agent's | id, role, content (cut at 64 KiB, with truncated), time |
tool.completed |
A tool call returned | tool_use_id, name, input, result and is_error, each cut at 4 KiB |
Every event names the AgentAPI it comes from, so one receiver can tell many apart:
{
"id": "unique-delivery-id",
"type": "task.completed",
"created_at": "2026-10-22T12:00:00Z",
"instance": {
"id": "my-host:3284",
"server_id": "agentapi-run-id",
"hostname": "my-host",
"port": 3284,
"pid": 4242,
"cwd": "/home/me/project",
"agent_type": "claude",
"transport": "native",
"agentapi_version": "1.0.0",
"url": "https://agent.example.com",
"workspace_url": "https://coder.example.com/@me/dev"
},
"data": {
"id": 4,
"prompt": "Fix the failing test",
"started_at": "2026-10-22T11:58:30Z",
"ended_at": "2026-10-22T12:00:00Z",
"duration_seconds": 90,
"status": "completed",
"reply_id": 5,
"reply": "Fixed: the test compared times in two time zones.",
"cost": {"usd": 0.42, "tokens": {"input": 12, "cache_read": 81000, "cache_write": 4200, "output": 1900}, "calls": 6},
"session_name": "Fix the failing test"
}
}The instance id is --instance-id, by default <hostname>:<port>, and name is --name, by
default the working directory's name; url is
--public-url, where the receiver can reach this AgentAPI to open its chat or call its API, and
workspace_url the Coder workspace it runs in, if any. Messages and tool calls from before AgentAPI
started, or of a session it opened from the agent's history, are not sent again.
agentapi server \
--webhook-url https://dashboard.example.com/agentapi/events \
--webhook-events 'task.*,request.*,login.*,limit.*,instance.*' \
--webhook-secret "$WEBHOOK_SECRET" \
--public-url https://agent.example.com \
-- claudeEach request includes X-AgentAPI-Delivery, X-AgentAPI-Event, X-AgentAPI-Timestamp and
X-AgentAPI-Instance headers. With --webhook-secret, X-AgentAPI-Signature-256 signs it:
sha256= and the hex HMAC-SHA256, keyed with the secret, of the timestamp, a . and the body. A
receiver checks it, and that the timestamp is recent, before trusting the event:
expected = "sha256=" + hmac.new(secret, f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, signature) and abs(time.time() - int(timestamp)) < 300Events are delivered in order, one at a time. Network errors, timeouts and HTTP 5xx, 408 or 429
responses trigger retries after 1, 2, 4… seconds, capped at 30 seconds. Other 4xx responses aren't
retried. Each delivery allows --webhook-max-attempts (3) attempts, each with a
--webhook-timeout (10s) timeout; exhausted deliveries are logged and discarded.
The queue holds 1,024 events and drops new ones when full. A receiver that misses events should
read the current state from the API or the next heartbeat. On shutdown, AgentAPI waits up to
5 seconds to deliver instance.stopped.
An optional Go text/template (--webhook-payload-template) reshapes the body for the receiver,
such as a chat service's incoming webhook. Available fields are .ID, .Type, .CreatedAt,
.Instance and .Data (the event's data with its JSON field names, such as {{.Data.prompt}}), and
for run.status_changed .ServerID, .Status, .PreviousStatus, .AgentType and .Transport.
A template that uses another field (such as .RunID, named .ServerID since 1.0) is refused at
startup and by PUT /webhook, since it would fail every delivery.
All flags have AGENTAPI_ environment variables (AGENTAPI_WEBHOOK_URL, AGENTAPI_WEBHOOK_EVENTS
with the events separated by spaces, AGENTAPI_WEBHOOK_SECRET, and so on). GET /webhook shows the
configuration (whether a secret is set, never the secret) and PUT /webhook changes it without a
restart; its events, secret, heartbeat_seconds, instance_id and public_url are kept when
left out.
The Explorer's Webhook tab configures the receiver URL, event groups, signing secret, instance ID, heartbeat and public URL. Delivery and payload contains the timeout, attempt count and template. You can configure a webhook here even if AgentAPI started without one.
Changes made here or through PUT /webhook last until AgentAPI restarts. With
--state-db, they survive restarts unless different webhook flags override
them. Send test event (POST /webhook/test) sends one signed webhook.test event to the saved
URL, regardless of the event selection, and shows the receiver's response.
The run-status event, which AgentAPI sends when --webhook-events isn't given:
{
"id": "unique-delivery-id",
"type": "run.status_changed",
"created_at": "2026-07-26T12:00:00Z",
"instance": {"id": "my-host:3284", "server_id": "agentapi-run-id", "agent_type": "claude", "transport": "pty"},
"data": {
"server_id": "agentapi-run-id",
"status": "stable",
"previous_status": "running",
"agent_type": "claude",
"transport": "pty"
}
}Another client, such as a chat bot, can answer the agent without the web UI. While the agent waits
for you, GET /handoff returns what it waits on, with an id and a kind: its latest response
(message), the terminal prompt it is showing (terminal) or, with the native transport, the
oldest pending request (request). Otherwise it returns {"request": null}.
POST /handoff/reply with that request_id, a unique reply id and the content answers it. A
reply to a response is sent as a normal user message, and a reply to a request answers it as
described under Requests. A terminal prompt takes a
displayed option number, enter or esc, or free text where the prompt has a text field (such as
Claude Code's plan feedback). The outcome is applied, superseded (no longer open, for example
because it was answered in the web UI), invalid (the reply doesn't fit; it stays open) or
uncertain, and nothing is answered twice. Combined with the webhooks above, an
integration can tell you when the agent stops and pass your answer back.
Claude, Codex and Pi MCP servers can be managed while AgentAPI is running. The implementation
preserves unrelated settings in .mcp.json (Claude), $CODEX_HOME/config.toml (Codex) or
~/.pi/agent/mcp.json (Pi).
The API and Session Explorer support:
- reading or replacing the complete MCP server map;
- creating, updating, and deleting individual servers;
- checking remote HTTP connectivity and resolving local stdio executables;
- saving, importing, exporting, and applying reusable MCP profiles;
- optionally restarting the agent to apply changes immediately.
When an agent is restarted, AgentAPI and its HTTP/SSE clients remain online. A PTY agent receives a
new process and session-log watcher, although its previous in-memory conversation context is not
retained. For MCP changes, a native agent reloads the same session and preserves the conversation
(an agent that can't resume its session, with resume false, starts a new session instead, and the
conversation is cleared). A reload during an active turn returns 409 because it would interrupt that
turn. Before a restart from the Explorer or DELETE /messages, AgentAPI updates Codex
(codex update) and Pi (pi update --self) when launching those CLIs directly. If an update
fails, AgentAPI restarts the installed version. Claude Code manages its own updates.
AgentAPI has no users or roles: whoever can reach the API can do what the agent can. Out of the box
it takes no token and accepts any connection whose Host header names the machine itself
(localhost, 127.0.0.1, [::1]), on every network interface. The host check keeps web pages
from calling it through DNS rebinding; it doesn't stop a program on the network, which can send any
Host header. Protect the server before anyone else can reach the port. When it takes no token and
listens on an address other machines can reach, AgentAPI logs a warning at startup.
All API endpoints can be protected with a Bearer token. Authentication is disabled by default for backward compatibility.
# Auto-generate a random token (printed to stderr on startup)
agentapi server --api-token -- claude
# Use a specific token
agentapi server --api-token=my-secret -- claude
# Via environment variable
AGENTAPI_API_TOKEN=my-secret agentapi server -- claudeWhen enabled, every API request must include Authorization: Bearer <token> or the chat UI's
session cookie. Static file routes (/, /chat/*) are exempt so browsers can open the chat UI
without a token. Tokens are compared in constant time.
The chat UI signs in by trading the token for a cookie, since browsers can't add an Authorization
header to the event stream or to images:
- Open
http://localhost:3284/chat/embed#token=<token>. With an auto-generated token the server prints this link on startup. The token is in the URL fragment, which the browser never sends to the server, a proxy or an access log; the page exchanges it and removes it from the address bar. A link to a server without--api-tokenjust opens the chat. - Or just open the chat: when the server answers 401 it asks for the token. The dialog names the server that asks and warns when it isn't the page's own origin, and says so when the browser didn't keep the session cookie. After Cancel, Reconnect now asks again.
POST /auth/session with Authorization: Bearer <token> sets the cookie
(agentapi_session_<hash>, HttpOnly, SameSite=Strict, Path=/, and Secure over HTTPS or behind a
proxy that sends X-Forwarded-Proto: https). It holds an HMAC of the token, not the token, so
changing the token signs every browser out, and the name comes from it, so servers with different
tokens on one host keep their own sign-ins. DELETE /auth/session signs out, and
GET /auth/session (no credentials needed) reports auth_required and authenticated. Because
the cookie is SameSite=Strict, pages on other sites can't make requests with it. A chat UI served
from another origin (e.g. bun run dev on port 3000) needs that origin in --allowed-origins;
--allowed-origins '*' doesn't allow requests with cookies. Ports don't make another site, and the
default allowed origins include http://localhost:3000 and http://localhost:3001 for chat
development: on a shared machine, set --allowed-origins to the origins you use, so a signed-in
browser's cookie only works for them.
By default, the server listens on all network interfaces. Use --bind (or AGENTAPI_BIND) to
listen on one address only, e.g. loopback so nothing else on the network can connect. It takes an IP
address or hostname without a port; the port stays --port.
agentapi server --bind 127.0.0.1 -- claude
agentapi server --bind ::1 -- claude # IPv6 loopback
AGENTAPI_BIND=127.0.0.1 agentapi server -- claude--bind decides who can connect; --allowed-hosts below decides which Host headers are accepted
from those who can.
By default, the server only allows requests with the Host header set to localhost, 127.0.0.1
or [::1]. To host AgentAPI elsewhere, set AGENTAPI_ALLOWED_HOSTS or --allowed-hosts.
Hosts must be hostnames only (no ports); the server ignores the port portion of incoming requests when authorizing. This
checks the Host header only; to keep the server off the network altogether, also listen on
loopback with --bind 127.0.0.1 (see Bind address).
To allow requests from any host, use * as the allowed host.
agentapi server --allowed-hosts '*' -- claudeTo allow a specific host, use:
agentapi server --allowed-hosts 'example.com' -- claudeTo specify multiple hosts, use a comma-separated list when using the --allowed-hosts flag, or a
space-separated list when using the AGENTAPI_ALLOWED_HOSTS environment variable.
agentapi server --allowed-hosts 'example.com,example.org' -- claude
# or
AGENTAPI_ALLOWED_HOSTS='example.com example.org' agentapi server -- claudeBy default, the server allows CORS requests from http://localhost:3284, http://localhost:3000,
and http://localhost:3001. Set AGENTAPI_ALLOWED_ORIGINS or --allowed-origins to change which
origins may make cross-origin requests.
To allow requests from any origin, use * as the allowed origin:
agentapi server --allowed-origins '*' -- claudeTo allow a specific origin, use:
agentapi server --allowed-origins 'https://example.com' -- claudeTo specify multiple origins, use a comma-separated list when using the --allowed-origins flag, or
a space-separated list when using the AGENTAPI_ALLOWED_ORIGINS environment variable. Origins must
include the protocol (http:// or https://) and support wildcards (e.g.,
https://*.example.com):
agentapi server --allowed-origins 'https://example.com,http://localhost:3000' -- claude
# or
AGENTAPI_ALLOWED_ORIGINS='https://example.com http://localhost:3000' agentapi server -- claude- Have the agent run anything it can. Native agents start in the bypass
permission mode, where tools run without asking, unless the command line
picks another mode or Claude Code runs as root outside a sandbox (it starts
in
defaultthen); Claude Code gets--allow-dangerously-skip-permissions(except as root outside a sandbox), so a client can switch any session to bypass; Codex's default overrides the policies in~/.codex/config.tomlunless the command line picks one; and the ACP transport approves every permission request. - Run commands in the terminal (native transport, on by default;
--terminal=falseturns it off). - Read files in the agent's working directory with the file viewer endpoints;
paths outside it, also through symlinks, are refused, and the diff only
reads the git repository. An HTML preview's scripts run in a sandbox
without access to the chat or the API.
GET /files/findlists the paths of files below the working directory whose path ends with the one asked for. - Read the conversations of the agent's earlier sessions in its working
directory, and the steps of the subagents it ran, from the agent's own logs
(
~/.claude,~/.codex,~/.pi), and what they cost, and open any of those sessions. - Upload files of up to 10 MB; only images are served back.
- Change the MCP servers and the webhook, and restart the agent. The webhook can send the
conversation (
message.created,tool.completed) to any URL;GET /webhooknever returns its secret.
Use --api-token together with --bind or a reverse proxy whenever someone else can reach the
server.
AgentAPI itself is small: one Go process of about 30 MiB that uses about 0.1% of a CPU core while the agent is idle. Most of the cost is the agent CLI it runs, at 100–350 MiB.
These numbers were measured on 2026-10-18 with AgentAPI v0.24.0 (a 29.5 MiB binary), Claude Code 2.1.302, codex-cli 0.163.0 and Pi 1.3.0, on Linux with a 4-core Intel Xeon Silver 4210 (2.2 GHz) and one chat (an SSE client) connected. Each case had one short turn: a reply of about 200 words, without tools. Memory is resident memory (RSS); CPU is a share of one core while idle, and CPU seconds for a whole turn.
| Transport | Agent | AgentAPI memory | AgentAPI CPU, idle | AgentAPI CPU per turn | Agent memory | Agent CPU per turn |
|---|---|---|---|---|---|---|
| Native | Claude Code | 27–30 MiB | 0.1% | 0.08 s | 230–290 MiB | 1.1 s |
| Native | Codex | 27–28 MiB | 0.1% | 0.11 s | 205–265 MiB | 0.43 s |
| Native | Pi | 27–28 MiB | 0.1% | 0.12 s | 100–120 MiB | 0.37 s |
| PTY | Claude Code | 29–34 MiB | 0.15% | 0.29 s | 265–300 MiB | 1.4 s |
| PTY | Codex | 34 MiB | 0.2–0.7% | 0.66 s | 245–345 MiB | 6.3 s |
| PTY | Pi | 28–35 MiB | 0.1% | 0.59 s | 120–130 MiB | 1.2 s |
| Native | native-demo (no agent process) |
27 MiB | 0.03% | 0.07 s |
- While the agent is idle, both transports cost AgentAPI next to nothing: polling the screen (PTY) costs about as little as waiting for the protocol (native). The exception was Codex with the PTY transport (0.2–0.7% in the table): to find the session's log, AgentAPI read Codex's recent session files every 500 ms, more of them before the first message. It now reads each file once, and AgentAPI idles at 0.1% with PTY Codex too (measured after the fix).
- A turn costs AgentAPI 3–6 times more CPU with the PTY transport (typing the message, reading the screen, following the session log), and the agent too: Codex's terminal UI took 6.3 CPU seconds for a turn its app-server answered in 0.4.
- An agent's own memory moves during a session: Claude Code grew to 314 MiB about a minute and a half after it started (its own background work, 0.1% CPU idle otherwise), and Codex gave back about 55 MiB after its first turn.
- AgentAPI's code (about 17 MiB of its memory) is shared by instances started from the same binary: with four running, each costs about 13–24 MiB (PSS).
- Not counted: MCP servers the agent starts, the commands it runs, the shell terminal (one shell while it is open) and the browser tab.
To size a machine, plan per session about 30 MiB for AgentAPI plus the agent (about 350 MiB for Claude Code or Codex, 150 MiB for Pi) and whatever its MCP servers and commands need.
Start an HTTP server to control an agent. To pass startup arguments to the agent, place the full
agent command after the -- separator.
agentapi server -- claude --allowedTools "Bash(git*) Edit Replace"You may also use agentapi to run the Aider and Goose agents:
agentapi server -- aider --model sonnet --api-key anthropic=sk-ant-apio3-XXX
agentapi server -- gooseClaude Code, Codex and Pi run with the native transport unless you pass
--transport pty for their terminal UI (see Pi coding agent):
agentapi server -- pi
agentapi server --transport pty -- piKimi Code can run through its interactive terminal UI:
agentapi server -- kimiKimi Code also provides a native ACP server. After logging in once with kimi and /login, you
can use AgentAPI's ACP transport:
agentapi server --type=kimi --experimental-acp -- kimi acpFlags can also be set with environment variables: AGENTAPI_ and the flag's name in capitals, with
_ for - (AGENTAPI_ALLOWED_HOSTS). Lists are comma-separated in a flag and space-separated in a
variable.
| Flag | Default | Description |
|---|---|---|
-t, --type |
from the agent command | Agent type; see Supported agents |
-p, --port |
3284 |
Port to listen on |
--bind |
all interfaces | Address to listen on; see Bind address |
--transport |
native for Claude Code, Codex and Pi, else pty |
native, pty or acp; see Transports |
--experimental-acp |
false |
The same as --transport acp |
--state-db |
SQLite file that keeps the conversation (native and PTY) and resumes a native agent's session; see Persistent sessions | |
--terminal |
true |
Shell in the agent's working directory for the chat (native); see Terminal |
--auto-continue |
false |
Continue a turn a usage limit stopped once the limit resets (native); see Usage limits |
--prices |
JSON file of model prices per million tokens, which add to or replace the built-in ones; see Session costs | |
-s, --state-file |
Deprecated: imported once into a state database next to it (--state-db names another) |
|
--load-state, --save-state |
Deprecated: --load-state=false skips the import; --save-state has no effect |
|
-I, --initial-prompt |
First message for the agent; read from stdin when piped | |
-W, --term-width |
80 |
Width of the emulated terminal, and of TTY mode's lines (PTY) |
-H, --term-height |
1000 |
Height of the emulated terminal (PTY) |
--api-token |
off | Require a Bearer token; without a value, a random token is generated and printed; see API token authentication |
-a, --allowed-hosts |
localhost, 127.0.0.1, [::1] |
Accepted Host headers, * for any; see Allowed hosts |
-o, --allowed-origins |
http://localhost:3284, http://localhost:3000, http://localhost:3001 |
Origins allowed to call the API from a browser, * for any; see Allowed origins |
--webhook-url |
URL the webhook events are POSTed to; see Webhooks | |
--webhook-events |
run.status_changed |
Events to send: types, groups (task.*) or * |
--webhook-secret |
Secret that signs every delivery (X-AgentAPI-Signature-256) |
|
--webhook-heartbeat |
60s |
How often instance.heartbeat is sent; 0 turns it off |
--instance-id |
<hostname>:<port> |
Identifies this AgentAPI in GET /summary and every webhook event; see Watching many instances (--webhook-instance-id is its deprecated old name) |
--name |
the working directory's name | What clients that watch many AgentAPIs call this one |
--public-url |
URL this AgentAPI is reached at, in GET /summary and every webhook event |
|
--webhook-timeout |
10s |
Timeout of each delivery attempt |
--webhook-max-attempts |
3 |
Delivery attempts |
--webhook-payload-template |
Go text/template for the POST body |
|
-c, --chat-base-path |
/chat |
Base path of the chat UI |
--pid-file |
File to write the server's process ID to, for shutdown scripts | |
-P, --print-openapi |
Print the OpenAPI schema and exit |
Update the AgentAPI binary to the latest release from GitHub.
agentapi update # download and install the latest version
agentapi update --check # only check if an update is available
agentapi update --force # update even if already at the latest versionRelease binaries are verified against the release's checksums.txt SHA-256 manifest before the
running executable is replaced. An update is rejected if the manifest is missing, malformed, or does
not match the download.
Attach to a running agent's terminal session (PTY transport; ACP and native agents have no terminal to attach to).
agentapi attach --url localhost:3284A server started with --api-token needs the token: pass --api-token or set
AGENTAPI_API_TOKEN. Press Ctrl+C to detach from the session.
The server describes its API with OpenAPI: openapi.json in this repository,
/openapi.json on a running server, and a documentation UI at /docs. It listens on port 3284 by
default. Endpoints marked native need the native transport.
| Endpoint | Description |
|---|---|
GET /messages |
All messages of the conversation |
POST /message |
Send a message, or queue it while the agent is busy; dispatch_mode is queue (default), steer or interrupt (native) |
DELETE /messages |
Clear the conversation and the queue, and restart the agent (native: in a new session; 400 with ACP) |
GET /rich-messages |
The structured conversation: thinking, tool calls with results, usage |
GET /timeline |
Normalized session events, for export or another UI; ?subagents=true adds the subagents' (native) |
GET /status |
Status, lifecycle, server ID, turn, epoch, session name, version, open dialog; native: the agent's session ID, capabilities, pending requests, settings, usage, usage limit stop, terminal, login |
GET /title |
The session's human-readable title |
GET /events |
Server-sent events; ?sync=1 for incremental replay on reconnect, and task and queue updates; ?only=summary for the summary alone |
GET /summary |
What a client that watches many AgentAPIs shows of this one: instance, status, attention, title, latest task, requests, queue, usage; see Watching many instances |
GET /tasks |
The conversation's tasks: each message of yours and the agent's turn on it |
GET /workspace |
The Coder workspace URL, when AgentAPI runs in one |
| Endpoint | Description |
|---|---|
GET /queue |
The messages waiting for the agent |
PUT /queue |
Reorder the queue ({"ids": [...]}, every queued message once) |
PUT /queue/{id}, DELETE /queue/{id} |
Edit or remove a queued message |
POST /queue/{id}/steer |
Move a queued message into the running turn (native) |
| Endpoint | Description |
|---|---|
POST /interrupt |
Stop the running turn |
GET /requests, GET /requests/{id} |
The requests the agent waits on (approvals, questions, plans) and the recently closed ones |
POST /requests/{id}/respond |
Answer a request: option_id, or answers keyed by question, with optional text |
GET /settings, PUT /settings |
The permission mode, model and reasoning effort, with the values they can take; PUT changes the ones in its body |
POST /compact |
Compact the agent's context (Codex, Pi) |
POST /login/retry |
Check the agent's login again now, while login_required is reported (409 when no login is needed) |
POST /limit/continue, DELETE /limit/continue |
Continue a turn a usage limit stopped, now or at the reset; cancel a scheduled continuation |
DELETE /limit |
Forget a usage limit stop, so the queued messages go out |
GET /sessions |
The agent's sessions in its working directory (Claude Code, Codex, Pi), with what each cost and the total |
GET /cost |
What the open session cost, and its model calls (Claude Code, Codex, Pi) |
GET /subagents, GET /subagents/{id} |
The subagents the agent ran in its session, and one's task and messages (Claude Code, Codex) |
POST /sessions/{id}/resume |
Open one of them, or a copy of it ({"fork": true}), in place of the conversation |
| Endpoint | Description |
|---|---|
GET /terminal |
The shell's output as server-sent events (native, unless --terminal=false) |
POST /terminal/input, POST /terminal/resize |
Type into the shell; set its size |
GET /files/content |
A file in the agent's working directory, cut at 1 MiB |
GET /files/diff |
The file's uncommitted changes against HEAD |
GET /files/preview |
An HTML file with the files it refers to inlined, for a sandboxed iframe |
GET /files/image |
An image file in the agent's working directory (raster images judged by content, and SVG) |
GET /files/find |
The files below the working directory whose path ends with the one given, ignoring case |
| Endpoint | Description |
|---|---|
POST /upload |
Store a file (up to 10 MB) and return its path, for @"<path>" in a message |
GET /uploads/{checksum}/{name} |
An uploaded image (PNG, JPEG, GIF, WebP or BMP, judged by content), for the chat |
| Endpoint | Description |
|---|---|
GET /handoff, POST /handoff/reply |
The human-reply API: what the agent waits on, and an answer to it |
GET /webhook, PUT /webhook |
Read or change the webhook: its URL, events, secret, heartbeat and instance |
POST /webhook/test |
Send a webhook.test event to the webhook URL once, and return the receiver's answer |
GET /mcp, PUT /mcp |
The MCP servers and the managed config file; PUT replaces them, ?restart=true restarts the agent (native: on its session if it can resume it; 400 with ACP) |
POST /mcp/check |
Check remote HTTP servers and resolve stdio executables |
POST /mcp/servers, PATCH /mcp/servers/{name}, DELETE /mcp/servers/{name} |
Create, update or remove one MCP server |
GET /mcp/profiles |
Export the saved MCP profiles |
PUT /mcp/profiles/{name}, DELETE /mcp/profiles/{name} |
Import, replace or remove a profile |
POST /mcp/profiles/{name}/apply |
Replace the MCP configuration with a profile |
| Endpoint | Description |
|---|---|
GET /auth/session |
Whether the API needs a token, and whether the caller has one (no credentials needed) |
POST /auth/session, DELETE /auth/session |
Sign a browser in with the token (session cookie), or sign it out |
The chat itself is served at /chat/ (and /chat/embed), and TTY mode reads the agent's screen
from /internal/screen, which isn't part of the public API.
AgentAPI is a single Go binary: an HTTP server (chi, with huma generating the OpenAPI schema) that runs one agent process and serves the chat UI, a Next.js app embedded in the binary. Every transport implements the same conversation interface and reports to the same event emitter, so the HTTP API, the event stream, the queue and the chat work the same over all three. See docs/architecture.md for the packages, the data flow of each transport, the event stream, persistence and the security model.
make build # out/agentapi, with the chat UI embedded
make gen # regenerate openapi.json and the version
make lint # Go, TypeScript, shellcheck and actionlint
go test ./... # Go tests, including the e2e smoke testTests run with CGO_ENABLED=0. Commit openapi.json with every API change, and regenerate it only
with make gen (see AGENTS.md). The chat UI lives in chat/:
bun run dev serves it against a running AgentAPI, and
agentapi server --type custom -- native-demo runs a simulated native agent for working on it
without a real one.
The fork fixes wide-character cursor tracking, PTY lifecycle leaks, concurrent event delivery, message tracking races, ACP shutdown and TUI redraw artifacts. It also fixes JSONL watcher flushing, content-block merging during Claude delta streaming, and switching the watched file when Claude Code parks a session in a new JSONL file. These fixes address long sessions, process replacement and temporary browser or network interruptions.
Pending feedback, we're considering the following features:
AgentAPI currently lets developers control coding agents programmatically. We hope the major agents will eventually offer official SDKs. Whether AgentAPI is still needed will depend on whether vendors adopt a common API or continue to use their own formats.
If vendors adopt a common API, we'll deprecate AgentAPI and recommend the official SDKs. If their formats remain different, we'll work toward a universal adapter so developers can switch agents without changing their code.























