Connect a client to WebSocket to stream chat answers, follow a background job, or display notifications without repeatedly polling the API. This reference helps integration developers choose among the three channels and implement authentication and message handling. Dashboard refresh events use a distinct authentication and message contract described in Dashboard WebSocket API.
Prerequisites
Sign in and obtain a valid access token. For project-bound access, prepare the
project UUID and an explicit access grant for that user and project. The WebSocket
checks this grant when the client sends X-Project-Id. Job subscriptions
also need a job_id. Use your installation’s API address: wss for HTTPS and
ws for a trusted local HTTP environment.
Authentication boundary
For chat and job status, send the token in the HTTP handshake header:
Authorization: Bearer <access-token>
Send the token through the authentication method for the route. A non-empty token query parameter causes the server to reject the HTTP handshake with status 403 before the WebSocket opens. The handler uses the rejection 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.
Connect to chat and job status through a client that supports headers or an authenticated same-origin gateway. The notifications route supports browser WebSockets: after the connection opens, send the token as the first message within 5 seconds:
{"token": "<access-token>"}
After checking the token and access, the server sends connected. The client can then send
ping and receive notifications. An authentication timeout closes the connection with
code 1008 and reason Auth timeout.
Dashboard events are described in Dashboard WebSocket API.
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 first checks subscription access. An unknown job or a job outside the caller’s scope produces Job subscription denied and close code 1008. After a subscription is authorized, the server reads the job state; if that read returns no job, it sends Job not found and closes with code 4004.
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 is required. Omitted payload defaults to {} and timestamp to the current time.
A chat.query needs payload.query. 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 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"
}
The values optional-session and optional-scenario stand for your session and scenario identifiers. Both fields are optional; omit them for a request without an explicit session or scenario.
The chat handler sends chat.scenario for scenario selection, chat.chunk for answer fragments, and chat.done when the response ends. Assemble the answer from the chunks and correlate them through request_id. A processing failure produces error with the same request identifier.
Job status
Connect to the 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. Retrieve the stored state through HTTP/REST and receive live notifications through WebSocket.
Notifications
The notifications route is receive-oriented. After authentication, send ping messages. The notification’s payload may contain title, message, level, and action_url. Validate action_url against the application’s navigation policy before opening it.
Connect to notifications
In this example, the service client sends X-Project-Id in the handshake
to subscribe in a project context. This example uses Node.js and ws.
In your client project directory, install the dependency with npm install ws, save the code as
notifications.mjs, and run node notifications.mjs. Set CODEGRAPH_ACCESS_TOKEN,
CODEGRAPH_PROJECT_ID, and CODEGRAPH_WS_URL through your approved configuration channel.
The URL variable contains the full notifications route. Do not pass credentials as command-line
arguments. The client sends the token first, waits for connected, then sends ping.
The console prints pong. If the connection closes, it prints the close code and
reason; 1008 project_access_denied means that the project access grant needs to
be checked. Press Ctrl+C to stop the client.
import WebSocket from "ws";
for (const name of ["CODEGRAPH_ACCESS_TOKEN", "CODEGRAPH_PROJECT_ID", "CODEGRAPH_WS_URL"]) {
if (!process.env[name]) throw new Error("Missing " + name);
}
const socket = new WebSocket(
process.env.CODEGRAPH_WS_URL,
{
headers: {
"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);
} else if (message.type === "connected") {
socket.send(JSON.stringify({ type: "ping", payload: {} }));
} else if (message.type === "pong") {
console.log("pong");
}
});
socket.on("open", () => {
socket.send(JSON.stringify({ token: process.env.CODEGRAPH_ACCESS_TOKEN }));
});
socket.on("close", (code, reason) => {
console.error("WebSocket closed:", code, reason.toString());
});
socket.on("error", () => {
console.error("WebSocket connection failed; check the API address and server availability.");
});
Never log the token or full Authorization header.
Close codes and recovery
Rejections before the WebSocket opens appear as HTTP 403. The table describes server policy codes; clients receive a WebSocket close code only after a successful upgrade.
| 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 state not found after subscription authorization | 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.
WebSocket routes are not part of the REST OpenAPI operation set.