Workspaces
CRUD over the workspaces table — the row that every other admin surface scopes against. Tokens, adapters, datasets, inference logs, audit rows, cost rollups, and policies are all keyed by workspace_id; a workspace is the unit of multi-tenant isolation in bigwave.
The router lives at packages/shared/workspace_routes.py. Pydantic models: WorkspaceEntry, WorkspaceCreateRequest, WorkspaceUpdateRequest.
Endpoints
| Method | Path | Auth | Summary |
|---|---|---|---|
| GET | /admin/workspaces | Bearer | List all workspaces (platform-admin scope). |
| POST | /admin/workspaces | Bearer | Create a new workspace; slug must be unique. |
| GET | /admin/workspaces/{workspace_id} | Bearer | Fetch one workspace. |
| PATCH | /admin/workspaces/{workspace_id} | Bearer | Update name (only mutable field). |
| DELETE | /admin/workspaces/{workspace_id} | Bearer | Delete a workspace; 204 on success. |
GET /admin/workspaces
Returns {"workspaces": WorkspaceEntry[]}. Unlike most admin routes, this is platform-wide — listing all workspaces is the entire point of the surface. Authorization is the bearer-token gate; the same BIGWAVE_ADMIN_TOKEN controls access to every other admin route, so a caller that already holds it can already touch every tenant's data.
{
"workspaces": [
{
"id": "ws_01HF…",
"slug": "default",
"name": "Default",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
]
}curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
http://localhost:8000/admin/workspacesPOST /admin/workspaces
Body WorkspaceCreateRequest:
{
"slug": "support-team",
"name": "Support Team"
}slug must be lowercase alphanumeric with internal hyphens (1–64 chars; no leading/trailing hyphen — enforced by _SLUG_RE). Duplicate slug returns 409 Conflict. name is 1–256 chars.
Returns 201 with the created WorkspaceEntry.
curl -X POST -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug":"support-team","name":"Support Team"}' \
http://localhost:8000/admin/workspacesGET /admin/workspaces/{workspace_id}
Returns the WorkspaceEntry for the given id. Returns 404 when the id does not exist.
PATCH /admin/workspaces/{workspace_id}
Body WorkspaceUpdateRequest:
{ "name": "Support Team (renamed)" }Only name is mutable — slug is immutable so existing audit / cost rows referencing the slug stay valid. Returns the updated WorkspaceEntry; 404 when missing.
DELETE /admin/workspaces/{workspace_id}
Returns 204 No Content. 404 when missing.
Cascade caveat. Deleting a workspace does not cascade delete tokens / adapters / inference rows owned by it. Operator is responsible for clearing dependent rows first; otherwise orphaned rows remain visible only via direct DB inspection (the
/admin/*surfaces filter by workspace and will not surface them).
Operator notes
- Workspace fence. This is the only admin surface where the caller can name a workspace other than its own — the router itself manages workspace rows, so the fence is the bearer-token gate, not a per-row principal check. See workspace fence for the per-row contract every other admin surface implements.
- Slug immutability.
PATCHcannot changeslugby design. The slug is the human-friendly identifier that ends up in CLI output, audit rows, and external integrations — letting it mutate creates dangling references. - Conflict semantics.
POSTreturns409on slug collision.PATCHandDELETEreturn404when the target id does not exist — there is no409on those. 503causes.app.state.workspace_repois wired in lifespan; routes return503with"workspace repository not initialised"when missing.- Bootstrap. The lifespan creates a
defaultworkspace on first boot; operators rarely callPOSTuntil they start onboarding additional tenants.