Skip to content
Hoody.com

A container’s desktop applications are reachable at HTTPS URLs. Open one in a browser on a phone, tablet, laptop, or TV and you get the running desktop, with VS Code, a web browser, LibreOffice, or any other Linux GUI program on it.

Containers created with hoody_kit: true include hoody-display, which serves that desktop over HTTP to an HTML5 client. Nothing is installed on the viewing device.


  • Desktop environments - Any GUI application runs in the browser through the HTML5 client
  • Device support - Phone, tablet, laptop, TV, or anything else with a browser
  • Multiplayer sessions - Several people view and control one desktop at the same time
  • URL parameters - Over 50 options for theming, performance, and input
  • Screenshot API - Capture the desktop as a PNG programmatically
  • Computer Use API - Mouse, keyboard, and window control over HTTP, across dozens of endpoints
  • No client software - No VNC or RDP client to install; the browser is the client
  • Hardware acceleration - H264 video encoding
  • Clipboard sync - Copy and paste between the local machine and the container

Full parameter, response, and example documentation for each endpoint lives in the API reference.

Web client interface:

Screenshot and thumbnail API:

System information:

Mouse control:

  • POST /api/v1/display/mouse/click - Click a mouse button
  • POST /api/v1/display/mouse/double-click - Double-click
  • POST /api/v1/display/mouse/move - Move cursor to absolute position
  • POST /api/v1/display/mouse/move-relative - Move cursor by offset
  • POST /api/v1/display/mouse/down - Press and hold mouse button
  • POST /api/v1/display/mouse/up - Release mouse button
  • POST /api/v1/display/mouse/scroll - Scroll
  • GET /api/v1/display/mouse/location - Get current cursor position

Keyboard control:

  • POST /api/v1/display/keyboard/type - Type a string of text
  • POST /api/v1/display/keyboard/key - Press key combinations (X11 keysym notation)
  • POST /api/v1/display/keyboard/key-down - Hold a key down
  • POST /api/v1/display/keyboard/key-up - Release a held key

Window management:

  • GET /api/v1/display/windows - List all windows
  • POST /api/v1/display/window/focus - Focus/activate a window
  • POST /api/v1/display/window/move - Move a window
  • POST /api/v1/display/window/resize - Resize a window
  • POST /api/v1/display/window/minimize - Minimize a window
  • POST /api/v1/display/window/close - Close a window
  • POST /api/v1/display/window/raise - Raise window to top
  • GET /api/v1/display/window/active - Get active window ID
  • POST /api/v1/display/window/search - Search windows by title pattern
  • GET /api/v1/display/window/{windowId}/geometry - Get window position and size
  • GET /api/v1/display/window/{windowId}/name - Get window title
  • GET /api/v1/display/window/{windowId}/properties - Get extended window properties

Clipboard:

  • GET /api/v1/display/clipboard - Read clipboard text from a buffer selection
  • POST /api/v1/display/clipboard - Write text to a clipboard buffer

Compound actions (Computer Use):

  • POST /api/v1/display/input/click-at - Move cursor and click
  • POST /api/v1/display/input/type-at - Move, click, and type in one operation
  • POST /api/v1/display/input/drag - Drag from one position to another
  • POST /api/v1/display/input/select - Select a range via click + shift-click
  • POST /api/v1/display/input/act - Execute one action with optional screenshot
  • POST /api/v1/display/input/wait - Wait with optional screenshot
  • POST /api/v1/display/input/batch - Execute a sequence of actions atomically
  • POST /api/v1/display/input/reset - Emergency release all held inputs
  • GET /api/v1/display/input/display-geometry - Get display dimensions

A container can run several display instances at once, each with its own URL:

https://{project}-{container}-display-1.{server}.containers.hoody.com
https://{project}-{container}-display-2.{server}.containers.hoody.com
https://{project}-{container}-display-3.{server}.containers.hoody.com

The usual arrangement is one display per application:

  • display-1 - Main IDE (VS Code)
  • display-2 - Web browser (Firefox/Chrome)
  • display-3 - Office applications (LibreOffice)
  • display-4 - Graphics editor (GIMP)
  • display-5 - Database tools

