API reference

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.

Approvals and stop work the same from the console and the API, because they share one backend. A web client built on this surface can offer a Stop button and an approval card, and they behave exactly like the console's.

API key abilities

AbilityCovers
chat:sendChatting, adding to a running reply, stats, stop, approvals, and pairing a server.
bots:readListing agents.
conversations:readListing and reading sessions, and listing rollback points.
conversations:writeRolling back, and clearing remembered approvals.
bots:write and llm:readSwitching an agent's model.

Endpoints

Method & pathWhat it does
GET /api/v1/botsLists the workspace's agents, to pick one to chat with.
POST /api/v1/chat/send-streamSends 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/streamThe persistent conversation channel. Receives what happens outside your own send, including the continuation after an approval.
POST /api/v1/chat/steerAdds 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/steerWithdraws one queued message the agent hasn't read yet. See Withdraw a queued message.
GET /api/v1/chat/statsToken and cost figures for a conversation - what the terminal's /cost shows.
POST /api/v1/chat/stopInterrupts the current turn. Body {"conversation_id"}; returns {"stopped"}.
GET /api/v1/hitlLists pending approvals for a conversation.
POST /api/v1/hitl/{id}/respondAnswers one approval.
POST /api/v1/machinesPairs a server; returns its id and one-time key.
GET /api/v1/conversationsLists sessions; supports an external_user_id filter.
GET /api/v1/conversations/{id}Returns a session's messages.
GET /api/v1/conversations/{id}/rewindLists the points a session can be rolled back to.
POST /api/v1/conversations/{id}/rewindRolls a session back - its files, its conversation, or both.
POST /api/v1/conversations/{id}/truncateRemoves every message after a given one. Doesn't touch files.
DELETE /api/v1/conversations/{id}/code-approvalsClears the approvals a session remembers. Returns {"cleared": true}.
PATCH /api/v1/bots/{id}/modelSwitches 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:

FieldWhat it does
code_machine_idThe 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_workdirThe absolute path of the project folder on that machine.
code_runner_idOptional. 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}/trigger works on the computer where hania is 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_nametool_argscontext
code_writeaction "write", path, contentA unified diff.
code_editaction "edit", path, old_string, new_string, replace_allA unified diff.
code_apply_patchaction "apply_patch", patchEach file's diff, joined by newlines. A deleted file is the single line delete <path>.
code_bashaction "bash", commandThe command.
code_bash_startaction "bash_start", commandThe 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:

modeWhat it restores
code_and_conversationThe files and the conversation.
codeThe files only.
conversationThe 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.

StatusWhen
400The message_id isn't valid or isn't one of the user's messages, or the mode is unknown. The reason is in error.
404The conversation or the message wasn't found.
409A turn is still running, or it's a team conversation.
409 MACHINE_UNAVAILABLEA machine holding the file changes is offline or no longer paired. Nothing was restored.
502 REWIND_FAILEDA 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.