> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orgo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed VMs

> Embed virtual computers into your applications

## Overview

Embed Orgo virtual computers directly into your web apps. Build AI agent interfaces, automation dashboards, or any product with live VM displays.

<Note>
  You can use any VNC client to connect to Orgo computers. The `orgo-vnc` package is a React component for convenience.
</Note>

## Connection details

Every computer is reachable same-origin under `www.orgo.ai`. The connection base is shown as **Connection URL** in Computer Settings.

|                     |                                                                                              |
| ------------------- | -------------------------------------------------------------------------------------------- |
| **Connection base** | `https://www.orgo.ai/desktops/{instance_id}` (read `instance_id` from `GET /computers/{id}`) |
| **WebSocket VNC**   | `wss://www.orgo.ai/desktops/{instance_id}/ws/websockify?token={password}`                    |
| **Password**        | `GET /computers/{id}/vnc-password` - **rotates on restart**, fetch fresh, do not hardcode    |

The WebSocket form is what the `orgo-vnc` component, noVNC, and any websockify-compatible client use.

You can embed from **any domain**. There is no origin allowlist and nothing to register with us — the desktop password is the credential, so it is the only thing that decides whether a connection is allowed.

## Two credentials, and which one goes in the browser

This is the single most important thing to get right when embedding.

|                      | Where it lives                               | What it opens                                                                                   |
| -------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| **API key** (`sk_…`) | **Your server only.** Never in browser code. | Your entire Orgo account: every computer, billing, and minting more keys. It also bypasses MFA. |
| **Desktop password** | Safe to hand to the browser.                 | One computer. Rotates on restart.                                                               |

Your server holds the API key, calls `GET /computers/{id}/vnc-password` with it, and returns **only the password** to your page.

<Warning>
  An `sk_` API key is **rejected** if it arrives from a browser — passing one as `?token=` returns WebSocket close `4001`. Use the desktop password.
</Warning>

<Warning>
  **Treat the desktop password as root on that computer.** The same value opens `/ws/terminal` and `POST /desktops/{instance_id}/proxy/bash`, not just the display. Anyone who can read it from your page can run commands on the VM.

  So: fetch it per-session from your own backend, never inline it into a client bundle (in Next.js, **do not** put it in a `NEXT_PUBLIC_*` variable — those are compiled into the JavaScript every visitor downloads), and point embeds at a disposable computer rather than one holding real work.
</Warning>

### Connect with raw noVNC (no React)

Fetch the password on your server, then hand it to the client:

```js app/api/desktop/route.js expandable theme={null}
// SERVER — your backend. The API key never leaves this file.
export async function GET() {
  const headers = { Authorization: `Bearer ${process.env.ORGO_API_KEY}` };

  const computer = await fetch(`https://www.orgo.ai/api/computers/${process.env.ORGO_COMPUTER_ID}`, { headers })
    .then(r => r.json());

  const { password } = await fetch(
    `https://www.orgo.ai/api/computers/${process.env.ORGO_COMPUTER_ID}/vnc-password`,
    { headers },
  ).then(r => r.json());

  // Only the per-computer password crosses to the browser — never the API key.
  return Response.json({ instanceId: computer.instance_id, password });
}
```

```js Browser theme={null}
// BROWSER
import RFB from '@novnc/novnc';

const { instanceId, password } = await fetch('/api/desktop').then(r => r.json());

const rfb = new RFB(
  document.getElementById('screen'),
  `wss://www.orgo.ai/desktops/${instanceId}/ws/websockify?token=${encodeURIComponent(password)}`,
  { credentials: { password } }
);

// Adjust after construction
rfb.viewOnly = true;       // read-only mode (no input)
rfb.scaleViewport = true;  // fit display to container
rfb.qualityLevel = 6;      // 0–9 (default 6)
rfb.compressLevel = 2;     // 0–9 (default 2)

