Skip to content

WebSocket API Reference

The WebSocket endpoint enables real-time, bidirectional communication for streaming text, tool execution, session management, and provider switching -- all over a single persistent connection.

Connection

Endpoint

ws://localhost:7337/ws?token=sk_seepient_...

Authentication is performed via the token query parameter. The server validates the API key during the HTTP upgrade. If the key is invalid, the upgrade is rejected with 401 Unauthorized.

Upgrade lifecycle

  1. Client initiates a WebSocket upgrade to /ws?token=sk_seepient_...
  2. Server validates the API key
  3. On success, the connection is established and ready for messages
  4. On failure, the socket is destroyed with a 401 response

Connection close on auth failure

If authentication fails after upgrade (should not happen in normal flow), the server sends an error message with code UNAUTHORIZED and closes the connection with code 4001.


Client-to-Server messages

All client messages are JSON. Every message must include a type field.

chat

Send a user message and receive a streaming response.

json
{
  "type": "chat",
  "id": "client-msg-001",
  "message": "Explain closures in JavaScript",
  "options": {
    "model": "claude-sonnet-4-6-20260320",
    "provider": "anthropic",
    "tools": ["execute_shell_command"],
    "maxSteps": 10,
    "skills": ["code-review"]
  },
  "sessionId": "550e8400-e29b-41d4-a716-446655440000"
}
FieldTypeRequiredDescription
idstringYesClient-generated message ID for correlation
messagestringYesThe user prompt
options.modelstringNoModel ID (overrides connection default)
options.providerstringNoProvider name (overrides connection default)
options.toolsstring[]NoTool names or group names to enable
options.maxStepsnumberNoMax agent loop iterations (default: 10)
options.skillsstring[]NoSkill names to activate
sessionIdstringNoResume an existing session

abort

Cancel the current in-flight chat request. The AbortSignal is passed through to the provider SDK, so the underlying HTTP request to the LLM is cancelled at the network level.

json
{
  "type": "abort",
  "reason": "User cancelled"
}
FieldTypeRequiredDescription
reasonstringNoOptional reason for abort

resume

Resume an existing session by ID. The server replies with session_resumed containing the message history.

json
{
  "type": "resume",
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "lastMessageId": "msg_005"
}
FieldTypeRequiredDescription
sessionIdstringYesSession to resume
lastMessageIdstringNoLast message the client has seen

reconnect

Reconnect after a dropped connection. The server replies with replay containing messages after lastSeenId.

json
{
  "type": "reconnect",
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "lastSeenId": "msg_005"
}
FieldTypeRequiredDescription
sessionIdstringYesSession to reconnect to
lastSeenIdstringNoLast message ID the client processed

switch_provider

Change the active provider and/or model for subsequent messages on this connection.

json
{
  "type": "switch_provider",
  "provider": "openai",
  "model": "gpt-5.4"
}
FieldTypeRequiredDescription
providerstringYesNew provider name
modelstringNoNew default model

list_models

Request the list of models available from your configured inference providers. Seepient Agent does not host models — it forwards requests to your provider APIs. The server replies with models_list.

json
{
  "type": "list_models"
}

list_skills

Request the list of available skills. The server replies with skills_list.

json
{
  "type": "list_skills"
}

ping

Heartbeat / latency measurement. The server replies with pong.

json
{
  "type": "ping",
  "clientTime": "2026-04-08T12:00:00.000Z"
}
FieldTypeRequiredDescription
clientTimestringYesISO 8601 client timestamp

Server-to-Client messages

ack

Confirms receipt of a client message. Sent immediately after a chat message is received.

json
{
  "type": "ack",
  "clientMsgId": "client-msg-001",
  "serverMsgId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2026-04-08T12:00:01.000Z"
}
FieldTypeDescription
clientMsgIdstringMatches the id from the client's chat message
serverMsgIdstringServer-generated ID for this generation
timestampstringISO 8601 timestamp

text

Streaming text delta. Multiple text messages are sent as the model generates tokens.

