Skip to content
← Blog

How to embed a live AI agent computer in your app (and why it shows a black screen)

Put a computer's live screen in your product with noVNC, and read the close code when it stays black.

TL;DR

To embed an AI agent's computer in your app, connect a VNC client in the browser to wss://www.orgo.ai/desktops/{id}/ws/websockify?token={password}. Every Orgo computer streams over that one URL, and any websockify-compatible client such as noVNC works, so you need no Orgo package (Embed computers). Your server holds the sk_ API key, reads the computer's password with it, and hands only that password to the page. If the screen stays black, read the WebSocket close code. It says why the connection was refused.

What does "embedding an agent computer" mean?

It means putting the live screen of the cloud computer your agent is driving inside your own product, so your user can watch the agent work, take over the mouse, or step in to approve something. An Orgo computer is a cloud desktop with a browser, reachable over HTTP and VNC. The HTTP API is how the agent acts (screenshot, click, type, bash). VNC is how a person sees and touches the same screen.

An embed is different from showing screenshots after the fact. It is a live, two-way stream of the actual desktop.

What URL do I connect to?

Plain text
wss://www.orgo.ai/desktops/{id}/ws/websockify?token={password}
ValueWhere it comes from
idThe computer's UUID, as POST /computers and GET /computers/{id} return it. Its instance_id also works, but it changes when a sleeping computer wakes on a new host, so use the id.
passwordGET /computers/{id} returns it as vnc_password, and GET /computers/{id}/vnc-password returns it as password. It can change, so fetch it for each session rather than storing it.

Both endpoints need your API key, so call them from your server (Embed computers). A key whose owner has View only access to the computer's workspace gets no password: vnc_password comes back null, and the password endpoint refuses the request.

Which credential goes in the browser?

Only the computer's password. Never the API key.

CredentialLivesOpens
API key (sk_...)Your server onlyAn account-wide key opens your whole Orgo account: every computer, billing, and minting more keys. Keys are never asked for MFA.
Computer passwordSent only to people allowed to control that computerOne computer

Orgo enforces this: an sk_ key sent as ?token= from a browser closes the socket with 4001.

Treat the computer password as root on that machine. The same value opens the terminal WebSocket and the bash proxy, not only the screen. So fetch it per session from your backend, gate that route with your own auth, keep it out of your client bundle (in Next.js, never a NEXT_PUBLIC_* variable), and point public embeds at a disposable computer rather than one holding real work.

How do I embed it with noVNC?

Two pieces. First, a server route trades your API key for the two values the browser needs:

ts
// app/api/computer/route.ts runs on your server. The API key never leaves it.
// Check your own user's session here first: anyone who can call this route can drive the computer.
export async function GET() {
  const res = await fetch(
    `https://www.orgo.ai/api/computers/${process.env.ORGO_COMPUTER_ID}`,
    {
      headers: { Authorization: `Bearer ${process.env.ORGO_API_KEY}` },
      cache: 'no-store',
    },
  );
  if (!res.ok) return Response.json({ error: 'lookup_failed' }, { status: 502 });
  const computer = await res.json();
  return Response.json(
    { id: computer.id, password: computer.vnc_password },
    { headers: { 'Cache-Control': 'no-store' } },
  );
}

Then the page. Install noVNC 1.7.0 or later with npm install @novnc/novnc. Earlier versions have no package-root export, so the import below fails on them.

js
import RFB from '@novnc/novnc';
 
const { id, password } = await fetch('/api/computer').then((r) => r.json());
 
// Open the WebSocket yourself so you can read its close code.
const ws = new WebSocket(
  `wss://www.orgo.ai/desktops/${id}/ws/websockify?token=${encodeURIComponent(password)}`,
);
ws.addEventListener('close', (e) => console.log('closed', e.code, e.reason));
 
const rfb = new RFB(document.getElementById('screen'), ws, {
  credentials: { password },
});
 
rfb.viewOnly = false;      // true stops this client sending input (see view-only below)
rfb.scaleViewport = true;  // fit the screen to its container
rfb.addEventListener('credentialsrequired', () => rfb.sendCredentials({ password }));

noVNC accepts a WebSocket object in place of a URL (noVNC API). Passing your own is how you get the close code, which noVNC's disconnect event does not carry. There is no origin allowlist and nothing to register: you can embed from any domain, and the computer's password is the only thing that decides access.

Why is my embedded computer a black screen?

