> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inputapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket connection and runtime

> Configure server clients, connection bounds, and the shared transport module.

These examples run from the documentation directory using Node.js 22.18+ or 24 LTS and pinned `ws@8.22.0`. Execute them in a terminal or application server. Browser-native WebSocket authentication differs; do not copy a long-lived API key into a webpage.

## Choose a protocol

| Capability | Guide | API reference |
| - | - | - |
| Sequential text turns on one connection | [Responses WebSocket](/en/guides/responses-websocket.md) | [Handshake and events](/en/api-reference/responses-websocket.md) |
| Realtime text and audio | [Realtime](/en/guides/realtime.md) | [Session and events](/en/api-reference/realtime.md) |

HTTP SSE uses `data:` / `event:` lines. Each WebSocket application message is JSON without those prefixes, and `[DONE]` is not the completion signal. `ws` handles framing, UTF-8 reconstruction, and ping/pong.

## Environment

```sh theme={null}
npm ci
export API_BASE_URL="https://gateway.example.com/v1"
export API_KEY="YOUR_API_KEY"
export API_MODEL="YOUR_MODEL_ID"
export API_WS_TIMEOUT_MS="60000"
```

`API_BASE_URL` includes `/v1` and any deployment prefix. The shared module converts HTTPS to WSS or HTTP to WS, then appends the endpoint. Credentials, query strings, and fragments are not accepted in the base URL. Realtime puts the model in the URL query; Responses puts it in create events. Use HTTPS/WSS in production.

This is server-side Bearer authentication. HTTP 101 only confirms upgrade; channel selection, session configuration, and generation may still fail. Do not follow redirects automatically. Correct the API URL before retrying. See [production integration](/en/guides/production.md) for protocol, access, and proxy checks.

## Shared transport module

Both examples import `examples/websocket-client.mjs`. Copy it alongside the example when moving to another project, and install `ws@8.22.0`. It is a supporting module, not a standalone request script.

```javascript theme={null}
import WebSocket from 'ws';

// Shared by both server-side examples. No reconnect or automatic retry.
export async function openWebSocket(endpoint, query = {}) {
  const key = process.env.API_KEY;
  const model = process.env.API_MODEL;
  const timeout = Number(process.env.API_WS_TIMEOUT_MS ?? 60000);
  if (!key?.trim() || !model?.trim() || !Number.isInteger(timeout) || timeout < 100 || timeout > 120000) {
    throw new Error('Set API_KEY, API_MODEL, and a 100..120000 ms timeout.');
  }
  let url;
  try { url = new URL(process.env.API_BASE_URL); }
  catch { throw new Error('Set a valid HTTP(S) API_BASE_URL including /v1.'); }
  if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
    throw new Error('API_BASE_URL must use HTTP(S) without credentials, query, or fragment.');
  }
  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
  url.pathname = `${url.pathname.replace(/\/+$/, '')}/${endpoint}`;
  for (const [name, value] of Object.entries(query)) url.searchParams.set(name, value);
  const ws = new WebSocket(url, {
    headers: { Authorization: `Bearer ${key}` },
    followRedirects: false, perMessageDeflate: false,
    handshakeTimeout: timeout, maxPayload: 1024 * 1024,
  });
  const queue = [];
  let queuedBytes = 0;
  let waiting;
  let fault;
  let closed = false;
  let rejectOpen;
  const opened = new Promise((resolve, reject) => {
    rejectOpen = reject;
    ws.once('open', resolve);
  });
  function fail(message) {
    if (fault || closed) return;
    fault = new Error(message);
    rejectOpen(fault);
    waiting?.reject(fault);
    waiting = undefined;
    ws.terminate();
  }
  const deadline = setTimeout(() => fail('WebSocket workflow deadline exceeded.'), timeout);
  ws.on('unexpected-response', (_request, response) => {
    response.resume();
    fail(`WebSocket handshake failed (HTTP ${response.statusCode}).`);
  });
  ws.on('error', () => fail('WebSocket transport failed.'));
  ws.on('close', () => fail('WebSocket closed before workflow completion.'));
  ws.on('message', (data, isBinary) => {
    if (closed || fault) return;
    if (isBinary) { fail('Expected a JSON text message, not a binary frame.'); return; }
    let event;
    try { event = JSON.parse(data.toString()); }
    catch { fail('Invalid JSON event.'); return; }
    if (!event || typeof event !== 'object' || Array.isArray(event) || typeof event.type !== 'string') {
      fail('Invalid event envelope.'); return;
    }
    if (event.type === 'error') { fail('Server returned a WebSocket error event.'); return; }
    if (waiting) {
      const pending = waiting;
      waiting = undefined;
      pending.resolve(event);
      return;
    }
    queuedBytes += data.length;
    if (queue.length >= 256 || queuedBytes > 4 * 1024 * 1024) {
      fail('WebSocket receive queue limit exceeded.'); return;
    }
    queue.push({ event, bytes: data.length });
  });
  try { await opened; }
  catch (error) { clearTimeout(deadline); throw error; }
  return {
    model,
    async send(event) {
      if (fault) throw fault;
      if (closed || ws.readyState !== WebSocket.OPEN) throw new Error('WebSocket is not open.');
      await new Promise((resolve, reject) => ws.send(JSON.stringify(event), (error) => {
        if (error) reject(new Error('WebSocket send failed.'));
        else resolve();
      }));
    },
    async receive() {
      if (fault) throw fault;
      if (closed) throw new Error('WebSocket is closed.');
      if (queue.length) {
        const next = queue.shift();
        queuedBytes -= next.bytes;
        return next.event;
      }
      if (waiting) throw new Error('Only one event reader is allowed.');
      return await new Promise((resolve, reject) => { waiting = { resolve, reject }; });
    },
    close() {
      closed = true;
      clearTimeout(deadline);
      waiting?.reject(new Error('WebSocket reader closed.'));
      waiting = undefined;
      ws.close(1000);
      // Bound shutdown even if the peer never acknowledges the close frame.
      const cleanup = setTimeout(() => ws.terminate(), 1000);
      cleanup.unref();
      ws.once('close', () => clearTimeout(cleanup));
    },
  };
}
```

## Example bounds

The total deadline includes handshake, configuration, sending, and waiting for the two-turn or one-turn workflow. It defaults to 60 seconds and accepts 100–120000 milliseconds. Incoming messages are limited to 1 MiB, with at most 256 queued messages / 4 MiB. These protect the examples' resources; they are not gateway or provider limits.

Invalid JSON, binary frames, error events, early close, and timeout terminate the examples. Logs use fixed diagnostics instead of remote error bodies or keys; consult gateway logs when necessary. The examples do not automatically retry, reconnect, cancel, or execute tools. Closing the connection does not guarantee upstream cancellation.

HTTP(S) proxy environment variables do not automatically configure a `ws` proxy agent. Integrate a verified Agent explicitly if your environment needs one. These examples connect directly to the gateway and keep TLS verification enabled.

[阅读中文](/zh/guides/websocket.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.