Skip to content
Hoody.com

Four endpoints cover a session’s event surface. They differ on whether they attach (consume the exclusive attach slot), what they return, and which transport they use. Choose based on what your client needs:

  • stream attaches to a live session over WebSocket or SSE and receives events as they happen. The connection consumes the exclusive attach slot.
  • replay returns the gateway’s buffered event tail without attaching. Use it to catch up after a reload, then resume stream with ?since=...&incarnation=....
  • state returns a consistent snapshot plus the stream watermark, without attaching. Use it after a reload that kept no cursor, or to recover a parked gate’s identity.
  • transcript reads the persisted conversation without attaching. Use it for a non-live session, or for history older than the replay ring.

All endpoints are container-scoped. The base URL for each is https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com.

Attach to a session’s event stream over WebSocket (primary) or SSE. Each frame carries a gateway-stamped seq and the incarnation that stamped it. Resume from the replay ring with ?since=<seq>&incarnation=<id>. A gap past eviction emits an SSE event: lagged frame (code replay_gap) then streams from min_seq. WS clients can post inline confirm, answer, cancel, and workflow_message frames to drive a parked gate.

NameInTypeRequiredDescription
idpathstringYesThe session id.
sincequeryintegerNoResume from this gateway int64 seq (also accepted as the Last-Event-ID header).
incarnationquerystringNoThe incarnation the since cursor belongs to. When it differs from the live session’s, the cursor is treated as invalid and the full retained ring is sent.
Last-Event-IDheaderstringNoSSE resume cursor, the gateway int64 seq to resume from. Sent automatically by an SSE client on reconnect.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against.
X-Hoody-ContainerheaderstringNoPer-request bound remote container (omitted = local). Rejected on routes with no container dimension.
X-Hoody-RealmheaderstringNoPer-request realm selector: global or a 24-hex id. Rejected on active-only / no-realm routes.
realmquerystringNoIn-query alias of X-Hoody-Realm, read only when the header is absent.
Terminal window
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/67e89abc123def456789abc1/stream?since=42&incarnation=inc-20240115103000123456" \
-H "Authorization: Bearer <token>" \
-N

WebSocket upgrade accepted. The response has no body. The connection switches to the WebSocket frame protocol: each frame is a JSON object of the form {"seq": <int64>, "incarnation": "<id>", "turn_id"?: "<id>", "gate"?: {"id": "<id>", "generation": <int>, "type": "confirm" | "question"}, "event": {...}}.

Replay a live session’s buffered event tail. Returns the gateway’s own ring snapshot, not a daemon reply, with min_seq and max_seq plus the incarnation they belong to for resuming the stream. While a gate is parked, the response includes a pending_gate whose id is the addressable identity to echo on POST /sessions/{id}/confirm or /answer. For a non-live session, use GET /sessions/{id}/transcript instead.

NameInTypeRequiredDescription
idpathstringYesThe session id.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container (omitted = local). Rejected on routes with no container dimension.
X-Hoody-RealmheaderstringNoPer-request realm selector: global or a 24-hex id. Rejected on active-only / no-realm routes.
realmquerystringNoIn-query alias of X-Hoody-Realm, read only when the header is absent.
Terminal window
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/67e89abc123def456789abc1/replay" \
-H "Authorization: Bearer <token>"
{
"session_id": "67e89abc123def456789abc1",
"incarnation": "inc-20240115103000123456",
"min_seq": 1,
"max_seq": 42,
"events": [
{"seq": 1, "incarnation": "inc-20240115103000123456", "event": {"type": "event.turn_started"}},
{"seq": 2, "incarnation": "inc-20240115103000123456", "event": {"type": "event.text_delta", "text": "I'll look at the build output."}}
],
"pending_gate": {
"id": "01HMW7D3Q4K5J6V7Y8Z9X0C1BV",
"generation": 1,
"type": "confirm",
"tool_name": "bash",
"gate_cause": "approval_policy",
"risk": "destructive",
"human_only": true
}
}

