HTTP API
Local apps/clankie service on http://127.0.0.1:4310. There is no HTML
index: / is 404 and GET /health is the public probe.
Most other routes take an Authorization: Bearer <token> header. Pairing
redeem/complete use their one-time offer secrets instead. Persistent tokens
live in the credential broker (Keychain service bot.clankie.credentials):
| Principal | Keychain account | Typical use |
|---|---|---|
| Operator | clankie_operator |
TUI operator reads/writes, pairing, devices, play stop |
| Captain | clankie_captain (also CLANKIE_CAPTAIN_TOKEN) |
/operator/v1/dispatch, /captain/v1/lanes |
| Discord text | clankie_discord_bridge |
Discord channel turns, presence, person memory |
| Discord voice | clankie_discord_voice_bridge |
Voice briefing; voice-lane channel turns |
| Discord user text | clankie_discord_user_bridge |
Lab-user channel turns and presence |
| Discord user voice | clankie_discord_user_voice_bridge |
Lab-user voice-lane turns |
| Device | minted at pairing complete | /v1/devices/self* |
Discord-lane routes 403 if the bearer is the console captain token (steer
source api). Import this file in Yaak (Settings → Import/Export) or run
apps/clankie/scripts/setup-yaak.py.
Base URL http://127.0.0.1:4310 · version 0.2.0 · the raw document is openapi.yaml.
This is the local contract on your machine. From the internet only the public network surface is reachable, through
api.clankie.bot, and your machine still decides every grant.
Bearers
| Bearer | Where it lives |
|---|---|
| operator | Keychain clankie_operator |
| captain | Keychain clankie_captain / CLANKIE_CAPTAIN_TOKEN |
| discord text | Keychain clankie_discord_bridge or clankie_discord_user_bridge |
| discord voice | Keychain clankie_discord_voice_bridge or clankie_discord_user_voice_bridge |
| device | Device session token from pairing complete |
Routes
| Method | Route | Summary | Bearer |
|---|---|---|---|
| GET | /health |
Public liveness | none |
| GET | /v1/herdr |
Running service's worker-runtime binding | operator |
| GET | /v1/discord/presence-status |
Operator presence snapshot | operator |
| GET | /v1/discord/user-session/opt-in |
Current user-session opt-in | captain or operator |
| POST | /v1/discord/user-session/opt-in |
Record user-session ToS acceptance | operator |
| DELETE | /v1/discord/user-session/opt-in |
Revoke the active user-session opt-in | operator |
| GET | /v1/discord/stream-watch |
Latest observed Discord screen shares | captain or operator |
| POST | /v1/discord/stream-watch |
Bridge report of shares and optional stills | captain |
| GET | /v1/discord/readiness |
Discord captain readiness | discord text or discord voice |
| POST | /v1/discord/voice-briefing |
Compose the realtime voice briefing | discord voice |
| POST | /v1/discord/presence-session-events |
Apply a Discord presence phase event | discord text or discord voice |
| GET | /v1/discord/presence-sessions |
Full presence session records | captain |
| GET | /v1/discord/voice-history |
Completed voice stays | captain |
| GET | /v1/discord/voice-transcripts |
Page through retained Discord voice transcripts | captain |
| POST | /v1/discord/presence-actions |
Execute a policy-gated Discord action | discord text or discord voice |
| POST | /v1/captain/channel-turns |
One Discord message becomes one captain turn | discord text or discord voice |
| POST | /v1/captain/presence |
Report captain lease heartbeat | captain |
| POST | /operator/v1/dispatch |
Operator conversation contract | captain |
| GET | /operator/v1/voice-chat |
Open a private local voice chat | captain |
| GET | /captain/v1/lanes |
Observable captain lanes | captain |
| GET | /v1/captain/turn-metrics |
Recent settled captain turns | operator |
| GET | /v1/captain/prompt |
The system prompt a lane's session starts from | operator or captain |
| GET | /v1/captain/memory-card |
The memory card that lane's next run injects | operator or captain |
| GET | /v1/mcp |
Server-to-client notification stream for an open session | operator or captain or discord text or discord voice |
| POST | /v1/mcp |
A lane's tool bank over streamable-HTTP MCP | operator or captain or discord text or discord voice |
| DELETE | /v1/mcp |
End an MCP session | operator or captain or discord text or discord voice |
| GET | /v1/seat/events |
Long-poll the seat's outbox | operator or captain |
| POST | /v1/seat/events/{id}/reply |
Answer an escalation from the seat | operator or captain |
| GET | /v1/fleet/seats/{paneId}/events |
Long-poll a fleet seat's mailbox | operator or captain |
| GET | /v1/memory |
Browse every captain episode and Discord person fact | operator |
| POST | /v1/memory/discord-people/proposals |
Upsert a Discord person fact | discord text or discord voice |
| GET | /v1/memory/discord-people/{guildId}/{userId} |
Recall Discord person facts | discord text or discord voice |
| DELETE | /v1/memory/discord-people/{guildId}/{userId} |
Delete every fact for a Discord person | operator |
| GET | /v1/memory/discord-people/{guildId}/{userId}/export |
Operator export of a Discord person | operator |
| PATCH | /v1/memory/discord-people/{guildId}/{userId}/{factId} |
Edit one Discord person fact without changing its identity or provenance | operator |
| DELETE | /v1/memory/discord-people/{guildId}/{userId}/{factId} |
Forget one Discord person fact | operator |
| GET | /v1/memory/captain-episodes |
Recall episodes for a lane | captain |
| POST | /v1/memory/captain-episodes |
Record a self-authored episode | captain |
| PATCH | /v1/memory/captain-episodes/{lane}/{episodeId} |
Edit one episode's note, visibility, or retention | operator |
| DELETE | /v1/memory/captain-episodes/{lane}/{episodeId} |
Forget one captain episode | operator |
| POST | /v1/embodiment/intents |
Ask to start or stop play | captain |
| GET | /v1/embodiment/sessions/live |
Live play session | operator or captain |
| POST | /v1/embodiment/sessions/live/stop |
Operator kill-switch (ordinary stop intent) | operator |
| GET | /v1/embodiment/sessions/live/activity |
Present-tense self-observation | operator or captain |
| GET | /v1/embodiment/sessions/live/still |
One still of the live play screen | operator or captain |
| GET | /v1/embodiment/sessions/live/story |
Bounded story of this playthrough | operator or captain |
| GET | /v1/embodiment/sessions/{id} |
One embodiment session | captain |
| GET | /v1/browser/tools |
Live browser tool catalog | operator or captain |
| POST | /v1/browser/call |
Call a browser tool | operator or captain |
| POST | /v1/media/images |
Generate or edit an image | operator or captain |
| POST | /v1/media/videos |
Start or resume a video render | operator or captain |
| POST | /v1/pairing/offer |
Mint a one-time pairing offer | operator |
| POST | /v1/pairing/redeem |
Redeem an offer into a pending device | none |
| POST | /v1/pairing/complete |
Activate a pending device | none |
| GET | /v1/devices |
List paired devices | operator |
| POST | /v1/devices/{id}/revoke |
Revoke a device | operator |
| GET | /v1/devices/self |
Device reads its own registration | device |
| POST | /v1/devices/self/push |
Device records or clears its push delivery reference | device |
| POST | /v1/devices/self/session/refresh |
Renew a device session token | device |
Health
GET /health — Public liveness
Bearer: none
| Status | Meaning |
|---|---|
| 200 | Service is up (application/json) |
Operator
GET /v1/herdr — Running service's worker-runtime binding
Pending settings do not change this binding until service restart.
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Selected local worker runtime (application/json) |
| 401 | Operator authentication required |
| 503 | Operator authentication or runtime binding unavailable |
GET /v1/discord/presence-status — Operator presence snapshot
Phase and counts only. What clankie status reads.
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Present-tense presence (application/json) |
POST /v1/discord/user-session/opt-in — Record user-session ToS acceptance
Bearer: operator
Request body (required JSON)
{
"schemaVersion": 1,
"characterId": "clankie",
"acknowledgement": "I accept Discord user-session transport risk on my account.",
"guildIds": [
"123456789012345678"
],
"channelIds": [
"123456789012345678"
],
"dmPolicy": "deny"
}
| Status | Meaning |
|---|---|
| 201 | Recorded |
DELETE /v1/discord/user-session/opt-in — Revoke the active user-session opt-in
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Revoked |
| 409 | Nothing active to revoke |
GET /v1/discord/stream-watch — Latest observed Discord screen shares
Metadata plus up to four chronological stills. Raw video is never stored.
Bearer: captain or operator
| Status | Meaning |
|---|---|
| 200 | Current share projection |
GET /v1/discord/voice-transcripts — Page through retained Discord voice transcripts
Captain bearer only. Exact speech is returned only while
discord.voiceTranscriptLoggingEnabled is enabled; otherwise the route
returns an empty page with enabled: false.
Bearer: captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | cursor |
no | string, matching ^[0-9]{12}$ |
Exclusive cursor from a previous page. |
| query | limit |
no | integer, 1–200, default 100 |
| Status | Meaning |
|---|---|
| 200 | Bounded transcript page (application/json) |
GET /operator/v1/voice-chat — Open a private local voice chat
Upgrades to a WebSocket for captain bearers whose source lane is api.
Binary client frames are 24 kHz mono signed 16-bit little-endian PCM;
{"schemaVersion":1,"type":"commit"} ends one utterance. Binary
server frames use the same PCM format. JSON server frames report
status, attributed transcript, response_done, or error events.
Raw audio is not retained. The realtime room can call the captain through
the shared operator-conversation service when work is required.
Bearer: captain
| Status | Meaning |
|---|---|
| 101 | WebSocket opened |
| 403 | Bearer is not an operator API captain |
| 503 | Local realtime voice is unavailable |
Captain
POST /v1/captain/channel-turns — One Discord message becomes one captain turn
Bearer: discord text or discord voice
Request body (required JSON)
{
"schemaVersion": 1,
"deliveryId": "yaak-delivery-1",
"identity": {
"presenceSessionId": "replace-me",
"correlationId": "yaak-1",
"profileHash": "unversioned",
"characterId": "clankie",
"credentialRef": "discord_bot",
"transportKind": "bot"
},
"trigger": {
"kind": "message",
"id": "1",
"channelId": "123456789012345679",
"actorId": "123456789012345680",
"body": "hello from Yaak"
}
}
| Status | Meaning |
|---|---|
| 200 | Settled turn |
POST /v1/captain/presence — Report captain lease heartbeat
Bearer: captain
Request body (required JSON)
{
"schemaVersion": 1,
"type": "captain.heartbeat",
"eventId": "yaak-hb-1",
"leaseId": "replace-me",
"generationId": "replace-me",
"occurredAt": "2026-08-15T20:00:00.000Z"
}
| Status | Meaning |
|---|---|
| 202 | Accepted |
POST /operator/v1/dispatch — Operator conversation contract
One route, discriminated op: list, roster, get, create,
fork, close, send, cancel, replay, tail, autonomy,
terminal_tail. The TUI and relay both speak this. Seat scopes
identify Herdr direct-send/projection threads. cancel interrupts one
accepted run: the captain aborts the live model turn and the durable
log settles that run as cancelled. fork clones the current Pi leaf
into an ephemeral child conversation. Pane-scoped send and
state_stance requests qualify their pane ID with the source socket.
A mismatched send loses its pane association; a mismatched stance
returns 409 without changing a worker.
Bearer: captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| header | x-clankie-herdr-socket |
no | string | Caller Herdr session's exact socket path, required for pane identity. |
Request body (required JSON)
List conversations
{
"op": "list",
"schemaVersion": 1
}
List live Herdr seats
{
"op": "roster",
"schemaVersion": 1
}
Create a global conversation
{
"op": "create",
"schemaVersion": 1,
"scope": {
"kind": "global"
},
"title": "Yaak probe"
}
Get one conversation
{
"op": "get",
"schemaVersion": 1,
"conversationId": "replace-me"
}
Fork an ephemeral side conversation
{
"op": "fork",
"schemaVersion": 1,
"parentConversationId": "replace-me"
}
Close a non-default conversation or discard a side fork
{
"op": "close",
"schemaVersion": 1,
"conversationId": "replace-me"
}
Send a message turn
{
"op": "send",
"schemaVersion": 1,
"turn": {
"schemaVersion": 1,
"kind": "message",
"conversationId": "replace-me",
"surfaceClientId": "yaak",
"expectedRevision": 0,
"message": "ping from Yaak"
}
}
Replay a page
{
"op": "replay",
"schemaVersion": 1,
"replay": {
"schemaVersion": 1,
"conversationId": "replace-me",
"surfaceClientId": "yaak",
"limit": 50
}
}
Read autonomy state for one conversation
{
"op": "autonomy",
"schemaVersion": 1,
"conversationId": "replace-me",
"command": {
"action": "status"
}
}
| Status | Meaning |
|---|---|
| 200 | Discriminated result |
GET /captain/v1/lanes — Observable captain lanes
Bearer: captain
| Status | Meaning |
|---|---|
| 200 | Lane listing |
GET /v1/captain/turn-metrics — Recent settled captain turns
Counters, execution identity and reported usage for recent settled
operator and Discord turns, newest first, from the durable
turn-settled.jsonl the captain appends. Never a transcript, tool
argument, tool output or credential.
execution is the model, provider and effort that actually ran the turn,
captured as it executed rather than read back from configuration.
usage is totalTokens summed over the assistant messages the provider
reported plus reports, the number that contributed. Both are null
when unknown — a turn settled before the capture existed, or a provider
that reported nothing. Null never means zero, and contextTokensStart /
contextTokensEnd are context occupancy, never usage or a charge.
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | limit |
no | integer, 1–100, default 20 | 1–100; clamped, never refused. Defaults to 20. |
| query | runId |
no | string | Narrow to one run. |
| Status | Meaning |
|---|---|
| 200 | Bounded settled-turn metrics, newest first |
| 401 | Operator authentication required |
GET /v1/captain/prompt — The system prompt a lane's session starts from
Plain text, for a seat launcher in another harness. sections is a
comma-separated subset of identity, persona, reach, address,
model; omitted, it is the four a pi session starts with (model is
the per-run card, asked for by name). The lane must be the bearer's
own — the operator credential and the console captain token are
operator, a Discord bridge bearer is the lane it serves.
Bearer: operator or captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | lane |
no | string, one of operator, discord_voice, discord_presence, gameplay |
Defaults to the bearer's own lane. |
| query | sections |
no | string, e.g. persona,model |
| Status | Meaning |
|---|---|
| 200 | The assembled prompt (text/plain) |
| 400 | Unknown lane or section name |
| 403 | Lane is not the bearer's own |
GET /v1/captain/memory-card — The memory card that lane's next run injects
Plain text, for a per-turn hook. Filtered by lane the same way the pi session's own injection is: operator-private episodes reach only the operator lane. Empty when the lane has recalled nothing yet. The lane must be the bearer's own.
Bearer: operator or captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | lane |
no | string, one of operator, discord_voice, discord_presence, gameplay |
Defaults to the bearer's own lane. |
| Status | Meaning |
|---|---|
| 200 | The lane's recall card (text/plain) |
| 400 | Unknown lane |
| 403 | Lane is not the bearer's own |
GET /v1/mcp — Server-to-client notification stream for an open session
Requires mcp-session-id; the bearer must resolve to the session's lane.
Bearer: operator or captain or discord text or discord voice
| Status | Meaning |
|---|---|
| 200 | SSE stream |
| 400 | No session id |
| 403 | The session belongs to another lane |
| 404 | Unknown or expired session |
POST /v1/mcp — A lane's tool bank over streamable-HTTP MCP
Streamable-HTTP MCP over Clankie's authored tool registry. The bearer
selects the lane and the lane selects the tools: the operator credential
and the console captain token are operator, a Discord bridge bearer is
the lane it serves, and a connection lists exactly that lane's authority
plan (authored tools, the browser catalog, and the connected-service
tools that lane may reach). An initialize POST opens a session and
returns its id in mcp-session-id; every later request must carry that
header and resolve to the same lane. DELETE ends the session.
clankie mcp is the stdio bridge to this route (ADR 0152).
Bearer: operator or captain or discord text or discord voice
| Status | Meaning |
|---|---|
| 200 | JSON-RPC response for this request |
| 400 | No session id on a non-initialize request |
| 403 | The session belongs to another lane |
| 404 | Unknown or expired session |
DELETE /v1/mcp — End an MCP session
Bearer: operator or captain or discord text or discord voice
| Status | Meaning |
|---|---|
| 200 | Session closed |
| 403 | The session belongs to another lane |
| 404 | Unknown or expired session |
GET /v1/seat/events — Long-poll the seat's outbox
Wakes, herdr completion watches, and room escalations queued for a
bound head (ADR 0152). Polling is what binds the seat: clankie mcp
asks again the moment a poll returns and pushes each event into the
session as a channel notification. Operator bearer only; a Discord
bridge bearer is 403. wait is capped at 30000 ms.
Bearer: operator or captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | wait |
no | integer, 0–30000, default 0 |
| Status | Meaning |
|---|---|
| 200 | Events taken from the outbox, possibly none (application/json) |
| 403 | Not the operator lane |
POST /v1/seat/events/{id}/reply — Answer an escalation from the seat
The reply lands in the escalating conversation as his own message and settles that run.
Bearer: operator or captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | id |
yes | string |
Request body (required JSON)
{
"schemaVersion": 1,
"text": "On it — the build is green."
}
| Status | Meaning |
|---|---|
| 200 | Replied |
| 404 | Nothing is waiting on that event |
GET /v1/fleet/seats/{paneId}/events — Long-poll a fleet seat's mailbox
A DM or room turn queued for a bound fleet seat (ADR 0161). Polling is
what binds the mailbox: clankie mcp --seat asks again the moment a
poll returns and pushes each event into the session as a channel
notification. Operator bearer only; a Discord bridge bearer is 403.
wait is capped at 30000 ms. unknown_seat (404) is the pane before
herdr has classified the harness; the bridge retries.
Bearer: operator or captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | paneId |
yes | string | |
| query | wait |
no | integer, 0–30000, default 0 |
| Status | Meaning |
|---|---|
| 200 | Events taken from the mailbox, possibly none (application/json) |
| 403 | Not the operator lane |
| 404 | No messageable agent in that pane |
Discord
GET /v1/discord/user-session/opt-in — Current user-session opt-in
Bearer: captain or operator
| Status | Meaning |
|---|---|
| 200 | Opt-in or null |
POST /v1/discord/stream-watch — Bridge report of shares and optional stills
Bearer: captain
| Status | Meaning |
|---|---|
| 204 | Report accepted |
GET /v1/discord/readiness — Discord captain readiness
Requires a Discord text or voice bridge bearer.
Bearer: discord text or discord voice
| Status | Meaning |
|---|---|
| 200 | Ready |
| 503 | A required check is down |
POST /v1/discord/voice-briefing — Compose the realtime voice briefing
Voice-bridge bearer only. Request carries ids; persona is owner-authored.
Bearer: discord voice
Request body (required JSON)
{
"schemaVersion": 1,
"guildId": "123456789012345678",
"channelId": "123456789012345679",
"consentedUserIds": [
"123456789012345680"
]
}
| Status | Meaning |
|---|---|
| 200 | Bounded instructions + briefing |
POST /v1/discord/presence-session-events — Apply a Discord presence phase event
Bearer: discord text or discord voice
Request body (required JSON)
A JSON object.
| Status | Meaning |
|---|---|
| 200 | Accepted or idempotent replay |
GET /v1/discord/presence-sessions — Full presence session records
Bearer: captain
| Status | Meaning |
|---|---|
| 200 | Session list |
GET /v1/discord/voice-history — Completed voice stays
Bearer: captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | limit |
no | integer, 1–32, default 5 |
| Status | Meaning |
|---|---|
| 200 | Recent stays |
POST /v1/discord/presence-actions — Execute a policy-gated Discord action
Needs a live-session claim in headers:
x-clankie-discord-presence-session,
x-clankie-discord-presence-phase,
x-clankie-discord-presence-revision.
Bearer: discord text or discord voice
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| header | x-clankie-discord-presence-session |
yes | string | |
| header | x-clankie-discord-presence-phase |
yes | string | |
| header | x-clankie-discord-presence-revision |
yes | integer |
Request body (required JSON)
{
"schemaVersion": 1,
"idempotencyKey": "yaak-typing-1",
"action": "discord.presence.typing_start",
"identity": {
"presenceSessionId": "replace-me",
"correlationId": "yaak-1",
"profileHash": "unversioned",
"characterId": "clankie",
"credentialRef": "discord_bot",
"transportKind": "bot"
},
"payload": {
"kind": "typing_start",
"channelId": "123456789012345679"
}
}
| Status | Meaning |
|---|---|
| 200 | Action result |
| 409 | Stale live claim or action unavailable |
Memory
GET /v1/memory — Browse every captain episode and Discord person fact
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Complete operator-only memory catalog |
POST /v1/memory/discord-people/proposals — Upsert a Discord person fact
Bearer: discord text or discord voice
Request body (required JSON)
{
"schemaVersion": 1,
"proposalId": "yaak-proposal-1",
"fact": {
"schemaVersion": 1,
"factId": "yaak-fact-1",
"subject": {
"guildId": "123456789012345678",
"userId": "123456789012345680"
},
"kind": "person-fact",
"body": "Prefers short replies.",
"visibility": {
"scope": "guild"
},
"provenance": {
"correlationId": "yaak-1",
"sourceEventId": "yaak-src-1",
"sourceSurface": "discord_text",
"rawTranscript": false
},
"confidence": 0.8,
"createdAt": "2026-08-15T20:00:00.000Z",
"updatedAt": "2026-08-15T20:00:00.000Z"
}
}
| Status | Meaning |
|---|---|
| 201 | Stored |
GET /v1/memory/discord-people/{guildId}/{userId} — Recall Discord person facts
Bearer: discord text or discord voice
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | guildId |
yes | string | |
| path | userId |
yes | string | |
| query | channelId |
no | string | |
| query | query |
no | string |
| Status | Meaning |
|---|---|
| 200 | Facts and optional recall card |
DELETE /v1/memory/discord-people/{guildId}/{userId} — Delete every fact for a Discord person
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | guildId |
yes | string | |
| path | userId |
yes | string |
| Status | Meaning |
|---|---|
| 200 | Deleted ids |
GET /v1/memory/discord-people/{guildId}/{userId}/export — Operator export of a Discord person
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | guildId |
yes | string | |
| path | userId |
yes | string |
| Status | Meaning |
|---|---|
| 200 | Export bundle |
PATCH /v1/memory/discord-people/{guildId}/{userId}/{factId} — Edit one Discord person fact without changing its identity or provenance
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | guildId |
yes | string | |
| path | userId |
yes | string | |
| path | factId |
yes | string |
Request body (required JSON)
{
"body": "Prefers concise replies.",
"confidence": 0.9
}
| Status | Meaning |
|---|---|
| 200 | Updated fact |
| 404 | Fact not found |
DELETE /v1/memory/discord-people/{guildId}/{userId}/{factId} — Forget one Discord person fact
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | guildId |
yes | string | |
| path | userId |
yes | string | |
| path | factId |
yes | string |
| Status | Meaning |
|---|---|
| 204 | Forgotten |
GET /v1/memory/captain-episodes — Recall episodes for a lane
Without query, the bounded recent card. With one, on-demand search over everything retained, filtered by what that lane may see. Both branches answer with a rendered card and never with episode records, so no caller reads another memory's provenance ids.
Bearer: captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| query | lane |
yes | string, one of operator, discord_voice, discord_presence, gameplay |
|
| query | query |
no | string | Search terms; every term must appear in the note or the room it happened in. |
| query | limit |
no | integer, 1–… | Matches to return (default 8, capped at 32). |
| Status | Meaning |
|---|---|
| 200 | Recall card — the recent one, or the search's when a query was given |
POST /v1/memory/captain-episodes — Record a self-authored episode
Recording only ever adds. An id the store already holds is a conflict, not an update; a byte-identical retry returns the original. Editing an existing note goes through PATCH. A Discord bearer may only write the lane it serves (discord_text writes discord_presence, discord_voice writes its own).
Bearer: captain
Request body (required JSON)
{
"schemaVersion": 1,
"episodeId": "yaak-ep-1",
"lane": "operator",
"targetId": "self",
"summary": "Tried the HTTP API from Yaak.",
"visibility": "operator_private",
"retained": false,
"provenance": {
"characterId": "clankie",
"sessionId": "captain",
"selfAuthored": true,
"rawTranscript": false
},
"occurredAt": "2026-08-15T20:00:00.000Z"
}
| Status | Meaning |
|---|---|
| 200 | Recorded id |
| 403 | The bearer's lane may not author an episode in that lane |
| 409 | Retained memory is full, or the episode id already exists. Nothing was changed in either case. |
PATCH /v1/memory/captain-episodes/{lane}/{episodeId} — Edit one episode's note, visibility, or retention
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | lane |
yes | string, one of operator, discord_voice, discord_presence, gameplay |
|
| path | episodeId |
yes | string |
Request body (required JSON)
{
"summary": "Corrected note about what happened.",
"visibility": "operator_private",
"retained": true
}
| Status | Meaning |
|---|---|
| 200 | Updated episode |
| 404 | Episode not found |
| 409 | Retained memory is full; nothing was changed |
DELETE /v1/memory/captain-episodes/{lane}/{episodeId} — Forget one captain episode
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | lane |
yes | string, one of operator, discord_voice, discord_presence, gameplay |
|
| path | episodeId |
yes | string |
| Status | Meaning |
|---|---|
| 204 | Forgotten |
Embodiment
POST /v1/embodiment/intents — Ask to start or stop play
Bearer: captain
Request body (required JSON)
start
{
"kind": "start",
"schemaVersion": 1,
"intentId": "yaak-start-1",
"originLane": "operator",
"requestedBy": "local-operator",
"requestedAt": "2026-08-15T20:00:00.000Z",
"environmentId": "pokemon-firered",
"budget": {}
}
stop
{
"kind": "stop",
"schemaVersion": 1,
"intentId": "yaak-stop-1",
"originLane": "operator",
"requestedBy": "local-operator",
"requestedAt": "2026-08-15T20:00:00.000Z",
"sessionId": "replace-me"
}
| Status | Meaning |
|---|---|
| 200 | Accepted, refused (play_session_active when another local or hosted session is live), or stop requested |
GET /v1/embodiment/sessions/live — Live play session
Captain or operator.
Bearer: operator or captain
| Status | Meaning |
|---|---|
| 200 | { session } or { session: null } |
POST /v1/embodiment/sessions/live/stop — Operator kill-switch (ordinary stop intent)
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Stop submitted |
| 404 | Not playing |
GET /v1/embodiment/sessions/live/activity — Present-tense self-observation
Bearer: operator or captain
| Status | Meaning |
|---|---|
| 200 | not_playing, pending, or snapshot |
GET /v1/embodiment/sessions/live/still — One still of the live play screen
Read-only glance. PNG as base64. No controller.
Bearer: operator or captain
| Status | Meaning |
|---|---|
| 200 | not_playing, pending, or still |
GET /v1/embodiment/sessions/live/story — Bounded story of this playthrough
Journal projection — objective, maps, last notable moments. Not the raw log.
Bearer: operator or captain
| Status | Meaning |
|---|---|
| 200 | not_playing, pending, or card |
GET /v1/embodiment/sessions/{id} — One embodiment session
Bearer: captain
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | id |
yes | string |
| Status | Meaning |
|---|---|
| 200 | Session |
| 404 | Unknown |
Browser
GET /v1/browser/tools — Live browser tool catalog
Bearer: operator or captain
| Status | Meaning |
|---|---|
| 200 | Catalog |
| 503 | Browser host unavailable |
POST /v1/browser/call — Call a browser tool
Calls one raw agent-browser MCP tool from the live catalog.
Bearer: operator or captain
Request body (required JSON)
{
"schemaVersion": 1,
"tool": "agent_browser_snapshot",
"arguments": {}
}
| Status | Meaning |
|---|---|
| 200 | Result or typed refusal |
Media
POST /v1/media/images — Generate or edit an image
Bearer: operator or captain
Request body (required JSON)
{
"schemaVersion": 1,
"prompt": "a small robot sitting at a terminal",
"aspectRatio": "1:1"
}
| Status | Meaning |
|---|---|
| 200 | ok or refused |
| 503 | Media plane unconfigured |
POST /v1/media/videos — Start or resume a video render
Bearer: operator or captain
Request body (required JSON)
{
"schemaVersion": 1,
"prompt": "a small robot waving",
"durationSeconds": 6
}
| Status | Meaning |
|---|---|
| 200 | ok, pending, or refused |
Pairing
POST /v1/pairing/offer — Mint a one-time pairing offer
The offer secret appears once in the response. Do not log it.
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Offer wire (deep link + code) |
POST /v1/pairing/redeem — Redeem an offer into a pending device
Unauthenticated. The offer secret or typed code is the capability.
Bearer: none
Request body (required JSON)
{
"code": "123-456",
"device": {
"name": "Yaak",
"platform": "macos"
}
}
| Status | Meaning |
|---|---|
| 200 | Pending device + completion token; a configured gateway also returns the host-scoped base URL |
POST /v1/pairing/complete — Activate a pending device
Bearer: none
Request body (required JSON)
{
"completionToken": "replace-me",
"acceptedGrants": {
"chat": true,
"steer": true,
"terminalObserve": true,
"terminalControl": false
}
}
| Status | Meaning |
|---|---|
| 200 | Device token issued; relayUrl is the host-scoped gateway base when configured |
Devices
GET /v1/devices — List paired devices
Secret-free rows. push carries the device's last delivery state —
{ registrationId, sequence, enabled } — so the listing is truthful
about a device that turned notifications off or whose registration the
gateway invalidated. The APNs token and delivery key are never here
(ADR 0159).
Bearer: operator
| Status | Meaning |
|---|---|
| 200 | Secret-free device rows |
POST /v1/devices/{id}/revoke — Revoke a device
Bearer: operator
| In | Name | Required | Type | Description |
|---|---|---|---|---|
| path | id |
yes | string |
| Status | Meaning |
|---|---|
| 200 | Revoked row |
| 404 | Unknown |
GET /v1/devices/self — Device reads its own registration
Bearer: device
| Status | Meaning |
|---|---|
| 200 | Self row |
POST /v1/devices/self/push — Device records or clears its push delivery reference
Body is { registrationId, sequence, enabled }. The host stores only the
reference, its version and whether delivery is on — never the APNs token
or the delivery key, which the app registers with the gateway directly
(ADR 0159). Disabling records that state at its version rather than
forgetting it, so ordering survives it.
An older sequence is 409 stale_push_registration. The same
sequence is accepted only when it restates what was already recorded:
a different registrationId, or the same one with enabled flipped, is
409 conflicting_push_registration. That is also what refuses a
re-enable at the version a gateway invalidation was recorded against.
Bearer: device
| Status | Meaning |
|---|---|
| 200 | Accepted reference, echoed with its enabled state |
| 400 | Malformed body |
| 401 | Device session is unknown, expired, or revoked |
| 409 | Stale or conflicting registration version |
POST /v1/devices/self/session/refresh — Renew a device session token
Bearer: device
| Status | Meaning |
|---|---|
| 200 | New token; relayUrl republishes the host-scoped gateway base when configured |