API Reference¶
This document provides a complete reference for the AI Sessions REST API and WebSocket protocol.
Base URL¶
| Environment | URL |
|---|---|
| Production | https://ai-sessions.limacharlie.io |
| Staging | https://ai-sessions-staging.limacharlie.io |
Authentication¶
All API requests require a valid LimaCharlie JWT token in the Authorization header:
For WebSocket connections, you can also pass the token as a query parameter:
Rate Limits¶
| Operation | Limit |
|---|---|
| Registration | 10 requests/minute per user |
| Session creation | 10 requests/minute per user |
| WebSocket messages | 100 messages/second per connection |
REST API Endpoints¶
Registration¶
Register User¶
Register the authenticated user for the AI Sessions platform.
Response: 200 OK¶
Error Responses:
401: Invalid or missing JWT token403: Email domain not in allowed list409: User already registered
Deregister User¶
Deregister the user and delete all associated data. This terminates all active sessions and deletes stored credentials.
Response: 200 OK¶
Sessions¶
List Sessions¶
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
status |
string | Filter by status: starting, running, ended |
limit |
integer | Max results (default 50, max 200) |
cursor |
string | Pagination cursor |
Response: 200 OK¶
{
"sessions": [
{
"id": "abc123",
"status": "running",
"region": "us-central1",
"created_at": "2025-01-15T10:30:00Z",
"started_at": "2025-01-15T10:30:05Z",
"lc_auth_type": "jwt",
"allowed_tools": ["Bash", "Read"],
"denied_tools": ["Write"]
}
],
"next_cursor": "xyz789"
}
Create Session¶
Request Body:
{
"lc_credentials": {
"type": "org_api_key",
"org_api_key": "xxxxxxxx"
},
"allowed_tools": ["Bash", "Read", "Write"],
"denied_tools": ["WebFetch"]
}
Response: 201 Created¶
{
"session": {
"id": "abc123",
"status": "starting",
"region": "us-central1",
"created_at": "2025-01-15T10:30:00Z"
}
}
Error Responses:
400: Invalid request body403: Not registered or no Claude credentials409: Maximum concurrent sessions (10) reached
Get Session¶
Response: 200 OK¶
{
"session": {
"id": "abc123",
"status": "running",
"region": "us-central1",
"created_at": "2025-01-15T10:30:00Z",
"started_at": "2025-01-15T10:30:05Z",
"terminated_at": null,
"end_reason": null,
"exit_code": null,
"error_message": null,
"allowed_tools": ["Bash", "Read"],
"denied_tools": ["Write"]
}
}
Terminate Session¶
Response: 200 OK¶
Delete Session Record¶
Delete a terminated session from history. Only sessions in the ended state can be deleted.
Response: 200 OK¶
Fork Preflight Check¶
Inspect a source session before forking. Reports whether the source can be forked, a suggested name, and any MCP servers the source uses that are missing from the forker's profile (these must be acknowledged when forking).
Forking your own session needs no special permission. Forking a session owned by the organization (an API/D&R session) requires the ai_agent.set permission on the org.
Query Parameters¶
| Parameter | Description |
|---|---|
profile_id |
The forker's profile ID. Defaults to the caller's default profile if omitted. |
Response: 200 OK¶
{
"source_session_id": "abc123",
"source_session_type": "user",
"source_status": "ended",
"source_name": "Investigation 2025-01-15",
"source_lifetime_days": 3,
"default_fork_name": "Fork of Investigation 2025-01-15",
"source_mcp_servers": ["virustotal"],
"missing_mcps": ["virustotal"],
"is_forkable": true,
"is_forkable_reason": "",
"history_available": true
}
source_session_type is user or api. When missing_mcps is non-empty, pass those names in acknowledge_missing_tools on the fork request.
Fork Session¶
Create a new session forked from the given source. The fork inherits the source's conversation context but starts with the forker's profile. The source must be in a forkable state (dormant or ended, within its retention window).
All fields are optional:
{
"name": "Continued investigation",
"profile_id": "profile-xyz",
"initial_prompt": "Pick up where the previous session left off and check the new IOCs.",
"acknowledge_missing_tools": ["virustotal"]
}
| Field | Type | Description |
|---|---|---|
name |
string | Name for the forked session (defaults to a derived name). |
profile_id |
string | Profile to use for the fork (defaults to the caller's default profile). |
initial_prompt |
string | Initial prompt for the forked session. |
acknowledge_missing_tools |
array | MCP servers present in the source but missing from the fork profile, acknowledged by the caller. |
Response: 201 Created¶
Returns the new session object, the same shape as Create Session. The new session's forked_from_session_id field references the source.
Errors¶
| Status | Meaning |
|---|---|
403 |
Forking an org-owned session without the ai_agent.set permission. |
409 |
Source is not in a forkable state, or a create/fork/resume is already in progress. |
410 |
Source workspace archive is no longer available. |
412 |
Source uses MCP servers missing from the fork profile; acknowledge them via acknowledge_missing_tools. |
429 |
Maximum concurrent sessions reached. |
Profiles¶
List Profiles¶
Response: 200 OK¶
{
"profiles": [
{
"id": "profile123",
"name": "Investigation",
"description": "Profile for security investigations",
"is_default": true,
"allowed_tools": ["Bash", "Read"],
"denied_tools": ["Write"],
"permission_mode": "acceptEdits",
"model": "claude-sonnet-4-20250514",
"max_turns": 100,
"max_budget_usd": 10.0,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
]
}
Create Profile¶
Request Body:
{
"name": "Investigation",
"description": "Profile for security investigations",
"allowed_tools": ["Bash", "Read", "Grep"],
"denied_tools": ["Write", "Edit"],
"permission_mode": "acceptEdits",
"model": "claude-sonnet-4-20250514",
"max_turns": 100,
"max_budget_usd": 10.0,
"mcp_servers": {
"virustotal": {
"type": "http",
"url": "https://vt-mcp.example.com",
"headers": {
"x-apikey": "hive://secret/vt-api-key"
}
}
},
"is_default": false
}
Response: 201 Created¶
Error Responses:
400: Invalid request body409: Maximum profiles (10) reached
Get Profile¶
Update Profile¶
Delete Profile¶
Note: The default profile cannot be deleted.
Set Default Profile¶
Capture Session as Profile¶
Request Body:
Claude Authentication¶
Start OAuth Flow¶
Response: 200 OK¶
{
"oauth_session_id": "oauth123",
"expires_in": 300,
"message": "Poll /auth/claude/url for the OAuth URL"
}
Get OAuth URL¶
Response: 200 OK (URL Ready)¶
{
"status": "url_ready",
"url": "https://console.anthropic.com/oauth/authorize?...",
"message": "Visit the URL to authorize"
}
Response: 200 OK (Pending)¶
Submit OAuth Code¶
Request Body:
Response: 200 OK¶
Store API Key¶
Request Body:
Response: 200 OK¶
Get Credential Status¶
Response: 200 OK¶
Delete Credentials¶
File Transfer¶
Request Upload URL¶
Request Body:
Response: 200 OK¶
{
"upload_url": "https://storage.googleapis.com/...",
"upload_id": "upload123",
"target_path": "/workspace/uploads/data.csv",
"expires_at": "2025-01-15T11:30:00Z"
}
Error Responses:
413: File size exceeds limit (100 MB)
Notify Upload Complete¶
Request Body:
Response: 200 OK¶
Request Download URL¶
Request Body:
Response: 200 OK¶
WebSocket Protocol¶
Connection¶
Endpoint:
Authentication:
- Header:
Authorization: Bearer <JWT> - Query parameter:
?token=<JWT>
Connection Errors¶
| Code | Description |
|---|---|
| 4001 | Invalid or missing authentication |
| 4003 | Session belongs to different user |
| 4004 | Session not found |
| 4009 | Session not running |
| 4100 | Session ended |
| 4101 | Connection reset |
| 4500 | Internal error |
Message Format¶
All messages are JSON objects:
{
"type": "message_type",
"timestamp": "2025-01-15T10:30:00Z",
"session_id": "abc123",
"payload": { ... }
}
Client to Server Messages¶
prompt¶
Send a user prompt to Claude.
interrupt¶
Interrupt the current Claude operation.
heartbeat¶
Keep the connection alive (send every 30 seconds).
upload_request¶
Request a signed URL for file upload.
{
"type": "upload_request",
"payload": {
"request_id": "req_123",
"filename": "data.csv",
"content_type": "text/csv",
"size": 1024
}
}
upload_complete¶
Notify that file upload has completed.
{
"type": "upload_complete",
"payload": {
"request_id": "req_123",
"filename": "data.csv",
"path": "/workspace/uploads/data.csv"
}
}
download_request¶
Request a signed URL for file download.
{
"type": "download_request",
"payload": {
"request_id": "req_456",
"path": "/workspace/output.txt"
}
}
Server to Client Messages¶
assistant¶
Claude's response content.
{
"type": "assistant",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"content": [
{
"type": "text",
"text": "Here are the files in the current directory:\n\n- file1.txt\n- file2.py"
}
],
"model": "claude-sonnet-4-20250514"
}
}
tool_use¶
Claude is invoking a tool.
{
"type": "tool_use",
"timestamp": "2025-01-15T10:30:01Z",
"payload": {
"id": "tool_abc123",
"name": "Bash",
"input": {
"command": "ls -la"
}
}
}
tool_result¶
Result of a tool execution.
{
"type": "tool_result",
"timestamp": "2025-01-15T10:30:02Z",
"payload": {
"tool_use_id": "tool_abc123",
"content": "total 16\ndrwxr-xr-x 2 user user 4096 Jan 15 10:00 .\n..."
}
}
user¶
Echo of user input (for display purposes).
{
"type": "user",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"text": "List all files in the current directory"
}
}
system¶
System messages from Claude.
{
"type": "system",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"message": "Working directory: /workspace"
}
}
result¶
Final result of a Claude operation.
{
"type": "result",
"timestamp": "2025-01-15T10:35:00Z",
"payload": {
"success": true,
"summary": "Listed directory contents successfully"
}
}
session_status¶
Session status update.
{
"type": "session_status",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"status": "running"
}
}
session_end¶
Session has ended.
{
"type": "session_end",
"timestamp": "2025-01-15T10:35:00Z",
"payload": {
"reason": "completed",
"exit_code": 0
}
}
End Reasons:
completed: Session completed normallyfailed: Session encountered an execution errorjob_completed: Session runner process exiteduser_requested: User terminated the sessionorg_api_requested: Session was terminated via the org APImax_duration_exceeded: Session exceeded its maximum durationstartup_timeout: Session failed to start within the allowed timeheartbeat_stale: Lost connection to the session runner
session_error¶
An error occurred in the session.
{
"type": "session_error",
"timestamp": "2025-01-15T10:35:00Z",
"payload": {
"error": "Claude process died unexpectedly",
"details": "Exit code: 1"
}
}
error¶
General error message.
{
"type": "error",
"timestamp": "2025-01-15T10:35:00Z",
"payload": {
"message": "Rate limit exceeded",
"code": "rate_limited"
}
}
Error Codes:
session_not_found: Session no longer existssession_not_running: Session is not in running statesession_crashed: Session process crashedinvalid_message: Malformed message receivedrate_limited: Too many messages sent
upload_url¶
Response to upload_request.
{
"type": "upload_url",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"request_id": "req_123",
"upload_id": "upload_789",
"url": "https://storage.googleapis.com/...",
"target_path": "/workspace/uploads/data.csv",
"expires_at": "2025-01-15T11:30:00Z"
}
}
download_url¶
Response to download_request.
{
"type": "download_url",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"request_id": "req_456",
"url": "https://storage.googleapis.com/...",
"expires_at": "2025-01-15T11:30:00Z"
}
}
Connection Management¶
Heartbeat¶
- Client: Send
heartbeatmessage every 30 seconds - Server: Sends WebSocket ping frames every 30 seconds
- Timeout: Connection closed after 60 seconds of inactivity
Reconnection¶
If the connection is lost:
- Reconnect using the same session ID
- Server sends any buffered messages (up to 60 seconds old)
- If session has ended, server sends
session_endmessage
Message Size¶
Maximum message size is 1 MB. Use file transfer for larger payloads.
Example: Complete Session Flow¶
const jwt = 'your-limacharlie-jwt';
const baseUrl = 'https://ai-sessions.limacharlie.io';
// 1. Create session
const createResp = await fetch(`${baseUrl}/v1/sessions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
allowed_tools: ['Bash', 'Read', 'Write']
})
});
const { session } = await createResp.json();
// 2. Wait for session to be running
let status = 'starting';
while (status === 'starting') {
await new Promise(r => setTimeout(r, 1000));
const resp = await fetch(`${baseUrl}/v1/sessions/${session.id}`, {
headers: { 'Authorization': `Bearer ${jwt}` }
});
const data = await resp.json();
status = data.session.status;
}
// 3. Connect via WebSocket
const ws = new WebSocket(
`wss://ai-sessions.limacharlie.io/v1/sessions/${session.id}/ws?token=${jwt}`
);
// 4. Set up heartbeat
const heartbeat = setInterval(() => {
ws.send(JSON.stringify({ type: 'heartbeat' }));
}, 30000);
// 5. Handle messages
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'assistant':
console.log('Claude:', msg.payload.content);
break;
case 'tool_use':
console.log('Using tool:', msg.payload.name);
break;
case 'tool_result':
console.log('Tool result:', msg.payload.content);
break;
case 'session_end':
console.log('Session ended:', msg.payload.reason);
clearInterval(heartbeat);
break;
case 'error':
console.error('Error:', msg.payload.message);
break;
}
};
// 6. Send a prompt
ws.send(JSON.stringify({
type: 'prompt',
payload: { text: 'Hello! List the files in the current directory.' }
}));
// 7. Later: interrupt if needed
// ws.send(JSON.stringify({ type: 'interrupt' }));
// 8. Cleanup
ws.onclose = () => {
clearInterval(heartbeat);
};