Read a session’s recoverable state without attaching. Returns one consistent snapshot: the daemon’s state object paired with this gateway’s stream watermark (incarnation and seq). The watermark is read before the daemon snapshot, so any event that lands during the read is replayed, never lost. While a gate is parked, the response includes pending_gate_ref whose id is the addressable identity to echo on POST /sessions/{id}/confirm or /answer (the daemon’s own numeric gate_id is diagnostic only and is never what those routes take). Works for a live or dormant session; a dormant one carries no incarnation or seq. Response is Cache-Control: no-store and is active-realm-scoped.

NameInTypeRequiredDescription
idpathstringYesThe session id.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container (omitted = local). Rejected on routes with no container dimension.
X-Hoody-RealmheaderstringNoPer-request realm selector: global or a 24-hex id. Rejected on active-only / no-realm routes.
realmquerystringNoIn-query alias of X-Hoody-Realm, read only when the header is absent.
Terminal window
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/67e89abc123def456789abc1/state" \
-H "Authorization: Bearer <token>"
{
"session_id": "67e89abc123def456789abc1",
"live": true,
"epoch": "epoch-20240115103000123456",
"state": "parked",
"current_turn_id": "67e89abc123def456789abc2",
"queued_turn_ids": [],
"approval": {"policy": "ask", "session_allow": []},
"pending_gate": {
"type": "confirm",
"gate_id": 3,
"generation": 1,
"tool_name": "bash",
"gate_cause": "approval_policy",
"human_only": true,
"lease_required": true,
"params": {"command": "rm -rf build"}
},
"last_detach": null,
"incarnation": "inc-20240115103000123456",
"seq": 42,
"pending_gate_ref": {
"id": "01HMW7D3Q4K5J6V7Y8Z9X0C1BV",
"generation": 1,
"type": "confirm",
"tool_name": "bash",
"gate_cause": "approval_policy",
"human_only": true
}
}

GET /api/v1/agent/sessions/{id}/transcript

Section titled “GET /api/v1/agent/sessions/{id}/transcript”

Read a session’s persisted conversation without attaching. Returns the message projection from the session record: text, thinking, tool_use, and tool_result blocks (and on an outcome_claims session, an outcome_claims block following each report_outcome tool result), plus turn boundaries. The endpoint never attaches, never resumes, and never consumes the exclusive attach slot. For a live session the content is fresh to the last persist point; the mid-turn streaming tail is served by GET /sessions/{id}/replay (live ring) and GET /sessions/{id}/stream instead. X-Hoody-Config-Dir selects the record store; X-Hoody-Cwd is not a match filter here.

NameInTypeRequiredDescription
idpathstringYesThe session id.
after_turnqueryintegerNoExclusive completed-turn skip cursor: return content strictly after completed turn N (0 = full transcript; values past the end clamp; negative or non-integer = 400).
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override selecting the record store.
X-Hoody-RealmheaderstringNoPer-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm.
realmquerystringNoPer-request realm selector — the in:query alias of the X-Hoody-Realm header (read only when the header is absent): “global” (not tied to a realm) or a 24-hex realm id. On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 not_found. Rejected (400 realm_scope_unsupported) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm.
Terminal window
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/67e89abc123def456789abc1/transcript?after_turn=0" \
-H "Authorization: Bearer <token>"
{
"session_id": "67e89abc123def456789abc1",
"live": false,
"realm": "_global",
"name": "Fix the build",
"model": "claude-opus-4",
"cwd": "/home/user/project",
"turns_total": 2,
"turns_returned": 2,
"after_turn": 0,
"turn_boundaries": [1, 4],
"messages": [
{
"role": "user",
"blocks": [
{"kind": "text", "text": "Please fix the build."}
]
},
{
"role": "assistant",
"blocks": [
{"kind": "thinking", "text": "Let me check the build output first."},
{"kind": "text", "text": "I'll look at the error."},
{"kind": "tool_use", "tool_id": "toolu_01abc", "tool_name": "bash", "input": {"command": "npm run build"}},
{"kind": "tool_result", "tool_id": "toolu_01abc", "tool_name": "bash", "output": "Error: ENOENT: no such file or directory", "is_error": true},
{"kind": "text", "text": "The issue is a missing config file."}
]
}
],
"started_at": "2024-01-15T10:00:00Z",
"last_request_at": "2024-01-15T10:05:00Z"
}