Because the proxy rejects a bad connection by accepting the WebSocket upgrade and then closing it. noVNC tells your code only that it disconnected (it prints the close code to the browser console, nowhere else), and the canvas stays black. Log the close code and match it here (Embed computers). The close reason carries a short description too, such as Computer not found.

CodeWhat happenedFix
4001Authentication rejectedSend the computer password, not an sk_ key. Fetch it again, since it may have changed
4003The computer is not available: stopped, asleep, not ready yet, or its trial or subscription endedStart or resume it, then reconnect once its status is running
4004No such computerCheck the computer id
4006An API key scoped to another workspaceUse a key for this workspace, or an account-wide key. Only clients that connect with a key see this
4007An Orgo sign-in that has not passed MFAComplete MFA, then reconnect. A page that connects with the password does not see this
4008Idle for 30 minutesExpected. Reconnect on the next user action
4500Proxy errorRetry
4502The proxy has no host address on record for the computerRetry after a short wait
4503The proxy could not look up the computerRetry

A 1000 close means the proxy's own connection to the computer failed or ended, and 1001 means the Orgo server restarted. Reconnect after either. Retry 4500, 4502 and 4503 after a short wait, 4003 once the computer is running, and 4008 when the user comes back. The rest need an input changed first. A wrong path gets no Orgo close code at all: a URL outside /desktops/{id}/ws/ fails the handshake, and the browser reports 1006.

Why does my embed disconnect after 30 minutes?

An embed nobody uses for 30 minutes gets 4008. The proxy closes the connection after 30 minutes with no keyboard, pointer, or clipboard input from a viewer. It measures input, not pixels, so a computer an agent is busy driving while nobody watches still counts as idle (Embed computers). Treat 4008 as a clean close and reconnect when the user comes back. Do not treat it as an outage.

Can I give users a view-only screen?

Not inside your app today. rfb.viewOnly = true only disables that one client's controls. The password in the page still grants full control, and a visitor can turn the setting off.

Orgo enforces view-only on the server only for people signed in to Orgo. Invite them to the workspace with View only access and Orgo's proxy drops their input, but they watch in Orgo, not in your product, and View only members can still chat with the workspace's agents. A public embed with a separate view-only password is not offered by the API today (Embed computers).

How do I show what the agent is doing, not just the pixels?

Pair the screen with the Events WebSocket at wss://www.orgo.ai/desktops/{id}/ws/events?token={password}. It takes the computer password too. An API key does not work on this socket.

After you send a subscribe message, a Linux computer pushes window focus, open and close, clipboard, process start and stop, screen resolution, audio stream, and idle or active events. File change events are defined but not delivered on Linux today. A Windows computer emits only window_focus, clipboard, and file_change (Events WebSocket). That is enough to render an activity strip ("Focus moved to Google Chrome") next to the live screen. Event names are not validated, so a typo subscribes successfully and then delivers nothing.

Embedding vs screenshots vs Orgo's own viewer

ApproachLive?Can the viewer take over?Where it runsBest for
Embedded VNC (noVNC)YesYes. The password always grants controlInside your productWatch-and-approve flows, human takeover
Screenshot endpoint (GET /computers/{id}/screenshot)No, one image per requestNoYour backend or UIAudit trail, thumbnails, the agent's own loop
Orgo workspace with View only accessYesNo. Orgo's proxy drops their inputIn OrgoTeammates or clients watching runs

Does this scale to one computer per user?

Yes. Give every user their own computer and add capacity from code with the capacity API (Scale to thousands). On a paid plan you can add up to 100 computers on your own, fewer if they are larger than Medium. Past that, the API returns 403 capacity_cap and Orgo raises the limit for your account on request. Each embed then points at that user's own computer, which also keeps one user's password from opening another user's screen. How to scale AI agent computers to thousands covers sizes and capacity in detail.

FAQ

Do I need an Orgo SDK to embed a computer? No. Any websockify-compatible VNC client connects. The examples here use noVNC.

Should I use the orgo-vnc React package? If you want a React component, use 0.3.0 or later. Version 0.3.0, published in October 2026, connects to the current URL with ?token= and reports every close through onClose (the raw code) and onError (a decoded reason). The 0.2.x versions connect to a per-computer hostname that no longer exists, so they cannot connect.

Can I embed from any domain? Yes. There is no origin allowlist. The computer's password is the credential.

Is it safe to put the computer password in the page? Only for people allowed to control that computer. It is root on that machine. Fetch it per session behind your own auth, and use disposable computers for public demos.

Does an agent driving the computer keep the embed alive? No. The 30-minute idle timer counts input from viewers, not agent activity.