This reference covers the three application WebSockets used for chat, job status, and notifications. Dashboard refresh events use a distinct authentication and message contract described in Dashboard WebSocket API.
Authentication boundary
Production clients must send an HTTP handshake header:
Authorization: Bearer <access-token>
A token in the URL is rejected. The server closes a connection containing a token query parameter with code 1008 and reason “Query token transport is not allowed”.
For project-bound access, also send the durable project identifier in the handshake:
X-Project-Id: <project-uuid>
The server verifies the current user state, token revocation, project access, job ownership where applicable, and connection/message quotas. A loopback client may omit credentials only when the explicit local-development unauthenticated-surface setting is enabled and both client and requested host are local.
The browser-native WebSocket constructor cannot set an Authorization header. For a browser application, use an authenticated same-origin gateway or the separate dashboard WebSocket contract. Do not move bearer credentials into a query string.
Routes
| Route | Client messages | Server purpose |
|---|---|---|
| /api/v1/ws/chat | chat.query, ping | Stream chat scenario selection, chunks, final response, completion, and errors |
| /api/v1/ws/jobs/{job_id} | ping | Send current and subsequent status for an owned background job |
| /api/v1/ws/notifications | ping | Push notifications for the authenticated user |
The job route sends code 4004 if the job does not exist. A job that exists but is outside the caller’s scope is denied with policy code 1008.
Message envelope
Every application message is JSON with this shape:
{
"type": "chat.query",
"payload": {},
"timestamp": "2026-08-24T00:00:00Z",
"request_id": "optional-correlation-id"
}
type and payload are required by the model. timestamp is generated by the server when omitted. request_id is optional and should be preserved when correlating streamed chat messages.
Current message types
| Type | Typical direction | Meaning |
|---|---|---|
| chat.query | client → server | Start a chat request |
| chat.scenario | server → client | Report selected scenario |
| chat.chunk | server → client | Stream a partial answer |
| chat.response | server → client | Return the assembled answer |
| chat.done | server → client | Finish the request |
| chat.error | server → client | Report a chat-specific failure |
| job.started | server → client | Return initial or started job state |
| job.progress | server → client | Report job progress |
| job.completed | server → client | Report successful completion |
| job.failed | server → client | Report failed or cancelled completion |
| notification | server → client | Push a user notification |
| error | server → client | Report an envelope, policy, or processing error |
| ping | client → server | Ask for an application-level keep-alive response |
| pong | server → client | Respond to ping |
| cpg.update.complete | server → client | Report completed CPG update |
| connected | server → client | Confirm connection and return its identifiers |
| disconnected | server → client | Indicate a managed disconnect |
| authenticated | server → client | Confirm a supported authentication transition |
| auth_required | server → client | Request a supported authentication transition |
Not every message type is produced by every route. Consumers must ignore recognized messages that are irrelevant to their current workflow and must not treat an unknown message as success.
Chat request
Send chat.query only to the chat route:
{
"type": "chat.query",
"payload": {
"query": "Show callers of the selected symbol",
"session_id": "optional-session",
"scenario_id": "optional-scenario",
"language": "en"
},
"request_id": "req-123"
}
A normal stream may contain chat.scenario, one or more chat.chunk messages, chat.response, and chat.done. An error message terminates the successful interpretation of that request even if the socket remains open.
Job status
Connect to the exact job route with a job identifier owned by the authenticated user. The server sends job.started with the current status. For terminal states it follows with job.completed or job.failed. Keep the HTTP/REST job response as the durable source; WebSocket delivery is a notification channel and can be interrupted.
Notifications
The notifications route is receive-oriented. Send only ping messages. Notification payloads may contain title, message, level, and action_url. Validate an action URL against the application’s navigation policy before opening it.
Client handshake example
A service client must support custom handshake headers. The following Node.js example uses the ws package:
import WebSocket from "ws";
const socket = new WebSocket(
"wss://codegraph.example/api/v1/ws/notifications",
{
headers: {
Authorization: "Bearer " + process.env.CODEGRAPH_ACCESS_TOKEN,
"X-Project-Id": process.env.CODEGRAPH_PROJECT_ID
}
}
);
socket.on("message", (raw) => {
const message = JSON.parse(raw.toString());
if (message.type === "notification") {
console.log(message.payload);
}
});
socket.on("open", () => {
socket.send(JSON.stringify({ type: "ping", payload: {} }));
});
Never log the token or full Authorization header.
Close codes and recovery
| Code | Meaning | Recovery |
|---|---|---|
| 1008 | Query-token, authorization, ownership, project-scope, or rate-limit policy failure | Correct policy/input; do not retry in a tight loop |
| 4001 | Missing, invalid, expired, or revoked access token | Obtain a new access token and reconnect |
| 4004 | Job not found | Verify the job identifier through the REST API before reconnecting |
Use bounded exponential backoff with jitter for transient disconnects. Refresh credentials before reconnecting after 4001. Do not automatically retry 1008 until the rejected condition has changed.
Source contracts
- src/api/websocket/routes.py — routes, bearer-header authentication, scope checks, and close behavior
- src/api/websocket/models.py — envelope, payload models, and message enum
- src/api/websocket/authorization.py — user, project, token, and job decisions
- src/api/websocket/rate_limiter.py — connection and message quotas
WebSocket routes are not part of the REST OpenAPI operation set.