Basalt docs
basaltapp.io

Instance administration

The operator back office: every workspace on this instance with its metadata and plan, the plan assignment, the append-only trail of operator actions, and the inbox of reports people have sent (see Feedback). Metadata only, with that one exception — the operator of a hosted instance is a data processor, so these routes cannot return page titles, page contents or comments, and a report is readable here only because somebody wrote and sent it to the operator. Session-only, and restricted to the operators named in OPERATORS.

6 operations. Every path is relative to the instance origin; every response body is JSON unless stated. The endpoint descriptions below come straight from the server’s own route table. 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 (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–100
  • q · string — length 1–200
  • plan · "free" | "pro" | "business" | "enterprise" | "self_hosted" | "team" Show only workspaces on this plan. Deprecated spellings are accepted for compatibility and resolved before anything is stored or answered: team means pro, and self_hosted means enterprise — the deployment mode that id named was withdrawn (ADR-0024) and the id retired (ADR-0029 D1). Responses only ever spell the four live ids.

Response 200 — application/json

  • items · object[] — required
    • id · string (uuid) — required
    • slug · string — required, length 1–100
    • name · string — required
    • createdAt · string (date-time) — required
    • memberCount · integer — required, 0–9007199254740991
    • pageCount · integer — required, 0–9007199254740991
    • storageBytes · integer — required, 0–9007199254740991
    • plan · "free" | "pro" | "business" | "enterprise" — required
    • planSince · string (date-time) | null — required
    • lastActivityAt · string (date-time) | null — required
  • nextCursor · string | null — required

Response default — application/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required
    • reason · string — length 1–64

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 (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) — required
  • slug · string — required, length 1–100
  • name · string — required
  • createdAt · string (date-time) — required
  • memberCount · integer — required, 0–9007199254740991
  • pageCount · integer — required, 0–9007199254740991
  • storageBytes · integer — required, 0–9007199254740991
  • plan · "free" | "pro" | "business" | "enterprise" — required
  • planSince · string (date-time) | null — required
  • lastActivityAt · string (date-time) | null — required
  • membersByRole · object — required
    • owner · integer — required, 0–9007199254740991
    • admin · integer — required, 0–9007199254740991
    • member · integer — required, 0–9007199254740991
    • guest · integer — required, 0–9007199254740991
  • owners · object[] — required
    • userId · string (uuid) — required
    • name · string — required
    • email · string (email) — required
    • joinedAt · string (date-time) — required
  • attachmentCount · integer — required, 0–9007199254740991
  • limits · object — required
    • storageBytes · integer | null — required
    • fileSizeBytes · integer | null — required
    • members · integer | null — required
    • guests · integer | null — required
    • versionHistoryDays · integer | null — required
  • over · "storageBytes" | "fileSizeBytes" | "members" | "guests" | "versionHistoryDays"[] — required

Response default — application/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required
    • reason · string — length 1–64

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 (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" | "pro" | "business" | "enterprise" | "self_hosted" | "team" — required The plan to assign. Deprecated spellings are accepted for compatibility and resolved before anything is stored or answered: team means pro, and self_hosted means enterprise — the deployment mode that id named was withdrawn (ADR-0024) and the id retired (ADR-0029 D1). Responses only ever spell the four live ids.
  • note · string — length 0–500

Response 200 — application/json

  • id · string (uuid) — required
  • slug · string — required, length 1–100
  • name · string — required
  • createdAt · string (date-time) — required
  • memberCount · integer — required, 0–9007199254740991
  • pageCount · integer — required, 0–9007199254740991
  • storageBytes · integer — required, 0–9007199254740991
  • plan · "free" | "pro" | "business" | "enterprise" — required
  • planSince · string (date-time) | null — required
  • lastActivityAt · string (date-time) | null — required
  • membersByRole · object — required
    • owner · integer — required, 0–9007199254740991
    • admin · integer — required, 0–9007199254740991
    • member · integer — required, 0–9007199254740991
    • guest · integer — required, 0–9007199254740991
  • owners · object[] — required
    • userId · string (uuid) — required
    • name · string — required
    • email · string (email) — required
    • joinedAt · string (date-time) — required
  • attachmentCount · integer — required, 0–9007199254740991
  • limits · object — required
    • storageBytes · integer | null — required
    • fileSizeBytes · integer | null — required
    • members · integer | null — required
    • guests · integer | null — required
    • versionHistoryDays · integer | null — required
  • over · "storageBytes" | "fileSizeBytes" | "members" | "guests" | "versionHistoryDays"[] — required

Response default — application/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required
    • reason · string — length 1–64

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 (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–100
  • workspaceId · string (uuid)

Response 200 — application/json

  • items · object[] — required
    • id · string (uuid) — required
    • at · string (date-time) — required
    • action · "workspace.plan.set" — required
    • operatorUserId · string (uuid) — required
    • operatorEmail · string (email) — required
    • workspaceId · string (uuid) | null — required
    • workspaceSlug · string | null — required
    • detail · object — required
  • nextCursor · string | null — required

Response default — application/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required
    • reason · string — length 1–64

GET /api/v1/admin/feedback

The feedback inbox, newest first, optionally narrowed by status, kind or product. The one operator surface that holds user-written text, and it is not an exception to the metadata-only rule: a report is a message somebody composed and sent to the operator, having seen everything attached to it. Joins no workspace content and links nowhere into one. Instance operator only (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.

listAdminFeedback · token scope: session only

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100
  • status · "open" | "closed"
  • kind · "bug" | "idea"
  • product · "basalt" | "lithic" | "tecto"

Response 200 — application/json

  • items · object[] — required
    • id · string (uuid) — required
    • at · string (date-time) — required
    • kind · "bug" | "idea" — required
    • status · "open" | "closed" — required
    • message · string — required
    • product · "basalt" | "lithic" | "tecto" | null — required
    • reporter · object — required
      • userId · string (uuid) — required
      • name · string — required
      • email · string (email) — required
    • workspace · object | null — required
      • id · string (uuid) — required
      • name · string — required
    • context · object — required
      • route · string | null — required
      • appVersion · string | null — required
      • viewport · object | null — required
        • width · integer — required, 0–100000
        • height · integer — required, 0–100000
      • pointer · "coarse" | "fine" | null — required
      • trace · string | null — required
    • note · string | null — required
    • closedAt · string (date-time) | null — required
  • nextCursor · string | null — required

Response default — application/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required
    • reason · string — length 1–64

PATCH /api/v1/admin/feedback/{reportId}

Close or reopen a report, and/or set the operator's own note about it. The note is never shown to the sender. Instance operator only (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.

updateAdminFeedback · token scope: session only

Path parameters

  • reportId · string (uuid) — required

Request body — application/json, required

  • status · "open" | "closed"
  • note · string | null

Response 200 — application/json

  • id · string (uuid) — required
  • at · string (date-time) — required
  • kind · "bug" | "idea" — required
  • status · "open" | "closed" — required
  • message · string — required
  • product · "basalt" | "lithic" | "tecto" | null — required
  • reporter · object — required
    • userId · string (uuid) — required
    • name · string — required
    • email · string (email) — required
  • workspace · object | null — required
    • id · string (uuid) — required
    • name · string — required
  • context · object — required
    • route · string | null — required
    • appVersion · string | null — required
    • viewport · object | null — required
      • width · integer — required, 0–100000
      • height · integer — required, 0–100000
    • pointer · "coarse" | "fine" | null — required
    • trace · string | null — required
  • note · string | null — required
  • closedAt · string (date-time) | null — required

Response default — application/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required
    • reason · string — length 1–64