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:

If this guide disagrees with runtime behavior, treat server/core code as authoritative.

Goosetower gateway sources:

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 /health
  • GET /openapi.yaml

Protected routes:

  • all /v1/** routes require Authorization: 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.rs and crates/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 /health is public and reports Goosetower service health.
  • GET /v1/health, GET /v1/sources, and GET /v1/metrics require Authorization: Bearer <goosetower-api-token>.
  • GET /v1/debug/protocol, /v1/debug/sources, /v1/debug/subscriptions, /v1/debug/materializer, and /v1/debug/audit require the same bearer token and only work when debug.endpoints_enabled = true.
  • POST /v1/dev/tickets requires the same bearer token and only works when debug.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 from proto/goosetower/v1/realtime.proto.

Realtime auth:

  • Browsers authenticate with a short-lived, signed, single-use URL ticket.
  • The gateway checks the WebSocket Origin header against the exact configured server.allowed_gooseweb_origins allowlist before upgrade.
  • Ticket claims include issuer, audience, subject, workspace ID, scopes, allowed origins, expiry, issued-at time, and jti nonce.
  • 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 require gateway:command.

Realtime protocol behavior:

  • The server sends Hello after connection open with connection ID, server time, heartbeat interval, max message size, protocol version, and resume support.
  • Clients send binary Protobuf RealtimeEnvelope frames. Text frames are not accepted.
  • Ping frames receive Pong; oversized WebSocket messages are closed by the WebSocket layer according to websocket.max_message_bytes.
  • Subscribe creates per-connection interest state and returns a Snapshot for views such as board, approval_inbox, session, team, process_tail, ledger, fleet, and worktrees.
  • 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, and created_at_client_unix_ms. Duplicate command_id values are answered with CommandDuplicate from 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:

  • accept
  • accepted
  • decline
  • declined
  • reject
  • rejected

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 identifier
  • high_watermark: the greatest global runtime_events.id visible to the read, or 0 for an empty event ledger
  • records: 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/stream
  • GET /v1/sessions/{session_id}/events/stream
  • GET /v1/teams/{team_id}/events/stream
  • GET /v1/processes/{process_id}/events/stream

Replay endpoints:

  • GET /v1/events
  • GET /v1/sessions/{session_id}/events
  • GET /v1/teams/{team_id}/events
  • GET /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_seq query param takes precedence
  • otherwise stream endpoints use the Last-Event-ID header
  • invalid Last-Event-ID returns HTTP 400
  • 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 id
  • event: runtime event kind
  • data: JSON-serialized RuntimeEventRecord

Example:

curl -N -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/v1/sessions/$SESSION_ID/events/stream?after_seq=0"

Providers

Provider IDs:

  • codex
  • claude
  • acp

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/status exists 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/capabilities
  • POST /v1/mcp/invoke

Important constraints:

  • MCP request body limit is 64 KiB.
  • tool_name is required, also accepts camelCase toolName.
  • caller_agent_id is required, also accepts camelCase callerAgentId.
  • invocation_id is optional, also accepts camelCase invocationId.
  • closed/failed caller sessions are rejected with 400.
  • namespace, when present, must match the tool prefix. gg_process accepts gg_process_*; gg_team accepts gg_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_kill
  • gg_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}/interrupt returns 202 Accepted
  • DELETE /v1/teams/{team_id} returns 204 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