Skip to content

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 ​

MethodPathAuthSummary
GET/admin/workspacesBearerList all workspaces (platform-admin scope).
POST/admin/workspacesBearerCreate a new workspace; slug must be unique.
GET/admin/workspaces/{workspace_id}BearerFetch one workspace.
PATCH/admin/workspaces/{workspace_id}BearerUpdate name (only mutable field).
DELETE/admin/workspaces/{workspace_id}BearerDelete 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.

json
{
  "workspaces": [
    {
      "id": "ws_01HF…",
      "slug": "default",
      "name": "Default",
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": "2026-01-01T00:00:00Z"
    }
  ]
}
bash
curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
  http://localhost:8000/admin/workspaces

POST /admin/workspaces ​

Body WorkspaceCreateRequest:

json
{
  "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.

bash
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/workspaces

GET /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:

json
{ "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. PATCH cannot change slug by 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. POST returns 409 on slug collision. PATCH and DELETE return 404 when the target id does not exist — there is no 409 on those.
  • 503 causes. app.state.workspace_repo is wired in lifespan; routes return 503 with "workspace repository not initialised" when missing.
  • Bootstrap. The lifespan creates a default workspace on first boot; operators rarely call POST until they start onboarding additional tenants.

Dual-licensed: MIT OR Apache-2.0