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
| Environment | URL |
|---|---|
| Local (docker compose) | http://localhost:8088 |
| Customer Portal env | NEXT_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 tag | Base path | Auth | Doc |
|---|---|---|---|
| public | /v1/public/… | None (bootstrap) | 02-shell-bootstrap.md |
| app-shell | …/apps/…/shell-config | Keycloak + read | 02-shell-bootstrap.md |
| apps | /v1/workspaces/{id}/apps | Keycloak | 09-manifest-and-routes.md |
| app-releases | …/apps/{id}/releases | Keycloak + write | 09-manifest-and-routes.md |
| vault | /v1/workspaces/{id}/vault | Keycloak + ReBAC | 04-data-vault.md |
| files | /v1/workspaces/{id}/files | Keycloak + ReBAC | 05-files.md |
| secrets | /v1/workspaces/{id}/secrets | Keycloak + ReBAC | 06-secrets.md |
| llm | /v1/workspaces/{id}/llm | Keycloak + ReBAC | 07-llm-gateway.md |
| process_log | /v1/workspaces/{id}/logs | Keycloak + read | — |
| workspace-auth | /v1/workspaces/{id}/auth | Keycloak | 03-authentication.md |
Usually not called from federated UI
| Tag | Why |
|---|---|
| admin | Operator-only (OPERATOR_EMAILS) |
| webhooks | IdP / internal callbacks |
| llm-internal | LiteLLM webhook ingest |
| provisioning | Workspace saga (Portal) |
| runners, coderunner | Coderunner path, not shell_federated |
| billing | Account-level; Portal account pages |
Public bootstrap endpoints
| Method | Path | Returns |
|---|---|---|
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/:
| Type | Used for |
|---|---|
ShellConfigResponse | Bootstrap |
QueryRequest / QueryResultPage | Vault query |
UploadInitRequest / UploadInitResponse | Files |
SecretList / SecretReveal | Secrets |
ModelList | LLM 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.
Related
- 11-local-dev-recipes.md — curl without codegen
- 12-troubleshooting.md — HTTP status codes
- Full bootstrap reference: shell-controlplane-integration.md