Each display runs independently, with its own:

  • Screen resolution
  • Window manager state
  • Application set
  • Performance profile

Any of them opens in any browser, on a phone, tablet, laptop, or TV, with nothing to install and nothing to configure.

Terminal integration: When you create a terminal session with display: "5", the kit exports DISPLAY=:5 into that shell, connecting it to display-5. The conventional pattern is to match the terminal number to the display number (e.g. terminal-5 paired with display: "5"), but there is no automatic mapping: you must set the display field explicitly at session creation. Run firefox in such a terminal and it appears in display-5.

Manual display selection: Set the DISPLAY environment variable to target a specific display:

Terminal window
# In any terminal
export DISPLAY=:5
# Now GUI programs open in display-5
firefox & # Opens in display-5
code . # Opens in display-5

Applications can be spread across displays and driven from any terminal.

The same URL works on every device that has a browser:

  • Laptop browser → the full desktop
  • Phone browser → the same desktop, touch-optimized
  • Tablet → useful for presentations
  • TV → the desktop on a large screen
  • Smart watch → monitoring dashboards
  • Smart glasses (future) → AR overlays of your infrastructure

The browser is the only thing running on the device. Applications execute on the server, so the device’s own processor and memory do not limit what you can run.

Share the URL and everyone who opens it sees and controls the same desktop:

https://{project}-{container}-display-1.{server}.containers.hoody.com/?sharing=true

Everyone connected:

  • Sees the same screen in real time
  • Can type and click at the same time as everyone else
  • Sees cursors showing who is doing what
  • Sees each change as it happens

Common uses:

  • Team debugging, where everyone is looking at the same error
  • Teaching, with instructor and students on one desktop
  • Pair programming in a single IDE
  • Customer support, working inside the customer’s own session

See: Multiplayer by Default → for the collaboration model.

The client takes its configuration from the query string:

# Read-only dashboard
?readonly=true&decorations=false&toolbar=false&reconnect=true
# Low-bandwidth mode
?encoding=jpeg&bandwidth_limit=1000000&video=false&sound=false
# Collaborative session
?sharing=true&steal=false
# macOS user setup
?swap_keys=true&keyboard_layout=us
# Dark mode with floating menu
?floating_menu=true&dark_mode=true

Over 50 parameters control the UI, performance, input, feature flags, and session behavior.

See: Web Client Interface → for the full list.

Capture the current desktop state over HTTP:

Terminal window
# Capture current screenshot
GET /api/v1/display/screenshot?displayId=1
# Get as base64 for AI vision
GET /api/v1/display/screenshot?base64=true
# Lightweight thumbnail
GET /api/v1/display/thumbnail/last

Typical uses:

  • Vision-model analysis of UI state
  • Verification in automated tests
  • Documentation screenshots
  • Monitoring dashboards
  • Time-lapse recordings

See: Screenshot API →

A display is an ordinary URL, so it loads in an <iframe>:

<!-- Embed desktop in webpage -->
<iframe src="https://{project}-{container}-display-1.{server}.containers.hoody.com"
width="1280" height="720" />
<!-- Multiple displays in one page -->
<iframe src="https://prod-container-display-1.{server}.containers.hoody.com" />
<iframe src="https://staging-container-display-1.{server}.containers.hoody.com" />
<iframe src="https://PROJECT_ID-CONTAINER_ID-display-1.{server}.containers.hoody.com" />

Composing iframes gives you a custom dashboard whose panels are live desktops rather than screenshots of them.

See: Embeddability Revolution →


RDP Client (installed) → RDP Server (configured) → Desktop (complex)

Where that model gets awkward:

  • The client has to be installed, which is difficult on mobile devices
  • Ports and firewall rules have to be configured
  • The stream cannot be embedded in a page
  • One connection kicks out the other, so it is effectively single-user
  • The protocol is binary, so an AI agent cannot read the screen
  • Setup is involved
Any Browser → Display URL → Desktop (immediately)