// Surface failures instead of showing a blank canvas.
rfb.addEventListener('securityfailure', () => console.error('VNC auth failed'));
rfb.addEventListener('credentialsrequired', () => rfb.sendCredentials({ password }));
```

<Note>
  Always handle `credentialsrequired` and `securityfailure`. Without them noVNC can stop mid-handshake with no `connect`, no `disconnect` and no thrown error — the canvas simply stays black and nothing is logged.
</Note>

## Setup

<Steps>
  <Step title="Install">
    ```bash theme={null}
        npm install orgo-vnc
    ```
  </Step>

  <Step title="Get credentials">
    1. Go to [orgo.ai/start](https://www.orgo.ai/start)
    2. Open a workspace and select a computer
    3. Click the **⋮** menu → **Computer Settings**
    4. Copy the **Hostname**, and create an API key for your server

    Point the embed at a **disposable** computer — see the warning above about what the password grants.
  </Step>

  <Step title="Configure environment">
    Create `.env.local` in your project root. The hostname is public; the API key is not.

    ```bash theme={null}
        NEXT_PUBLIC_ORGO_COMPUTER_HOST=your-hostname
        ORGO_API_KEY=sk_live_...
        ORGO_COMPUTER_ID=your-computer-id
    ```

    <Warning>
      Only `NEXT_PUBLIC_*` values are safe to expose — Next.js compiles them into the client bundle. Keep `ORGO_API_KEY` un-prefixed so it stays on the server, and never add a `NEXT_PUBLIC_` password: the password rotates on restart, so a baked-in one breaks anyway, and until it does it is a root shell sitting in your JavaScript.
    </Warning>
  </Step>

  <Step title="Add a server route for the password">
    The password rotates on restart, so fetch it per session rather than storing it.

    ```ts app/api/desktop-password/route.ts expandable theme={null}
        // Gate this with YOUR OWN auth — anyone who can call it can drive the computer.
        export async function GET() {
          const res = await fetch(
            `https://www.orgo.ai/api/computers/${process.env.ORGO_COMPUTER_ID}/vnc-password`,
            { headers: { Authorization: `Bearer ${process.env.ORGO_API_KEY}` }, cache: 'no-store' },
          );
          const { password } = await res.json();
          return Response.json({ password }, { headers: { 'Cache-Control': 'no-store' } });
        }
    ```
  </Step>

  <Step title="Use the ComputerDisplay component">
    <CodeGroup>
      ```tsx app/page.tsx expandable theme={null}
          'use client';
          import { useEffect, useState } from 'react';
          import { ComputerDisplay } from 'orgo-vnc';

          const HOST = process.env.NEXT_PUBLIC_ORGO_COMPUTER_HOST!;

          export default function Home() {
            const [connected, setConnected] = useState(false);
            const [password, setPassword] = useState<string | null>(null);

            // Fetched per session from your own server, never bundled.
            useEffect(() => {
              fetch('/api/desktop-password')
                .then(r => r.json())
                .then(d => setPassword(d.password))
                .catch(() => setPassword(null));
            }, []);

            return (
              <div className="grid place-items-center min-h-screen p-8">
                <div className="w-full max-w-4xl flex flex-col gap-3">
                  <p className="text-sm text-foreground/60 flex items-center gap-2">
                    <span className={`w-2 h-2 rounded-full ${connected ? 'bg-emerald-500' : 'bg-foreground/30 animate-pulse'}`} />
                    {connected ? `Connected to ${HOST}` : 'Connecting...'}
                  </p>
                  <div className="aspect-[4/3] rounded-lg overflow-hidden bg-foreground/5">
                    {password && (
                      <ComputerDisplay
                        hostname={HOST}
                        password={password}
                        background="transparent"
                        readOnly={false}
                        onConnect={() => setConnected(true)}
                        onDisconnect={() => setConnected(false)}
                        onError={(e) => console.error(e)}
                      />
                    )}
                  </div>
                </div>
              </div>
            );
          }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Props

| Prop               | Type       | Default     | Description                                 |
| ------------------ | ---------- | ----------- | ------------------------------------------- |
| `hostname`         | `string`   | required    | Computer hostname                           |
| `password`         | `string`   | required    | Computer password                           |
| `readOnly`         | `boolean`  | `false`     | Disable user interaction                    |
| `background`       | `string`   | `undefined` | Background color                            |
| `scaleViewport`    | `boolean`  | `true`      | Scale display to fit container              |
| `clipViewport`     | `boolean`  | `false`     | Clip display to container bounds            |
| `resizeSession`    | `boolean`  | `false`     | Resize remote session to match              |
| `showDotCursor`    | `boolean`  | `false`     | Show dot cursor when remote cursor hidden   |
| `compressionLevel` | `number`   | `2`         | Compression level (0-9)                     |
| `qualityLevel`     | `number`   | `6`         | Image quality (0-9)                         |
| `onConnect`        | `function` | `undefined` | Called when connected                       |
| `onDisconnect`     | `function` | `undefined` | Called when disconnected                    |
| `onError`          | `function` | `undefined` | Called on error                             |
| `onClipboard`      | `function` | `undefined` | Called when clipboard data received         |
| `onReady`          | `function` | `undefined` | Called with handle for programmatic control |

## Programmatic Control

Use the `onReady` callback to get a handle for programmatic control:

```tsx theme={null}
const [handle, setHandle] = useState<ComputerDisplayHandle | null>(null);

<ComputerDisplay
  hostname={HOST}
  password={PASSWORD}
  onReady={setHandle}
/>

// Later...
handle?.reconnect();
handle?.disconnect();
handle?.sendClipboard('text to send');
await handle?.pasteFromClipboard();
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Quick Start" icon="play" href="/quickstart">
    Full SDK setup
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Control computers programmatically
  </Card>
</CardGroup>
