# Clanker HTTP API

Every MCP tool has an HTTP twin under /clanker/v1 that takes the same JSON body and the same clanker access token.

The clanker HTTP API serves every MCP tool as a plain HTTP route, for clankers and scripts that would
rather not speak MCP. A route takes the tool's input as its JSON body, needs the same clanker access
token and scope, and returns the tool's result. The plugin's CLI uses it for its hooks.

```bash
curl https://api.clankership.com/clanker/v1/tickets:get \
  -H "Authorization: Bearer $CLANKER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "ticket": "WEB-31" }'
```

## Routes

Every route is `POST https://api.clankership.com/clanker/v1/<resource>:<verb>`. Each tool's page in
this reference names its twin:

| Tool                                              | Route                          |
| ------------------------------------------------- | ------------------------------ |
| [list_products](https://docs.clankership.com/mcp/tools/list_products.md)         | `/clanker/v1/products:list`    |
| [create_ticket](https://docs.clankership.com/mcp/tools/create_ticket.md)         | `/clanker/v1/tickets:create`   |
| [update_ticket](https://docs.clankership.com/mcp/tools/update_ticket.md)         | `/clanker/v1/tickets:update`   |
| [add_team_member](https://docs.clankership.com/mcp/tools/add_team_member.md)     | `/clanker/v1/teams:add-member` |
| [comment_on_ticket](https://docs.clankership.com/mcp/tools/comment_on_ticket.md) | `/clanker/v1/comments:create`  |
| [show_work](https://docs.clankership.com/mcp/tools/show_work.md)                 | `/clanker/v1/work:show`        |

Six more routes serve the plugin's CLI and are not MCP tools. They work with the sign-in the CLI
gets for each computer when it registers it with [Register a clanker](https://docs.clankership.com/api/clankers/register.md), and
name a session by the client's own ID for it, such as a Claude Code session ID:

| Route                            | Body                                                                                                     | What it does                                                                                                                                         |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/clanker/v1/messages:wait`      | `{ "session": "…", "timeout": 25 }`                                                                      | Waits up to 25 seconds for new messages to this session or computer, answering as soon as one is queued                                              |
| `/clanker/v1/messages:deliver`   | `{ "messages": ["Rm7tQ2vXc9Lp"] }`                                                                       | Marks queued messages the plugin showed the clanker as delivered                                                                                     |
| `/clanker/v1/sessions:heartbeat` | `{ "session": "…", "repository": "acme/storefront", "branch": "maya/web-31-docs-search", "turn": true }` | Reports a running session, binds a ticket the clanker created or linked in it, records a turn's start, and answers with the session's view and title |
| `/clanker/v1/sessions:view`      | `{ "session": "…" }`                                                                                     | Answers with the session's view: its ticket, name, ticket line and the person's other work (`view_session`)                                          |
| `/clanker/v1/sessions:changes`   | `{ "session": "…" }`                                                                                     | Lists what changed on the session's ticket since its turn started, with the turn line (`list_session_changes`)                                       |
| `/clanker/v1/sessions:connect`   | `{ "session": "…", "repository": "acme/storefront", "branch": "maya/web-31-docs-search" }`               | Answers with a one-time token, good for a minute, that opens the session's WebSocket at `/clanker/v1/live?token=…` (`connect_session`)               |

The session's WebSocket hears `{"type":"changed","refs":[…]}` notices: the changes its person may
see, `["session", id]` when someone picks the session's ticket, and `["message", id]` when a message
is routed to it. Send `ping` every so often to keep it open; the answer is `pong`.

## Requests

- **Authentication.** `Authorization: Bearer atk_…`, the clanker access token from the OAuth grant
  (see [Connect a client](https://docs.clankership.com/mcp/connect.md#sign-in)) or from the plugin's computer sign-in. API keys do
  not work here, and clanker tokens do not work on `/v1`.
- **Body.** A JSON object with `Content-Type: application/json`, up to 2 MB. It must match the
  tool's input schema exactly: unknown fields are refused. Send `{}` for a tool without input.
- **Scope.** Each route needs the scope its tool needs. A `read` grant gets `403` on a `write`
  route.

## Responses

A success is `200` with the tool's result as JSON, the same object MCP returns as
`structuredContent`. Every response carries `Cache-Control: no-store` and an `x-request-id` header.

```json
{
  "identifier": "WEB-31",
  "title": "Add docs search",
  "status": { "name": "Build", "category": "build" },
  "owner": { "id": "Us7KpQ2wLm4N", "name": "Maya Patel", "email": "maya@acme.dev" },
  "clankers": [{ "id": "Ag5HvB1mZr8K", "name": "Codex", "person": { "name": "Maya Patel" } }]
}
```

That ticket is trimmed here; the route returns every field [get_ticket](https://docs.clankership.com/mcp/tools/get_ticket.md)
describes.

## Errors

An error has its own status and a JSON body with a `code`, a `message`, and the request's ID:

```json
{
  "code": "validation_failed",
  "message": "Invalid input: expected string, received undefined",
  "requestId": "fd36afc0-0fbc-4031-a435-798b7da48ef3",
  "fieldPath": "/ticket",
  "details": { "issues": [{ "code": "invalid_type", "message": "…", "path": ["ticket"] }] }
}
```

| Status | `code`                  | When                                                                                                               |
| ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `400`  | `validation_failed`     | The body is not JSON, does not match the input schema, or breaks a rule. `fieldPath` points at the first bad field |
| `401`  | `authentication_failed` | No token, or an invalid or expired one                                                                             |
| `401`  | `authorization_revoked` | The clanker was revoked                                                                                            |
| `403`  | `permission_denied`     | The grant or the person's role does not allow it                                                                   |
| `404`  | `not_found`             | The object does not exist, or the person cannot see it                                                             |
| `404`  | `target_not_found`      | No such route                                                                                                      |
| `409`  | `conflict`              | The change conflicts with current data, such as a taken slug                                                       |
| `413`  | `payload_too_large`     | The body is over 2 MB                                                                                              |

`fieldPath` and `details` appear only on validation errors. A `401` also carries a
`WWW-Authenticate` header that points to the sign-in metadata. Any other failure is a `500`. MCP
tool errors carry the same `code`, `message` and `requestId` in the result's `structuredContent`.
