Skip to main content

WebSocket API

The WebSocket API enables real-time, streaming chat with your Biel.ai project. Use it when you need token-by-token AI responses instead of waiting for the full response via the REST API.

Endpoint: wss://app.biel.ai/ws/messages/{projectId}/

To continue an existing chat: wss://app.biel.ai/ws/messages/chat/{chatId}/

Connect​

Open a WebSocket connection to:

wss://app.biel.ai/ws/messages/{projectId}/

Replace {projectId} with your project slug (e.g., jfvzioa17q).

Send a message​

Send a JSON message to the WebSocket:

{
"projectId": "your-project-slug",
"from": "user",
"message": "How do I authenticate?",
"url": "https://docs.example.com/auth",
"email": "[email protected]"
}
FieldTypeRequiredDescription
projectIdstringNoProject slug for client bookkeeping; the connection path selects the project.
fromstringNoClient message label; use "user" when included.
messagestringYesThe user's message (Markdown supported).
chatIdstringNoChat session UUID. Omit for the first message — included in the AI response.
urlstringNoPage URL where the chat is happening. Used for domain validation.
emailstringNoEmail or ID for conversation tracking.
apiKeystringNoAPI key with chat_create permission. Required for private projects.
thinkModebooleanNoEnable reasoning mode for deeper analysis.
retrievalModestringNoRegular-mode retrieval: auto or instant. Omit for the existing default. Unsupported values are ignored; thinkMode: true takes precedence. See Reasoning modes.
capabilitiesarray of stringsNoInclude "activity.v1" together with a valid requestId to receive operational progress updates.
requestIdstringNoUnique caller-generated identifier for this turn: 1–80 characters from A-Z, a-z, 0-9, ., _, :, or -. Negotiating servers echo it on answer and status frames.
restoreTokenstringNoOpaque conversation restore token returned by the server. Retain it with the chat ID and send it on subsequent turns. A chat ID alone is not authorization.
metadatastringNoTag to identify the conversation source.

Receive a response​

The server sends JSON messages back. Partial messages stream token-by-token; the final message includes sources.

{
"projectId": "your-project-slug",
"chatId": "550e8400-e29b-41d4-a716-446655440000",
"from": "ai",
"messageId": "msg-uuid",
"message": "To authenticate, use the...",
"timestamp": "2026-01-15T10:30:00Z",
"isPartial": false,
"sources": [
{
"title": "Authentication guide",
"url": "https://docs.example.com/auth"
}
]
}
FieldTypeDescription
projectIdstringProject slug.
chatIdstringChat session UUID. Use this in subsequent messages to continue the conversation.
fromstringAlways "ai".
messageIdstringMessage UUID.
messagestringAI response content (Markdown).
timestampstringISO 8601 timestamp.
isPartialbooleantrue for streaming tokens, false for the final message.
sourcesarraySource references (only in final message). Each has title and url.
incompletebooleantrue if generation could not complete, including output limits or interruption. Only in the final message.
stopReasonstringProvider stop reason or a classified recovery reason on the final frame.
continuationCountintegerNumber of automatic continuations for this generation request.
continuationAvailablebooleanWhether the latest capped response can be continued through the REST API.
requestIdstringTurn identifier, when progress was negotiated. May be absent on older servers.
restoreTokenstringOpaque conversation restore token. Store it with chatId; older servers may omit it.
interruptedbooleanOptional negotiated marker that generation was interrupted. Keep any useful draft and offer an explicit retry.
isErrorbooleanOptional negotiated error marker. Do not treat an error message as a replacement for an existing draft.
retryablebooleanOptional negotiated indication that retry is appropriate. Wait for the connection to recover before sending a retry.

Receive progress updates​

To request progress, include both the capability and a new turn identifier in your message:

{
"projectId": "your-project-slug",
"from": "user",
"message": "How do I authenticate?",
"capabilities": ["activity.v1"],
"requestId": "turn-1",
"retrievalMode": "auto"
}

A supporting server sends status frames alongside the answer stream:

{
"type": "status",
"version": 1,
"requestId": "turn-1",
"sequence": 2,
"stage": "searching_docs",
"state": "started",
"detail": { "searchRound": 1 }
}
FieldDescription
type, version"status" and 1 identify this progress extension.
requestIdIdentifies the active turn.
sequenceIncreases within each turn, starting at 1.
stagecontextualizing, searching_docs, reviewing_results, preparing_docs, searching_web, writing_answer, checking_sources, completed, failed, or interrupted.
statestarted, completed, or failed.
detailOptional details within the object: searchRound, documentCount, reason, or retryable. reason can be generation_failed, stream_interrupted, or disconnected.

Status frames describe actual operations, rather than private model reasoning. They have no from or message field: handle them separately from chat messages. Only accept supported frames for the active turn and ignore stale or duplicate sequence numbers. Not every operation appears in every answer, and a final completed status can arrive after the final answer.

Hide successful progress when the final answer arrives. Preserve a useful interrupted draft and let the user choose whether to retry; do not automatically replay requests. The widget follows this behavior and offers spinner or loading dots as a display option.

Compatibility​

Clients that omit progress negotiation retain the existing answer stream, without status frames. New clients must also accept answers from older servers that omit requestId, restoreToken, and progress updates. Keep a generic loading indicator in that case. Updating the backend alone does not change an older installed widget's interface.

Authentication​

The WebSocket API uses the same authentication model as the REST API:

  • Public projects — No API key required. Domain restrictions still apply.
  • Private projects — Include apiKey in the first message payload with chat_create permission.

Domain validation is checked on every message using the url field. If your project has allowed domains configured, the url must match one of them.

Rate limiting​

LimitThresholdPenalty
Per chat session15 messages/minuteRejected until window resets
Per IP address100 messages/minuteIP blocked for 1 hour
Burst detection10 messages within a 30-second windowRejected while the burst threshold is met

Continue a capped answer​

Automatic continuation happens on the backend for both REST and WebSocket clients. For a manual continuation, use the REST message endpoint with chat_uuid and continue_message_id, omit message, and send the stored restore token in X-Chat-Restore-Token. The response contains the original assistant message UUID and its complete text including the appended suffix. Replace that message in your UI; do not append a new user or assistant turn.