---
title: HTTP API
order: 4
---
# HTTP API

> Every MCP tool has a plain-HTTP twin. A runner with no MCP support drives the identical contract with curl. Bring-your-own-agent is only honest if the transport is not a privilege.

Base: `https://mainmind.app`. Bearer-authenticated routes take `Authorization: Bearer <token>`;
tenant selection is `?tenant=` on GET and `tenant` in the body on POST, defaulting
to the mount's own organization. Where a token comes from, what errors look like,
and a worked example are on [Auth, errors, examples](/docs/using-the-api).

## Public

No credential at all. The right first call when you are checking connectivity.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| GET | `/api/health` | public | Liveness and which channels of the runtime contract are open. | {ok, service, version, channels}. `channels` says which parts of the runtime contract are open. |
| GET | `/api/surface` | public | This surface, as JSON, the machine-readable twin of ARCHITECTURE.md. What the deployed worker actually speaks. | The whole surface as JSON: {service, version, roles, notes, tools[], http[]}. |
| GET | `/llms.txt` | public | Agent-readable index of the public docs (llms.txt convention). | text/markdown. The agent index of these docs. |
| POST | `/api/access` | public | Ask for access. Body: {email, note?} as JSON or as a plain form post. A repeat address updates the note rather than adding a row. | {ok} or {ok, already:true}. A form post redirects back to the landing with ?access=thanks or ?access=error. |

## Your knowledge

Getting a company file in, and reading it back out.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| POST | `/api/projection` | bearer | Push a projection of a company file. Body: {tenant?, commit?, mode: replace\|merge, nodes: [{path, content}]}. Replace mode deletes ONLY the pushing tenant's rows. | {ok, tenant, built_at, ingested, total, findings[], vectors{embedded_nodes, chunks, errors[]}}. findings reports frontmatter lines the profile does not accept rather than dropping them. |
| GET | `/api/nodes` | bearer | List projected nodes. Query: tenant?, kind?, limit?. Includes projection freshness and kind counts. | {tenant, projection{built_at, commit_sha, node_count, source}, kinds[{kind, n}], nodes[]}. Default limit 200, maximum 500, clamped silently. |

## Runs

Opening a run before real work, keeping it alive, and closing it.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| GET | `/api/runs` | bearer | Open and recent runs. Query: tenant?. | {open[], recent[]}. Open runs carry a derived stale flag. Capped at 40 open and 12 recent, with no limit parameter and no paging. |
| POST | `/api/runs` | bearer | Open a run. Body: {tenant?, task, actor_label, harness?, process?, doing?, scopes?}. | {ok, run_id, started_at}. Carry the run_id through heartbeat, events and finish. |
| POST | `/api/runs/:id/heartbeat` | bearer | Report a run alive; update doing/scopes. | {ok, run_id, heartbeat_at}, or 404 {error} when no open run has that id. |
| POST | `/api/runs/:id/finish` | bearer | Close a run with a terminal status. | {ok, run_id, status} with the terminal status that was recorded. |

## Events

The operational feed the whole team reads.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| GET | `/api/events` | bearer | Recent events. Query: tenant?, limit?. | {tenant, events[]}, newest first. Default limit 50, maximum 200, clamped silently. |
| POST | `/api/events` | bearer | Record an operational event. Body: {tenant?, type, title, detail?, amount?, needs_you?, actor?, run_id?}. | {ok, ts, type} with the timestamp the event was recorded at. |

## Decisions

Raising a question, and finding out what was answered.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| POST | `/api/asks` | bearer | Create a decision link. Body: {tenant?, ask, becomes, because?, cost?, diff?, branch?, base_sha?, run_id?, pr_number?, repo?}. | {ok, key, url}. The URL is the decision page, and holding it is what confers the right to answer. |
| GET | `/api/asks` | bearer | Rulings given that nothing has carried out yet (the lander's poll). | {asks[]}: rulings given but not yet carried out. This is the lander's poll, a lander being a machine that holds your git credential. |
| GET | `/api/asks/:key` | bearer | One ask's status and ruling, what the founder answered. | {key, status, ruling, ruled_at, ask, branch}. ruling is the answer in the founder's own words. |
| POST | `/api/asks/:key/done` | bearer | Mark a ruling carried out (the lander merged/closed the PR). | {ok}. Marks a ruling carried out so the poll stops returning it. |

## Members

Inviting people and taking access away. The founder's own surface.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| GET | `/api/members` | bearer | List members and invite codes (the Founder's own surface). | {members[]} with tenant, slug, name, role, status and invite code. |
| POST | `/api/members` | bearer | Invite a member. Body: {tenant?, name, role: founder\|steward\|observer, charter?}. | {ok, tenant, slug, name, role, invite_code}. The invite code is shown once, here. |
| POST | `/api/members/:slug/revoke` | bearer | Revoke a member. Stops new authorizations; never touches the repository. | {ok, slug}. Stops new authorizations. It never touches the repository. |

## The mount and the ruling link

Reachable by people rather than by token.

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| GET | `/d/:key` | link | The decision page: one question, evidence attached, yes or no. Holding the link is holding the right to rule. | An HTML page rather than JSON: one question, the evidence, and two buttons. |
| GET | `/mcp` | oauth | The MCP mount (streamable HTTP). OAuth via personal invite code; the founder's machine bearer also works. | The MCP streamable HTTP transport. Unauthenticated requests answer 401 with a WWW-Authenticate header pointing at the OAuth metadata. |

## Everything else

| Method | Path | Auth | What | Returns |
|---|---|---|---|---|
| GET | `/api/access` | bearer | The access requests waiting on you, newest first. The founder's own surface. | {requests[]} with created_at, email, note, source and status. |

> Every organization is a tenant. Every projected node, run, event, ask and member row carries a tenant slug, every read is scoped by it, and a replace-mode projection push can only ever delete its own tenant's rows. The product's own org is tenant 'hq' in the same database, tenant #2 is the vendor.