What changes with a display URL:

  • The browser is the client, so there is nothing to install
  • Traffic arrives over HTTPS on port 443 through Hoody Proxy, so no ports are configured
  • The URL embeds directly: <iframe src="display-url" />
  • Sharing the URL shares the session, without a separate collaboration step
  • Screenshots and the HTTP API make the desktop readable by an AI agent
  • Every interaction is an HTTP request, so the session can be logged
  • Phone browsers work without extra configuration

A phone browser opening a display URL gets whatever runs in that desktop:

// Phone browser opens display URL
https://{project}-{container}-display-1.{server}.containers.hoody.com
// Inside that desktop:
- Full VS Code IDE
- Chrome browser with DevTools
- Terminal sessions
- Database tools
- Any Linux GUI application

None of these applications runs on the phone. The container runs them and the browser renders the result.


Every input a person can give a desktop is also an HTTP endpoint: mouse, keyboard, window management, and compound actions, across dozens of REST routes.

Anything that can make an HTTP request can drive a GUI application through them, whether that is an automation script, a remote operator, or an AI agent working from screenshots.

Absolute and relative cursor movement, clicks, and scrolling, at pixel precision:

Terminal window
# Move cursor to position
hoody display mouse move --x 640 --y 480 --display-id 10 -c <container-id>
# Click at current position
hoody display mouse click --button 1 --display-id 10 -c <container-id>
# Double-click
hoody display mouse double-click --button 1 --display-id 10 -c <container-id>
# Scroll down
hoody display mouse scroll --direction down --clicks 5 --display-id 10 -c <container-id>

Available mouse buttons: 1 (left), 2 (middle), 3 (right), 4 to 7 (extra)

Type text and send any key combination the OS understands:

Terminal window
# Type text
hoody display keyboard type --text "Hello, World!" --delay 50 --display-id 10 -c <container-id>
# Press key combination
hoody display keyboard key --keys '["ctrl+s"]' --display-id 10 -c <container-id>
# Hold key
hoody display keyboard key-down --key "Shift_L" --hold-ms 2000 --display-id 10 -c <container-id>

List the windows on a desktop, then focus, move, resize, or close any of them:

Terminal window
# List visible windows
hoody display windows list --only-visible --display-id 10 -c <container-id>
# Focus a window
hoody display windows focus --window-id 83886081 --display-id 10 -c <container-id>
# Move a window
hoody display windows move --window-id 83886081 --x 100 --y 100 --display-id 10 -c <container-id>
# Resize a window
hoody display windows resize --window-id 83886081 --width 1024 --height 768 --display-id 10 -c <container-id>
# Search for windows by name
hoody display windows search --pattern "Firefox" --name --only-visible --display-id 10 -c <container-id>

Each of these endpoints combines several primitives into one atomic operation, so a click-then-type sequence costs a single round trip:

Terminal window
# Click at specific position
hoody display input click-at --x 640 --y 480 --button 1 --display-id 10 -c <container-id>
# Type at position
hoody display input type-at --x 300 --y 400 --text "Hello" --delay 50 --display-id 10 -c <container-id>
# Drag between positions
hoody display input drag --start-x 100 --start-y 100 --end-x 300 --end-y 300 --display-id 10 -c <container-id>
# Execute action with screenshot
hoody display input act --action mouse/click --params button=1 --screenshot --display-id 10 -c <container-id>

An agent finds the browser window, navigates to a login page, fills the form, and submits it:

