Skip to content

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:

Authorization: Bearer <LC-JWT>

For WebSocket connections, you can also pass the token as a query parameter:

wss://ai-sessions.limacharlie.io/v1/sessions/{sessionId}/ws?token=<LC-JWT>

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

POST /v1/register

Register the authenticated user for the AI Sessions platform.

Response: 200 OK
{
  "registered": true,
  "registered_at": "2025-01-15T10:30:00Z"
}

Error Responses:

  • 401: Invalid or missing JWT token
  • 403: Email domain not in allowed list
  • 409: User already registered

Deregister User

DELETE /v1/register

Deregister the user and delete all associated data. This terminates all active sessions and deletes stored credentials.

Response: 200 OK
{
  "deregistered": true
}

Sessions

List Sessions

GET /v1/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

POST /v1/sessions

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 body
  • 403: Not registered or no Claude credentials
  • 409: Maximum concurrent sessions (10) reached

Get Session

GET /v1/sessions/{sessionId}
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

DELETE /v1/sessions/{sessionId}
Response: 200 OK
{
  "terminated": true
}

Delete Session Record

DELETE /v1/sessions/{sessionId}/record

Delete a terminated session from history. Only sessions in the ended state can be deleted.

Response: 200 OK
{
  "deleted": true
}

Fork Preflight Check

GET /v1/sessions/{sessionId}/fork-preflight

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

POST /v1/sessions/{sessionId}/fork

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

GET /v1/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

POST /v1/profiles

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
{
  "profile": {
    "id": "profile123",
    "name": "Investigation",
    ...
  }
}

Error Responses:

  • 400: Invalid request body
  • 409: Maximum profiles (10) reached

Get Profile

GET /v1/profiles/{profileId}

Update Profile

PUT /v1/profiles/{profileId}

Delete Profile

DELETE /v1/profiles/{profileId}

Note: The default profile cannot be deleted.

Set Default Profile

POST /v1/profiles/{profileId}/default

Capture Session as Profile

POST /v1/sessions/{sessionId}/capture-profile

Request Body:

{
  "name": "My Session Config",
  "description": "Captured from session abc123"
}

Claude Authentication

Start OAuth Flow

POST /v1/auth/claude/start
Response: 200 OK
{
  "oauth_session_id": "oauth123",
  "expires_in": 300,
  "message": "Poll /auth/claude/url for the OAuth URL"
}

Get OAuth URL

GET /v1/auth/claude/url?session_id={oauth_session_id}
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)
{
  "status": "pending",
  "message": "Waiting for OAuth URL to be generated"
}

Submit OAuth Code

POST /v1/auth/claude/code

Request Body:

{
  "session_id": "oauth123",
  "code": "authorization_code_from_anthropic"
}
Response: 200 OK
{
  "success": true,
  "status": "completed",
  "message": "Claude credentials stored successfully"
}

Store API Key

POST /v1/auth/claude/apikey

Request Body:

{
  "api_key": "sk-ant-api03-xxxxx"
}
Response: 200 OK
{
  "success": true,
  "message": "API key stored successfully"
}

Get Credential Status

GET /v1/auth/claude/status
Response: 200 OK
{
  "has_credentials": true,
  "credential_type": "api_key",
  "created_at": "2025-01-15T10:30:00Z"
}

Delete Credentials

DELETE /v1/auth/claude

File Transfer

Request Upload URL

POST /v1/io/sessions/{sessionId}/upload

Request Body:

{
  "filename": "data.csv",
  "content_type": "text/csv",
  "size": 1024
}
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

POST /v1/io/sessions/{sessionId}/upload/complete

Request Body:

{
  "upload_id": "upload123"
}
Response: 200 OK
{
  "success": true,
  "path": "/workspace/uploads/data.csv"
}

Request Download URL

POST /v1/io/sessions/{sessionId}/download

Request Body:

{
  "path": "/workspace/output.txt"
}
Response: 200 OK
{
  "download_url": "https://storage.googleapis.com/...",
  "expires_at": "2025-01-15T11:30:00Z"
}

WebSocket Protocol

Connection

Endpoint:

wss://ai-sessions.limacharlie.io/v1/sessions/{sessionId}/ws

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.

{
  "type": "prompt",
  "payload": {
    "text": "List all files in the current directory"
  }
}

interrupt

Interrupt the current Claude operation.

{
  "type": "interrupt"
}

heartbeat

Keep the connection alive (send every 30 seconds).

{
  "type": "heartbeat"
}

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 normally
  • failed: Session encountered an execution error
  • job_completed: Session runner process exited
  • user_requested: User terminated the session
  • org_api_requested: Session was terminated via the org API
  • max_duration_exceeded: Session exceeded its maximum duration
  • startup_timeout: Session failed to start within the allowed time
  • heartbeat_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 exists
  • session_not_running: Session is not in running state
  • session_crashed: Session process crashed
  • invalid_message: Malformed message received
  • rate_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 heartbeat message 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:

  1. Reconnect using the same session ID
  2. Server sends any buffered messages (up to 60 seconds old)
  3. If session has ended, server sends session_end message

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);
};