Hania Code API
The terminal client is built on the public v1 surface, and any integrator can use the same endpoints. Authenticate with a hania_sk_ key as a bearer token; see Authentication & API keys.
API key abilities
| Ability | Covers |
|---|---|
chat:send | Chatting, adding to a running reply, stats, stop, approvals, and pairing a server. |
bots:read | Listing agents. |
conversations:read | Listing and reading sessions, and listing rollback points. |
conversations:write | Rolling back, and clearing remembered approvals. |
bots:write and llm:read | Switching an agent's model. |
Endpoints
| Method & path | What it does |
|---|---|
GET /api/v1/bots | Lists the workspace's agents, to pick one to chat with. |
POST /api/v1/chat/send-stream | Sends a turn and streams the reply as server-sent events. This is where a conversation is bound to a computer or server. |
GET /api/v1/chat/stream | The persistent conversation channel. Receives what happens outside your own send, including the continuation after an approval. |
POST /api/v1/chat/steer | Adds a message to the reply that is running, which the agent reads after its current step - what the terminal does when you type while the agent works. See Add to a running reply. |
DELETE /api/v1/chat/steer | Withdraws one queued message the agent hasn't read yet. See Withdraw a queued message. |
GET /api/v1/chat/stats | Token and cost figures for a conversation - what the terminal's /cost shows. |
POST /api/v1/chat/stop | Interrupts the current turn. Body {"conversation_id"}; returns {"stopped"}. |
GET /api/v1/hitl | Lists pending approvals for a conversation. |
POST /api/v1/hitl/{id}/respond | Answers one approval. |
POST /api/v1/machines | Pairs a server; returns its id and one-time key. |
GET /api/v1/conversations | Lists sessions; supports an external_user_id filter. |
GET /api/v1/conversations/{id} | Returns a session's messages. |
GET /api/v1/conversations/{id}/rewind | Lists the points a session can be rolled back to. |
POST /api/v1/conversations/{id}/rewind | Rolls a session back - its files, its conversation, or both. |
POST /api/v1/conversations/{id}/truncate | Removes every message after a given one. Doesn't touch files. |
DELETE /api/v1/conversations/{id}/code-approvals | Clears the approvals a session remembers. Returns {"cleared": true}. |
PATCH /api/v1/bots/{id}/model | Switches the agent's chat model. |
Send a coding turn
POST /api/v1/chat/send-stream requires external_user_id, which scopes sessions to the person using your integration. Pass new_conversation to start a fresh session, or a conversation_id to continue one. The reply streams as server-sent events on the response.
These fields say where the code work happens. They're the only way to bind a conversation to a computer or server:
| Field | What it does |
|---|---|
code_machine_id | The machine to run file and shell operations on. Take the id from the console's Machines page, or from the response when you pair a server. |
code_workdir | The absolute path of the project folder on that machine. |
code_runner_id | Optional. Without it, the request goes to the machine's newest runner. |
They're saved on the conversation, so later turns - including ones sent from the console - keep working on the same machine and folder without sending them again. A few things to know:
- They only take effect for an agent with Hania Code on. Sent to any other agent, they're saved but not used.
- They aren't checked when saved. A machine id from another account is accepted, then fails when the agent tries to use it.
- A conversation with nothing bound can't do any coding: every Hania Code action fails. Agent runs are different: one started with
POST /api/v1/agents/{id}/triggerworks on the computer wherehaniais open with that agent, as described in runs and teams, and fails with "no Hania Code session is open for this agent" when there isn't one.
Pair a server
POST /api/v1/machines with a name for the server pairs it and returns its id and a one-time token. Start the runner on the server with that token (see Servers), and send the id as code_machine_id. A name that's already taken returns 409. It needs an API key with chat:send, and on Free it's refused because Free includes no machines.
The terminal client calls the same endpoint with an extra install_id to register the computer it runs on. That form is for the terminal client; integrations should pair without it.
Approvals
When the agent proposes a change that has to ask (see approvals), the turn pauses. The pending request appears on GET /api/v1/hitl for the conversation, with the kind of change in tool_name, its details in tool_args, and the content to review in context:
tool_name | tool_args | context |
|---|---|---|
code_write | action "write", path, content | A unified diff. |
code_edit | action "edit", path, old_string, new_string, replace_all | A unified diff. |
code_apply_patch | action "apply_patch", patch | Each file's diff, joined by newlines. A deleted file is the single line delete <path>. |
code_bash | action "bash", command | The command. |
code_bash_start | action "bash_start", command | The command. |
The diffs come from git diff on the machine, in the usual diff --git form. If git isn't installed there, context is empty for writes, edits, and patches, so show the change from tool_args instead. Answer the request with:
curl -X POST https://app.hania.ai/api/v1/hitl/REQUEST_ID/respond \
-H "Authorization: Bearer $HANIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "approve"}'
A pending row may also carry remember_rule, for example {"action": "bash", "target": "npm test", "label": "run npm test"}. When it's there, offer a second way to approve, labelled with label, and send {"action": "approve", "remember": true}. That exact file or command then runs with no card for the rest of the session. The field is absent when the rule can't be stated exactly, such as a patch over several files or a target holding * or ?; then offer the usual choices. remember is ignored on a denial, on other tools, and where no rule was offered, so don't send it with edited arguments. DELETE /api/v1/conversations/{id}/code-approvals clears what a session remembers.
action is approve, approve_edited, or deny, with an optional text (the reason, on a deny) and optional edited_args (your corrected version, with approve_edited). Only these fields of edited_args are used: path and content for a write; path, old_string, new_string, and replace_all for an edit; patch for a patch; command for both command types. The action itself can't be changed. The turn resumes on its own; receive the continuation on GET /api/v1/chat/stream rather than re-sending anything.
Sessions and stats
Sessions are ordinary conversations. GET /api/v1/conversations?external_user_id=… lists a user's sessions for a resume picker, and GET /api/v1/conversations/{id} returns the messages. GET /api/v1/chat/stats returns the conversation's token and cost figures.
Rollback
GET /api/v1/conversations/{id}/rewind lists the session's own prompts, oldest first, as the points you can roll back to. Each has a message_id, its content, created_at, and code_changes, which is true when that point has file changes to undo.
POST /api/v1/conversations/{id}/rewind with a message_id from that list and a mode rolls back to that point:
mode | What it restores |
|---|---|
code_and_conversation | The files and the conversation. |
code | The files only. |
conversation | The conversation only. |
It returns files_restored, files_removed, and skipped_paths (a list of paths that weren't restored). When the conversation is rolled back, the conversation channel receives a resync event with the reason rewound, so any other window showing it reloads.
| Status | When |
|---|---|
400 | The message_id isn't valid or isn't one of the user's messages, or the mode is unknown. The reason is in error. |
404 | The conversation or the message wasn't found. |
409 | A turn is still running, or it's a team conversation. |
409 MACHINE_UNAVAILABLE | A machine holding the file changes is offline or no longer paired. Nothing was restored. |
502 REWIND_FAILED | A machine failed while restoring. |
File changes can only be restored on the machine that made them, so that machine has to be online. POST /api/v1/conversations/{id}/truncate is still available when you only want to remove the messages after a given one; it never touches files.