Session Management API
Manage API sessions for a specific agent and chat_id. Sessions are stored at sessions/api-{chat_id}/ — symmetric with Telegram (telegram-{id}) and Discord (discord-{id}).
chat_id identifies the caller. Use any stable string (e.g. "myapp", "user-123", "getpod"). It is required on all session endpoints — pass it as:
- Query string for
GETandDELETErequests:?chat_id=myapp - Request body for
POSTandPATCHrequests:{"chat_id": "myapp", ...}
POST /api/v1/agents/:agentId/greeting
Stream a proactive welcome into an existing session. The endpoint reads GREETING.md from the agent's workspace and sends its content to the agent as a trigger prompt via SSE. Only the assistant response is stored in session history — the trigger prompt is invisible (uses store_user_message: false internally).
Returns 204 No Content if GREETING.md does not exist or is empty.
Auth: Write or Admin key required.
Two-step flow:
- Create the session first:
POST /api/v1/agents/:agentId/sessions→ redirect the user to the chat UI with the returnedsession_id. - Once in the chat UI, trigger the greeting:
POST /api/v1/agents/:agentId/greetingwith thatsession_idand the samechat_id→ stream the assistant's opening message as SSE with typing animation visible to the user.
Request:
| Field | Required | Description |
|---|---|---|
session_id | Yes | ID of an existing session to deliver the greeting into. Must already exist under chat_id — an unknown id is 404, never a new session |
chat_id | Yes | Same chat_id used when the session was created; names the history bucket (api-{chat_id}) the greeting is stored in |
Breaking change:
chat_idis now required. It used to default tosession_id, which filed the greeting underapi-{session_id}— an index the real chat never reads, so the opening message vanished from history while still consuming the one-shotGREETING.md. Pass the samechat_idyou created the session with.
curl -N -X POST \
-H "X-Api-Key: my-write-key" \
-H "Content-Type: application/json" \
-d '{"session_id": "7f3a1c2d-89ab-4def-b012-345678901234", "chat_id": "myapp"}' \
http://localhost:10850/api/v1/agents/getpod/greetingResponse 200 (SSE stream) — greeting is streaming:
data: {"type":"text_delta","text":"Hello! "}
data: {"type":"text_delta","text":"Welcome to GetPod."}
data: {"type":"result","text":"Hello! Welcome to GetPod.","session_id":"7f3a1c2d-..."}
data: [DONE]If the session has an active request in flight, the endpoint returns 409 before sending SSE headers.
Response 204 — GREETING.md not found or empty; nothing sent to session.
GREETING.md format:
Place the file at ~/.claude-gateway/agents/{agentId}/workspace/GREETING.md. Its content is used as the prompt sent to the agent. It is not concatenated into the agent system prompt — it is a one-time trigger only.
The user's environment is ready. Send a warm, concise welcome message
introducing yourself and what you can help with.Notes:
GREETING.mdis deleted before streaming begins. Subsequent calls return 204 immediately, making the endpoint idempotent. Re-provisioningGREETING.mdenables a new greeting on the next call.- A
session_idthat names no session underchat_idreturns404withcode: "SESSION_NOT_FOUND". The check runs before the unlink, so a rejected call leavesGREETING.mdintact for the real session. - The SSE stream format matches
POST /messageswithstream: true— use the same client-side handler. - If the agent errors mid-stream, an
{"type":"error","message":"...","code":"..."}SSE event is sent and the stream closes (codeomitted when the failure carries none).
GET /api/v1/agents/:agentId/sessions
List all API sessions for a given chat_id.
curl -H "X-Api-Key: my-secret-key-123" \
"http://localhost:10850/api/v1/agents/alfred/sessions?chat_id=myapp" | jq{
"sessions": [
{
"id": "da19d84a-6a36-4f57-b419-d322d82c4db8",
"name": "Project Planning",
"createdAt": 1775737709000,
"lastActivity": 1775823600000
}
]
}POST /api/v1/agents/:agentId/sessions
Create a new API session. Optionally auto-generates a session name by summarising a prompt.
The gateway mints the id; there is no way to choose one. Along with omitting session_id on POST /messages, this is the only way a session comes into existence.
Request body:
| Field | Required | Description |
|---|---|---|
chat_id | Yes | Caller identity |
prompt | No | Initial user intent — used to auto-generate a session name |
name | No | Explicit session name (overrides auto-generated name) |
curl -X POST \
-H "X-Api-Key: my-secret-key-123" \
-H "Content-Type: application/json" \
-d '{"chat_id": "myapp", "prompt": "I want to discuss the deployment plan for Q3"}' \
http://localhost:10850/api/v1/agents/alfred/sessions | jq{
"sessionId": "da19d84a-6a36-4f57-b419-d322d82c4db8",
"sessionName": "Q3 Deployment Plan",
"createdAt": 1775737709000
}GET /api/v1/agents/:agentId/sessions/:sessionId/info
Get info for a specific session — name, message count, and context usage.
curl -H "X-Api-Key: my-secret-key-123" \
"http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a/info?chat_id=myapp" | jq{
"sessionId": "da19d84a-6a36-4f57-b419-d322d82c4db8",
"sessionName": "Q3 Deployment Plan",
"messageCount": 42,
"archivedCount": 0,
"contextUsedPct": 90,
"contextTokens": 180000,
"contextWindow": 200000,
"contextModel": "claude-sonnet-5",
"model": "claude-sonnet-5"
}Context fields use the latest measured agent request, matching the dashboard token report, rather than message counts or a pre-compaction peak. Measurements expire when the latest agent decision is older than one hour. Missing measurements are null; unknown capacity leaves contextWindow and contextUsedPct null. contextModel identifies the measured model, while model is the selected model. Sessions outside orchestration use their stored lastInputTokens and model, with freshness based on lastActive; they do not have an orchestration request ledger. Message counts remain in the structured API for compatibility; /session text omits them and displays — when context is unavailable.
Error responses:
| Status | When |
|---|---|
| 404 | Session not found |
PATCH /api/v1/agents/:agentId/sessions/:sessionId
Rename a session.
Request body:
| Field | Required | Description |
|---|---|---|
chat_id | Yes | Caller identity |
session_name | Yes | New session name (snake_case preferred; sessionName also accepted for backward compatibility) |
curl -X PATCH \
-H "X-Api-Key: my-secret-key-123" \
-H "Content-Type: application/json" \
-d '{"chat_id": "myapp", "session_name": "Q3 Infra Discussion"}' \
http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a | jq{
"sessionId": "da19d84a-6a36-4f57-b419-d322d82c4db8",
"sessionName": "Q3 Infra Discussion"
}Notes:
- Request body accepts
session_name(snake_case, preferred) orsessionName(camelCase, backward compatibility). When both are present,session_nametakes priority. - The response body always uses camelCase (
sessionName), consistent with all other API responses.
DELETE /api/v1/agents/:agentId/sessions/:sessionId
Delete a session. Returns 204 No Content on success.
curl -X DELETE \
-H "X-Api-Key: my-secret-key-123" \
"http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a?chat_id=myapp"POST /api/v1/agents/:agentId/sessions/:sessionId/clear
Reset the Claude Code context for this session without deleting chat history or attachments. The next message starts a new CLI session and loads up to the latest 50 history messages once; subsequent managed turns resume the new CLI session. Existing tasks remain unchanged. An active response or compaction must finish before clearing. The reset survives a gateway restart. This endpoint runs at once; the /clear chat command asks for confirmation first.
Request body: { "chat_id": "myapp" }
curl -X POST \
-H "X-Api-Key: my-secret-key-123" \
-H "Content-Type: application/json" \
-d '{"chat_id": "myapp"}' \
http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a/clear | jq{ "success": true, "historyUnchanged": true, "historyLimit": 50 }POST /api/v1/agents/:agentId/sessions/:sessionId/compact
Run native Claude Code /compact on the existing CLI session. Gateway chat history stays unchanged; recent messages are not appended again. A busy response must finish first. Missing transcripts and unconfirmed CLI compaction fail explicitly, without falling back to history summarization. This endpoint runs at once; the /compact chat command asks for confirmation first.
Request body: { "chat_id": "myapp" }
curl -X POST \
-H "X-Api-Key: my-secret-key-123" \
-H "Content-Type: application/json" \
-d '{"chat_id": "myapp"}' \
http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a/compact | jq{ "success": true, "native": true, "historyUnchanged": true }POST /api/v1/agents/:agentId/sessions/:sessionId/stop
Interrupt the currently in-flight turn for this session. In legacy mode this sends SIGINT and returns the simple response below. With orchestration enabled, it also returns a scoped task-selection menu; see orchestration stop semantics.
Request body: { "chat_id": "myapp" }
curl -X POST \
-H "X-Api-Key: my-secret-key-123" \
-H "Content-Type: application/json" \
-d '{"chat_id": "myapp"}' \
http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a/stop | jq{ "stopped": true }POST /api/v1/agents/:agentId/sessions/:sessionId/restart
Gracefully restart the session (kills the subprocess and notifies when back online). This endpoint runs at once; the /restart chat command asks for confirmation first.
Request body: { "chat_id": "myapp" }
curl -X POST \
-H "X-Api-Key: my-secret-key-123" \
-H "Content-Type: application/json" \
-d '{"chat_id": "myapp"}' \
http://localhost:10850/api/v1/agents/alfred/sessions/da19d84a/restart | jq{ "restarting": true }