const base = 'https://{project}-{container}-display-1.{server}.containers.hoody.com';
// 1. Find the browser window
const { windows } = await fetch(`${base}/api/v1/display/windows`).then(r => r.json());
const browser = windows.find(w => w.name.includes('Firefox'));
// 2. Focus it
await fetch(`${base}/api/v1/display/window/focus`, {
method: 'POST',
body: JSON.stringify({ windowId: browser.windowId })
});
// 3. Navigate to URL via address bar
await fetch(`${base}/api/v1/display/input/batch`, {
method: 'POST',
body: JSON.stringify({
actions: [
{ action: 'keyboard/key', params: { keys: ['ctrl+l'] } }, // Focus address bar
{ action: 'wait', params: { ms: 200 } },
{ action: 'keyboard/type', params: { text: 'https://app.example.com/login' } },
{ action: 'keyboard/key', params: { keys: ['Return'] } },
{ action: 'wait', params: { ms: 2000 } }, // Wait for page load
{ action: 'screenshot' } // Verify it loaded
]
})
});
// 4. Fill in login form
await fetch(`${base}/api/v1/display/input/type-at`, {
method: 'POST',
body: JSON.stringify({ x: 640, y: 380, text: 'user@example.com' })
});
await fetch(`${base}/api/v1/display/input/type-at`, {
method: 'POST',
body: JSON.stringify({ x: 640, y: 450, text: 'supersecretpassword' })
});
// 5. Submit and capture result
const result = await fetch(`${base}/api/v1/display/input/act`, {
method: 'POST',
body: JSON.stringify({
action: 'keyboard/key',
params: { keys: ['Return'] },
screenshot: true
})
}).then(r => r.json());
// result.screenshot.image.dataUrl is the PNG as a data URI; send it to a vision model to verify the login

These calls act on the pointer and the keyboard rather than through a CLI wrapper or a browser-specific automation driver, so the same sequence works against any Linux GUI application, in any window, from anything that can make a curl request.


The same container, opened from three devices in turn:

Terminal window
# Laptop: Configure display
https://PROJECT_ID-CONTAINER_ID-display-1.{server}.containers.hoody.com/?dark_mode=true&swap_keys=true
# Phone (later): Same URL, same environment
https://PROJECT_ID-CONTAINER_ID-display-1.{server}.containers.hoody.com/?dark_mode=true&swap_keys=true
# Tablet (during presentation): Same environment
https://PROJECT_ID-CONTAINER_ID-display-1.{server}.containers.hoody.com/?dark_mode=true

It is one machine reachable from a phone, laptop, tablet, or TV. Nothing is synchronized between them, because each device is attached to the same running instance.

Several people working in one desktop:

// Create the collaborative session
const displayUrl = 'https://{project}-{container}-display-1.{server}.containers.hoody.com';
const collaborativeUrl = `${displayUrl}/?sharing=true&steal=false`;
// Send the URL to the team. Everyone sees the same desktop and can:
// - Open files in the shared VS Code
// - Type in the shared terminal
// - Click in the shared browser
// - Edit in the shared applications
// Everyone is attached to one session, in real time

A support agent joins the customer’s desktop instead of starting a screen share:

Terminal window
# Customer shares the display URL
https://PROJECT_ID-CONTAINER_ID-display-1.{server}.containers.hoody.com/?sharing=true
# Support agent opens it on a phone during a commute,
# sees the customer's desktop, and types the fix directly
# There is no screen-share session to set up
# and no need to ask "can you see my screen?"

One URL keeps control, the other is read-only:

# Presenter URL (full control)
?sharing=true&steal=false&readonly=false
# Viewers URL (watch only)
?sharing=true&steal=false&readonly=true

The presenter keeps control and viewers watch in real time. Useful for:

  • Product demos
  • Architecture reviews
  • Training sessions
  • Live coding demonstrations

Capture the desktop and hand the image to a vision model:

// 1. Capture screenshot via HTTP
const response = await fetch(
'https://{project}-{container}-display-1.{server}.containers.hoody.com/api/v1/display/screenshot?base64=true'
);
const { image, info } = await response.json();
// 2. Send to a vision model via Hoody AI (any provider you've configured)
const analysis = await ai.chat.completions.create({
model: 'your-vision-model',
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'What errors do you see in this IDE?' },
{ type: 'image_url', image_url: { url: `data:image/png;base64,${image.data}` }}
]
}]
});
// 3. AI describes what it sees
console.log(analysis.choices[0].message.content);
// "I see a syntax error on line 23: unclosed bracket..."

With the screenshot in hand, a model can do visual debugging, UI analysis, and accessibility testing against what is actually on screen.

