Skip to content

Every other platform treats multi-tenancy as an afterthought. IAM roles. Org charts. Nested permission hierarchies that take a PhD to debug. You end up with a CI token that can nuke production because someone forgot to scope it.

Hoody built isolation into the architecture from day one. Two primitives. Two problems solved.

Projects organize your resources — containers, quotas, team permissions. Think of them as folders for your computers.

Realms isolate API visibility. A realm-scoped token physically cannot see resources outside its realm. Not “permission denied.” Not “unauthorized.” The resources do not exist in its universe.

Together, they give you organizational clarity (projects) and security isolation (realms) without a 47-page IAM policy document.


A project is a boundary for containers. Every container belongs to exactly one project. Projects give you:

  • Container groupingfrontend, backend, ml-pipeline, staging
  • Team permissionsread, edit, delete per member
  • Quotas — resource limits, prespawn settings
  • Billing — costs tracked per project

When you create a container, you create it inside a project. When you list containers, you can filter by project. When you share access with a teammate, you share at the project level.

Terminal window
# Create a project
hoody projects create --alias "production-api"
# List your projects
hoody projects list
# Create a container inside the project
hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "api-server" \
--hoody-kit

Here is the question that every multi-tenant system eventually faces: how do you stop a token from seeing things it should not see?

Traditional platforms add permission checks. The resource exists, the token just cannot access it. That means a misconfigured permission can expose everything. One overly permissive role, one leaked token, and the blast radius is your entire account.

Realms take a fundamentally different approach. A realm does not restrict access to resources. It removes resources from existence.

A realm is a 24-hex identifier (e.g., 507f1f77bcf86cd799439011) that acts as an API isolation scope. Resources have a realm_ids: string[] field. Auth tokens can be restricted to specific realms. And the API host itself carries the realm scope:

Unscoped: https://api.hoody.icu
Realm-scoped: https://507f1f77bcf86cd799439011.api.hoody.icu

When you call a realm-scoped host:

  • Read operations return only resources whose realm_ids includes that realm
  • Write operations automatically merge the realm into realm_ids on the created resource

When your token is realm-restricted and you call the unscoped host, the API rejects it. The token literally cannot operate without a realm scope.

Terminal window
# List containers visible in a specific realm
hoody --base-url "https://507f1f77bcf86cd799439011.api.hoody.icu" \
containers list
# Create a project in a realm (auto-assigned realm_ids)
hoody --base-url "https://507f1f77bcf86cd799439011.api.hoody.icu" \
projects create --alias "prod-services"
# Discover your token's realm restrictions
hoody auth get-current

Projects and realms solve different problems, and they layer perfectly.

Projects are organizational. You use them to group containers by function — frontend, backend, data-pipeline. Team members get permissions at the project level. Quotas are set per project.

Realms are security boundaries. You use them to isolate environments — production, staging, client-A, client-B. Auth tokens are restricted per realm. API visibility is scoped per realm.

The combination is powerful:

Realm: production
├── Project: api-services
│ ├── Container: auth-server
│ ├── Container: user-api
│ └── Container: payment-api
├── Project: frontend
│ ├── Container: web-app
│ └── Container: admin-dashboard
└── Project: infrastructure
├── Container: monitoring
└── Container: log-aggregator
Realm: staging
├── Project: api-services
│ └── Container: staging-api (copy of prod)
└── Project: frontend
└── Container: staging-web (copy of prod)

A CI token restricted to the staging realm can deploy all day long. It cannot see, touch, or even know that the production realm exists.

When you create a container from a realm-scoped host, the parent project must already belong to that realm. Otherwise Hoody rejects the request with a 403.

This prevents a subtle but dangerous inconsistency: creating a realm-scoped container under an out-of-scope project.

Terminal window
# Create a realm-restricted auth token for your CI pipeline
hoody auth create \
--alias "ci-staging-deploy" \
--expires-at "2026-07-12T00:00:00Z" \
--realm-ids "60d5f1f3a3b4f9c3e8a1b2c3" \
--no-allow-no-realm
# This token can only operate on the staging realm
# It cannot see production resources -- they do not exist in its world

There is one case where a realm-restricted token can call the unscoped base host: discovery.

A freshly issued realm-restricted token does not know which realm host to use. So Hoody allows GET /api/v1/auth/tokens/me on https://api.hoody.icu for any token. The response carries a restrictions object that tells the client:

  • restrictions.allowed_realm_ids — which realms this token can access
  • restrictions.requires_realm_scope — whether a realm-scoped host is required
  • restrictions.active_realm_id — the currently active realm (if any)

SDK clients and automation tools use this to self-configure on startup. Call /me, learn your realms, switch to the correct host.


The most common pattern. Production token cannot accidentally delete staging containers. Staging token cannot see production data.

Realm: production → Token: prod-deploy (expires: never, IP-locked)
Realm: staging → Token: ci-staging (expires: 90d)
Realm: development → Token: dev-team (expires: 30d)

