Contract — wbsp-api HTTP Endpoints
Feature: 057 Close Increment 3 (P3)
Base path: /api/v1
Auth: Authorization: Bearer <token> on every endpoint except GET /healthz.
Conventions
- All endpoints accept and return
application/json. - Errors are
{ "error": "<message>" }with appropriate HTTP status. - Identity in URL params (
{tenant},{name},{email}) is taken from path, not body — so authz middleware can decide before the body is parsed. - Write endpoints are serialised per-app inside
Deployer(existing lock).
1. Application endpoints (tenant-scoped)
All require Identity.TenantSet ∋ {tenant} from URL path.
POST /api/v1/apps/{tenant}/{name}/deploy
Body: { "config": <AppConfig YAML or JSON>, "destination": "<name>" }.
200: { "result": <DeployResult> }.
401: missing/invalid token.
403: tenant not in caller's mapping.
4xx: validation errors as today.
POST /api/v1/apps/{tenant}/{name}/stop
Body: optional { "enclave": "<name>" } (defaults to inferring from registry).
200: { "result": <StopResult> }.
POST /api/v1/apps/{tenant}/{name}/maintenance
Body: { "reason": "<string>", "enclave": "<name>?" }.
200: { "result": <MaintenanceResult> }.
DELETE /api/v1/apps/{tenant}/{name}
Query: ?enclave=<name> optional.
200: { "result": <RemoveResult> }.
GET /api/v1/apps/{tenant}/{name}
200: { "entry": <AppEntry> }.
404: not found.
GET /api/v1/apps
Query: ?tenant=<name> optional; if absent, fan out across caller's
TenantSet.
200: { "entries": [<AppEntry>...] }.
2. Identity endpoint (authenticated)
GET /api/v1/me
200: { "user": { "email": "...", "sub": "...", "admin": false, "tenants": ["acme"] } }.
401: missing/invalid token.
3. Admin endpoints (admin=true required)
POST /api/v1/admin/users
Body: { "email": "...", "sub": "...?" }.
201: { "user": <User> }. Creates a placeholder user before they have logged
into HAP for the first time — sub may be set later via PATCH or on first
login.
GET /api/v1/admin/users
200: { "users": [<User>...] }.
POST /api/v1/admin/users/{email}/tenants
Body: { "tenant": "..." }.
200: { "user": <User> } (idempotent grant).
DELETE /api/v1/admin/users/{email}/tenants/{tenant}
POST /api/v1/admin/users/{email}/admin
- Idempotent grant. No floor check on grant.
DELETE /api/v1/admin/users/{email}/admin
- Fails 409 with body
{ "error": "cannot revoke admin: would leave zero admins" }if removing this user's admin flag would leave zero admins (FR-015).
POST /api/v1/admin/tokens
Body: { "user_email": "...", "label": "..." }.
201: { "token": { "id": "...", "label": "...", "bearer": "<JWT>" } }.
The bearer field is the only time the full JWT is ever returned —
operator must capture it (curl pipe to WBSP_API_TOKEN).
GET /api/v1/admin/tokens
200: { "tokens": [<TokenMetadata>...] }. No bearer field on list.
DELETE /api/v1/admin/tokens/{id}
4. Health
GET /api/v1/healthz
No auth. 200: { "status": "ok" } when the deployer + users store both
respond. 503 with { "status": "...", "details": [...] } otherwise. Used by
the load balancer + the CLI's API-URL probe.
5. Versioning
All endpoints live under /api/v1. A future v2 path is reserved; v1 is the
only version this feature ships.
6. Pagination
Out of scope (the platform scale is low-thousands of apps total). List endpoints return everything matching the caller's authz.
7. Rate limiting
Out of scope for this feature. wbsp-api runs behind the platform's Traefik gateway which can apply rate-limit middleware if needed later.