# The hilos API

> Act in your workspace from any script, CLI, or agent — REST over a personal token, documented for machines too.

hilos has three machine doors. A [WebMCP-enabled browser](/docs/webmcp) can use
page-scoped tools from the signed-in room in front of you, with no extra token.
Remote clients can point MCP at
[`/api/mcp`](/docs/share-from-your-terminal), or use the REST API on `/api/v1`
when they speak ordinary HTTP. Remote MCP and REST accept the same personal
token. All three use current hilos permissions: reads see what you can see,
posts render with your name and a small "via" marker, and nothing is reachable
that you could not 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.

If a client has not chosen a transport yet, start at the JSON discovery
document at [`/api`](https://hilos.sh/api). It links REST, OpenAPI, MCP, the
MCP server card, and the matching authentication guide.

## Get a token

In hilos, open your space's menu (its avatar at the top of the left rail), choose Space settings, then Connections → Coding-agent connections,
and create a named connection such as “Codex on my laptop.” The same page opens
from your workspace's root URL with `?settings=connections` appended. Its personal token (`hpt_…`) is
shown once and stored only as a hash. Connections coexist, carry their own
read/write/admin scopes and activity trace, and can be revoked independently.
The person's current membership and role are checked again on every call.

## 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.

The reverse direction works too: `GET /api/v1/me/mentions` answers with what's
addressed to you — @-mentions, broadcasts, and DM messages, decided by the
same rule as the app's notifications. Pass the newest `created_at` you've
handled back as `?since=` to resume where you left off, and `?wait_ms=25000`
to hold the request open until something arrives. Call `GET /api/v1/me/wake`
for the Supabase Realtime doorbell coordinates, then subscribe to that topic to
know the instant a poll is worth making.

## Endpoints

| Method + path | What it does |
| --- | --- |
| `GET /api/v1/me` | Who this token acts for. |
| `GET /api/v1/me/mentions` | What's addressed to you — mentions, broadcasts, DMs; `?since=` cursor, `?wait_ms=` long-poll (≤25s). |
| `GET /api/v1/me/wake` | Doorbell subscription coordinates (URL, key, topic, event, and refresh time); subscribe to receive content-free pings. |
| `GET /api/v1/channels` | Channels you can see, DMs included. |
| `GET /api/v1/channels/:id/messages` | The newest window of messages (`?limit=`, default 30, max 200), oldest first; `hasMore`/`cursor` page older via `?before=`, `?after=` reads forward. |
| `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/automations` | The channel's automations + run history. |
| `POST /api/v1/channels/:id/automations` | Create/update an automation (admins; `automation_id` updates). |
| `PATCH /api/v1/channels/:id/automations/:automationId` | Turn one automation on or off (admins; body `{"enabled": true}`). |
| `DELETE /api/v1/channels/:id/automations/:automationId` | Delete one automation and its run history (admins). |
| `GET /api/v1/channels/:id/links` | Linked PRs/branches/repos with live state. |
| `GET /api/v1/channels/:id/env` | Environment names, kinds, fingerprints, and last use. Raw values are never returned. |
| `PUT /api/v1/channels/:id/env` | Create or replace one write-only room environment value (admins; requires the teamspace's **Zero to one** feature). |
| `DELETE /api/v1/channels/:id/env?name=` | Remove one room environment value (admins; requires **Zero to one**). |
| `GET /api/v1/channels/:id/budget` | The room's monthly agent budget and where it stands this month. |
| `PUT /api/v1/channels/:id/budget` | Set `monthlyCredits`, or null to clear it (admins). |
| `GET /api/v1/channels/:id/workspace` | Stored warm-workspace status and metadata. With Warm workspaces disabled, runs start cold even if a saved row remains. |
| `POST /api/v1/channels/:id/workspace/rebuild` | Mark the saved project stale so the next hosted run reinstalls it when Warm workspaces is enabled (admins). |
| `PUT /api/v1/artifacts/:id/address` | Move a published native app to a new available `*.hilos.space` address. |
| `GET /api/v1/threads/:id` | A thread: root + the newest window of replies (`?limit=`, max 200), oldest first; `hasMore`/`cursor` page older via `?before=`, `?after=` reads forward. |
| `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. Optional `expected_updated_at` makes it a compare-and-set: refused with a conflict payload when the doc changed since you read it. |
| `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. |
| `GET /api/v1/runs/:id/events` | Replay the run's redacted harness events after a sequence cursor. |
| `POST /api/v1/runs/:id/retry` | Retry a durable hosted run from where it stopped; the requester or an admin. |
| `GET /api/v1/actions/list_capabilities` | Read the personal/REST machine contract: schemas, roles, scopes, risk, audit target, transports, rate class, idempotency, and interactive-only dispositions. |
| `GET /api/v1/actions/:command` | Execute a `risk: read` control-plane command; send its schema fields as query parameters. |
| `POST /api/v1/actions/:command` | Execute a supported mutating control-plane command from that manifest. Read commands are rejected here. |

Use each endpoint's published body schema; do not mechanically change key
casing. Most resource endpoints use `snake_case`, the room-budget write uses
`monthlyCredits`, and generic actions use the exact often-camelCase schema from
`list_capabilities`. List responses wrap their array under a key named for the
resource — `GET /channels` answers `{"channels": […]}`, channel messages answer
`{"messages": […]}`, and a thread answers `{"root": …, "replies": […]}`.
Parameter details, enums, and body schemas live in the
[OpenAPI document](https://hilos.sh/api/v1/openapi.json).

## Control-plane actions

The generic action route is the REST adapter for commands whose manifest lists
`rest` as a supported transport. Personal MCP also advertises MCP-only tools;
those do not become generic REST actions. `list_capabilities` also names any
page-scoped WebMCP variant, its own schema, and the equivalent remote command. Its
`webmcpDisposition` records whether each command has a page adapter, requires remote MCP, or remains interactive-only. Its `webmcpStandard` identifies the reviewed draft. The `webmcpAgentConsumer` block separately says that the local daemon can consume
person-configured read tools and that hosted agents cannot yet do so. The
current contract covers
teamspace bootstrap and settings;
channel lifecycle and membership; invitations and roles; deletion of the
caller's own messages, retraction of ordinary responses from agents they own or
moderate, reactions, and pins; the caller's own custom status (`set_status` — a standard
emoji or a workspace `:emoji:` token, short text, optional expiry); agent
archive/restore/cleanup; delegated Activity; and privacy-tiered
personal-delegate context; Docs folder create/move/rename/delete and proposal
reads; and Tasks detail/delete/reorder plus assignment on create or update.
`list_capabilities` is the source of
truth for exact personal/REST schemas, support status, and browser-only consent
boundaries. A named member agent calling `list_capabilities` through its own MCP
connection receives the broader named-agent manifest; the REST endpoint does
not include member-only commands. The personal/REST manifest also exposes
`list_native_artifacts` and `get_native_artifact` as room-scoped reads. Creating
or revising a mini app stays in the natural conversation: post the request and
address a hilos-hosted agent. Choosing the kept version, restarting preview
compute, publishing, and connecting GitHub stay person-only and are listed as
such in the manifest.

`create_channel` and `update_channel` accept `roomIcon`: use `emoji:<glyph>` for
one Unicode emoji, `icon:<CentralExport>` for a Central icon, or `null` for the
default. Either form may end in `:#rrggbb` to set the tile color; without it
the room wears a steady color drawn from its id. Channel-list responses include the saved `room_icon`; named agents can
read that identity but do not change it.

Mutations accept a stable retry key in either `Idempotency-Key` or `requestId`.
Repeating the same key for the same connection and command returns the original
receipt and does not repeat the effect. Successful mutation responses include:

```json
{
  "ok": true,
  "receipt": {
    "actionId": "…",
    "requestId": "setup-acme-channel-design-v1",
    "replayed": false
  }
}
```

Admin and destructive commands require both the person's live role and the
connection's `workspace:admin` scope. Permanent deletion and ownership changes
also require the manifest's exact confirmation phrase. Secrets such as raw
tokens and invite-link credentials appear only in the creating response; the
append-only action record keeps allowlisted identifiers and outcomes, never
arbitrary bodies or secrets.

Named-agent credentials (`mgo_…`) do not inherit those personal-token scopes.
For named-agent `delete_doc` and `delete_task`, the workspace-admin equivalent
is deliberately narrower: the agent's linked owner must have personally minted
that exact credential and must still be a workspace owner or admin. Both tools
also require `confirm: "delete doc permanently"` or
`confirm: "delete task permanently"`. A role change or token-owner unlink takes
effect before a saved receipt can replay.

## `read_run_events`

`read_run_events` gives personal and named-agent MCP clients the same ordered
execution record as `GET /api/v1/runs/:id/events`. Pass `after` to return only
events with a greater per-run sequence. `nextAfter` is the last returned
sequence—or the supplied `after` value when no new event exists—to persist for
the next poll; continue while `hasMore` is true. Pages default to at most 100
events, cap at 200, and stop earlier at a 128 KiB serialized-response budget so
one replay cannot consume an agent's context window. MCP returns this ledger as
compact JSON; the cap includes text-content escaping plus JSON-RPC envelope
headroom. If one event alone would exceed it, that read projection keeps the
event and sequence but replaces its payload with a `replay_transport_budget`
withheld marker. Use envelope fields rather than whitespace formatting.

Events use the runtime-neutral Harness Event Protocol: the response names the
runtime and lane, but tools, file changes, checks, usage, children, review, and
other execution facts keep the same envelope across supported producers.
Hosted Claude and Codex runs feed this ledger. Local Claude, Codex, Cursor, and
OpenCode runs require `hilos-agent` 0.11.1 or newer. A remote Eve agent emits
events only for an orchestrator dispatch turn carrying a durable
`dispatchRunId`; ordinary Eve mentions, DMs, ambient replies, and agent-to-agent
replies do not. Unknown future event names and payload versions remain in the
ordered replay with `known: false`, so an older client can advance its cursor
without losing the rest of the run.

Payloads and summaries are redacted and bounded before storage. A
`redaction: "withheld"` event says some exhaust was intentionally omitted; it
does not mean the command produced nothing. The caller must still be a current
non-guest member who can read the run's room. Named agents may read a sibling
run only when their token can access that room; hosted-run tokens remain in
their source room, and channel-bound tokens remain bound. Named-agent replay is
also refused while the target room contains a guest, so member-only exhaust
cannot be echoed back into guest-readable chat. The room UI does not print this
exhaust into chat—the detailed console is an on-demand surface.

For a fan-out plan, authorized non-guest room readers can open the manager's
same redacted ledger from the plan card, then open each specialist's Activity
view from its roster row. `read_channel` and `get_thread` attach a content-free
`runGraph` to that plan message with the manager and specialist run ids,
statuses, and timing, including when the plan card lives inside a thread.
It omits the graph whenever the room contains a guest and never exposes runtime
sessions, transcript paths, prompts, or the private lineage table.

## Errors

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

```json
{
  "error": {
    "code": "not_found",
    "message": "Channel not found in workspace",
    "hint": "Check the id and token scope. Missing and inaccessible resources both return 404."
  }
}
```

`unauthorized` (401) · `invalid_request` (400) · `permission_required` (403) ·
`not_found` (404) · `conflict` (409) ·
`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.

Unauthorized responses include `WWW-Authenticate`; rate-limit responses include
`Retry-After`. Unknown `/api/*` paths use the same `code`, `message`, and `hint`
shape instead of returning an HTML page.

## Versions and deprecation

The major version is part of the URL. Additive fields may appear within
`/api/v1`; clients should ignore fields they do not use. A breaking request or
response change gets a new major path. If a v1 operation is retired, hilos will
document the replacement and send standard `Deprecation` and `Sunset` headers
before removal.

## 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`.

## Founder invites

Founder-only tools manage complimentary Pro and Max invitations. They require
both a personal connection with `workspace:admin` scope and a current verified
founder account. Workspace owners and named agents do not gain this authority.
The same operations are available in Admin → Invitations.

- `list_founder_invites`: read the latest 200 invitations and gift status.
- `create_founder_invite`: prepare a gift with `email`, optional `plan` (`pro`
  or `max`), `months` (1–12), `subject`, `note`, `acceptWithinDays` (1–90), and
  `creditsPerMonth` (1–50,000). A UUID `requestId` is required. Defaults: Max,
  one month, 30 days to accept, and the selected plan's one-seat monthly
  allowance shared by one workspace. This operation does not send email.
- `preview_founder_invite`: `inviteId` returns the exact email and private link.
- `send_founder_invite`: `inviteId` and UUID `requestId` send the invitation.
  Use `resend: true` and a new UUID only for an intentional additional send.
  After a reported failure, pass the saved `send_attempt_id` as
  `deliveryAttemptId` with a new `requestId` to retry the same provider attempt.
- `revoke_founder_invite`: `inviteId` revokes an unclaimed invitation.
- `extend_founder_gift`: `inviteId` and `months` set the absolute total duration
  of an active gift, up to 12 months. The current allowance is not refilled.
- `update_founder_invite_notes`: `inviteId` and `notes` save private interview
  notes (up to 4,000 characters).

Reads use `GET /api/v1/actions/:command`; mutations use `POST` JSON. Reuse the
same request ID when retrying. Delivery requests have a separate persisted
attempt ID so an uncertain email can be retried without sending a duplicate.
The recipient verifies the invited email and accepts through the web flow.
Gift credits refresh on calendar-month anniversaries, do not accumulate, and
expire at the end. Purchased credits and workspace content are preserved.
There is no automatic subscription or charge. Paid upgrades use Stripe Checkout.

## Welcome progress

Your personal connection can help you continue setting up a workspace:

- `get_onboarding` reads your saved answers and current setup progress.
- `save_onboarding` saves an `answers` patch, such as `intent`, `agentTools`, or
  `connectionInterests`. Other answers stay as they were.
- `finish_onboarding` finishes the welcome and, if you described a project,
  creates an editable doc called “Our first idea.” Repeating it keeps the same doc.

Each takes `workspaceId`. Writes accept `requestId` for retries. The same commands
are available at `/api/v1/actions/:command`; `list_capabilities` returns their full
input schemas. Selected tools are preferences, so selecting one does not connect
it, invite anyone, or start a paid run. `skillInterests` contains agent template
slugs for choosing a starting point.

These answers belong to your personal connection. Named agents can read the
project brief through the existing Docs tools, without access to your personal
welcome answers. On the web the welcome lives at `/w/:workspaceId/welcome`; you
can return to it from Home at any time, and the iOS app has its own screens.

For a direct return to setup, open `/w/:workspaceId?setup=1`. After the welcome,
`setupGoal` can focus `invite`, `agent`, `repo`, `connections`, `skills`, or
`first_contribution`. A channel link with
`?members=create&agentTemplate=reviewer` opens the existing create-agent form
with that gallery template. Other known gallery slugs work too; unknown values
are ignored. Opening these links never creates an agent by itself.


## Founder outreach

Platform founders can use `/admin/outreach` to find people, keep private notes,
and set a follow-up date. Write an email, choose **Review email** to save a draft, and then
**Send email** to send from Pabs at hilos, with replies going to the founder's
email address. Drafts and send results stay in the person's history. An
unconfirmed send must be retried from that same history entry; a provider result
means the email was accepted for sending, not that it was read or delivered.
Email opt-outs, bounces, complaints, and do-not-contact records block outreach.

**Gift a month** opens a personal Pro or Max invitation with the recipient
filled in. Preview its note and terms before sending. The gift starts when the
recipient accepts it, with no card or automatic renewal. Existing invitations
appear in the same dialog.

These outreach controls are interactive-only platform administration. Workspace
MCP, REST, and WebMCP credentials cannot read customer records or send founder
correspondence. Founder gift commands retain their separate verified-founder
personal MCP and REST access.
