Instance administration
The operator back office: every workspace on this instance with its metadata and plan, the plan assignment, and the append-only trail of operator actions. Metadata only — the operator of a hosted Basalt is a data processor and these routes cannot return page titles, page contents or comments. Session-only, and restricted to the operators named in BASALT_OPERATORS.
4 operations. Every path is relative to the instance origin; every response body is JSON unless stated. See API for authentication, errors and pagination.
GET /api/v1/admin/workspaces
Every workspace on this instance with its billing-relevant METADATA: name, slug, creation date, member count, live page count, stored bytes, plan and last activity. Newest first, keyset-paginated, optionally narrowed by name/slug or plan. Carries no page titles, no bodies and no comments — the operator is a processor, not a reader (GDPR Art. 28). Instance operator only (BASALT_OPERATORS); everybody else gets 403, a workspace owner included. Session-only twice over: the route sits outside /workspaces/{workspaceId}, which tokens/scopes.ts classifies session-only, and the operator guard refuses a token-authenticated request outright.
listAdminWorkspaces · token scope: session only
Query parameters
cursor· string — length 1–∞limit· integer — 1–100q· string — length 1–200plan·"free"|"team"|"business"|"self_hosted"
Response 200 — application/json
items· object[] — requiredid· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredcreatedAt· string (date-time) — requiredmemberCount· integer — required, 0–9007199254740991pageCount· integer — required, 0–9007199254740991storageBytes· integer — required, 0–9007199254740991plan·"free"|"team"|"business"|"self_hosted"— requiredplanSince· string (date-time) | null — requiredlastActivityAt· string (date-time) | null — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — required
GET /api/v1/admin/workspaces/{targetWorkspaceId}
One workspace in detail: the list row plus memberships per role, the owners (the billing counterparties — owners only, never the whole roster), the attachment count, and the plan limits with whichever of them it currently exceeds. Metadata only. Instance operator only (BASALT_OPERATORS); everybody else gets 403, a workspace owner included. Session-only twice over: the route sits outside /workspaces/{workspaceId}, which tokens/scopes.ts classifies session-only, and the operator guard refuses a token-authenticated request outright.
getAdminWorkspace · token scope: session only
Path parameters
targetWorkspaceId· string (uuid) — required
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredcreatedAt· string (date-time) — requiredmemberCount· integer — required, 0–9007199254740991pageCount· integer — required, 0–9007199254740991storageBytes· integer — required, 0–9007199254740991plan·"free"|"team"|"business"|"self_hosted"— requiredplanSince· string (date-time) | null — requiredlastActivityAt· string (date-time) | null — requiredmembersByRole· object — requiredowner· integer — required, 0–9007199254740991admin· integer — required, 0–9007199254740991member· integer — required, 0–9007199254740991guest· integer — required, 0–9007199254740991
owners· object[] — requireduserId· string (uuid) — requiredname· string — requiredemail· string (email) — requiredjoinedAt· string (date-time) — required
attachmentCount· integer — required, 0–9007199254740991limits· object — requiredstorageBytes· integer | null — requiredfileSizeBytes· integer | null — requiredmembers· integer | null — requiredguests· integer | null — requiredversionHistoryDays· integer | null — required
over·"storageBytes"|"fileSizeBytes"|"members"|"guests"|"versionHistoryDays"[] — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — required
PUT /api/v1/admin/workspaces/{targetWorkspaceId}/plan
Assign a workspace's plan — the write that makes the billing work usable before payments are wired. Idempotent: re-assigning the plan a workspace already has changes nothing and records nothing. Every real change writes an append-only workspace.plan.set entry to the operator audit trail IN THE SAME TRANSACTION, so a plan change with no record of who made it cannot exist. A downgrade never removes, hides or locks anything — it only narrows what the next write may add. Instance operator only (BASALT_OPERATORS); everybody else gets 403, a workspace owner included. Session-only twice over: the route sits outside /workspaces/{workspaceId}, which tokens/scopes.ts classifies session-only, and the operator guard refuses a token-authenticated request outright.
setAdminWorkspacePlan · token scope: session only
Path parameters
targetWorkspaceId· string (uuid) — required
Request body — application/json, required
plan·"free"|"team"|"business"|"self_hosted"— requirednote· string — length 0–500
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredcreatedAt· string (date-time) — requiredmemberCount· integer — required, 0–9007199254740991pageCount· integer — required, 0–9007199254740991storageBytes· integer — required, 0–9007199254740991plan·"free"|"team"|"business"|"self_hosted"— requiredplanSince· string (date-time) | null — requiredlastActivityAt· string (date-time) | null — requiredmembersByRole· object — requiredowner· integer — required, 0–9007199254740991admin· integer — required, 0–9007199254740991member· integer — required, 0–9007199254740991guest· integer — required, 0–9007199254740991
owners· object[] — requireduserId· string (uuid) — requiredname· string — requiredemail· string (email) — requiredjoinedAt· string (date-time) — required
attachmentCount· integer — required, 0–9007199254740991limits· object — requiredstorageBytes· integer | null — requiredfileSizeBytes· integer | null — requiredmembers· integer | null — requiredguests· integer | null — requiredversionHistoryDays· integer | null — required
over·"storageBytes"|"fileSizeBytes"|"members"|"guests"|"versionHistoryDays"[] — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — required
GET /api/v1/admin/audit
The operator audit trail, newest first, optionally narrowed to one workspace. Append-only: a database trigger refuses UPDATE, DELETE and TRUNCATE, so an entry can be neither edited nor deleted by any code path, this one included. Instance operator only (BASALT_OPERATORS); everybody else gets 403, a workspace owner included. Session-only twice over: the route sits outside /workspaces/{workspaceId}, which tokens/scopes.ts classifies session-only, and the operator guard refuses a token-authenticated request outright.
listAdminAudit · token scope: session only
Query parameters
cursor· string — length 1–∞limit· integer — 1–100workspaceId· string (uuid)
Response 200 — application/json
items· object[] — requiredid· string (uuid) — requiredat· string (date-time) — requiredaction·"workspace.plan.set"— requiredoperatorUserId· string (uuid) — requiredoperatorEmail· string (email) — requiredworkspaceId· string (uuid) | null — requiredworkspaceSlug· string | null — requireddetail· object — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — required