Skip to content
Hoody.com

Multi-tenancy on most platforms is a permission problem: every resource is visible, and nested IAM policies decide who may touch what. Scope one policy wrong and a CI token can delete production. Hoody splits the problem across two primitives.

Projects organize your resources: containers, quotas, team permissions. They work like folders for your computers.

Realms isolate API visibility. A token restricted to a realm cannot see resources outside it. The API does not answer “permission denied” or “unauthorized”; the resources are absent from every response.


A project is a boundary for containers. Every container belongs to exactly one project: you create every container inside a project, filter container listings by project, and share access with teammates at the project level. Projects give you:

  • Container grouping: frontend, backend, ml-pipeline, staging
  • Team permissions: read, edit, delete per member
  • Quotas: container limits per project (max_containers)
  • Billing: costs tracked per project
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

Every multi-tenant system has to stop a token from seeing resources it should not see. The usual answer is permission checks: the resource exists, and the token is denied access to it. A misconfigured permission can therefore expose everything; one overly permissive role or one leaked token puts the whole account on the table.

A realm does not restrict access to resources. It filters them out of the API: to a realm-scoped token, out-of-realm resources appear in no response.

A realm is a 24-hex identifier (e.g., 507f1f77bcf86cd799439011) that scopes API visibility. Resources carry 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.com
Realm-scoped: https://507f1f77bcf86cd799439011.api.hoody.com

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 a realm-restricted token calls the unscoped host, the API rejects the request. Such a token can only operate through a realm-scoped host.

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

Projects and realms solve different problems, and most deployments use both. Projects are organizational. You use them to group containers by function: frontend, backend, data-pipeline. Team members get permissions at the project level, and 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, and API visibility is scoped per realm.

Combined, a deployment looks like this:

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 as much as it needs to. It cannot see or modify production, and no API response it receives reveals that the production realm exists.

When you create a container from a realm-scoped host, the API merges the subdomain realm into the container’s realm_ids. The parent project must already belong to that realm; otherwise the API rejects the create with 403: "Project is not in requested realm {realmId}". This prevents a realm-scoped container from ending up under a project outside that realm.

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; the API filters them out

There is one case where a realm-restricted token can call the unscoped base host: discovery. A client holding a freshly issued realm-restricted token does not yet know which realm host to use, so Hoody allows GET /api/v1/auth/tokens/me on https://api.hoody.com 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, read the realm list, and switch to the matching host.


The most common layout. A production token cannot delete staging containers, even by accident, and a 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)

For 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

Give each AI agent a realm-restricted token scoped to the containers it manages. The rest of your account is out of the agent’s reach: outside its realm, the token’s API contains nothing.

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 ends immediately.

Realms, auth tokens, and the SDK combine into a resale pattern: every customer you onboard gets their own isolated Hoody API, with containers, terminals, files, browsers, AI agents, cron, and databases, without you building any of it. You are the provider and Hoody is the infrastructure; your customers hold a token you issued rather than a Hoody account.

import { HoodyClient } from 'hoody-sdk';
// You are the platform provider: log in with account credentials
const hoody = await HoodyClient.authenticate('https://api.hoody.com', {
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 and get endpoints never return it again; store it now.
const customerToken = created.data!.token;
// Hand the customer: token + https://507f1f77bcf86cd799439011.api.hoody.com

What each customer gets:

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:

  • Permissions: external_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 immediately without deleting the token
  • Public profiles: attach metadata (company name, tier, display info) to tokens via public_storage. Pair it with a public_key (ED25519, 64 hex chars) so the profile is resolvable for third-party lookup; both can be set at creation or later via PUT /api/v1/auth/tokens/me/public-profile (at least one of the two fields must be provided).

The pattern is the same at ten customers or ten thousand. Each customer gets a realm-scoped endpoint and a token, and you manage them all through the same SDK.


  1. Use projects for application boundaries: frontend, backend, ops, ml-pipeline.
  2. Use realms for environment and tenant isolation: production, staging, per-client realms.
  3. Issue separate auth tokens per realm and per application; it makes auditing and revocation easier.
  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 and realms isolate: a project groups containers for a team or application, and a realm decides which resources a token’s API contains. Together they cover multi-tenancy without a separate policy layer to maintain.