Workspaces and tenants

Running more than one isolated workspace on a single instance — what changes when you do, and what to set up first.

#Workspaces and tenants

Every instance starts with one workspace. You can add more, and each one is fully isolated: its own users, roles, collections, files, settings, branding and sign-in configuration. Nothing in one workspace can see or reach another.

This is optional and dormant. An instance with one workspace behaves exactly as it always has — nothing to configure, no cost, no performance difference. The capability only wakes up when you give a workspace an address of its own.

#The moment that changes everything

While every workspace shares your instance's single address, requests cannot be told apart by hostname, so they all resolve to your primary workspace. That is the normal single-workspace state.

Giving any workspace a subdomain or a custom domain switches the whole instance to host-based routing. From that request onward:

  • Each workspace answers only on its own address.
  • Your primary workspace answers on the addresses it already used.
  • A hostname that matches nothing is refused, not quietly handed to the primary workspace.

That last point is deliberate. The alternative — falling back to the primary workspace for an unrecognised host — means a misconfigured DNS record silently serves one workspace's data at another's address, and nothing looks wrong.

Because it changes routing for everything, the first host claim is gated. Settings → Tenants shows you what is missing before you try, rather than letting you fill in a form and hit an error, and System → Tenants reports the same readiness without offering the controls.

#What to have ready

Two things must be true before a workspace can take a host:

  • INTERNAL_PROXY_SECRET set on both workers. The dashboard proxies its API calls, which replaces the browser's hostname with the gateway's own. Without this secret the gateway cannot recover the original, so every workspace would resolve to the primary one — and the instance would look like it was working.

    You should not have to do anything here. npm run setup and npm run update generate this value and set it on both workers, including repairing an instance where it ended up on only one of them. It is listed because the readiness check names it, and because an instance set up before that existed — or one whose Cloudflare token lacked the permission at the time — will still be missing it. Re-running npm run update is the fix; setting it by hand on both workers with the same value also works.

  • BETTER_AUTH_URL set. The instance has to recognise its own address, or once routing is on it would refuse your own dashboard.

For subdomains you also need a wildcard DNS record and a route on your Cloudflare zone, pointing *.yourdomain.com at the gateway. That part is done in the Cloudflare dashboard, not here.

#Checking the wildcard actually works

Neither the DNS record nor the route is readable from inside the instance, so the instance cannot confirm them — but it can tell you whether traffic for such a hostname has ever arrived, which is the same evidence. Ask it:

curl -s -H "Authorization: Bearer $TOKEN" "https://your-instance/api/v1/system/tenancy"

The routing block reports base_domain, how many workspaces are routed by subdomain, and whether a request on a subdomain of your base domain has reached the instance:

{
	"routing": {
		"base_domain": "configured",
		"subdomain_workspaces": 3,
		"wildcard_traffic_seen": false,
		"warning": "…"
	}
}

If workspaces are routed by subdomain and wildcard_traffic_seen is false, you get a warning naming what to check. Read it the way you read any such flag: false immediately after a deploy means nothing — the instance may simply not have served that traffic yet. It is meaningful once your instance has been up and serving for a while and nobody has reached those hostnames.

The point is the timing. Without this, the first sign that the wildcard was never wired is a customer telling you their address does not work. Create one workspace, visit its hostname, and check this before onboarding anyone.

Keep tenant hostnames one level deepacme.yourdomain.com, not team.acme.yourdomain.com. Cloudflare's universal certificate only covers the first level, and a deeper name would need a certificate you provision yourself.

#The lifecycle

Workspaces move through: provisioning → active → suspended → archiving.

  • Create reserves the slug, allocates storage and seeds the six standard roles in one write. If any part fails, none of it lands — a workspace with no roles would appear healthy in the list and then fail every request it served, so the list shows a provisioning-health flag for exactly this.
  • Suspend stops the workspace answering without touching its data. Reversible.
  • Release frees the hostnames immediately, so the address can be reused, and marks the workspace archived. The data is retained, not deleted.