json
{
  "type": "text",
  "delta": "A closure is ",
  "serverMsgId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
FieldTypeDescription
deltastringIncremental text chunk
serverMsgIdstringCorrelates with the ack

tool_call

Indicates the agent is invoking a tool.

json
{
  "type": "tool_call",
  "callId": "call_abc123",
  "name": "execute_shell_command",
  "args": { "command": "ls -la" }
}
FieldTypeDescription
callIdstringUnique tool call identifier
namestringTool name
argsobjectArguments passed to the tool

tool_progress

Progress update during a long-running tool execution.

json
{
  "type": "tool_progress",
  "callId": "call_abc123",
  "percentage": 50,
  "output": "Processing file 5 of 10..."
}
FieldTypeDescription
callIdstringMatches the tool_call
percentagenumberProgress percentage (0-100)
outputstringOptional partial output

tool_result

Final result of a tool invocation.

json
{
  "type": "tool_result",
  "callId": "call_abc123",
  "output": "README.md\npackage.json\nsrc/",
  "success": true
}
FieldTypeDescription
callIdstringMatches the tool_call
outputstringTool execution result
successbooleanWhether the tool succeeded

progress

Agent loop progress update.

json
{
  "type": "progress",
  "step": 2,
  "totalSteps": 5,
  "percentage": 40,
  "activity": "Executing tool: execute_shell_command"
}
FieldTypeDescription
stepnumberCurrent step number
totalStepsnumberEstimated total steps (may be 0 if unknown)
percentagenumberProgress percentage
activitystringDescription of current activity

done

Signals the end of a generation. Always sent as the final message in a chat flow.

json
{
  "type": "done",
  "serverMsgId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "usage": {
    "promptTokens": 245,
    "completionTokens": 187,
    "totalTokens": 432,
    "cost": 0.0041
  },
  "finishReason": "stop"
}
FieldTypeDescription
serverMsgIdstringCorrelates with the ack
usageobjectToken usage and cost
usage.promptTokensnumberTokens in the prompt
usage.completionTokensnumberTokens in the completion
usage.totalTokensnumberTotal tokens consumed
usage.costnumberEstimated cost in USD
finishReasonstring"stop", "max_steps", "error", or "aborted"

error

An error occurred. May be sent at any time during a chat flow or connection lifecycle. Error frames carry generic wire text; operators diagnose details from the server's JSON-line log. Codes a client can receive:

CodeRetryableWhen
FORBIDDENNoThe key lacks the scope the message requires (agent:run for chat, agent:read for resume/reconnect)
RATE_LIMITEDYesThe key exceeded the shared per-key rate limit (WS messages consume the same budget as REST)
REQUEST_IN_FLIGHTYesAnother chat turn is running on this connection or session
SESSION_ERRORNoSession lookup/persistence failed mid-turn
SESSION_NOT_FOUNDNoSession expired, foreign-owned, or missing
SESSION_LIMITNoPer-key concurrent-session cap reached
STREAM_ERRORNoThe model stream failed
PROVIDER_ERRORYesLLM provider error (rate limit, auth, outage)
INTERNAL_ERRORNoUnexpected server error

error

An error occurred. May be sent at any time during a chat flow or connection lifecycle.

json
{
  "type": "error",
  "code": "PROVIDER_ERROR",
  "retryable": true,
  "message": "OpenAI API returned 429: rate limit exceeded",
  "provider": "openai"
}
FieldTypeDescription
codestringError code (see table below)
retryablebooleanWhether the client should retry
messagestringHuman-readable error description
providerstringProvider that caused the error (optional)
toolstringTool that caused the error (optional)

pong

Response to a client ping.

json
{
  "type": "pong",
  "serverTime": "2026-04-08T12:00:01.500Z"
}
FieldTypeDescription
serverTimestringISO 8601 server timestamp

models_list

Response to list_models.

json
{
  "type": "models_list",
  "models": {
    "openai": ["gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.3-instant", "gpt-5.3-codex", "o3", "o3-mini"],
    "anthropic": ["claude-sonnet-4-6-20260320", "claude-opus-4-6-20260320", "claude-haiku-4-5-20251001"],
    "glm": ["opus", "sonnet", "haiku"],
    "openai-compatible": ["(user-configured)"]
  }
}

skills_list

Response to list_skills.

json
{
  "type": "skills_list",
  "skills": [
    { "name": "code-review", "description": "...", "tags": ["code"] }
  ]
}

