Shell API
Every route is served under /api by an Ignis controller in api/src/controllers/ — the definitive contract. Auth is two layers: a strategy verifies the caller, then an @authorize decorator gates the route by permission.
| Strategy | Verifies | Used by |
|---|---|---|
jwt | A Shell RS256 token | Signed-in users and satellites |
pat | A veni_… personal access token (X-Veni-Token) | The CLI and scripts |
A handful of routes are public (no strategy): sign-in, the OAuth callback, JWKS, and PAT introspection — the things a caller needs before it has a token.
What satellites & the CLI depend on
The external contract — the endpoints other systems actually call:
| Endpoint | Purpose | Auth |
|---|---|---|
GET /api/.well-known/jwks.json | Public keys a satellite verifies Shell tokens with — needs the RSA key | public |
POST /api/auth/exchange | Exchange a Shell JWT for a service JWT | JWT |
POST /api/auth/local-login · GET /api/auth/login · GET /api/auth/callback | Password login · OAuth start · OAuth callback | public |
POST /api/auth/resolve-provider | Email → the org's SSO/login URL (per-org routing) | public |
POST /api/personal-tokens/introspect | Validate a PAT → user (used by the Builder to gate the CLI download) | public |
POST /api/auth/cli/device · POST /api/auth/cli/device/token | The CLI device flow — start, then poll for the minted token | public (the device_code is the credential) |
GET /api/auth/cli/device/:userCode · …/approve · …/deny | The browser half — what the approval page at <shell UI>/cli reads and posts | JWT |
The REST surface, by concern
Each base path is one controller, grouped by the same clusters as the database. The permission family it enforces is in parentheses.
| Area | Base path | What it manages |
|---|---|---|
| Identity | /api/auth | Sign-in, token exchange/refresh, me profile & orgs, switch-org, invites, password reset |
| Identity | /api/personal-tokens | Personal access tokens — create (raw shown once), list, revoke, introspect |
| Organizations | /api/organizations | Org CRUD, SSO config, SMTP, portal-domain, subscription (ORGANIZATIONS_*) |
| RBAC | /api/users | User CRUD, per-user role assignment, invitations (USERS_*) |
| RBAC | /api/roles · /api/permissions | Roles, role↔permission grants, the permission list (ROLES_*, PERMISSIONS_*) |
| App registry | /api/admin/apps · /api/admin/orgs | Register / list / update apps, and the per-org catalog (requireAdmin, APPS_CREATE) |
| Billing | /api/subscriptions · /api/plans · /api/invoices | Billing, the plan catalog, invoices |
| Platform | /api/scim/v2 · /api/scim-tokens | SCIM v2 provisioning (its own bearer auth) + token management |
| Platform | /api/dashboard · /api/mcp · /api/tenant · /api/config | Dashboard widgets & AI briefing, the MCP endpoint, host→org resolution, public client config |
gRPC — service-to-service
Three Connect-over-HTTP services, mounted at the root (not under /api). The rbac and quota services require a service JWT whose aud matches — so only trusted services call them; AuthGateway brokers on the caller's Shell JWT:
| Service | Purpose | Caller presents |
|---|---|---|
/auth.v1.AuthGateway/* | Brokered token exchange between systems | the caller's Shell JWT |
/rbac.v1.RbacService/* | Live permission checks against the Shell's RBAC | service JWT · aud: shell-rbac |
/quota.v1.QuotaService/* | Plan quota / usage limits | service JWT · aud: shell-quota |
Health & extending
GET /api/health is the liveness endpoint (Ignis's health-check component — what the Docker healthcheck hits). To add an endpoint, add a controller in api/src/controllers/ (or a route to an existing one), gate it with @authorize, and put the logic in a service — the layered architecture has the shape.