Skip to content

Dashboard API

The customer-facing REST contract served by the §13 API binary — conventions, the core-resource catalog, and an index to the feature pages' endpoints. Each feature page owns its endpoints (same rule as tables); field lists derive from the Database Schema; the OpenAPI spec is implementation-time output. The Shuttle-facing API is separate (§25). (Design record: specs/2026-07-11-dashboard-api-design.md.)

Conventions

  • Base: https://api.starform.io/api/v1 · IDs are UUIDs · timestamps RFC3339.
  • URL shape — flat by UUID: reads/writes address resources directly (/services/{id}); only list/create endpoints nest one level under the parent (/projects/{id}/services).
  • Auth: the Auth & Sessions cookies + required custom header on mutations (CSRF). Pre-launch API tokens (§39.2 #20) reuse these same endpoints with a bearer header.
  • Errors — one envelope, always: the HTTP status plus {"code": "env_protected", "message": "...", "detail": {...}}code is stable and machine-matchable; message is human text.
  • Pagination: cursor-based — ?cursor=&limit= (default 50, max 100); responses carry next_cursor (null at the end).
  • Permission column = minimum §15 role; Developer* = Developer, blocked in protected environments (§15.5 — Admins pass). Enforcement is the §15.6 middleware, not the handler.

Core resources

Auth & identity — login/refresh/logout live on Auth & Sessions.

Method Path Permission Purpose
GET /me any authed Current user + workspace/project memberships

Workspaces & members (anti-escalation, §15.3: no actor may grant/modify/remove a role ≥ their own; ownership moves only by Owner-initiated transfer)

Method Path Permission Purpose
POST /workspaces any authed Create workspace
GET /workspaces any authed List my workspaces
GET / PATCH /workspaces/{id} Member / Admin Read · rename, settings
DELETE /workspaces/{id} Owner Delete workspace
GET / POST /workspaces/{id}/members Member / Admin List · invite
PATCH / DELETE /workspaces/{id}/members/{user_id} Admin Change role · remove
POST /workspaces/{id}/transfer-ownership Owner Transfer the Owner role

Projects & members

Method Path Permission Purpose
GET / POST /workspaces/{id}/projects Member / WS Admin List visible · create (carries default_region, FR-077)
GET / PATCH / DELETE /projects/{id} Viewer / Proj Admin / Proj Admin Read · settings · delete
GET / POST /projects/{id}/members Viewer / Proj Admin List · add
PATCH / DELETE /projects/{id}/members/{user_id} Proj Admin Change role · remove

Environments (names RFC 1123 ≤30 chars, FR-070; production auto-created protected, §15.5)

Method Path Permission Purpose
GET / POST /projects/{id}/environments Viewer / Proj Admin List · create (is_ephemeral flag)
GET / PATCH / DELETE /environments/{id} Viewer / Proj Admin / Proj Admin Read · toggle is_protected · delete (tombstone)

Observability reads (every query passes the FR-065 server-side tenant filter — no request field can widen scope)

Method Path Permission Purpose
GET /service-environments/{id}/logs Viewer ?since&until&level&q&limit; live=true → SSE tail (FR-049)
GET /service-environments/{id}/metrics/{metric} Viewer metric ∈ latency · rps · error_rate · throughput · cpu · memory · network; ?window&step

Regions

Method Path Permission Purpose
GET /regions any authed Active regions (placement pickers)

Feature endpoints — where they live

Surface Home
Services · service-environment instantiation (+ PUT payload) Services & Environments
Builds · deployments · deploy/rollback (+ payload) Starforge › Build Lifecycle
Databases (+ credentials, tier resize) Managed Databases
Var Groups (+ entries payload, attachments) Var Groups
Usage / billing Billing
Login / refresh / logout Auth & Sessions

Deferred, noted for completeness: custom domains (§39.3 #27) · buckets (§39.3 #16) · API tokens (§39.2 #20) · rate limiting (§39.2 #23) · invoice endpoints (§39.1 #14).

Key payloads

Error envelope · every non-2xx response
{ "code": "env_protected",
  "message": "Developers cannot deploy to protected environment 'production'.",
  "detail": { "environment_id": "…", "required_role": "project_admin" } }
Logs · GET …/logs?live=true → SSE frames · else a JSON page
// SSE: one event per line
event: log
data: {"ts":"2026-07-11T14:03:02.114Z","level":"error","message":"connection refused","pod":"api-6f…"}

// non-live JSON: { "lines": [ {…} ], "next_cursor": "…" }
Metrics · GET …/metrics/latency?window=1h&step=60s
{ "metric": "latency", "unit": "ms",
  "series": [ { "quantile": "p99", "points": [ ["2026-07-11T14:00:00Z", 182.4],  ] },
              { "quantile": "p95", "points": [  ] } ] }

Cross-cutting semantics

  • Async pattern: anything Shuttle must apply returns 202 + the resource carrying status; the client polls the resource (or watches deploy state in Stardeck). No separate operations API at MVP.
  • Deletes are tombstones (§25.1/FR-073): DELETE returns 202; the entity shows "status": "deleting" on GET until Shuttle confirms (§25.4), then 404s.
  • Permissions are middleware, not handler logic: each row's requirement maps to a §15.6 helper (RequireProjectDeveloper(), RequireEnvironmentWrite(), …); Developer* rows call the env-protection variant.
  • Reads never fan wider than the session: the observability endpoints go through the telemetry query broker (§13), which injects the FR-065 filter as bound parameters — the query params shape the window, never the tenant.

Cross-references

Auth flow + cookies → Auth & Sessions · roles and the protection flag → §15 · the tables behind every payload → Database Schema · the Shuttle-facing API → §25. Canonical map: Canonical Sources.