Understand Containers
What makes a Hoody container different from a VM, a Docker image, and everything else. Containers →
One login. One container. Then every terminal, file, and database on it is a URL.
In this walkthrough, you’ll spawn a computer, run code on it, and drive it from anywhere — a CLI, a TypeScript program, or plain curl. No deployment step. No certificates. Just HTTP.
Hoody has one control plane and many front doors. They all talk to the same account and the same containers — switching is a paste, not a migration.
Four ways to call it (the walkthrough below tabs through the first three):
| Door | Get in |
|---|---|
| CLI | npm install -g hoody-sdk — or zero-install: npx hoody-sdk <command> |
| TypeScript SDK | npm install hoody-sdk@beta (Node.js 22.19+ or Bun) |
| Raw HTTP | curl with a Bearer token on api.hoody.icu |
| Browser SDK | Pinned CDN build — exposes window.HoodySDK in any static page |
Three doors that need no code:
| Door | What happens |
|---|---|
ssh hoody.com | A sandboxed Hoody CLI that signs you in and launches the Hoody Agent TUI — mouse support included — from any terminal on the planet; nothing to install |
os.hoody.com | The Hoody Agent in any browser — Step 4 |
@hoody.com | Paste it into ChatGPT, Claude, or any web-fetching AI agent — it fetches a Skill and drives your account with a token you give it |
New here? Create an account at hoody.com/signup — it comes with a free-tier server. Then authenticate through your door:
# Install the CLI (it ships inside the hoody-sdk package)...npm install -g hoody-sdk# ...or skip the install entirely: npx hoody-sdk <command>
# Interactive sign-in (`hoody signup` if you don't have an account yet)hoody login// npm install hoody-sdk@betaimport { HoodyClient } from 'hoody-sdk';
const hoody = await HoodyClient.authenticate('https://api.hoody.icu', { username: process.env.HOODY_EMAIL!, password: process.env.HOODY_PASSWORD!,});# Print a token from your CLI session (or mint a scoped one with `hoody auth create`)export HOODY_TOKEN=$(hoody login --print-token)
# Every control-plane call carries it as a Bearer headercurl -H "Authorization: Bearer $HOODY_TOKEN" \ https://api.hoody.icu/api/v1/users/auth/meThe chain is short: your account holds servers, a project organizes containers, and a container is a full Linux computer — Debian, systemd, the works. Your free-tier server is already on the account; grab its id, then build on it.
# Your free-tier server is already there — grab its server_id# (-o json prints the response data itself, no envelope to unwrap)SERVER_ID=$(hoody servers list -o json \ | jq -r '[.[] | select(.status == "active")][0].server_id')
# Create a project, then spawn a Kit container on that serverPROJECT_ID=$(hoody projects create --alias "my-first-project" -o json | jq -r '.id')CONTAINER=$(hoody containers create --project $PROJECT_ID --server-id $SERVER_ID \ --name "dev-box" --hoody-kit -o json)CONTAINER_ID=$(echo "$CONTAINER" | jq -r '.id')SERVER_NAME=$(echo "$CONTAINER" | jq -r '.server_name')
# Wait until status is "running" — then the Kit URLs are livehoody containers get $CONTAINER_ID -o json | jq -r '.status'// Your free-tier server is already there — grab its server_idconst rentals = (await hoody.api.serverRental.list()).data ?? [];const serverId = rentals.find(r => r.status === 'active' && r.server_id)!.server_id!;
// Create a project, then spawn a Kit container on that serverconst project = await hoody.api.projects.create({ alias: 'my-first-project' });const { data: container } = await hoody.api.containers.create(project.data!.id, { server_id: serverId, name: 'dev-box', hoody_kit: true, // preinstall the Kit service layer: terminal, files, sqlite, agent, ...});
// Poll until "running", then scope a client to the boxwhile ((await hoody.api.containers.get(container!.id)).data!.status !== 'running') { await new Promise(r => setTimeout(r, 1000));}const box = await hoody.withContainer(container!);# Your free-tier server is already there — grab its server_idSERVER_ID=$(curl -s -H "Authorization: Bearer $HOODY_TOKEN" \ https://api.hoody.icu/api/v1/servers \ | jq -r '[.data[] | select(.status == "active")][0].server_id')
# Create a projectPROJECT_ID=$(curl -s -X POST https://api.hoody.icu/api/v1/projects/ \ -H "Authorization: Bearer $HOODY_TOKEN" -H "Content-Type: application/json" \ -d '{"alias": "my-first-project"}' | jq -r '.data.id')
# Spawn a Kit container on that serverCONTAINER=$(curl -s -X POST https://api.hoody.icu/api/v1/projects/$PROJECT_ID/containers \ -H "Authorization: Bearer $HOODY_TOKEN" -H "Content-Type: application/json" \ -d '{"server_id": "'"$SERVER_ID"'", "name": "dev-box", "hoody_kit": true}')CONTAINER_ID=$(echo "$CONTAINER" | jq -r '.data.id')SERVER_NAME=$(echo "$CONTAINER" | jq -r '.data.server_name')
# Wait until status is "running" — then the Kit URLs are livecurl -s -H "Authorization: Bearer $HOODY_TOKEN" \ https://api.hoody.icu/api/v1/containers/$CONTAINER_ID | jq -r '.data.status'Every Kit service on your container answers at a stable HTTPS URL, the moment it exists:
https://{projectId}-{containerId}-{service}-{index}.{serverName}.containers.hoody.icuEighteen services share that grammar — terminal, files, browser, display, code, exec, daemon, cron, watch, sqlite, curl, pipe, run, notes, notifications, tunnel, proxyLogs, and a built-in AI agent — two of them under short URL slugs (notifications → n, proxyLogs → logs) — plus http-{port} for anything you start on a port. Let’s use three of them.
# Execute a shell command on your containerhoody terminal sessions exec -c $CONTAINER_ID --ephemeral \ --command "echo 'Hello from the cloud!'"// One-shot helper: runs, waits, returns the outputconst result = await box.terminal.execution.execute({ command: "echo 'Hello from the cloud!'", wait: true });console.log(result.data.stdout); // → Hello from the cloud!# Start the command...COMMAND_ID=$(curl -s -X POST \ "https://$PROJECT_ID-$CONTAINER_ID-terminal-1.$SERVER_NAME.containers.hoody.icu/api/v1/terminal/execute?ephemeral=true" \ -H "Content-Type: application/json" \ -d '{"command": "echo Hello from the cloud!", "wait": true}' | jq -r '.command_id')
# ...and fetch its outputcurl -s "https://$PROJECT_ID-$CONTAINER_ID-terminal-1.$SERVER_NAME.containers.hoody.icu/api/v1/terminal/result/$COMMAND_ID" \ | jq -r '.stdout'# Read a file from your container (-o raw prints it verbatim)hoody files get -c $CONTAINER_ID /etc/hostname -o rawconst file = await box.files.get('/etc/hostname', { responseType: 'text' });console.log(file.data);curl "https://$PROJECT_ID-$CONTAINER_ID-files-1.$SERVER_NAME.containers.hoody.icu/api/v1/files/etc/hostname"Every Kit container ships a zero-setup SQLite service — nothing to provision, nothing to connect.
# Run a SQL transaction on the built-in SQLitehoody db exec-transaction -c $CONTAINER_ID --db app --create-db-if-missing \ --transaction '[{"statement":"CREATE TABLE IF NOT EXISTS greetings (message TEXT)"},{"statement":"INSERT INTO greetings VALUES ('"'"'Hello, Hoody!'"'"')"},{"query":"SELECT * FROM greetings"}]' \ -o jsonconst result = await box.sqlite.database.executeTransaction( { transaction: [ { statement: "CREATE TABLE IF NOT EXISTS greetings (message TEXT)" }, { statement: "INSERT INTO greetings VALUES ('Hello, Hoody!')" }, { query: "SELECT * FROM greetings" }, ], }, { db: 'app', create_db_if_missing: true },);console.log(result.data);// → results: [..., { success: true, resultHeaders: ['message'], resultSet: [{ message: 'Hello, Hoody!' }] }]curl -X POST "https://$PROJECT_ID-$CONTAINER_ID-sqlite-1.$SERVER_NAME.containers.hoody.icu/api/v1/sqlite/db?db=app&create_db_if_missing=true" \ -H "Content-Type: application/json" \ -d '{"transaction": [{"statement": "CREATE TABLE IF NOT EXISTS greetings (message TEXT)"}, {"statement": "INSERT INTO greetings VALUES ('"'"'Hello, Hoody!'"'"')"}, {"query": "SELECT * FROM greetings"}]}'You’ve been driving your computer through the API. Now open the visual experience: go to os.hoody.com, sign in, and you land with the Hoody Agent front and center — chat with it, run terminals, browse files, and manage every container from one screen.
The part that matters: the Agent is not hosted by us. It’s served by your own container, so every Kit container you spin up carries its own agent, reachable directly at:
https://{projectId}-{containerId}-agent-1.{serverName}.containers.hoody.icuOpen it in any browser. Embed it in an iframe. Share it with a teammate. Phone, laptop, TV, tablet — same environment, same state. The inception is real: the agent that manages your containers is itself running in a container.
Three more ways into the exact same account — each one sentence away:
ssh hoody.com — the Hoody Agent, rendered as a TUI. The gateway drops you into a memory-only sandboxed Hoody CLI session — nothing persists between connections — which launches hoody agent, mouse support included, on any machine with an ssh client. Nothing to install; sign in inside the CLI (or pass a scoped token as the SSH username — ssh <hdy_token>@hoody.com — for scripts and CI).@hoody.com — paste it into ChatGPT, Claude, Gemini, or any web-fetching AI agent. The agent fetches a Skill — a structured HTTP map of every capability on this page — and drives your account with a token you give it. The SSH of the AI era.https://cdn.jsdelivr.net/npm/hoody-sdk@1.0.0-beta.9/dist/hoody-sdk.browser.min.js) exposes window.HoodySDK in any static page. Hand pages short-lived scoped tokens, never your account credentials.You just:
curlservers list returns nothing active — a fresh free-tier server may still be provisioning, and provisioning time varies. Keep polling; it appears with status: "active" and a non-null server_id.running yet (poll it), the container was created with --no-hoody-kit / hoody_kit: false, or your URL uses server_id where the hostname needs server_name.api.hoody.icu — control-plane calls always need the Bearer token; re-run hoody login --print-token and re-export HOODY_TOKEN.terminal, files, db, …) target a container via -c $CONTAINER_ID; pass it explicitly or export it once as HOODY_CONTAINER_ID.projectId-containerId pair is the grant. That’s the open-by-default posture — add permission rules when you need auth groups, IP pins, or default-deny.server_id and server_name? server_id identifies the server in API calls (creating containers, rentals); server_name is the DNS segment in every container URL. Both come back in the container object.http-{port} routes are cataloged in The Hoody Kit.We didn’t add HTTP to computing. We rebuilt computing as HTTP. Your computer is already running. You just haven’t shared the URL yet.
Understand Containers
What makes a Hoody container different from a VM, a Docker image, and everything else. Containers →
Route and Lock Down URLs
How the proxy turns everything into a URL — and how permission rules gate it. Proxy →
Build Your First API
Drop a script in a container, get an authenticated HTTPS endpoint. Your First API →
Explore the Kit
All 18 HTTP services built into every Kit container, including the AI agent. The Hoody Kit →