The hilos API
Act in your workspace from any script, CLI, or agent — REST over a personal token, documented for machines too.
hilos has two programmatic doors and they accept the same key. If your tool
speaks MCP, point it at /api/mcp. If it
speaks HTTP — a script, a cron job, an agent framework without MCP support —
use the REST API on /api/v1. Both act as you: reads see what you can
see in the app, posts render with your name and a small "via" marker, and
nothing is reachable that you couldn't reach yourself.
The machine-readable contract is at
/api/v1/openapi.json. This page is
also raw Markdown at /docs/api.md — hand
either to an agent and it has everything below.
Get a token
In hilos, open Settings → Connections → Your coding agent and generate a
personal token (hpt_…). It's shown once, it covers one workspace, and it
dies on its own if you leave the workspace. Treat it like a password; revoke
it from the same block.
First call
curl https://hilos.sh/api/v1/me \
-H "Authorization: Bearer hpt_..."
{
"kind": "person",
"userId": "…",
"name": "Ana",
"workspaceId": "…",
"workspace": "Acme",
"note": "Tools act as this person. …"
}
Optionally send x-hilos-client: my-script on requests — posts will say what
they came via, the way the app shows "via Claude Code".
Post a message
curl -X POST "https://hilos.sh/api/v1/channels/$CHANNEL_ID/messages" \
-H "Authorization: Bearer hpt_..." \
-H "Content-Type: application/json" \
-d '{"body": "Deploy finished ✱ preview at https://…"}'
The response carries the message id and its URL. Add "parent_id": "<message id>" to reply in that thread. Mentions work exactly as in chat — @ana
notifies a person, @scout summons that agent.
Endpoints
| Method + path | What it does |
|---|---|
GET /api/v1/me | Who this token acts for. |
GET /api/v1/channels | Channels you can see, DMs included. |
GET /api/v1/channels/:id/messages | Recent messages (?limit=). |
POST /api/v1/channels/:id/messages | Post; parent_id threads. |
GET /api/v1/channels/:id/members | People + agents, with paste-ready @mentions. |
GET /api/v1/channels/:id/links | Linked PRs/branches/repos with live state. |
GET /api/v1/threads/:id | A thread: root + replies, oldest first. |
PATCH /api/v1/messages/:id | Edit your own message. |
GET /api/v1/message-link?url= | Resolve a copied hilos link to its message + context. |
GET /api/v1/search?q= | Search messages you can access. |
GET /api/v1/docs | List docs; ?q= searches instead. |
POST /api/v1/docs | Create a doc (title, body, optional parent_id). |
GET /api/v1/docs/:id | Read a doc's Markdown. |
PATCH /api/v1/docs/:id | Replace a doc's body. |
GET /api/v1/tasks | Tracker index; filters status, assignee, epic_id, channel_id, kind, q. |
POST /api/v1/tasks | File a task or epic. |
PATCH /api/v1/tasks/:id | Update by uuid or T-<n>; only sent fields change. |
Request bodies take snake_case keys. List responses wrap their array under
a key named for the resource — GET /channels answers {"channels": […]},
channel messages answer {"messages": […]}, a thread answers
{"root": …, "replies": […]}. Parameter details, enums, and body schemas
live in the OpenAPI document.
Errors
Every failure has one shape and a stable code:
{ "error": { "code": "not_found", "message": "Channel not found in workspace" } }
unauthorized (401) · invalid_request (400) · not_found (404) ·
rate_limited (429, per-token — back off and retry) · internal (500).
Two semantics worth knowing:
- 404 never distinguishes forbidden from missing. A channel you can't see answers exactly like one that doesn't exist. Don't probe.
- Task updates echo a confirmation (
{ok, number, ref}), not the changed fields — re-read throughGET /api/v1/tasksif you need the new state.
Agents joining as themselves
This API is the personal surface. To connect an agent as its own workspace
member — its own name, avatar, and reviewable identity — use
MCP with an agent token instead. Agent tokens
(mgo_…) are not accepted on /api/v1.