Client Builders
API guide
Use the Gooselake HTTP and SSE API for sessions, turns, events, approvals, providers, processes, worktrees, teams, diagnostics, and MCP gateway calls.
This document is the human-facing guide to the runtime HTTP/SSE API and the initial Goosetower browser gateway API.
Sources of truth:
- generated artifact:
openapi/runtime-server-openapi.yaml - route + handler code:
crates/runtime-server/src/http/ - OpenAPI generator:
crates/runtime-server/src/openapi.rs - shared runtime structs:
crates/runtime-core/src
If this guide disagrees with runtime behavior, treat server/core code as authoritative.
Goosetower gateway sources:
- gateway route + handler code:
crates/goosetower/src/http/mod.rs - ticket validation:
crates/goosetower/src/auth/mod.rs - WebSocket gateway:
crates/goosetower/src/gateway/mod.rs - Protobuf schema:
proto/goosetower/v1/
Quick API start
BASE_URL="http://127.0.0.1:8080"
TOKEN="<runtime-bearer-token>"
curl -fsS "$BASE_URL/health"
curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE_URL/v1/health"
Public routes:
GET /healthGET /openapi.yaml
Protected routes:
- all
/v1/**routes requireAuthorization: Bearer <token>
Auth failure returns HTTP 401 with:
{"error":"missing or invalid bearer token"}
API surface by group
See Endpoint Catalog for the full route list.
Top-level groups:
- Runtime/meta: health, version, consistent source bootstrap, OpenAPI, diagnostics
- Providers/auth: provider list/models plus Codex, Claude, and ACP auth endpoints
- Sessions: create/list/get/resume/close, turns, approvals, event replay/stream
- Global events: replay and stream
- Teams/comms: team lifecycle, spawn, messages, deliveries, retries, snapshots, interrupts
- Processes: run/list/get/logs/kill, replay and stream process events
- Worktrees: create/list/get/claim/release/cleanup
- MCP gateway: capabilities and invoke
- Goosetower browser gateway: ticket-authenticated Protobuf WebSocket at
/v1/realtime
Request/response precision
The generated OpenAPI currently prioritizes route/method/content-type coverage. Many request and response bodies are represented as broad JsonObject schemas.
For exact JSON fields, use:
- handler input structs in
crates/runtime-server/src/http/ - shared input/output structs in
crates/runtime-core/src/runtime.rsandcrates/runtime-core/src/services.rs - durable record structs in
crates/runtime-core/src/state.rs
Goosetower realtime gateway
Goosetower exposes the V0 browser gateway as a separate service from
gg-runtime-server. It routes browser commands to a configured Gooselake
runtime and keeps only in-memory gateway state for connection/session concerns.
Routes:
GET /healthis public and reports Goosetower service health.GET /v1/health,GET /v1/sources, andGET /v1/metricsrequireAuthorization: Bearer <goosetower-api-token>.GET /v1/debug/protocol,/v1/debug/sources,/v1/debug/subscriptions,/v1/debug/materializer, and/v1/debug/auditrequire the same bearer token and only work whendebug.endpoints_enabled = true.POST /v1/dev/ticketsrequires the same bearer token and only works whendebug.endpoints_enabled = true. It is a local-development ticket issuer, not the production web-auth path.GET /v1/realtime?ticket=<ticket>upgrades to a WebSocket using binary Protobuf frames fromproto/goosetower/v1/realtime.proto.
Realtime auth:
- Browsers authenticate with a short-lived, signed, single-use URL ticket.
- The gateway checks the WebSocket
Originheader against the exact configuredserver.allowed_gooseweb_originsallowlist before upgrade. - Ticket claims include issuer, audience, subject, workspace ID, scopes,
allowed origins, expiry, issued-at time, and
jtinonce. - Expired, replayed, wrong-audience, wrong-issuer, wrong-origin, missing-subject, missing-workspace, and missing-scope tickets are rejected.
- A connection ticket must include
gateway:connect; command frames requiregateway:command.
Realtime protocol behavior:
- The server sends
Helloafter connection open with connection ID, server time, heartbeat interval, max message size, protocol version, and resume support. - Clients send binary Protobuf
RealtimeEnvelopeframes. Text frames are not accepted. Pingframes receivePong; oversized WebSocket messages are closed by the WebSocket layer according towebsocket.max_message_bytes.Subscribecreates per-connection interest state and returns aSnapshotfor views such asboard,approval_inbox,session,team,process_tail,ledger,fleet, andworktrees.- Patches are faned out only to matching subscriptions.
- Outbound frames use priority lanes: critical, state, tokens, and bulk. State patches can coalesce by entity under backpressure; critical command/auth frames are prioritized.
- Commands must carry
command_id,target,base_entity_version, andcreated_at_client_unix_ms. Duplicatecommand_idvalues are answered withCommandDuplicatefrom in-memory TTL state.
V0 command routing supports:
- send turn
- resolve approval
- interrupt turn
- direct team message
- broadcast team message
- spawn team member
- retry delivery
- cancel delivery
- kill process
- start process
Gateway audit events are emitted over the realtime stream for connection open/close, subscription changes, auth refresh, command accepted/rejected/ duplicate, source gap, and snapshot resync.
Sessions
Create a provider-backed runtime session:
SESSION_JSON=$(curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider":"codex",
"model":"gpt-5.4-mini",
"cwd":"/workspace/repo",
"permission_mode":"default",
"metadata":{"purpose":"docs smoke"}
}' \
"$BASE_URL/v1/sessions")
SESSION_ID=$(echo "$SESSION_JSON" | jq -r '.id')
POST /v1/sessions accepts:
| Field | Required | Notes |
|---|---|---|
provider |
yes | codex, claude, or acp. |
model |
no | Provider-specific model ID. ACP can ignore global model catalogs. |
cwd |
no | Working directory for provider session. |
permission_mode |
no | Passed to providers; require_approval enables runtime approval gating where supported. |
metadata |
no | Arbitrary JSON object/value stored with the session. |
Send a turn:
TURN_JSON=$(curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"input":[{"type":"text","text":"Run ls and summarize the repo."}]}' \
"$BASE_URL/v1/sessions/$SESSION_ID/turns")
POST /v1/sessions/{session_id}/turns accepts:
| Field | Required | Notes |
|---|---|---|
input |
yes | Array of provider input objects. Text objects use { "type": "text", "text": "..." }. |
expected_turn_id |
no | Optional client-side concurrency guard. |
permission_mode |
no | Optional per-turn permission override. |
The response is an accepted turn, not necessarily terminal output:
{"session_id":"...","turn_id":"...","status":"accepted"}
Interrupt a turn:
curl -fsS -X POST -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/v1/sessions/$SESSION_ID/turns/$TURN_ID/interrupt"
Close a session:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason":"done"}' \
"$BASE_URL/v1/sessions/$SESSION_ID/close"
Approvals
When runtime approval gating is active, a pending approval is stored and emitted in the event stream. Respond with:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision":"accept"}' \
"$BASE_URL/v1/sessions/$SESSION_ID/approvals/$APPROVAL_ID"
Accepted decision values:
acceptaccepteddeclinedeclinedrejectrejected
The runtime normalizes to provider approval behavior when the provider supports it.
SSE and replay model
Consistent source bootstrap
GET /v1/bootstrap is the bearer-protected reconstruction boundary for
Goosetower. It returns:
source_epoch: a runtime-issued durable-store generation identifierhigh_watermark: the greatest globalruntime_events.idvisible to the read, or0for an empty event ledgerrecords: the current sessions, approvals, teams, team members, bounded-use team messages/deliveries, managed worktrees/claims, and processes needed by the materializer
The runtime reads the epoch, watermark, and all returned records from one
SQLite read transaction. A concurrent commit after the watermark read is not
partially visible in records. Every publishing store operation applies its
complete current-record mutation set and appends the corresponding runtime
event in one immediate SQLite transaction. Mutation, constraint, event-insert,
or commit failure rolls the whole operation back, so a crash cannot leave a
record ahead of the event ledger or an orphaned writer marker that blocks
bootstrap. Concurrent writers, including writers targeting the same record,
therefore become visible only as complete record-and-event commits. The
response query path does
not read provider credentials, turn internals, or diagnostic journals.
Bootstrap reads are bounded to 10,000 rows per participating table, 50,000
returned rows in aggregate, 16 MiB of measured text/JSON fields, and a final
24 MiB serialized-record ceiling. An
oversized source fails explicitly before hydration/serialization instead of
returning a partial snapshot. SQLite work for this endpoint runs on the
server’s blocking-work pool rather than the async HTTP executor.
The source epoch persists across ordinary runtime restarts. Alongside a
restrictively-permissioned, atomically replaced generation marker, the runtime
keeps a high-watermark authority checkpoint outside the database directory
($HOME/.gg/runtime-source-authority by default, or
GG_RUNTIME_SOURCE_AUTHORITY_DIR). Before a publishing SQLite transaction
commits global row H, the runtime durably advances this external authority to
H. An authority-write failure therefore rolls the SQLite transaction back;
if the authority advances but the database commit does not, initialization
observes the authority ahead of the database and rotates the epoch. Monotonic
checkpoint writes never lower an ahead authority within the same epoch.
Copy/replace restores and same-path
in-place rollbacks that could reuse global IDs rotate the epoch, even when the
database and adjacent marker are rolled back together. Identity-file read or
durability errors fail closed rather than silently fabricating continuity. On
Windows, store initialization currently fails closed because replace-existing
atomic file semantics are not yet implemented; the runtime does not serve with
a weaker path-only identity.
Goosetower treats the runtime value as authoritative; legacy configured
source_epoch values do not override it. It revalidates the runtime epoch
before every SSE reconnect. If the epoch changed, streaming pauses while the
new bootstrap snapshot is installed and the cursor is rebased to that
generation’s high watermark. Same-epoch bootstrap installs are discarded when
Tower has already reduced a newer cursor. A runtime that does not implement
this endpoint, or cannot be reached, is reported as offline/unavailable and
requires resynchronization rather than receiving a fabricated epoch.
SSE endpoints:
GET /v1/events/streamGET /v1/sessions/{session_id}/events/streamGET /v1/teams/{team_id}/events/streamGET /v1/processes/{process_id}/events/stream
Replay endpoints:
GET /v1/eventsGET /v1/sessions/{session_id}/eventsGET /v1/teams/{team_id}/eventsGET /v1/processes/{process_id}/events
Behavior:
- stream endpoints replay first, then deliver live events
- session/process streams subscribe before replay handoff to reduce missed events
after_seqquery param takes precedence- otherwise stream endpoints use the
Last-Event-IDheader - invalid
Last-Event-IDreturns HTTP400 - keepalive pings are sent every 10 seconds
- infinite SSE requests do not use the finite JSON request deadline
- reconnect cursors retain the highest contiguous row already decoded; health updates cannot move the cursor backward
SSE event envelope:
id: runtime sequence idevent: runtime event kinddata: JSON-serializedRuntimeEventRecord
Example:
curl -N -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/v1/sessions/$SESSION_ID/events/stream?after_seq=0"
Providers
Provider IDs:
codexclaudeacp
Model catalogs:
- Codex:
gpt-5.5,gpt-5.4,gpt-5.4-mini - Claude:
claude-sonnet-5,claude-opus-4-8,claude-fable-5,claude-haiku-4-5 - ACP: can return an empty list because model selection can be session-scoped inside the configured agent
Auth status examples:
curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE_URL/v1/providers"
curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE_URL/v1/providers/codex/auth/status"
curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE_URL/v1/providers/claude/auth/status"
curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE_URL/v1/providers/acp/auth/status"
ACP v1 notes:
- only
GET /v1/providers/acp/auth/statusexists for ACP auth - ACP auth is agent-managed
- no ACP logout/API-key/import routes exist in v1
- ACP permission requests fail the active turn clearly
See Provider Guide for full provider setup.
GET /v1/providers/{provider}/models returns the provider’s current model
catalog. Each model includes id, display_name, and reasoning_levels.
reasoning_levels is the provider-owned source of truth for client reasoning
or effort selectors; clients should not hardcode level names. The list may be
empty when a provider or model has no global reasoning selector, and ACP model
catalogs may be empty when model choice is session-scoped.
Processes
Start a process:
PROCESS_JSON=$(curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"echo hello","cwd":"/tmp","timeout_ms":30000}' \
"$BASE_URL/v1/processes")
POST /v1/processes accepts:
| Field | Required | Notes |
|---|---|---|
command |
yes | Command string. Shell behavior depends on [processes].allow_shell. |
cwd |
no | Working directory. |
timeout_ms |
no | Overrides configured default timeout. |
session_id |
no | Associates process ownership with a session. |
Read logs:
curl -fsS -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/v1/processes/$PROCESS_ID/logs?stream=stdout&tail_lines=100&max_bytes=65536"
Kill:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason":"operator stop"}' \
"$BASE_URL/v1/processes/$PROCESS_ID/kill"
Worktrees
Create:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_session_id":"'"$SESSION_ID"'",
"repo_root":"/workspace/repo",
"worktree_name":"feature-docs",
"branch_prefix":"gg",
"base_ref":"main",
"run_init_script":false
}' \
"$BASE_URL/v1/worktrees"
Important request fields:
| Field | Notes |
|---|---|
source_session_id |
Session used as source/owner context. |
repo_root |
Optional repo root; implementation can infer from session cwd when available. |
worktree_name |
Stable human-readable worktree identity. |
branch_prefix |
Optional generated branch prefix. |
base_ref |
Optional base branch/ref. |
deletion_policy |
Optional cleanup policy override. |
run_init_script |
Whether to run configured init script. |
team_id / operation_id |
Optional team/spawn traceability fields. |
Claims, release, and cleanup are separate endpoints so ownership can be represented explicitly.
Teams and comms
Create a team:
TEAM_JSON=$(curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Implementation Team","lead_agent_id":"'"$SESSION_ID"'","member_agent_ids":["'"$SESSION_ID"'"]}' \
"$BASE_URL/v1/teams")
Send direct message:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sender_agent_id":"'"$SESSION_ID"'",
"recipient_agent_id":"'"$OTHER_SESSION_ID"'",
"input":{"type":"text","text":"Review this patch."},
"priority":"normal",
"policy":"non_interrupting"
}' \
"$BASE_URL/v1/teams/$TEAM_ID/messages"
Send broadcast:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sender_agent_id":"'"$SESSION_ID"'",
"input":{"type":"text","text":"Status update?"},
"include_sender":false
}' \
"$BASE_URL/v1/teams/$TEAM_ID/broadcasts"
Snapshot:
curl -fsS -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/v1/teams/$TEAM_ID/view?include_delivery_map=true&message_limit=50"
Team delivery fields make multi-agent coordination inspectable: pending, deferred, injecting, injected, failed, or cancelled states are stored as records and replayed through team events.
HTTP routes and MCP team tools use the same underlying team services. A team created through POST /v1/teams is immediately visible to gg_team_status; messages sent through gg_team_message create the same message, delivery, and event records as POST /v1/teams/{team_id}/messages and POST /v1/teams/{team_id}/broadcasts; members added or removed through gg_team_manage use the same spawn, join, remove, worktree assignment, and cleanup paths as the HTTP team/member endpoints.
MCP gateway
Routes:
GET /v1/mcp/capabilitiesPOST /v1/mcp/invoke
Important constraints:
- MCP request body limit is
64 KiB. tool_nameis required, also accepts camelCasetoolName.caller_agent_idis required, also accepts camelCasecallerAgentId.invocation_idis optional, also accepts camelCaseinvocationId.- closed/failed caller sessions are rejected with
400. namespace, when present, must match the tool prefix.gg_processacceptsgg_process_*;gg_teamacceptsgg_team_*.
GET /v1/mcp/capabilities reports the enabled GG tool namespaces and tool names:
gg_process:gg_process_run,gg_process_status,gg_process_logs,gg_process_killgg_team:gg_team_status,gg_team_message,gg_team_manage
Team MCP tools advertise under gg_team when the runtime team MCP policy is enabled. The same response includes ggTeamManagePermissions so agents can see whether non-lead members may add or remove team members through MCP, and ggTeamModelPresets so agents can discover user-friendly model_preset names for gg_team_manage add mode. If team MCP is disabled, team tools are omitted from capabilities and direct gg_team_* invocations return an ok:false envelope with feature_disabled.
gg_team_status returns a team/member snapshot for an active team member. Member rows include activity state, last team-message context, managed-worktree metadata, added_by, and context_window_remaining_percentage. The percentage is derived from persisted provider usage when usage includes a context-window size and token counts. Codex and Claude sessions can report it after completed turns with usage; ACP remains null unless the configured ACP agent emits compatible usage data.
gg_team_message sends direct messages or broadcasts by setting recipient_agent_id to a member id or "broadcast". Optional image_paths are stored with the message and delivered as image input items for supported providers. gg_team_manage adds one member when remove_agent_ids is absent, and removes one or more members when remove_agent_ids is present. Add mode accepts optional model_preset and image_paths; selected presets set the spawned session provider/model and metadata, and add-mode images are attached to the canonical onboarding message. ACP image attachments are not modeled by this runtime yet; team MCP calls that would send image_paths to ACP sessions return an unsupported_provider_images error instead of dropping the attachment.
Agent-initiated membership management is configurable in runtime config:
[teams]
enabled = true
non_lead_can_add_members = false
non_lead_can_remove_members = false
[[teams.model_presets]]
name = "fast"
provider = "codex"
model = "gpt-5.4-mini"
thinking_effort = "low"
The lead can add and remove members by default. Non-lead members can use gg_team_manage add/remove only when the matching flag is enabled. This policy gates MCP-initiated membership control; authenticated HTTP team administration remains the human/client control plane.
Codex, Claude, and ACP provider sessions all receive the bundled gg-mcp-server configuration when enabled. The sidecar forwards provider tool calls to this gateway, so gg_process and gg_team behavior is provider-agnostic and uses the same success/error envelope across providers:
{"ok":true,"result":{}}
{"ok":false,"error":{"code":"unauthorized","message":"..."}}
Example:
curl -fsS -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"namespace":"gg_process",
"tool_name":"gg_process_run",
"caller_agent_id":"'"$SESSION_ID"'",
"args":{"command":"echo from mcp"}
}' \
"$BASE_URL/v1/mcp/invoke"
See MCP and Sidecars for sidecar details.
Error mapping
Common behavior:
- validation errors: HTTP
400, body{"error":"..."} - auth failures: HTTP
401, body{"error":"missing or invalid bearer token"} - not found / unknown entities: HTTP
404, body{"error":"..."} - internal/io/bootstrap errors: HTTP
500, body{"error":"..."}
Special status codes:
POST /v1/sessions/{session_id}/turns/{turn_id}/interruptreturns202 AcceptedDELETE /v1/teams/{team_id}returns204 No Content
OpenAPI generation and sync
Regenerate artifact:
make api-docs-refresh
Review sync-relevant file changes:
make api-docs-status
Fail fast when API files changed without docs:
make api-docs-check
Workflow reference: API Doc Sync Workflow