This document describes the WebSocket and REST APIs for interacting with Botical.
ws://HOST/ws?projectId={projectId}&token={authToken}
Query Parameters:
projectId(required): Project to connect totoken(required): JWT authentication token
On Connect: Server sends:
{
"type": "connected",
"payload": {
"connectionId": "conn_abc123",
"projectId": "project_xyz",
"userId": "user_456"
}
}{
id: string; // Unique request ID for response matching
type: string; // Request type (see Operations below)
payload: unknown; // Type-specific data
}{
id: string; // Matches request ID
type: "response";
success: boolean;
payload?: unknown; // Result data (if success)
error?: { // Error info (if !success)
code: string;
message: string;
details?: unknown;
}
}{
type: string; // Event type
payload: unknown; // Event data
}Create a new conversation session.
Request Payload:
{
title?: string; // Optional title (auto-generated if omitted)
agent?: string; // Agent ID to use (default: "default")
}Response Payload:
{
session: {
id: string;
slug: string;
title: string;
agent: string;
status: "active";
createdAt: number;
updatedAt: number;
}
}List sessions in the project.
Request Payload:
{
status?: "active" | "archived" | "deleted";
limit?: number; // Default: 50
cursor?: string; // For pagination
}Response Payload:
{
sessions: Session[];
nextCursor?: string;
}Get session details with recent messages.
Request Payload:
{
sessionId: string;
includeMessages?: boolean; // Default: true
messageLimit?: number; // Default: 50
}Response Payload:
{
session: Session;
messages?: Message[];
}Delete (archive) a session.
Request Payload:
{
sessionId: string;
permanent?: boolean; // Hard delete, default: false
}Response Payload:
{
deleted: true
}Send a message to the agent.
Request Payload:
{
sessionId: string;
content: string; // User message text
attachments?: { // Optional file attachments
path: string;
inline?: boolean;
}[];
}Response Payload:
{
messageId: string; // The assistant's response message ID
status: "completed" | "error";
}Streaming Events: While processing, server sends events (see Events section).
Cancel an in-progress message generation.
Request Payload:
{
sessionId: string;
}Response Payload:
{
cancelled: true
}Retry from a specific message.
Request Payload:
{
sessionId: string;
messageId: string; // User message to retry from
}Response Payload:
Same as message.send.
List available agents.
Request Payload:
{
mode?: "primary" | "subagent" | "all";
includeHidden?: boolean; // Default: false
}Response Payload:
{
agents: Agent[]
}Create a custom agent.
Request Payload:
{
name: string;
description?: string;
mode: "primary" | "subagent" | "all";
prompt?: string; // System prompt
providerId?: string; // LLM provider
modelId?: string; // Model ID
temperature?: number;
maxSteps?: number;
permissions?: Permission[];
}Response Payload:
{
agent: Agent
}Update an agent configuration.
Request Payload:
{
agentId: string;
updates: Partial<AgentConfig>;
}Response Payload:
{
agent: Agent
}List available tools.
Request Payload:
{
includeDisabled?: boolean; // Default: false
}Response Payload:
{
tools: Tool[]
}Create a custom tool.
Request Payload:
{
name: string;
description: string;
type: "code" | "mcp" | "http";
parametersSchema: JSONSchema;
// For type: "code"
code?: string;
// For type: "mcp"
mcpServer?: string;
mcpTool?: string;
// For type: "http"
httpUrl?: string;
httpMethod?: "GET" | "POST" | "PUT" | "DELETE";
}Response Payload:
{
tool: Tool
}Approve a pending tool execution.
Request Payload:
{
toolCallId: string;
sessionId: string;
remember?: boolean; // Save permission for future
scope?: "session" | "project";
}Response Payload:
{
approved: true
}Reject a pending tool execution.
Request Payload:
{
toolCallId: string;
sessionId: string;
reason?: string;
}Response Payload:
{
rejected: true
}List files in a directory.
Request Payload:
{
path?: string; // Default: "/" (project root)
recursive?: boolean; // Default: false
pattern?: string; // Glob pattern filter
}Response Payload:
{
files: {
path: string;
type: "file" | "directory";
size?: number;
mimeType?: string;
updatedAt: number;
}[]
}Read file contents.
Request Payload:
{
path: string;
encoding?: "utf-8" | "base64"; // Default: "utf-8"
}Response Payload:
{
path: string;
content: string;
mimeType?: string;
size: number;
}Write file contents.
Request Payload:
{
path: string;
content: string;
encoding?: "utf-8" | "base64";
createDirectories?: boolean; // Default: true
}Response Payload:
{
path: string;
size: number;
version: number;
}Delete a file or directory.
Request Payload:
{
path: string;
recursive?: boolean; // For directories, default: false
}Response Payload:
{
deleted: true
}Subscribe to a channel for events.
Request Payload:
{
channel: string; // "session:{id}" or "project:{id}"
}Response Payload:
{
subscribed: true;
channel: string;
}Unsubscribe from a channel.
Request Payload:
{
channel: string;
}Response Payload:
{
unsubscribed: true;
}Health check / keep-alive.
Request Payload: (none)
Response Payload:
{
pong: number; // Server timestamp
}Events are pushed from server to client without a request.
{
session: Session
}{
session: Session
}{
sessionId: string
}These events are sent during message generation:
New message started.
{
sessionId: string;
message: Message;
}Incremental text output.
{
sessionId: string;
messageId: string;
partId: string;
delta: string;
}Text part finished.
{
sessionId: string;
messageId: string;
partId: string;
text: string;
}Chain-of-thought text (if enabled).
{
sessionId: string;
messageId: string;
partId: string;
delta: string;
}Agent is calling a tool.
{
sessionId: string;
messageId: string;
partId: string;
toolName: string;
toolCallId: string;
input: unknown;
}Tool execution completed.
{
sessionId: string;
messageId: string;
partId: string;
toolCallId: string;
output: string;
metadata?: unknown;
}Tool execution failed.
{
sessionId: string;
messageId: string;
partId: string;
toolCallId: string;
error: string;
}Message generation finished.
{
sessionId: string;
messageId: string;
finishReason: "stop" | "tool-calls" | "length" | "error";
usage: {
inputTokens: number;
outputTokens: number;
cost: number;
}
}Message generation failed.
{
sessionId: string;
messageId: string;
error: {
code: string;
message: string;
}
}Agent needs permission to run a tool.
{
sessionId: string;
messageId: string;
toolCallId: string;
toolName: string;
input: unknown;
message: string; // Human-readable description
}Tool approval was handled.
{
sessionId: string;
toolCallId: string;
approved: boolean;
}{
path: string;
type: "file" | "directory";
}{
path: string;
sessionId?: string; // If changed by agent
}{
path: string;
}User connected to project.
{
userId: string;
username: string;
avatar?: string;
}User disconnected.
{
userId: string;
}User cursor position (for collaborative features).
{
userId: string;
sessionId: string;
position?: number;
}REST API provides CRUD operations for sessions, messages, and agents.
All REST endpoints return consistent JSON responses:
// Success response
{
data: T; // The requested resource(s)
meta?: { // Pagination/metadata (for lists)
total: number;
limit: number;
offset: number;
hasMore: boolean;
}
}
// Error response
{
error: {
code: string; // Machine-readable error code
message: string; // Human-readable message
details?: unknown; // Additional context
}
}Base path: /api/sessions
List sessions with pagination and filters.
Query Parameters:
projectId(required): Project IDstatus: Filter by status (active,archived,deleted)agent: Filter by agent nameparentId: Filter by parent session (for sub-agents)limit: Max results (default: 50, max: 100)offset: Skip results (default: 0)
Response:
{
data: Session[];
meta: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
}
}Create a new session.
Request Body:
{
projectId: string;
title?: string; // Auto-generated if omitted
agent?: string; // Default: "default"
parentId?: string; // For sub-agent sessions
providerId?: string; // LLM provider
modelId?: string; // Model ID
}Response: 201 Created
{
data: Session
}Get session by ID.
Query Parameters:
projectId(required): Project ID
Response:
{
data: Session
}Update session.
Request Body:
{
projectId: string;
title?: string;
status?: "active" | "archived" | "deleted";
agent?: string;
providerId?: string;
modelId?: string;
}Response:
{
data: Session
}Soft delete a session (sets status to "deleted").
Query Parameters:
projectId(required): Project ID
Response:
{
data: { deleted: true }
}List messages in a session.
Query Parameters:
projectId(required): Project IDrole: Filter by role (user,assistant,system)limit: Max results (default: 50)offset: Skip results (default: 0)
Response:
{
data: Message[];
meta: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
}
}Base path: /api/messages
Send a message and trigger agent orchestration.
Request Body:
{
projectId: string;
sessionId: string;
content: string; // User message text
userId: string; // User ID for permission context
providerId?: string; // Default: "anthropic"
modelId?: string; // Uses provider default
agentName?: string; // Override session's agent
canExecuteCode?: boolean; // Default: false
}Response: 201 Created
{
data: {
message: Message; // Assistant message
parts: MessagePart[]; // All message parts
usage: {
inputTokens: number;
outputTokens: number;
};
cost: number; // Estimated cost in USD
finishReason: "stop" | "tool-calls" | "length" | "error";
}
}Get message with all parts.
Query Parameters:
projectId(required): Project ID
Response:
{
data: Message & {
parts: MessagePart[];
}
}List message parts.
Query Parameters:
projectId(required): Project ID
Response:
{
data: MessagePart[];
meta: {
total: number;
}
}Base path: /api/agents
List available agents (built-in + custom).
Query Parameters:
projectId: Project ID (required for custom agents)mode: Filter by mode (primary,subagent)includeHidden: Include hidden agents (default: false)builtinOnly: Only built-in agents (default: false)customOnly: Only custom agents (default: false)
Response:
{
data: AgentConfig[];
meta: {
total: number;
builtinCount: number;
customCount: number;
}
}Create a custom agent.
Request Body:
{
projectId: string;
name: string; // Lowercase, hyphens, starts with letter
description?: string;
mode?: "primary" | "subagent" | "all"; // Default: "subagent"
hidden?: boolean; // Default: false
providerId?: string;
modelId?: string;
temperature?: number; // 0-2
topP?: number; // 0-1
maxSteps?: number;
prompt?: string; // System prompt
tools?: string[]; // Tool names
color?: string;
}Response: 201 Created
{
data: AgentConfig
}Errors:
400: Name is reserved or already exists400: Invalid name format
Get agent config by name.
Query Parameters:
projectId: Project ID (for custom agents)
Response:
{
data: AgentConfig
}Update custom agent.
Request Body:
{
projectId: string;
name?: string; // Rename agent
description?: string;
mode?: "primary" | "subagent" | "all";
hidden?: boolean;
providerId?: string;
modelId?: string;
temperature?: number;
topP?: number;
maxSteps?: number;
prompt?: string;
tools?: string[];
color?: string;
}Response:
{
data: AgentConfig
}Errors:
403: Cannot update built-in agents400: New name is reserved
Delete custom agent.
Query Parameters:
projectId(required): Project ID
Response:
{
data: { deleted: true }
}Errors:
403: Cannot delete built-in agents
Base path: /api/projects
List projects with pagination and filters.
Query Parameters:
ownerId: Filter by owner user IDmemberId: Filter by member user ID (includes projects where user is owner or member)type: Filter by type (local,remote)includeArchived: Include archived projects (default: false)limit: Max results (default: 50, max: 100)offset: Skip results (default: 0)
Response:
{
data: Project[];
meta: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
}
}Create a new project.
Request Body:
{
name: string; // Required, 1-200 chars
ownerId: string; // Required, user ID
description?: string; // Max 2000 chars
type?: "local" | "remote"; // Default: "local"
path?: string; // Local filesystem path
gitRemote?: string; // Git repository URL
iconUrl?: string; // Project icon URL
color?: string; // Hex color (#RRGGBB)
settings?: Record<string, unknown>;
}Response: 201 Created
{
data: Project
}Get project by ID.
Response:
{
data: Project
}Update project.
Request Body:
{
name?: string;
description?: string | null;
path?: string | null;
gitRemote?: string | null;
iconUrl?: string | null;
color?: string | null;
settings?: Record<string, unknown>;
}Response:
{
data: Project
}Archive a project (soft delete).
Response:
{
data: { archived: true }
}Base path: /api/projects/:id/members
List project members.
Response:
{
data: ProjectMember[];
meta: {
total: number;
}
}Add a member to the project.
Request Body:
{
userId: string;
role: "admin" | "member" | "viewer";
invitedBy?: string;
}Response: 201 Created
{
data: ProjectMember
}Errors:
409 CONFLICT: User is already a member
Update member role.
Request Body:
{
role: "admin" | "member" | "viewer";
}Response:
{
data: ProjectMember
}Errors:
403 FORBIDDEN: Cannot change owner's role
Remove a member from the project.
Response:
{
data: { removed: true }
}Errors:
403 FORBIDDEN: Cannot remove project owner
Base path: /auth
Request a magic link for passwordless login.
Request Body:
{
email: string;
}Response:
{
success: true;
message: string;
}Verify magic link token and create session.
Query Parameters:
token: Magic link token
Response: Redirects or returns session.
Logout current session.
Response:
{
success: true
}Get current user info.
Response:
{
user: User;
session: AuthSession;
}Base path: /credentials
List user's provider credentials.
Response:
{
credentials: ProviderCredential[];
}Store a new provider credential.
Request Body:
{
provider: "anthropic" | "openai" | "google";
apiKey: string;
name?: string;
isDefault?: boolean;
}Response:
{
credential: ProviderCredential;
}Delete a provider credential.
Response:
{
deleted: true
}Base path: /health
Basic health check.
Response:
{
status: "ok";
timestamp: number;
}Readiness check (includes database).
Response:
{
status: "ok" | "error";
timestamp: number;
checks: {
database: "ok" | "error";
}
}Liveness check with uptime.
Response:
{
status: "ok";
uptime: number;
}
---
## Error Codes
| Code | Description |
|------|-------------|
| `AUTH_REQUIRED` | No authentication token provided |
| `AUTH_INVALID` | Token is invalid or expired |
| `FORBIDDEN` | User lacks permission for operation |
| `NOT_FOUND` | Requested resource doesn't exist |
| `VALIDATION_ERROR` | Invalid request parameters |
| `RATE_LIMITED` | Too many requests |
| `INTERNAL_ERROR` | Server error |
| `LLM_ERROR` | LLM provider error |
| `TOOL_ERROR` | Tool execution failed |
| `CANCELLED` | Operation was cancelled |
---
## Related Documents
- [Architecture](./01-architecture.md) - System design
- [Data Model](./02-data-model.md) - Entity definitions
- [Realtime Communication](../implementation-plan/05-realtime-communication.md) - Detailed protocol