session_resumed

Response to resume. Contains the session's message history.

json
{
  "type": "session_resumed",
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "messages": [
    { "id": "msg_001", "role": "user", "content": "Hello", "timestamp": 1712505600000 },
    { "id": "msg_002", "role": "assistant", "content": "Hi there!", "timestamp": 1712505601000 }
  ]
}

replay

Response to reconnect. Contains messages the client may have missed.

json
{
  "type": "replay",
  "messages": [
    { "id": "msg_006", "role": "assistant", "content": "...", "timestamp": 1712505610000 }
  ],
  "currentStatus": "ready"
}

Error codes

CodeRetryableDescription
UNAUTHORIZEDNoInvalid or missing API key
INVALID_MESSAGENoMalformed JSON in client message
UNKNOWN_MESSAGE_TYPENoUnrecognized type field
PROVIDER_ERRORYesLLM provider returned an error
STREAM_ERRORNoInternal streaming failure
SESSION_NOT_FOUNDNoSession expired or does not exist
ABORTEDNoRequest aborted by client

Client examples

JavaScript / TypeScript (browser)

typescript
const ws = new WebSocket("ws://localhost:7337/ws?token=sk_seepient_...");

ws.onopen = () => {
  // Start a chat
  ws.send(JSON.stringify({
    type: "chat",
    id: crypto.randomUUID(),
    message: "Explain closures in JavaScript",
    options: {
      provider: "anthropic",
      tools: [],
      maxSteps: 5
    }
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case "ack":
      console.log("Server acknowledged:", msg.serverMsgId);
      break;
    case "text":
      process.stdout.write(msg.delta);
      break;
    case "tool_call":
      console.log(`Tool call: ${msg.name}`, msg.args);
      break;
    case "tool_result":
      console.log(`Tool result (${msg.success}):`, msg.output);
      break;
    case "done":
      console.log("\nDone. Tokens:", msg.usage.totalTokens);
      break;
    case "error":
      console.error("Error:", msg.code, msg.message);
      break;
  }
};

// Heartbeat every 30 seconds
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: "ping", clientTime: new Date().toISOString() }));
  }
}, 30000);

Python (websockets)

python
import asyncio
import json
import uuid
from websockets import connect

async def chat():
    uri = "ws://localhost:7337/ws?token=sk_seepient_..."

    async with connect(uri) as ws:
        # Send a chat message
        await ws.send(json.dumps({
            "type": "chat",
            "id": str(uuid.uuid4()),
            "message": "Write a Python hello world",
            "options": {
                "provider": "openai",
                "model": "gpt-5.4",
                "maxSteps": 5
            }
        }))

        # Receive streaming response
        async for raw in ws:
            msg = json.loads(raw)

            if msg["type"] == "text":
                print(msg["delta"], end="", flush=True)
            elif msg["type"] == "tool_call":
                print(f"\n[Tool] {msg['name']}({msg['args']})")
            elif msg["type"] == "tool_result":
                print(f"[Result] {msg['output'][:100]}")
            elif msg["type"] == "done":
                print(f"\n--- Done. Cost: ${msg['usage']['cost']:.4f}")
                break
            elif msg["type"] == "error":
                print(f"Error: {msg['code']} - {msg['message']}")
                break

asyncio.run(chat())

Message flow diagram

A typical chat exchange follows this sequence:

Client                              Server
  │                                    │
  │──── chat { id, message } ─────────►│
  │◄─── ack { clientMsgId } ──────────│
  │                                    │
  │◄─── text { delta: "A " } ─────────│
  │◄─── text { delta: "closure " } ───│
  │◄─── text { delta: "is..." } ──────│
  │                                    │
  │◄─── tool_call { name, args } ─────│
  │◄─── tool_result { output } ───────│
  │                                    │
  │◄─── text { delta: "Based on" } ───│
  │◄─── text { delta: "the files" } ──│
  │                                    │
  │◄─── done { usage, finishReason } ─│
  │                                    │

Streaming is real-time

Text deltas arrive as soon as the LLM generates tokens. Tool calls and results are sent inline. The done message is always the last message in a generation cycle.

Released under the Business Source License 1.1.