Three environments side by side, each of them read-only:

<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
<!-- Production -->
<iframe src="https://prod-container-display-1.{server}.containers.hoody.com/?readonly=true&toolbar=false" />
<!-- Staging -->
<iframe src="https://staging-container-display-1.{server}.containers.hoody.com/?readonly=true&toolbar=false" />
<!-- Development -->
<iframe src="https://PROJECT_ID-CONTAINER_ID-display-1.{server}.containers.hoody.com/?readonly=true&toolbar=false" />
</div>

Every panel is live, so all three environments stay visible without switching tabs.


The desktop runs on the server, so any machine with a browser and a connection reaches it: a café, a beach, an airport, a hotel room. If the laptop breaks, another device gets you back to the same session, and there is nothing on the laptop to carry.

One container runs a browser per account, each already signed in. Five team members open the same display URL and see all 20 browsers, so nobody has to be sent a password and nobody has to ask whose turn it is to post. There is nothing to synchronize between machines, because everyone is looking at one desktop.

Start coding on a laptop, continue on a tablet during the commute, finish on a phone at a café. The VS Code window, the terminal, and the open files are the same ones, because it is the same desktop rather than a synchronized copy.

An agent works against the display URL you have open. It:

  1. Takes screenshots to see what you are working on
  2. Types into the IDE through the input API
  3. Runs tests in the terminal
  4. Reads the resulting UI state from the next screenshot

The agent acts on the desktop alongside you rather than proposing edits for you to apply.

The instructor shares one display URL with 30 students. Everyone sees the same screen while the instructor demonstrates, and a student can take control when invited.

The customer sends their display URL. The agent opens it on whatever device is to hand, sees the customer’s desktop, and types the fix. There is no meeting to join and no need to talk the customer through each click.


On a fast connection:

?encoding=h264&video=true

On a slow or metered connection:

?encoding=jpeg&video=false&bandwidth_limit=500000
?readonly=true&steal=false&sharing=true

Viewers can watch but cannot type or click, which rules out accidental input during a demo. The same combination suits monitoring dashboards.

macOS keyboards need the key swap:

?swap_keys=true&keyboard_layout=us

That maps Cmd to Ctrl, so copy and paste keep the keys your hands expect.

?reconnect=true

The client reconnects by itself after an interruption. Worth setting for mobile use, where the connection moves between WiFi and cellular, and on unreliable networks generally.

Fetch thumbnails for a grid of previews, and a full screenshot only when someone looks closely:

// Get thumbnail (small, fast)
const thumb = await fetch('.../api/v1/display/thumbnail/last');
// Full screenshot only when needed
const full = await fetch('.../api/v1/display/screenshot/last');

A thumbnail is 320px wide where a full screenshot is 1920px, so a gallery of them costs a fraction of the bandwidth.

?sound=false&printing=false&clipboard=false&file_transfer=false

Each of these reduces bandwidth and CPU usage. Turn on only what the session needs.


Can I run desktop applications on my phone?

Section titled “Can I run desktop applications on my phone?”

Yes. The phone’s browser renders a full Linux desktop, so VS Code, LibreOffice, GIMP, and browsers all work. None of them execute on the phone; the applications run in the container and the server does the work.

What’s the difference between display-1, display-2, etc.?

Section titled “What’s the difference between display-1, display-2, etc.?”

Each number is a separate desktop environment with its own URL. display-1 might show VS Code, display-2 monitoring tools, display-3 browsers. They are isolated from each other, and one container can hold several of them.

How does multiplayer handle input from several people?

Section titled “How does multiplayer handle input from several people?”

The display server holds the state and synchronizes it. When one person types, the keystroke goes to the display server, which broadcasts the result to every connected client. Input is serialized, one keystroke at a time, while the visual updates reach everyone at once. It works like Google Docs, with a desktop as the shared document.

Yes. The Computer Use API exposes mouse and keyboard control over HTTP. An agent can capture screenshots with GET /screenshot to read the desktop state, move and click the mouse at exact coordinates, type text and press key combinations, manage windows (focus, resize, move, close), and chain compound actions through POST /api/v1/display/input/batch for multi-step workflows. See Computer Use API above.

