Groundfloor Docs

Shell API Reference

Control Plane endpoints used by federated Shell apps.

Audience: Federated app and Shell developers
Contract source: OpenAPI 3.x from FastAPI
Typed client: @groundfloor/api-client (orval + TanStack Query)

Control Plane is a first-class public API. Federated apps call the same workspace-scoped routes as the Customer Portal.


Base URL

EnvironmentURL
Local (docker compose)http://localhost:8088
Customer Portal envNEXT_PUBLIC_API_URL

No trailing slash. OpenAPI UI: {base}/docs · JSON: {base}/openapi.json


Regenerate OpenAPI and TypeScript client

After backend route changes in groundfloor-client-portal:

# 1. Generate apps/customer/openapi.json (no server required)
uv run python scripts/sync_openapi.py

# 2. Regenerate hooks + types
npm run codegen --workspace=@groundfloor/api-client

# 3. Commit openapi.json + packages/api-client/src/generated/

CI runs scripts/check_openapi_fresh.sh to prevent drift.

Use in Shell / federated remote

Add @groundfloor/api-client as a dependency (monorepo path or published package):

import {
  configureControlPlaneClient,
  setControlPlaneAuthProvider,
} from "@groundfloor/api-client";

configureControlPlaneClient({ baseURL: process.env.NEXT_PUBLIC_API_URL });
setControlPlaneAuthProvider(async () => keycloak.token ?? null);

See packages/api-client/README.md.


Tags relevant to federated apps

OpenAPI tagBase pathAuthDoc
public/v1/public/…None (bootstrap)02-shell-bootstrap.md
app-shell…/apps/…/shell-configKeycloak + read02-shell-bootstrap.md
apps/v1/workspaces/{id}/appsKeycloak09-manifest-and-routes.md
app-releases…/apps/{id}/releasesKeycloak + write09-manifest-and-routes.md
vault/v1/workspaces/{id}/vaultKeycloak + ReBAC04-data-vault.md
files/v1/workspaces/{id}/filesKeycloak + ReBAC05-files.md
secrets/v1/workspaces/{id}/secretsKeycloak + ReBAC06-secrets.md
llm/v1/workspaces/{id}/llmKeycloak + ReBAC07-llm-gateway.md
process_log/v1/workspaces/{id}/logsKeycloak + read
workspace-auth/v1/workspaces/{id}/authKeycloak03-authentication.md

Usually not called from federated UI

TagWhy
adminOperator-only (OPERATOR_EMAILS)
webhooksIdP / internal callbacks
llm-internalLiteLLM webhook ingest
provisioningWorkspace saga (Portal)
runners, coderunnerCoderunner path, not shell_federated
billingAccount-level; Portal account pages

Public bootstrap endpoints

MethodPathReturns
GET/v1/public/workspaces/{workspace_id}/apps/by-slug?slug={app_slug}One app's ShellConfigResponse (manifest + latest published release)
GET/v1/public/workspaces/{workspace_id}/apps[?kind=shell_federated]PublicShellAppList — the workspace's published apps for Shell navigation
GET/v1/public/workspaces/by-host?host=PublicWorkspaceAuthResponse — workspace id + auth mode for a host

GET …/apps lists only published apps of kind (default shell_federated) that have an active release. Non-federated kinds, archived apps, and apps with no published release are excluded. Each item reuses ShellConfigResponse; per-route layout blobs are trimmed to keep the payload small. No auth required; the Shell uses it to build its sidebar in every auth mode (including none). Sends Cache-Control: public, max-age=60.

curl -s "http://localhost:8088/v1/public/workspaces/{workspace_id}/apps" \
  | jq '.apps[] | {slug, app_kind, name: .manifest.displayName}'

Rate limit: RATE_LIMIT_PUBLIC_SHELL (default 120/min per IP).


Workspace-scoped pattern

Almost all customer APIs follow:

/v1/workspaces/{workspace_id}/<pillar>/…
Authorization: Bearer {keycloak_access_token}

Permissions checked via SpiceDB: read, write, delete, administer, ddl, etc. — 03-authentication.md.


Key request/response types

Generated in packages/api-client/src/generated/models/:

TypeUsed for
ShellConfigResponseBootstrap
QueryRequest / QueryResultPageVault query
UploadInitRequest / UploadInitResponseFiles
SecretList / SecretRevealSecrets
ModelListLLM catalog

Import from @groundfloor/api-client — do not hand-copy shapes.


Error shape

HTTP errors return FastAPI detail (string or JSON). The axios mutator wraps them as ControlPlaneApiError:

import { ControlPlaneApiError } from "@groundfloor/api-client";

try {
  await someMutation();
} catch (e) {
  if (e instanceof ControlPlaneApiError) {
    console.error(e.status, e.detail);
  }
}

CORS

Control Plane CORS_ORIGINS must include your Shell dev origin (e.g. http://localhost:3000). Browser calls from federated remotes fail with CORS errors if omitted — see 12-troubleshooting.md.


On this page