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

  1. Idempotent grant. No floor check on grant.

DELETE /api/v1/admin/users/{email}/admin

  1. 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.