Open a live view of the browser

October 14, 2026

Table of contents

  1. Request Headers
  2. Request Body
  3. Responses
  4. Model
  5. Advanced: your own viewer client
  6. Examples
  7. Try It

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 ttl seconds. 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: previewAvailable is true in its browser event or in GET /chats/chatId/browser/taskId.

The viewer page has these controls:

  • Take control and Release control, with a control link. 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

  • chatId is URL-encoded in the path (it contains : and @).
  • taskId is the taskId of the chat’s browser event.
Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
Request Body
{
  "mode": "control",
  "ttl": 120
}
  • mode is optional, what the link allows.
    Supported values: view (watch only), control (watch and take control). Default: view.
  • ttl is optional, how many seconds the link can be opened in. It is not the length of the session.
    Range: 15 to 300. Default: 60.
  • quality is optional, the picture quality the viewer starts with, from 0 (smallest) to 9 (sharpest). The viewer can change it.
    Range: 0 to 9. Default: 6.
  • compression is optional, how hard the picture is compressed, from 0 to 9. Higher uses less bandwidth and more time.
    Range: 0 to 9. Default: 1.
  • width and height are 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: width 320 to 1920, height 240 to 1080. Default: 1280 × 720.
Responses
  • 200 OK — open url before expiresAt.

    {
      "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 chatId or taskId.

    {
      "error": "mode must be view or control, not edit",
      "code": 400
    }
    
  • 401 Unauthorized

    Invalid API token.

    {
      "error": "useapi.net ⁝ Unauthorized",
      "code": 401
    }
    
  • 403 Forbidden — the chatId was 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/viewer returns the viewer page. It needs no API token: the ticket is the part of url after #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 url in 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 quality and compression: Low 2/9, Balanced 6/1, High 8/1, Max 9/1. A change applies to the next frames, without reconnecting.
  • The ticket works once: opening the WebSocket uses it up. A second open of the same link is refused.
  • It must be opened within ttl seconds.
  • 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 subprotocol useapi-viewer.
  • w and h are optional, your screen area in pixels. Without them, the link’s width and height are used. Sizes are kept within 320 to 1920 by 240 to 1080.
  • 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 op field: 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 view link never sends input: the server drops your mouse and keyboard events. With a control link, 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 resume ticket 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 (4000 and 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())
    
Try It