Open a live view of the browser
October 14, 2026
Table of contents
Create a link to a live view of the agent’s browser. Open it in any web browser, or give it to your user: the page shows the agent’s browser as it works. With mode control, the page can also take control of the browser, so you can click and type in it yourself, for example to sign in to a site, then give it back to the agent. The Live Browser Guide walks through the whole flow.
- The link works once, and it must be opened within
ttlseconds. The secret part of the link is after#, so it is never sent to a server when the page loads. - One viewer at a time per Muse account, because the account has one browser display. Close the open viewer, or revoke it, before you open another.
- A viewer session lasts up to 30 minutes. Then create a new link to go on watching.
- The browser must be open:
previewAvailableistruein itsbrowserevent or in GET /chats/chatId/browser/taskId.
The viewer page has these controls:
- Take control and Release control, with a
controllink. While you have control, your clicks and keys go to the browser. Control stays with you while the viewer is open, and goes back to the agent when you release it or close the viewer. - Picture quality: Low, Balanced, High or Max. A change applies at once.
- Close viewer, which ends the session.
The browser’s display follows the size of the viewer window. When the connection drops, the page reconnects by itself. Copying and pasting inside the browser works. Copy and paste between your computer and the browser is not supported.
If Muse can no longer show a browser, the viewer says so and the browser is stopped, which frees it for the account. Ask the agent to open the page again.
https://api.useapi.net/v1/muse/chats/
chatId/browser/taskId/viewer
chatIdis URL-encoded in the path (it contains:and@).taskIdis thetaskIdof the chat’sbrowserevent.
Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
API tokenis required, see Setup useapi.net for details.
Request Body
{
"mode": "control",
"ttl": 120
}
modeis optional, what the link allows.
Supported values:view(watch only),control(watch and take control). Default:view.ttlis optional, how many seconds the link can be opened in. It is not the length of the session.
Range:15to300. Default:60.qualityis optional, the picture quality the viewer starts with, from0(smallest) to9(sharpest). The viewer can change it.
Range:0to9. Default:6.compressionis optional, how hard the picture is compressed, from0to9. Higher uses less bandwidth and more time.
Range:0to9. Default:1.widthandheightare optional, the display size for a client that does not send its own. The hosted viewer page always sends the size of its window.
Range:width320to1920,height240to1080. Default:1280×720.
Responses
-
200 OK — open
urlbeforeexpiresAt.{ "chatId": "c-u12345-email:[email protected]:8e0bf21e-a3b5-4a1e-a05a-65d160469bfb-bot:muse", "taskId": "423bc12e-db16-4921-8670-08b5f3f5a5d6", "url": "https://api.useapi.net/v1/muse/viewer#t=Qm9w4kTz0gXc1yR8vN2sLpE7aHfJ3uWdK6iYbM5oZtA", "expiresAt": "2026-10-14T23:09:10.512Z", "mode": "control", "viewerId": "5f1c0a9e7b24", "quality": 6, "compression": 1 } -
400 Bad Request — a parameter is invalid, or the path holds no valid
chatIdortaskId.{ "error": "mode must be view or control, not edit", "code": 400 } -
Invalid API token.
{ "error": "useapi.net ⁝ Unauthorized", "code": 401 } -
403 Forbidden — the
chatIdwas issued to a different API token.{ "error": "This chat id does not belong to this API token", "code": 403 } -
404 Not Found — the chat or the browser was not found, or the account that holds the chat is not added.
{ "error": "Browser task not found in this chat", "code": 404 } -
409 Conflict — the browser is closed.
{ "error": "This browser is already closed", "code": 409 } -
503 Service Unavailable — Muse could not be reached. Retry shortly.
{ "error": "Could not connect to the Muse workspace of account [email protected] (…), please retry shortly", "code": 503 } -
596 Account Error
The account needs your attention, and it is paused until you add it again. You receive an email when this happens. There are two causes.
Meta asks for a security check on the account. Sign in once at muse.ai in a browser, complete the check, then add the account again with POST /accounts using the same email and password. The API keeps using its session, so no new sign-in is needed.
{ "error": "Account [email protected]: Meta asks for a security check on account [email protected] (\"This account must complete a security checkpoint on the web\"). Sign in once at https://muse.ai in a browser, complete the check, then add the account again with POST /accounts", "code": 596 }Or Muse refused to sign the account in again when its session needed renewing, most often because the password was changed. Add the account again with the current password.
{ "error": "Account [email protected]: Muse refused to sign in account [email protected] (…). Update its password with POST /accounts, or sign in at https://muse.ai to clear the block, then re-add it", "code": 596 }
When the link is opened, the viewer page shows why it cannot start, for example:
- The link was already opened, has expired or was revoked. Create a new one.
- The browser already has a viewer open, or the account has a viewer open on its other browser. Close that viewer or revoke it. A viewer that was closed abruptly frees its place within about 90 seconds.
Model
{ // TypeScript, all fields are optional
chatId: string
taskId: string
url: string // the viewer page, works once
expiresAt: string // ISO 8601: open the link before this time
mode: 'view' | 'control'
viewerId: string // the viewer's id
quality: number // the starting picture quality, 0 to 9
compression: number // the starting compression, 0 to 9
error?: string // errors only
code?: number // errors only
}
Advanced: your own viewer client
The hosted page is the easy way. This section has what you need to embed it, or to build your own client.
The hosted page
GET https://api.useapi.net/v1/muse/viewerreturns the viewer page. It needs no API token: the ticket is the part ofurlafter#t=, in the URL fragment, which browsers never send to a server.- The page loads its noVNC 1.4 client from the same origin,
https://api.useapi.net/v1/muse/viewer/novnc.js, and uses no third-party CDN. - It opens a WebSocket to the same address, with its screen size as
?w=&h=so the browser is shown pixel for pixel. - Open
urlin a new tab or window, or show it in an<iframe>in your own app. The page sends no header that forbids framing. - The quality selector maps to
qualityandcompression: Low2/9, Balanced6/1, High8/1, Max9/1. A change applies to the next frames, without reconnecting.
The link
- The ticket works once: opening the WebSocket uses it up. A second open of the same link is refused.
- It must be opened within
ttlseconds. - DELETE …/viewer revokes it, and ends sessions already open.
- One viewer per browser, and one per account, because the account has one display.
Connecting
wss://api.useapi.net/v1/muse/viewer?w=1280&h=720
Sec-WebSocket-Protocol: useapi-viewer, t.<ticket>
- In a browser:
new WebSocket('wss://api.useapi.net/v1/muse/viewer?w=1280&h=720', ['useapi-viewer', 't.' + ticket]). The server answers with the subprotocoluseapi-viewer. wandhare optional, your screen area in pixels. Without them, the link’swidthandheightare used. Sizes are kept within320to1920by240to1080.- Binary frames carry RFB 3.8 (the VNC protocol) both ways, without a password: the browser’s screen from the server, your mouse and keyboard from you. Set
binaryType = 'arraybuffer'. Any RFB client library can read them. - Text frames are JSON objects with an
opfield: the control channel.
Text frames you send
op | Fields | What it does |
|---|---|---|
takeover | Take control. A control link only: a view link answers with an error. | |
release | Give control back to the agent. | |
resize | w, h | Your screen area changed. The browser’s display follows it, and the screen stream then restarts with a reconnect. |
ack | bytes | The total binary bytes you received so far. The hosted page sends it every 256 KB. A client that sends acks and falls more than 16 MB behind is disconnected. A client that never sends one is not measured. |
ping | Keeps a quiet connection open. Send one about every 25 seconds. | |
close | End the session. Control goes back to the agent. |
Text frames you receive
op | Fields | Meaning |
|---|---|---|
attached | mode, width, height, quality, compression, controlOwner, controlExpiresAt | The session started. controlOwner is user when you have control, else agent. |
state | controlOwner, controlExpiresAt | Control changed hands. |
error | message | Something went wrong, or a request was refused. A close often follows. |
revoked | The link was revoked. The session ends. | |
reconnect | ticket | The screen stream restarts, for example after a resize. Close this socket and connect again at once with this ticket. |
resume | ticket | Keep it. If the connection drops unexpectedly, connect again with this ticket to go on in the same session. It works once. |
ping | Sent about every 25 seconds. Ignore it. |
Session rules
- A
viewlink never sends input: the server drops your mouse and keyboard events. With acontrollink, input reaches the browser only while you have control. - Control is kept alive while the session is open, and handed back to the agent when the session ends.
- A session lasts up to 30 minutes, counted from the first connection, across every resume.
- After an unexpected drop, reconnect with the
resumeticket within about 5 seconds to keep control. Later, the ticket still continues the session, but control is back with the agent. The hosted page reconnects by itself after about a second. - Do not resume after a close you or the server made on purpose (
1000), or after a refusal (4000and up).
Close codes
| Code | Meaning |
|---|---|
1000 | A normal end: you closed the session, the link was revoked, the 30-minute limit, the browser closed, or a reconnect. |
1003 | The server could not read what the client sent. |
1011 | An error or a drop, for example the connection to Muse. Resume with the resume ticket. |
4000 + HTTP status | The link could not open a session: 4410 already used, expired or revoked, 4409 this browser already has a viewer, 4429 the account has a viewer open on another browser, 4404 the account is not added. |
A refusal, and most ends with an error, come after an {"op": "error"} frame with the reason. A missing or malformed ticket is refused with HTTP 401 before the connection opens.
Examples
-
CHATID="c-u12345-email:[email protected]:8e0bf21e-a3b5-4a1e-a05a-65d160469bfb-bot:muse" TASKID="423bc12e-db16-4921-8670-08b5f3f5a5d6" curl -X POST "https://api.useapi.net/v1/muse/chats/$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "$CHATID")/browser/$TASKID/viewer" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer …" \ -d '{"mode": "control"}' -
const token = "API token"; const chatId = "chatId from POST /chats"; const taskId = "taskId from the stream's browser event"; const apiUrl = `https://api.useapi.net/v1/muse/chats/${encodeURIComponent(chatId)}/browser/${encodeURIComponent(taskId)}/viewer`; const response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, }, body: JSON.stringify({ mode: "control" }) }); const result = await response.json(); console.log("response", response.status, result); if (response.ok) window.open(result.url, "_blank"); -
import requests from urllib.parse import quote token = "API token" chatId = "chatId from POST /chats" taskId = "taskId from the stream's browser event" apiUrl = f"https://api.useapi.net/v1/muse/chats/{quote(chatId, safe='')}/browser/{quote(taskId, safe='')}/viewer" headers = { "Content-Type": "application/json", "Authorization" : f"Bearer {token}" } response = requests.post(apiUrl, headers=headers, json={"mode": "control"}) print(response, response.json())