This document is generated from the HTTP routes and behaviors currently implemented in the codebase. The default example server address is http://127.0.0.1:18080.
- Except for streaming endpoints, requests and responses use
application/json - Time fields use RFC3339 / ISO8601
- Most non-streaming errors are returned as plain-text response bodies
- SSE endpoints use
text/event-stream - The current API is mainly grouped into 3 areas:
- Core API:
/api/v1/* - Channel and participant API:
/api/v1/channels/* - Health check:
/healthz
- Core API:
- Most
/api/v1/*endpoints do not require authentication by default - The following endpoints require
Authorization: Bearer <token>, where the token is the server access token:GET /api/v1/channels/{channel}/participants/{id}/eventsPOST /api/v1/channels/csgclaw/participants/{id}/messagesGET /api/v1/agents/{id}/llm/modelsGET /api/v1/agents/{id}/llm/v1/modelsPOST /api/v1/agents/{id}/llm/chat/completionsPOST /api/v1/agents/{id}/llm/v1/chat/completionsPOST /api/v1/agents/{id}/llm/responsesPOST /api/v1/agents/{id}/llm/v1/responsesGET /api/v1/agents/{id}/llm/responsesGET /api/v1/agents/{id}/llm/v1/responses
- If the server runs with
no_auth, the checks above are skipped
Health check endpoint.
Example response:
ok
Returns the current server version.
Example response:
{
"version": "0.1.0"
}Returns the upgrade status.
Response fields:
current_versionlatest_versionupdate_availablecheckingupgradinglast_checked_atlast_error
Starts the upgrade helper.
On success, returns 202 Accepted:
{
"status": "accepted",
"message": "upgrade helper started"
}Returns 503 Service Unavailable if the upgrade manager is not configured.
Participants are channel-scoped identities used by rooms, messages, mentions, notifications, and runtime bridges. A participant can represent a human, an agent-backed channel identity, or a notification sender.
Returns participants for the specified channel.
Path parameters:
channel:csgclaworfeishu
Optional query parameters:
type:human,agent, ornotificationagent_id
Response fields:
idchanneltypenameavatarchannel_user_refchannel_user_kindchannel_app_refagent_idlifecycle_statuspresencementionablemetadatacreated_atupdated_at
Examples:
GET /api/v1/channels/csgclaw/participantsGET /api/v1/channels/csgclaw/participants?type=notificationGET /api/v1/channels/feishu/participants?agent_id=u-worker
Creates a participant in the specified channel.
Path parameters:
channel:csgclaworfeishu
Example request body:
{
"id": "qa",
"type": "agent",
"name": "QA",
"channel_user": {
"ref": "u-qa",
"kind": "local_user_id"
},
"agent_binding": {
"mode": "create",
"agent": {
"name": "QA",
"role": "worker",
"runtime_kind": "picoclaw_sandbox",
"from_template": "builtin.picoclaw-worker"
}
}
}Notes:
typeis required and must behuman,agent, ornotificationnameis required- The effective channel comes from the route path rather than the request body
agentparticipants can create or reuse an Agent throughagent_bindinghumanandnotificationparticipants do not create runtime agents- In the example above,
qais the participant ID;u-qais used only as the local channel user ref and generated backing agent ID. - For
csgclaw,channel_user.refis a local IM user ID - For
feishu,channel_user.refis the channel-native open ID
Examples:
POST /api/v1/channels/csgclaw/participantsPOST /api/v1/channels/feishu/participants
Returns one participant.
Updates editable participant fields such as name, avatar, mentionable, and
metadata.
Deletes the specified participant in the specified channel.
Returns 204 No Content on success.
Examples:
DELETE /api/v1/channels/csgclaw/participants/qaDELETE /api/v1/channels/feishu/participants/qa
/api/v1/agents* returns agent objects with fields like:
{
"id": "u-alice",
"name": "alice",
"description": "frontend dev",
"runtime_id": "codex",
"runtime_kind": "codex",
"image": "example/image:latest",
"box_id": "codex-session-alice",
"role": "worker",
"status": "running",
"created_at": "2026-05-16T08:00:00Z",
"profile": "api.gpt-5.4",
"runtime_options": {},
"agent_profile": {
"provider": "api",
"base_url": "https://api.example.com/v1",
"api_key_set": true,
"api_key_preview": "sk-1...",
"model_id": "gpt-5.4",
"reasoning_effort": "medium",
"profile_complete": true
},
"profile_complete": true,
"detection_results": []
}Notes:
- The real
api_keyis never returned inagent_profile runtime_optionsis sanitized before being exposed by the APIprofileis the normalized selector generated by the server, for exampleapi.gpt-5.4detection_resultsis used to present default profile detection results
Lists all agents.
The server reloads state before returning the latest snapshot.
Creates an agent.
Request fields:
idnamedescriptionimageruntime_kindfrom_templatereplacefield_maskrolestatuscreated_atprofileruntime_optionsagent_profile
Example request body:
{
"id": "u-alice",
"name": "alice",
"description": "frontend dev",
"runtime_kind": "codex",
"profile": "api.gpt-5.4",
"agent_profile": {
"provider": "api",
"base_url": "https://api.example.com/v1",
"api_key": "sk-xxx",
"model_id": "gpt-5.4",
"reasoning_effort": "medium"
}
}Additional notes:
nameis requiredreplace=trueenables replace logicfield_masklimits which fields are overwritten during replacementagent_profile.api_keyis write-only and will be redacted in reads
Gets a single agent.
Returns 404 if it does not exist.
Updates basic agent fields.
Supported fields:
namedescriptionimageruntime_optionsagent_profile
Example request body:
{
"description": "updated description",
"runtime_options": {
"sandbox": "default"
}
}Notes:
- Omitted fields are left unchanged
- If
agent_profile.api_keyis sent empty, the server keeps the existing key - If
agent_profile.envchanges,env_restart_requiredmay becometruein the response
Deletes an agent.
Returns 204 No Content on success.
Starts the agent and returns the updated agent object.
Stops the agent and returns the updated agent object.
Returns agent logs.
Query parameters:
lines: defaults to20follow:1/true/yes/onenables streaming follow mode
Response type: text/plain; charset=utf-8
Notes:
- With
follow=false, errors are returned as normal HTTP errors - With
follow=true, streaming-time errors may be written into the response body
Returns the redacted profile for a single agent.
Replaces the profile for a single agent.
The request body uses the agent_profile shape, for example:
{
"provider": "api",
"base_url": "https://api.example.com/v1",
"api_key": "sk-xxx",
"model_id": "gpt-5.4",
"reasoning_effort": "medium",
"headers": {
"x-org": "demo"
},
"env": {
"FOO": "bar"
}
}Notes:
- Unlike
PATCH /api/v1/agents/{id}, this endpoint semantically replaces the current profile with the new one - If
api_keyis empty, the server keeps the existing key
Recreates the agent using its current configuration and returns the new agent state.
Common failures:
404: agent does not exist400: profile is incomplete, or the runtime does not allow recreation
Returns the available model list for the given provider configuration.
Request fields:
agent_idproviderbase_urlapi_keyheaders
Example request body:
{
"provider": "api",
"base_url": "https://api.example.com/v1",
"api_key": "sk-xxx"
}Example response:
{
"provider": "api",
"models": ["gpt-5.4", "gpt-5.4-mini"]
}Notes:
- For
provider=codexorclaude_code, model choices are obtained through CLIProxy - For
provider=api, the server calls the target OpenAI-compatible/modelsendpoint - If
agent_idis provided andapi_keyis omitted in the request, the server may reuse the saved key from that agent - If
agent_idis omitted andapi_keyis omitted, the server may reuse the saved default API key only whenprovider=apiandbase_urlmatches the current default profile
Returns the redacted view of the current default agent profile.
This is commonly used by the frontend to initialize default provider/model state.
Lists all templates from readable registries.
Response fields:
idnamedescriptionruntime_kindimageupdated_atsource.namesource.kindworkspace.kind
Publishes an existing agent workspace to the hub.
Request body:
{
"agent_id": "u-alice",
"registry": "local"
}Notes:
agent_idis requiredregistryuses the default publish registry when omitted- Successful publish returns
201 Created
Returns template details.
In addition to the list view fields, this endpoint also returns:
workspace.entries
Example workspace.entries payload:
{
"workspace": {
"kind": "dir",
"entries": [
{"path":"SKILL.md","name":"SKILL.md","type":"file","depth":0,"size":128},
{"path":"assets","name":"assets","type":"dir","depth":0,"size":0}
]
}
}Reads a single file preview from the template workspace.
Query parameters:
path: required relative path
Response fields:
pathcontentsizetruncatedbinary
Notes:
- Non-UTF-8 files return
binary=true - Text content larger than
256 KiBis truncated and returned withtruncated=true - Absolute paths and
..traversal are rejected
Returns the local auth status for a provider.
provider is required.
The response is provided by CLIProxy and commonly includes:
providerauthenticatedlogin_requiredmessagesupports_login
Triggers provider login.
Request body:
{
"provider": "codex",
"no_browser": true
}Returns the current provider auth status on success.
Notes:
- Missing
providerreturns400 - Login failure returns
502 Bad Gateway
Returns the bootstrap config view.
Response fields:
default_manager_templatedefault_worker_templateruntime_kindeffective_manager_imagesupported_runtime_kindsruntime_default_images
Updates the default bootstrap templates.
Request body:
{
"default_manager_template": "builtin.manager",
"default_worker_template": "local.review-bot"
}Notes:
- Both fields are optional
- The updated config is validated after modification
- If the default templates change and the agent service is mounted, the gateway runtime is updated as well
These endpoints expose CSGClaw local IM data.
For the thread model, invariants, hidden context behavior, and bridge rules, see im-threads.md.
Returns IM bootstrap data.
Response fields:
current_user_idusersroomsinvite_draft_user_ids
Room message lists in the bootstrap response follow the default timeline contract: top-level messages only; thread replies are hidden.
Subscribes to the local IM event stream.
Returns text/event-stream. After the connection is established, the server first writes:
: connected
Then it streams JSON events as SSE data: frames. The heartbeat frame is:
: ping
Currently observed event types include:
message.createdroom.createdroom.members_addedthread.createdthread.updatedupgrade.status_changed
Event JSON fields:
typeroom_idroomusermessagethreadsenderupgrade
Lists local IM users.
Creates a local IM user.
Request body:
{
"id": "alice",
"name": "Alice",
"handle": "alice",
"role": "worker"
}Notes:
idis requirednameis requiredhandledefaults tonamewhen omitted- For
workeroragentroles, if participant and agent services are both enabled, prefer the participant API for agent-backed identities
Deletes a local IM user.
Deleting a user also rebuilds thread state from the surviving room messages. Thread roots sent by the deleted user are removed, hidden context snapshots are regenerated without deleted-user messages, and surviving thread creation times are preserved where possible.
Common responses:
204: deleted successfully404: user not found409: attempted to delete the current user
Lists local IM rooms.
Room message lists exclude thread replies by default. Root messages still expose their thread summaries when a thread exists.
Creates a room.
Request body:
{
"title": "Launch",
"description": "coordination",
"creator_id": "manager",
"member_ids": ["alice", "bob"],
"locale": "en"
}Compatibility field:
- Legacy
participant_idsis still accepted and mapped tomember_ids
Deletes a room and returns 204 on success.
Lists room members.
Adds members to the specified room.
Request body:
{
"inviter_id": "manager",
"user_ids": ["bob"],
"locale": "en"
}Notes:
- The path
{id}is used asroom_id - If
room_idis also present in the body, it must match the path value
Adds members by room ID, with semantics similar to POST /api/v1/rooms/{id}/members.
Request body:
{
"room_id": "room-1",
"inviter_id": "manager",
"user_ids": ["bob"],
"locale": "en"
}Returns the message list for the specified room.
room_id is required.
By default, thread replies are excluded from the room timeline. Add
include_thread_replies=true to include threaded replies in the returned
message list.
Sends a message.
Request body:
{
"room_id": "room-1",
"sender_id": "manager",
"content": "hello @alice",
"mention_id": "alice"
}Notes:
room_idis required- Returns
201 Createdon success - A successful send also publishes
message.createdto/api/v1/events - To send a thread reply, include
relates_to: {"rel_type":"m.thread","event_id":"<root_message_id>"} relates_to.rel_typecurrently supportsm.thread; the root must be a top-level message in the same room- A thread reply also publishes
thread.updated
Starts a thread from an existing top-level message. The thread identity is the
root message ID, matching Matrix m.thread relationship semantics without
using the raw /_matrix namespace.
Request body:
{
"root_message_id": "msg-root"
}Responses:
201 Created: a new thread state was created200 OK: the thread already existed and was returned idempotently
The response is a ThreadView:
{
"room_id": "room-1",
"root": { "id": "msg-root" },
"context": [],
"replies": [],
"summary": {
"root_id": "msg-root",
"reply_count": 0,
"participants": [],
"current_user_participated": true,
"context_summary": {
"root_excerpt": "root text",
"message_count": 1,
"before_count": 0,
"after_count": 0
}
}
}ThreadView.root is the visible root message, context is the hidden snapshot
for LLM context, replies is the visible thread reply list, and summary is
the root-level thread summary used by timelines and thread lists.
Thread context is snapshotted when the thread starts: up to five top-level messages before the root, the root message, and up to two top-level messages after it, capped by payload size. This context is not rendered as thread messages; it is hidden context for LLM-backed agents so a thread can begin with a clean conversation while still understanding what it was started from.
Lists room threads. include defaults to all; participated returns only
threads where the current user is the root sender or a reply participant.
limit and from implement offset-style pagination.
Returns one ThreadView, including the root message, hidden context window,
replies, and summary.
Returns Matrix-style child events for a thread root:
{
"chunk": []
}/api/v1/channels/csgclaw/* is essentially a mirrored entrypoint for the local IM API.
GET /api/v1/channels/csgclaw/usersPOST /api/v1/channels/csgclaw/usersDELETE /api/v1/channels/csgclaw/users/{id}
Notes:
GETandPOSTreuse the local IM user logicDELETEuses channel-specific delete handling, but still semantically deletes the local user
GET /api/v1/channels/csgclaw/roomsPOST /api/v1/channels/csgclaw/roomsDELETE /api/v1/channels/csgclaw/rooms/{id}GET /api/v1/channels/csgclaw/rooms/{id}/membersPOST /api/v1/channels/csgclaw/rooms/{id}/membersPOST /api/v1/channels/csgclaw/rooms/{id}/threadsGET /api/v1/channels/csgclaw/rooms/{id}/threadsGET /api/v1/channels/csgclaw/rooms/{id}/threads/{root_message_id}GET /api/v1/channels/csgclaw/rooms/{id}/relations/{event_id}/m.thread
GET /api/v1/channels/csgclaw/messages?room_id=...POST /api/v1/channels/csgclaw/messages
Returns the Feishu channel config view.
Optional query parameters:
bot_id
bot_id is the current Feishu credential/config key field name. It is not a
participant ID; participant-facing routes continue to use participant IDs.
Example response:
{
"bot_id": "u-manager",
"configured": true,
"app_id": "cli_xxx",
"app_secret": "present",
"admin_open_id": "ou_xxx"
}Note:
app_secretis returned as a status marker, not the real secret
Updates the Feishu channel config.
Request body:
{
"bot_id": "u-manager",
"app_id": "cli_xxx",
"app_secret": "secret",
"admin_open_id": "ou_xxx",
"reload": true
}Notes:
app_idandapp_secretare requiredbot_idmay come from either query or bodyreloaddefaults totruewhen omitted
Reloads the Feishu config.
Example response:
{
"status": "reloaded",
"feishu_bots": ["u-manager"]
}feishu_bots is the current response field name for reloaded Feishu
credential keys. Values are target agent IDs, not participant IDs.
Subscribes to mention events for the specified participant in Feishu.
Characteristics:
- Requires Bearer Token
- Returns
text/event-stream - Only forwards events whose message mentions the participant open_id
- Writes
: connectedimmediately after the stream is established
GET /api/v1/channels/feishu/usersPOST /api/v1/channels/feishu/usersDELETE /api/v1/channels/feishu/users/{id}
Example POST body:
{
"id": "ou_xxx",
"name": "Alice",
"handle": "alice",
"role": "member",
"avatar": "AL"
}GET /api/v1/channels/feishu/roomsPOST /api/v1/channels/feishu/roomsDELETE /api/v1/channels/feishu/rooms/{id}GET /api/v1/channels/feishu/rooms/{id}/membersPOST /api/v1/channels/feishu/rooms/{id}/members
Room creation and member-addition requests are mostly aligned with local IM and still use:
titledescriptioncreator_idmember_idslocale
Example add-members request:
{
"inviter_id": "manager",
"user_ids": ["dev"],
"locale": "zh-CN"
}GET /api/v1/channels/feishu/messages?room_id=...POST /api/v1/channels/feishu/messages
Example send-message request:
{
"room_id": "oc_xxx",
"sender_id": "manager",
"content": "hello",
"mention_id": "worker"
}Runtime clients use participant-scoped routes for channel messages and
agent-scoped routes for LLM provider traffic. The legacy /api/bots/* routes
are not registered.
For thread/session isolation rules used by runtime and Codex bridges, see im-threads.md.
Subscribes to the participant event stream.
Characteristics:
- Requires Bearer Token
- Returns
text/event-stream - Writes
: connectedimmediately after the stream is established - Uses
: heartbeatas the heartbeat comment - Uses
messageas the SSE event name - If the client sends
Last-Event-ID, the server may replay recent messages according to the replay rules
Example single event:
id: msg-1
event: message
data: {"message_id":"msg-1","room_id":"room-1","channel":"csgclaw","chat_id":"room-1","sender_id":"admin","text":"hello","thread_root_id":"msg-root","context":{"channel":"csgclaw","chat_id":"room-1","chat_type":"direct","topic_id":"msg-root","sender_id":"admin","message_id":"msg-1"},"thread_context":{"root_message_id":"msg-root","context":[{"id":"msg-root","sender_id":"admin","content":"root text"}],"summary":{"root_excerpt":"root text","message_count":1,"before_count":0,"after_count":0}}}
For thread replies, thread_root_id is the root message ID and
thread_context carries the deterministic hidden context captured when the
thread was started. Runtime/LLM bridges use it as prompt context; it is not a list
of thread replies. PicoClaw-native clients can use context.topic_id as the
same thread/session identifier.
Sends a message as the specified local CSGClaw participant.
Example request body:
{
"room_id": "room-1",
"text": "hello",
"thread_root_id": "msg-root"
}thread_root_id, topic_id, and context.topic_id are optional thread/topic
identifiers. When one is present, the participant response is sent as a reply inside
that IM thread. When all are omitted, the response is sent as a top-level room/DM
message; the server does not infer a thread from the participant's most recent room
event.
PicoClaw outbound message shape is also accepted:
{
"chat_id": "room-1",
"content": "hello",
"context": {
"channel": "csgclaw",
"chat_id": "room-1",
"topic_id": "msg-root"
}
}Forwards model-list requests to the LLM bridge.
Notes:
- Requires Bearer Token
- Response content type and body are determined by the upstream bridge
Forwards chat-completions requests to the LLM bridge.
Notes:
- Requires Bearer Token
- The request body is read and forwarded as-is
- Maximum single request-body read size is
10 MiB - Failures may return either plain-text errors or a JSON error payload such as:
{
"error": {
"code": "unauthorized",
"message": "upstream auth failed",
"provider": "openai"
}
}Forwards OpenAI-compatible Responses API requests to the LLM bridge. Codex runtime uses this entrypoint for provider traffic. If the selected upstream provider returns an unsupported Responses endpoint status, the bridge falls back to upstream chat completions and wraps the result in a Responses-compatible response for Codex.
The GET variants are websocket upgrade endpoints for Responses API sessions.
Example request body:
{
"model": "ignored-by-server",
"input": "Review this patch.",
"stream": true
}Notes:
- Requires Bearer Token
- The request is first forwarded to the selected profile's
base_url + /responses - If upstream
/responsesreturns404or405, the bridge retries viabase_url + /chat/completions - The
modelfield is overwritten with the agent's resolvedmodel_id - Responses forwarding does not inject the chat-only top-level
reasoning_effort - Upstream Responses headers, status, and body are copied through, including streaming responses such as
text/event-stream
CreateRoomRequest.participant_idsis still accepted and mapped tomember_idsMessage.mentionsremains backward-compatible with the legacy format:- New format:
[{ "id": "alice", "name": "Alice" }] - Legacy format:
["u-alice"]
- New format:
- The local
csgclawchannel routes are effectively mirrored entrypoints for/api/v1/users|rooms|messages
The following paths often seen in older docs are no longer registered in the current router and should not be treated as public APIs:
/api/v1/notify/{agent_id}/api/v1/channels/{channel}/bots/api/v1/channels/{channel}/bots/{id}/api/v1/channels/feishu/bots/{id}/events/api/bots/{id}/events/api/bots/{id}/messages/send/api/bots/{id}/llm/*- Any other legacy path not registered in
internal/api/router.go