Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.optexity.com/llms.txt

Use this file to discover all available pages before exploring further.

GET /api/v1/tasks//stream

Returns a short-lived WebSocket URL that streams the live browser view of a running task. Pair it with a noVNC client (e.g. @novnc/novnc) to embed the live screen in your own dashboard.

Description

While a task is running, its browser is rendered in a headed Chromium inside the worker container and exposed over VNC via websockify. This endpoint resolves a task to a WebSocket URL (wss://...) you can pass directly to a noVNC RFB client to render the live view in a <canvas>. The URL is only valid while the task is actively running. Once the task finishes (success / failure / cancellation) the upstream WebSocket closes and the endpoint returns an error.

Authentication

Requires an API key in the x-api-key header. The same key used for POST /api/v1/inference works here.

Parameters

Path Parameters

  • task_id string required UUID of the task whose live stream you want. Obtain this from the task_id field in your task creation response or task listing.

Headers

  • x-api-key string required Your Optexity API key.

Code Examples

Fetch the stream URL

curl -X GET \
  "https://inference-api.optexity.com/api/v1/tasks/<task_id>/stream" \
  -H "x-api-key: $OPTEXITY_API_KEY"

Success Response (200 OK)

{
    "stream_url": "wss://stream.optexity.com/tasks/<task_id>/websockify?token=..."
}
Response Fields:
FieldTypeDescription
stream_urlstringA wss:// WebSocket URL that speaks the noVNC wire protocol. Pass it directly to a RFB constructor.
The URL is short-lived (it embeds a signed token) and is only useful while the task is running. Re-fetch a fresh URL each time you (re)connect.

Error Responses

401 Unauthorized

{ "detail": "Invalid or missing API key" }

404 Not Found

{ "detail": "Task <task_id> not found" }
Returned when the task UUID doesn’t exist or doesn’t belong to your account.

409 Conflict

{ "detail": "Stream not available: task is not running" }
Returned when the task is queued, completed, failed, or cancelled — there is no live browser to stream.

Frontend Integration

Use @novnc/novnc’s RFB class. It attaches to a plain <div> and renders the live view into a <canvas> inside it. The full reference implementation is in LiveStreamViewer.jsx in the Optexity dashboard.
Install with npm install @novnc/novnc.

Reference implementations

import { useEffect, useRef, useState } from "react";
import RFB from "@novnc/novnc";

const INFERENCE_API_BASE_URL = "https://inference-api.optexity.com";

async function getStreamUrl(taskId, apiKey) {
    const res = await fetch(
        `${INFERENCE_API_BASE_URL}/api/v1/tasks/${taskId}/stream`,
        { headers: { "x-api-key": apiKey } },
    );
    if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.detail || `HTTP ${res.status}`);
    }
    return res.json();
}

export function LiveStreamViewer({ taskId, apiKey }) {
    const containerRef = useRef(null);
    const rfbRef = useRef(null);
    const [state, setState] = useState("loading");
    const [error, setError] = useState(null);

    useEffect(() => {
        let cancelled = false;

        (async () => {
            try {
                const { stream_url } = await getStreamUrl(taskId, apiKey);
                if (cancelled || !containerRef.current) return;

                setState("connecting");

                const rfb = new RFB(containerRef.current, stream_url, {
                    wsProtocols: ["binary"],
                    credentials: {},
                });
                rfb.viewOnly = true;
                rfb.scaleViewport = true;
                rfb.resizeSession = false;

                rfb.addEventListener("connect", () => setState("connected"));
                rfb.addEventListener("disconnect", (e) => {
                    setState(e.detail?.clean ? "disconnected" : "error");
                    if (!e.detail?.clean) setError("Connection lost");
                });
                rfb.addEventListener("credentialsrequired", () =>
                    rfb.sendCredentials({}),
                );
                rfb.addEventListener("securityfailure", () => {
                    setError("Security negotiation failed");
                    setState("error");
                });

                rfbRef.current = rfb;
            } catch (err) {
                if (!cancelled) {
                    setError(err.message);
                    setState("error");
                }
            }
        })();

        return () => {
            cancelled = true;
            if (rfbRef.current) {
                rfbRef.current.disconnect();
                rfbRef.current = null;
            }
        };
    }, [taskId, apiKey]);

    return (
        <div>
            {state !== "connected" && (
                <div>
                    {state === "loading" && "Initializing stream…"}
                    {state === "connecting" && "Connecting to browser…"}
                    {state === "disconnected" && "Stream ended"}
                    {state === "error" && (error || "Stream error")}
                </div>
            )}
            <div
                ref={containerRef}
                style={{
                    width: "100%",
                    height: "600px",
                    display: state === "connected" ? "block" : "none",
                }}
            />
        </div>
    );
}

Key RFB options

OptionRecommendedWhy
viewOnlytrueRead-only preview — disables input forwarding so a dashboard viewer can’t accidentally take over the browser.
scaleViewporttrueScales the remote framebuffer to fit your container without resizing the actual browser.
resizeSessionfalseDon’t try to negotiate a different remote display size — the worker runs at a fixed 1920×1080.
wsProtocols["binary"]Required for websockify’s binary subprotocol.
credentials{}The stream is auth’d via the signed token in the URL — no VNC password is needed.

Lifecycle tips

  • Fetch a fresh stream_url on every (re)connect attempt — the token is short-lived.
  • Call rfb.disconnect() on component unmount to release the underlying WebSocket.
  • Handle the disconnect event: e.detail.clean === true typically means the task finished gracefully (show “Stream ended”); clean === false is a genuine network/auth failure (show retry).
  • The endpoint returns 409 while the task is queued — poll task status first and only call /stream once the task is running.