Hoody Code Orchestrator
Section titled “Hoody Code Orchestrator”The Hoody Code Orchestrator is the control plane inside each Hoody container that exposes the browser-based VS Code interface. It loads the IDE shell, gates access behind password-based authentication, proxies HTTP and WebSocket traffic to applications running on local ports inside the container, and serves the supporting static, PWA, and security files that ship with the editor.
All endpoints run inside the container’s code service and are reached at:
https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com
VS Code Interface
Section titled “VS Code Interface”GET /api/v1/code
Section titled “GET /api/v1/code”Returns the main VS Code web interface. The response is the HTML shell that bootstraps the editor in the browser.
When authentication is enabled and the request has no session cookie, the server responds with a 302 redirect to /login. When no folder or workspace query parameter is supplied, the IDE reopens the folder or workspace that was open in the previous session, or the path that was passed on the command line.
Add ?extension=PUBLISHER.NAME to launch in extension-only mode: the file explorer is hidden and the extension’s UI is focused, which is useful for embedding an extension as a standalone web app.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
folder | query | string | No | Absolute path to the folder to open. Takes precedence over workspace. Stored in settings for the next session. |
workspace | query | string | No | Absolute path to a .code-workspace file. Used when folder is not provided. |
extension | query | string | No | Extension identifier in PUBLISHER.NAME form. Opens the IDE in extension-only mode (file explorer hidden, extension UI focused). Example: ms-python.python. |
ew | query | boolean | No | Empty Window flag. When present, clears the last opened folder or workspace from settings. |
locale | query | string | No | IETF language tag controlling the UI language (for example en, fr, zh-CN). |
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code?folder=/home/user/project&locale=en"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
await client.code.vscode.getVSCode({ folder: '/home/user/project', locale: 'en',});"<!DOCTYPE html>\n<html>\n<head>\n <title>VS Code</title>\n <meta charset=\"utf-8\">\n</head>\n<body>\n <!-- VS Code web interface -->\n</body>\n</html>"The response is sent with Content-Type: text/html; charset=utf-8 and a Content-Security-Policy header. The CSP is automatically extended when --external-js or --external-css flags are configured.
{ "headers": { "Location": "/login?to=%2F%3Ffolder%3D%2Fhome%2Fuser%2Fproject" }}The redirect target preserves the original query string so the user lands back on the requested folder after authenticating.
POST /api/v1/code/mint-key
Section titled “POST /api/v1/code/mint-key”Generates or retrieves the server’s web key half used for VS Code’s secure communications. The key is 256 bits (32 bytes), created once on first request, and persisted at user-data-dir/serve-web-key-half so it is reused across restarts.
The response body is the raw 32-byte binary key. Use a binary-safe client when fetching this endpoint.
curl -X POST "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/mint-key" \ -o serve-web-key-halfimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const key = await client.code.vscode.mintKey();The SDK returns the 32-byte key as a Uint8Array.
{ "contentType": "application/octet-stream", "schema": "binary", "minLength": 32, "maxLength": 32}The body is 32 raw bytes.
GET /api/v1/code/manifest.json
Section titled “GET /api/v1/code/manifest.json”Returns the Progressive Web App manifest used to install Hoody Code. The manifest name is configurable via --app-name. Display mode is fullscreen with window-controls-overlay listed under display_override.
Parameters
Section titled “Parameters”This endpoint takes no parameters.
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/manifest.json"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const manifest = await client.code.vscode.getManifest();{ "name": "hoody-code", "short_name": "hoody-code", "start_url": ".", "display": "fullscreen", "display_override": ["window-controls-overlay"], "description": "Run Code on a remote server.", "icons": [ { "src": "/_static/out/browser/media/favicon-192.png", "type": "image/png", "sizes": "192x192", "purpose": "any" }, { "src": "/_static/out/browser/media/favicon-512.png", "type": "image/png", "sizes": "512x512", "purpose": "any" }, { "src": "/_static/out/browser/media/favicon-maskable-512.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" } ]}Authentication
Section titled “Authentication”The authentication flow uses session cookies. The endpoints below are only active when the container is started with password authentication enabled.
GET /api/v1/code/login
Section titled “GET /api/v1/code/login”Returns the login page HTML. If the visitor is already authenticated, the server responds with a 302 redirect to the requested target.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
to | query | string | No | URL to redirect to after successful login. Defaults to /. |
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/login?to=/projects/hoody"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
await client.code.auth.getLoginPage({ to: '/projects/hoody' });"<!DOCTYPE html>\n<html>\n<head>\n <title>Login - hoody-code</title>\n</head>\n<body>\n <form method=\"POST\" action=\"/api/v1/code/login\">\n <input type=\"password\" name=\"password\" autofocus />\n <button type=\"submit\">Sign in</button>\n </form>\n</body>\n</html>"{ "headers": { "Location": "/projects/hoody" }}The redirect target is the to query parameter or / when none is provided.
POST /api/v1/code/login
Section titled “POST /api/v1/code/login”Authenticates a user with a password and, on success, sets the session cookie and redirects to the requested target.
Login attempts are rate limited to 2 per minute and 12 per hour. Failed attempts are logged with the source IP and user agent.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
to | query | string | No | URL to redirect to after successful login. Defaults to /. |
Request Body
Section titled “Request Body”The body is application/x-www-form-urlencoded.
| Name | Type | Required | Description |
|---|---|---|---|
password | string | Yes | Password to authenticate with. |
curl -X POST "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/login?to=/projects/hoody" \ -d "password=correct-horse-battery-staple" \ -c cookies.txtimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
await client.code.auth.login({ to: '/projects/hoody' });The SDK reads the password from the HOODY_PASSWORD environment variable and POSTs the form on your behalf. Call await client.code.auth.login(); to redirect to the default target of /.
"<!DOCTYPE html>\n<html>\n<head>\n <title>Login - hoody-code</title>\n</head>\n<body>\n <form method=\"POST\" action=\"/api/v1/code/login\">\n <p class=\"error\">Invalid password.</p>\n <input type=\"password\" name=\"password\" autofocus />\n <button type=\"submit\">Sign in</button>\n </form>\n</body>\n</html>"The 200 response is returned when the credentials are wrong or when the rate limit is exceeded. The HTML body contains the user-visible error message.
| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PASSWORD | Invalid password | The password provided is incorrect | Check your password and try again |
RATE_LIMITED | Too many login attempts | Rate limit exceeded (2 attempts/min or 12 attempts/hour) | Wait a few minutes before trying again |
{ "headers": { "Location": "/projects/hoody", "Set-Cookie": "hdy-session=eyJ1c2VyIjoiZGV2In0; HttpOnly; SameSite=Lax; Path=/" }}On success the response sets a session cookie and redirects to the to query parameter (or / by default). On failure the redirect goes back to the login page.
GET /api/v1/code/logout
Section titled “GET /api/v1/code/logout”Clears the session cookie and redirects to the home page. Only available when authentication is enabled.
Parameters
Section titled “Parameters”This endpoint takes no parameters.
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/logout"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
await client.code.auth.logout();{ "headers": { "Location": "/", "Set-Cookie": "hdy-session=; Max-Age=0; Path=/" }}The Set-Cookie header expires the session cookie.
Port Proxying
Section titled “Port Proxying”The orchestrator exposes applications running on local ports inside the container through two complementary routes. The proxy route strips the prefix before forwarding; the absproxy route keeps the full path so the proxied app can be aware it is mounted under a subpath.
Both routes require authentication unless the request is a OPTIONS preflight and --skip-auth-preflight is set.
GET /api/v1/code/proxy/{port}/{path}
Section titled “GET /api/v1/code/proxy/{port}/{path}”Proxies a request to a service running on a local port. The /proxy/:port prefix is stripped before forwarding, so https://.../proxy/3000/api/users reaches the upstream as http://localhost:3000/api/users.
All HTTP methods and WebSocket upgrades are supported.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
port | path | integer | Yes | Local port to proxy to (1024-65535). |
path | path | string | Yes | Path to append to the proxied request. |
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/proxy/3000/api/users"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const response = await client.code.proxy.resolve(3000, 'api/users');{ "description": "Proxied response", "body": "Whatever the upstream service on port 3000 returned for /api/users. The status code, headers, and body are passed through unchanged."}{ "description": "Unauthorized (authentication required)", "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
AUTHENTICATION_REQUIRED | Authentication required | You must be logged in to access proxied ports | Log in with your password first |
{ "description": "Bad gateway (port not accessible)", "statusCode": 502, "error": "Bad Gateway", "message": "Cannot connect to local port 3000"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PORT_UNREACHABLE | Cannot connect to local port | The specified port is not accessible or no service is running | Verify the application is running on the specified port |
GET /api/v1/code/absproxy/{port}/{path}
Section titled “GET /api/v1/code/absproxy/{port}/{path}”Proxies a request to a service running on a local port while keeping the full path, including the /absproxy/:port/ prefix. Use this when the proxied app needs to be aware that it is mounted under a subpath; the upstream must be configured to serve from /absproxy/:port/.
Set --abs-proxy-base-path on the server to customize the base path.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
port | path | integer | Yes | Local port to proxy to. |
path | path | string | Yes | Path to append to the proxied request. The /absproxy/:port/ prefix is preserved when forwarding. |
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/api/v1/code/absproxy/8080/health"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const response = await client.code.proxy.resolveAbsolute(8080, 'health');{ "description": "Proxied response", "body": "Whatever the upstream service on port 8080 returned. The full path, including /absproxy/8080/, is preserved when forwarding."}{ "description": "Unauthorized", "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}{ "description": "Bad gateway", "statusCode": 502, "error": "Bad Gateway", "message": "Cannot connect to local port 8080"}Static Assets
Section titled “Static Assets”GET /_static/{path}
Section titled “GET /_static/{path}”Serves static files from the build directory, including the compiled JavaScript and CSS bundles, images, icons, and the service worker.
Cache headers are tied to the build commit in production. In development mode caching is disabled. The service worker at /_static/out/browser/serviceWorker.js is served with Service-Worker-Allowed: / so it can register at the root scope.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
path | path | string | Yes | Path to the static file relative to the build directory (for example out/browser/workbench.js). |
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/_static/out/browser/workbench.js"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const bytes = await client.code.static.get('out/browser/workbench.js');{ "description": "Static file", "headers": { "Cache-Control": "public, max-age=31536000, immutable", "Service-Worker-Allowed": "/" }}Service-Worker-Allowed is set only for service worker files.
{ "description": "File not found", "statusCode": 404, "error": "Not Found", "message": "The requested static asset does not exist"}GET /hoody-code/injected/{script}
Section titled “GET /hoody-code/injected/{script}”Serves an injected JavaScript file from the extra/injected/ directory. When --hoody-code is enabled, these scripts are loaded sequentially after window.load on every VS Code page, so they can customize behavior and branding without modifying the build.
The same scripts are also available under /vscode/hoody-code/injected/{script}.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
script | path | string | Yes | Filename of the injected script to load. |
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/hoody-code/injected/hdy-custom.js"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const source = await client.code.static.getInjectedScript('hdy-custom.js');{ "description": "JavaScript file", "contentType": "application/javascript", "body": "// Custom Hoody Code injection\nwindow.HDY_CUSTOM = { initialized: true };"}{ "description": "Script not found", "statusCode": 404, "error": "Not Found", "message": "The requested injected script does not exist"}GET /robots.txt
Section titled “GET /robots.txt”Returns the robots.txt file describing the crawler policy for the editor’s web origin.
Parameters
Section titled “Parameters”This endpoint takes no parameters.
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/robots.txt"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const robots = await client.code.static.getRobots();{ "description": "Robots file", "contentType": "text/plain", "body": "User-agent: *\nDisallow: /\n"}GET /security.txt
Section titled “GET /security.txt”Returns the security.txt file used for coordinated vulnerability disclosure. The same content is also available at /.well-known/security.txt.
Parameters
Section titled “Parameters”This endpoint takes no parameters.
curl "https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com/security.txt"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-code-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
const policy = await client.code.static.getSecurityPolicy();{ "description": "Security policy", "contentType": "text/plain", "body": "Contact: mailto:security@hoody.com\nExpires: 2026-12-31T23:59:59z\nPreferred-Languages: en\n"}