Live Browser Guide
October 14, 2026
Table of contents
- How it works
- The browser event
- Watching and taking control
- Two browsers per account
- Example
- 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
- 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. - The chat’s stream sends a
browserevent with the browser’staskId, and another one each time the browser changes: its status, the open page, who has control. GET /chats/chatIdlists the chat’s browsers inbrowsers. - When
previewAvailableistrue, create a viewer link with POST /chats/chatId/browser/taskId/viewer. Usemodecontrolto allow taking control. - Open the link in a web browser, or give it to your user. The page shows the agent’s browser live. With a
controllink, Take control lets you click and type in it. - 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. - 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
ttlseconds, 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
controllink, 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.