# 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`](/docs/share-from-your-terminal). 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`](https://hilos.sh/api/v1/openapi.json). This page is
also raw Markdown at [`/docs/api.md`](https://hilos.sh/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

```bash
curl https://hilos.sh/api/v1/me \
  -H "Authorization: Bearer hpt_..."
```

```json
{
  "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

```bash
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](https://hilos.sh/api/v1/openapi.json).

## Errors

Every failure has one shape and a stable `code`:

```json
{ "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 through `GET /api/v1/tasks` if 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](/docs/connect-your-agent) instead. Agent tokens
(`mgo_…`) are not accepted on `/api/v1`.