SaaS multi-tenancy. Each client’s containers live in their own realm. Client-scoped tokens cannot see other clients’ infrastructure.

Realm: client-acme → Token: acme-api-key
Realm: client-globex → Token: globex-api-key
Realm: internal → Token: admin-full-access

The safest approach for automation. Each AI agent gets a realm-restricted token scoped to only the containers it manages. A misbehaving agent cannot affect containers outside its realm.

Realm: agent-deploy → Agent deploys to 3 containers
Realm: agent-monitor → Agent reads metrics from 10 containers
Realm: agent-test → Agent runs tests in isolated containers

Give a freelancer, auditor, or support engineer access to specific containers without exposing your entire account. Create a realm, assign the relevant containers, issue a restricted token with a short expiration and IP allowlist.

Terminal window
# Create a short-lived, IP-locked token for a freelancer
hoody auth create \
--alias "freelancer-debug" \
--expires-at "2026-04-20T00:00:00Z" \
--ip-whitelist "203.0.113.44" \
--realm-ids "507f1f77bcf86cd799439011" \
--no-allow-no-realm

When the work is done, disable or delete the token. Access vanishes instantly.

This is the pattern that changes what you can ship. Combine realms, auth tokens, and the full SDK, and every customer you onboard gets their own isolated Hoody API — containers, terminals, files, browsers, AI agents, cron, databases — without you building any of it.

You are the provider. Hoody is the infrastructure. Your customers never know.

import { HoodyClient } from '@hoody-ai/hoody-sdk';
// You: the platform provider — log in with account credentials
const hoody = await HoodyClient.authenticate('https://api.hoody.icu', {
username: process.env.PROVIDER_EMAIL!,
password: process.env.PROVIDER_PASSWORD!,
});
// Pick a realm ID for the new customer (24-hex)
const realmId = '507f1f77bcf86cd799439011';
// 1. Pre-create at least one project in the realm.
// The external_customer template denies projects.create,
// so the customer cannot create one themselves.
const project = await hoody.api.projects.create({
alias: 'acme-workspace',
realm_ids: [realmId],
});
// 2. Optionally pre-create containers in that project / realm.
// Anything you want the customer to see must carry their realm_id.
await hoody.api.containers.create(project.data!.id, {
server_id: process.env.SERVER_ID!,
name: 'acme-box-1',
hoody_kit: true,
realm_ids: [realmId],
});
// 3. Issue the customer a realm-scoped token
const created = await hoody.api.authTokens.create({
alias: 'Customer: Acme Corp',
permission_template: 'external_customer',
realm_ids: [realmId],
allow_no_realm: false,
ip_whitelist: ['203.0.113.0/24'],
expires_at: '2026-12-31T00:00:00Z',
});
// The token value is returned ONCE, at creation.
// List & get endpoints never return it again — store it now.
const customerToken = created.data!.token;
// Hand the customer: token + https://507f1f77bcf86cd799439011.api.hoody.icu

What each customer gets (without you building it):

CapabilityHow it works
Isolated containersRealm filtering — only their resources exist
Terminal accessbox.terminal.* — run commands, stream output
File managementbox.files.* — CRUD, glob, grep, archives
Browser automationbox.browser.* — headless Chromium, screenshots
GUI app streamingbox.display.* — X11 display in a URL
Scheduled tasksbox.cron.* — crontab via REST
Database accessbox.sqlite.* — SQL queries, key-value store
AI agentbox.agent.* — sessions, prompts, memory
Notificationsbox.notifications.* — push to desktop/mobile

What you control:

  • Permissionsexternal_customer blocks billing, AI, server management and projects.create by default (so you must pre-create projects in the realm). Use dev_team, read_only, or fully custom permissions for finer control.
  • IP allowlists — lock tokens to customer IP ranges
  • Expiration — auto-revoke after a date
  • Enable/disable — suspend access instantly without deleting the token
  • Public profiles — attach metadata (company name, tier, display info) to tokens via public_storage. Requires a public_key (ED25519, 64 hex chars) — can be set at creation or later via PUT /auth/tokens/me/public-profile. public_storage without a public_key is rejected.

Scale to ten customers or ten thousand. Each gets their own realm-scoped endpoint, their own token, their own universe. You manage it all through the same SDK.


  1. Use projects for application boundariesfrontend, backend, ops, ml-pipeline
  2. Use realms for environment and tenant isolationproduction, staging, per-client realms
  3. Issue separate auth tokens per realm and per application — easier auditing, easier revocation
  4. Use the bootstrap endpoint (GET /api/v1/auth/tokens/me) in SDK and automation startup flows to self-configure realm hosts

Terminal window
# List all realm IDs across your resources
hoody realms list

Projects organize. Realms isolate. One gives you clarity. The other gives you walls. Together, they give you multi-tenancy that does not require a 47-page IAM policy to understand.