Your primary workspace cannot be suspended or released. It is the instance's own, it holds the operator console, and it is what an unrecognised host still resolves to while routing is dormant.

#Plans and limits

Two separate operator controls, answering different questions:

  • Entitlementswhat this workspace's plan includes at all. AI operations, custom code execution, PDF export, and so on. Set from the plan editor on Settings → Tenants.
  • Limitshow much of it they may use. Covered in Usage and metering.

Both sit above the workspace's own permissions. A workspace admin decides who inside it may run an AI operation; you decide whether that workspace has AI operations at all. A workspace cannot change either — one that could raise its own ceiling is not limited.

A workspace with no plan is unrestricted, which is where every workspace starts and where every existing instance stays until you write one.

#Backing a workspace up on a schedule

Separate from the instance-wide database backups on System → Settings → Backups, which are your disaster recovery for the whole instance. This one is a workspace asking for a copy of its own collections and records.

Turn on Back Up This Workspace for that workspace and set how long to keep them. A copy is taken on the maintenance tick and written where only that workspace can read it:

curl -s -H "Authorization: Bearer $TOKEN" "https://your-instance/api/v1/system/tenant-backups"

The response tells you enabled as well as the list, because an empty list means two different things — "we have not taken one yet" and "you never asked for one" — and only the second has anything to do about it. Download one with ?key= from the list.

The most recent backup is never deleted, whatever the retention says. Retention decides how long spares are kept, not when the last copy expires. A workspace that stopped taking backups a year ago still has its last one.

#Getting a workspace's data out

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://your-instance/api/v1/system/tenants/<project-id>/export" > workspace.json

Collections and records, as one JSON payload. Operator-only — the same super-admin access as the rest of this page, and deliberately not the backup permission a workspace admin holds, because reading another workspace's records whole is the most privileged read there is. A workspace admin exports its own with GET /api/v1/system/export.

It is capped at 10,000 records per collection, and says when it hit the cap. limits.truncated names every collection that was cut short. For a complete export of a large collection use GET /api/v1/collections/<name>/records/export, which pages. You can ask for less with ?recordsPerCollection=100 — useful for a sample — but not for more.

The export is recorded in the target workspace's own audit log, not only in yours.

#Watching what every workspace is doing

System → Logs → Audit carries two audit views. The browser at the top is your own workspace's log, which is what a workspace admin sees and the only one they can reach. Beneath it, and only if you are an operator, is Audit — every workspace.

It leads with a count per workspace rather than a feed of recent entries, because a combined newest-first list is dominated by whichever workspace is busiest — the quiet ones never reach the first page, and a workspace that has gone quiet is usually the one worth noticing. Select a workspace to narrow the entries beneath the table.

A workspace you released still appears, labelled released. Its audit trail is kept on purpose: the record of what happened outlives the workspace it happened in, which is the point of an audit log.

Reading every workspace's audit trail needs operator access — the same super-admin-only control as the rest of this page, and deliberately not the ordinary audit-log permission a workspace admin holds.

#Who can do any of this

Only a super-admin. Tenant management is not a permission any role can be granted — it is not in the permission table at all, and the only thing that passes is the super-admin bypass.

That is deliberate: creating a workspace with a hostname reconfigures how the entire instance routes traffic. It is an action on the deployment, not an action inside a workspace, so it sits with whoever owns the deployment.

#If something looks wrong

  • A workspace shows a provisioning warning. Its roles did not seed. It will fail requests. Release it and create it again rather than trying to repair it.
  • A hostname returns 404 after you set it up. Check the DNS record and the zone route first; the instance refuses unrecognised hosts by design, so a 404 usually means the request never carried the hostname you expect.
  • Every workspace shows the primary one's branding. INTERNAL_PROXY_SECRET is missing or differs between the two workers. That is the failure the activation gate exists to prevent, so it should only be reachable if the secret was removed after activation.