Live Browser Guide

October 14, 2026

Table of contents

  1. How it works
  2. The browser event
  3. Watching and taking control
  4. Two browsers per account
  5. Example
  6. Endpoints

The Muse agent has a web browser on its own computer. In a chat it can open a site, read it, click through it and fill in forms. Through the useapi.net Muse API you can watch that browser live, take control of it with your mouse and keyboard, for example to sign in to a site for the agent, and then give it back. This guide walks through the flow end to end. The Live Demo shows it working: a browser card in the chat with a Watch live button.

How it works

  1. Ask the agent for something that needs a browser, for example Open https://example.com in your browser and keep it open, with POST /chats or POST /chats/chatId/messages.
  2. The chat’s stream sends a browser event with the browser’s taskId, and another one each time the browser changes: its status, the open page, who has control. GET /chats/chatId lists the chat’s browsers in browsers.
  3. When previewAvailable is true, create a viewer link with POST /chats/chatId/browser/taskId/viewer. Use mode control to allow taking control.
  4. Open the link in a web browser, or give it to your user. The page shows the agent’s browser live. With a control link, Take control lets you click and type in it.
  5. Give control back with Release control on the page, by closing the viewer, or with POST /chats/chatId/browser/taskId/release. The agent carries on.
  6. When the browser is no longer needed, close it with POST /chats/chatId/browser/taskId/stop.

The browser event

A browser event looks like this:

{
  "event": "browser",
  "taskId": "423bc12e-db16-4921-8670-08b5f3f5a5d6",
  "status": "Completed",
  "displayState": "completed",
  "stepTitle": "Open example.com in browser",
  "steps": 1,
  "site": "example.com",
  "url": "https://example.com/",
  "tabTitle": "Example Domain",
  "controlOwner": "agent",
  "previewAvailable": true,
  "parkedExpiresAt": "2026-10-15T01:06:42.438Z",
  "ended": false,
  "updated": "2026-10-14T23:06:42.438Z"
}

A browser starts as Queued, then Running, and is Completed when the agent’s step is done. After the agent’s answer the browser stays open until parkedExpiresAt, and can be watched until then. ended: true means it is closed for good. Keep the latest event per taskId: each one holds the browser’s whole state. A stream that resumes with Last-Event-ID sends the latest state of each browser again.

type Browser = { // TypeScript: the agent's browser in a chat
  taskId: string                 // the browser's id in this chat, for /chats/{chatId}/browser/{taskId}
  status: string | null          // e.g. Queued, Running, Completed, User controlled
  displayState: string | null    // e.g. starting, working, completed, paused
  stepTitle: string | null       // what the agent is doing, e.g. "Opening example page"
  steps: number | null           // how many steps the agent took
  site: string | null            // e.g. example.com
  url: string | null             // the open page
  tabTitle: string | null        // the open page's title
  controlOwner: string | null    // agent or user
  previewAvailable: boolean      // true: the browser is open and can be watched with a viewer link
  parkedExpiresAt: string | null // ISO 8601: the browser stays open until then after the agent's answer
  ended: boolean                 // true: the browser is closed for good
  updated: string | null         // ISO 8601
}

In GET /chats/chatId, each item of browsers also has afterMessageId, the message the browser came after, so you can show it at the right place in the conversation.

Watching and taking control

POST /chats/chatId/browser/taskId/viewer answers with a url such as https://api.useapi.net/v1/muse/viewer#t=…. Good to know:

  • The link works once, and must be opened within ttl seconds, 60 by default. Anyone who has the link can open it, so share it only with the person who should see the browser.
  • The account has one browser display, so one viewer at a time per account. Two browsers can run at once, and only one of them can be controlled at a time.
  • A viewer session lasts up to 30 minutes. Then create a new link.
  • With a control link, Take control hands you the mouse and keyboard. Control stays with you while the viewer is open, and goes back to the agent when you release it or close the viewer.
  • The page reconnects by itself when the connection drops, and the browser’s display follows the size of the page.
  • Picture quality can be changed on the page: Low, Balanced, High or Max.
  • Copying and pasting inside the browser works. Copy and paste between your computer and the browser is not supported.
  • To take a link back, revoke the viewers. Open sessions end within about 15 seconds.
  • If Muse can no longer show a browser, the viewer says so and the browser is stopped. Ask the agent to open the page again.

To show the hosted page in an <iframe> in your app, or to build your own client over the viewer’s WebSocket (RFB 3.8 plus a JSON control channel, with resume after a drop), see Advanced: your own viewer client.

Two browsers per account

A Muse account runs at most 2 browsers at a time. A third one waits with status Queued until one of them closes. Stop a browser when you are done with it, and always before you delete its chat: a browser of a deleted chat can no longer be reached. If the account’s browsers stay Queued and stopping does not help, reset the account, which deletes everything on it.

Example

This JavaScript reads a chat’s stream and prints a viewer link as soon as the agent’s browser can be watched. Read the stream as shown in the Chat Streaming Guide.

const token = "API token";
const api = "https://api.useapi.net/v1/muse";
const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };

const started = await fetch(`${api}/chats`, {
  method: "POST",
  headers,
  body: JSON.stringify({ message: "Open https://example.com in your browser and keep it open." }),
}).then(r => r.json());
const chat = encodeURIComponent(started.chatId);

const viewers = new Set();
const response = await fetch(`${api}/chats/${chat}/stream`, { headers });
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  const parts = buffer.split("\n\n");
  buffer = parts.pop();
  for (const part of parts) {
    const line = part.split("\n").find(l => l.startsWith("data: "));
    if (!line) continue;
    const e = JSON.parse(line.slice(6));
    if (e.event === "browser" && e.previewAvailable && !e.ended && !viewers.has(e.taskId)) {
      viewers.add(e.taskId);
      const viewer = await fetch(`${api}/chats/${chat}/browser/${e.taskId}/viewer`, {
        method: "POST",
        headers,
        body: JSON.stringify({ mode: "control" }),
      }).then(r => r.json());
      console.log("Open before", viewer.expiresAt, viewer.url);
    }
    if (e.event === "turn.done") console.log("answer done:", e.status);
  }
}

The same steps with curl:

CHAT="c-u12345-email%3Aexample%40gmail.com-chat%3A8e0bf21e-a3b5-4a1e-a05a-65d160469bfb-bot%3Amuse"
TASK="423bc12e-db16-4921-8670-08b5f3f5a5d6"

curl -H "Authorization: Bearer …" "https://api.useapi.net/v1/muse/chats/$CHAT/browser/$TASK"

curl -X POST -H "Authorization: Bearer …" -H "Content-Type: application/json" \
     -d '{"mode": "control"}' "https://api.useapi.net/v1/muse/chats/$CHAT/browser/$TASK/viewer"

curl -X POST -H "Authorization: Bearer …" "https://api.useapi.net/v1/muse/chats/$CHAT/browser/$TASK/release"

curl -X POST -H "Authorization: Bearer …" "https://api.useapi.net/v1/muse/chats/$CHAT/browser/$TASK/stop"

Endpoints

Endpoint What it does
GET /chats/chatId/browser/taskId The browser’s state now, who has control, and whether a viewer shows it.
POST /chats/chatId/browser/taskId/viewer A single-use live view link, to watch or take control.
DELETE /chats/chatId/browser/taskId/viewer Ends every live view of the browser.
POST /chats/chatId/browser/taskId/release Gives control back to the agent.
POST /chats/chatId/browser/taskId/stop Closes the browser for good.

The MCP server has the same actions as tools, so an AI assistant can hand you a live view link.