What happens if my internet drops while using a display?

Section titled “What happens if my internet drops while using a display?”

With ?reconnect=true (the default) the client reconnects once your connection returns. The desktop keeps running on the server throughout, so what you lose is the view rather than the session. On reconnect you see the current state, not the state you left.

Do displays work on touch devices like tablets?

Section titled “Do displays work on touch devices like tablets?”

Yes. The HTML5 client maps touch input on its own: tap for click, pinch for zoom, two-finger drag for scroll. ?keyboard=true brings up a virtual keyboard, which is what makes a touch-only device usable for full desktop control.

Extensively, all through URL parameters: floating menu style, window decorations, toolbar visibility, and dark mode.

See: UI Theming →

How much bandwidth does a display session use?

Section titled “How much bandwidth does a display session use?”

It depends on the encoding and how much the screen changes. H264 video runs 2-5 Mbps for smooth graphics; JPEG updates on a mostly static screen run 100-500 Kbps. ?encoding=jpeg&bandwidth_limit=1000000 caps a session at 1 Mbps.

Yes. A display URL loads in an iframe like any other page. Common patterns are dashboards showing live server state, documentation with an interactive example beside the text, and customer portals with a diagnostic desktop.

Typically 50-200ms, depending on distance to the server and network quality. The H264 encoding is tuned for interactive use. That is comfortable for coding, document editing, and web browsing, and not suitable for gaming or high-frequency trading. Latency is not a current priority for Displays, and further work on it is planned.


Display won’t load or shows a black screen

Section titled “Display won’t load or shows a black screen”

Check the container is running:

Terminal window
curl "https://api.hoody.com/api/v1/containers/{id}?runtime=true" \
-H "Authorization: Bearer $HOODY_TOKEN"

Verify that runtime_info.displays shows an active display with a PID.

Common causes:

  1. Container stopped - Start it:

    Terminal window
    curl -X POST "https://api.hoody.com/api/v1/containers/{id}/start" \
    -H "Authorization: Bearer $HOODY_TOKEN"
  2. Display service not started - Wait 30-60 seconds after container start for the services to initialize

  3. Wrong display number - Check the container’s runtime_info.displays for the available display IDs

Enable reconnect:

?reconnect=true

Reduce quality for stability:

?encoding=jpeg&video=false

Check the network:

  • Test on a different WiFi or cellular connection
  • Verify connectivity to the server
  • Check that a firewall isn’t blocking WebSocket

For macOS users:

?swap_keys=true&keyboard_layout=us

For other layouts:

?keyboard_layout=gb # UK
?keyboard_layout=de # German
?keyboard_layout=fr # French

Enable virtual keyboard on touch devices:

?keyboard=true

Enable the clipboard if it is disabled:

?clipboard=true

Check browser permissions:

  • The browser may block clipboard access
  • Try a different browser
  • Grant clipboard permissions when prompted

Some browsers restrict clipboard access for security. Copy and paste inside the remote desktop always works.

Optimize encoding:

# Fast connection
?encoding=h264
# Slow connection
?encoding=jpeg&video=false

Reduce bandwidth:

?bandwidth_limit=500000 # 500 Kbps max

Disable high-bandwidth features:

?sound=false&video=false

Check server load:

Terminal window
# Query system resources
GET /api/v1/system/resources

Check the URL parameter:

?readonly=false # Enable control

Verify permissions:

  • The display may have proxy permissions that restrict control
  • Check the container’s proxy-permissions configuration
  • Try removing the permissions temporarily for testing

Use session sharing:

?sharing=true&steal=false

Without it, sharing=false admits only one user, and steal=true (the default) lets a new connection kick the current one out. Collaboration needs sharing=true.


Other visual services:

Browser

Chrome automation as a REST API: drive a browser, scrape data, run tests.

Explore Browser →

Code

VS Code over HTTP: start an IDE on demand and share the session.

Explore Code →

Display configuration reference: