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

# Realtime

> One WebSocket per stream: plan once, send raw frames, get a reply per frame

A realtime session reads a stream of frames over one WebSocket. You open the socket with your API key, describe the read once in `session.create`, then send each frame as raw image bytes. Each frame gets one reply event, and a frame the session cannot read now is dropped, never queued.

| Socket | Reply per frame | Guide | Reference |
| - | - | - | - |
| `wss://gateway.vlm.run/v1/ws` | The model's reply, as [Chat Completions](https://docs.vlm.run/gateway/api-reference/post-chat-completions.md) returns it | [Realtime Frames](https://docs.vlm.run/gateway/realtime-frames.md) | [`WS /v1/ws`](https://docs.vlm.run/gateway/api-reference/ws-realtime) |
| `wss://gateway.vlm.run/v1/ws/systemone` | A [System One](https://docs.vlm.run/gateway/system-one.md) decision | [Realtime System One](https://docs.vlm.run/gateway/realtime-systemone.md) | [`WS /v1/ws/systemone`](https://docs.vlm.run/gateway/api-reference/ws-systemone) |

## When to use a session

Use a session when you read many frames from one source with the same model and method: a camera, a screen, or a decoded video. Use a `POST` for single images, documents, and anything you read once.

| | `POST` per frame | Realtime session |
| - | - | - |
| Connections | One request per frame | One socket for the session |
| Media | Base64 inside a JSON body | One binary message per frame, raw bytes |
| Model, method, questions | Sent and checked on every request | Sent once, at `session.create` |
| Access, credit, and capacity checks | Every request | Once per session |
| Backpressure | Requests queue, then time out | A frame the session cannot read now is dropped and reported |

We drop instead of queue because a queued frame answers for a moment you have already moved past. A drop tells you to send fewer frames, or to raise `max_inflight` on the session.

## Session lifecycle

Both sockets share this lifecycle. Only the `session.create` fields and the reply event differ.

1. **Upgrade.** Open the socket with `Authorization: Bearer <VLMRUN_API_KEY>` on the upgrade request. A request with no key, or with `Bearer vlmrun`, is refused with `401` before the socket opens.
2. **`session.create`.** Send it as the first JSON text message. The Gateway checks your plan, model access, credit, and capacity once, then replies `session.created` with the session's bounds. A refusal is an `error` event, then a close.
3. **Frames.** Send each frame as one binary message of raw JPEG, PNG, WebP, or GIF bytes. Each frame gets one event back: its reply, `frame.dropped`, or an `error` that names the frame.
4. **`session.close`.** Reads in flight finish and return. The server sends `session.stats` with the session's counts and cost, then closes with `1000`.

The standard `WebSocket` API cannot set request headers, so a browser cannot send `Authorization`. Open sessions from a server. The Node.js samples use the [`ws`](https://www.npmjs.com/package/ws) package, which takes `headers`. The Python samples pass the header to `websockets.connect` as `additional_headers`.

## Close codes

The server explains a refusal or a cut with an `error` event before it closes. The `error_type` values are listed on each socket's reference page.

| Code | Meaning | What to do |
| - | - | - |
| `1000` | Normal close. The reason is `idle`, `expired`, or `spent` when the Gateway ended the session, and empty after your `session.close`. | Open a new session when you have more frames. |
| `1008` | Refused at open: a bad `session.create`, an unknown model, no access, no credit, a model without realtime support, or the daily session cap. | Do not retry unchanged. Fix the request, or wait `retry_after` for the daily cap. |
| `1011` | Server error. | Reconnect with backoff. |
| `1012` | Server restart. | Reconnect. |
| `1013` | Try again later: your plan's sessions are all open, a session cap is full, or the model is starting. | Wait `retry_after` when the `error` has one, else back off, then reconnect. |

A replica restart can also cut the connection with no close frame. Reconnect and open a new session. A session does not resume: frames, counts, and spend start again from zero.

## Supported models

A model must opt in to realtime. On `/v1/ws`, a model without realtime support is refused at `session.create` with `invalid_request_error` and close `1008`.

| Socket | Model | Method | `max_fps` | Price on the socket |
| - | - | - | - | - |
| `/v1/ws` | [`usyd-community/vitpose-plus-large`](https://vlm.run/gateway/models/usyd-community-vitpose-plus-large) | `pose`: body keypoints per person | 20 | \$3 / hour the session is open |
| `/v1/ws` | [`geopavlakos/hamer`](https://vlm.run/gateway/models/geopavlakos-hamer) | `pose`: 2D and 3D hand joints | 20 | \$3 / hour the session is open |
| `/v1/ws/systemone` | [`google/diffusiongemma-26b-a4b-it`](https://vlm.run/gateway/models/google-diffusiongemma-26b-a4b-it), [`google/gemma-4-26b-a4b-it`](https://vlm.run/gateway/models/google-gemma-4-26b-a4b-it), [`qwen/qwen3.5-0.8b`](https://vlm.run/gateway/models/qwen-qwen3.5-0.8b), [`qwen/qwen3.8-27b`](https://vlm.run/gateway/models/qwen-qwen3.8-27b) | Your System One questions | n/a. `window` and `stride` set which frames are read. | Per decision, as `POST /typesafe/v1/systemone` |

`max_fps` is the read rate the model allows, on average. A frame that arrives sooner than that after the last read is dropped with `reason: "rate"`. A short burst bank absorbs network jitter.

## Billing

| Socket | Rows | What bills |
| - | - | - |
| `/v1/ws` | One row per session, written at the close however the session ends | Every second the session is open, from `session.created` to the close, at the model's hourly rate. Frames do not change the price. The per-call minimum of \$0.001 applies once per session. |
| `/v1/ws/systemone` | One row per decision | The same `usage` and `cost` as a posted System One read |

On `/v1/ws`, idle time bills like streaming time, so close the session when you stop sending frames. On `/v1/ws/systemone`, dropped frames and failed reads bill nothing.

On `/v1/ws`, a session open for 10 minutes bills 600 seconds at $3 / hour, which is $0.50, on either model. The session total is in `session.stats.cost`, and its `id` reads back with [`GET /v1/completions/{id}`](https://docs.vlm.run/gateway/api-reference/get-completion).

## Limits

Sessions are limited per organization plan, not per request. A session does not spend the per-minute request budget.

* **Sessions at once:** 1 on Free, up to 5 on paid plans. Then `rate_limit_error` and close `1013`.
* **Per session:** 10 minutes, 30 seconds idle, \$5 of spend, and 2 MiB per frame.
* **Capacity:** the Gateway and some models cap open sessions. A full cap is `capacity_error` with close `1013`.

The full tables are in [Rate Limits](https://docs.vlm.run/gateway/rate-limits.md#websocket-sessions).

## Next steps

> [card] [Realtime Frames](https://docs.vlm.run/gateway/realtime-frames.md)
>
> Stream frames to a pose model in Python or Node.js.

> [card] [Realtime System One](https://docs.vlm.run/gateway/realtime-systemone.md)
>
> Ask the same System One questions of every frame.

> [card] [WS /v1/ws reference](https://docs.vlm.run/gateway/api-reference/ws-realtime.md)
>
> Every event, field, and error type on the frames socket.

> [card] [Rate Limits](https://docs.vlm.run/gateway/rate-limits.md#websocket-sessions)
>
> Sessions per plan, per-session bounds, and capacity refusals.


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