Exact wire contract for every screen → endpoint. Base /api/v1; envelope per
00-shared/07 . All endpoints from src/modules/feature-flags/feature-flags.controller.ts
and the org-overlay from src/modules/organizations/organizations.controller.ts; business
rules from feature-flags.service.ts. Global guards: RateLimitGuard → JwtAuthGuard →
RbacGuard (app.module.ts:129-131). No flag endpoint is @Public(), and none declares
@Permissions() — effective auth = valid JWT only (OQ-5; perms declared at
permissions.constants.ts:78-80).
Aspect Contract
Base https://api.<domain>/api/v1
Headers Authorization: Bearer <accessToken>; x-request-id client-generated; Content-Type: application/json
Success {success:true, message:"OK", data, meta?, timestamp, requestId} (response-envelope.interceptor.ts:50-59)
Error {success:false, message, error:{code,details?}, timestamp, requestId} (http-exception.filter.ts:73-81)
Codes 400 VALIDATION_ERROR · 401 UNAUTHENTICATED · 403 PERMISSION_DENIED · 404 RESOURCE_NOT_FOUND · 409 DUPLICATE_RESOURCE · 422 BUSINESS_RULE_VIOLATION · 429 RATE_LIMITED · 5xx INTERNAL_SERVER_ERROR (http-exception.filter.ts:27-35)
Tenancy tenantId from JWT only; never in body. Reads auto-scoped (base.repository.ts:20-30); platform-admin reads bypass scope; upsert requires tenant context (feature-flag.repository.ts:37)
Rate limit global api tier 100/min default (rate-limit.constants.ts:6, rate-limit.guard.ts:36)
Caching none in code; blueprint: Redis sl:global:featureflag:{name} poll+cache 30 s (planned) (CACHE_ARCHITECTURE.md:33,48)
Offline reads from last-good client cache; writes blocked
Retry backoff on 5xx/network; no auto-retry on 429; bulk retry = failed subset only
Endpoint GET /feature-flags (feature-flags.controller.ts:23-28)
Query ?module=<string> optional → findByModule else findAll
Success 200 data: [FeatureFlagDoc…] sorted by key asc (feature-flags.service.ts:11) — plain array, no meta (not paginated)
Errors 401; 5xx
Enabled tab GET /feature-flags/enabled (feature-flags.controller.ts:30-34) → data: [docs with enabled:true] (feature-flag.repository.ts:28-30) — this is the gating set the client cubit mirrors
Client flow fetch on screen open; RefreshIndicator refetch; cache last-good per sl:cache:featureflags:{query}
{
"_id": "…", "tenantId": "…",
"key": "channels.whatsapp", "enabled": false,
"label": "WhatsApp", "description": "…", "module": "channels",
"createdBy": "…", "updatedBy": "…",
"isDeleted": false, "deletedAt": null, "deletedBy": null,
"version": 0, "createdAt": "…", "updatedAt": "…"
}
(feature-flag.schema.ts:7-22, base.schema.ts:10-34; timestamps:true)
Endpoint GET /feature-flags/:key (feature-flags.controller.ts:36-40)
Success 200 data: doc
Errors 404 RESOURCE_NOT_FOUND "Feature flag "" not found." (feature-flags.service.ts:29); 401
Note soft-deleted flags are invisible (scoped isDeleted:false) → 404
Endpoint PUT /feature-flags (feature-flags.controller.ts:42-46) — upsert: create or update
Body UpdateFeatureFlagDto: {key: string (req), enabled: boolean (req), label?: string, description?: string, module?: string} (update-feature-flag.dto.ts:4-26)
Success 200 data: doc — {new:true} upsert result (feature-flag.repository.ts:41)
Persistence truth $set: {enabled, label} only (feature-flag.repository.ts:40) — description/module dropped on every write (OQ-3)
Errors 400 VALIDATION_ERROR (missing key/enabled, wrong types — details[]); 5xx incl. E11000 duplicate on re-create after soft-delete (OQ-4)
Client flow create mode: new key → new doc; edit mode: key read-only, label/enabled editable; never claim description/module saved
Endpoint PUT /feature-flags/bulk (feature-flags.controller.ts:48-52)
Body JSON array of UpdateFeatureFlagDto (feature-flags.controller.ts:50)
Success 200 data: [docs] — applied sequentially, no transaction, no rollback (feature-flags.service.ts:40-44)
Errors 400 whole-body validation (array-item errors in details[]); 5xx mid-loop → partial apply
Client flow per-row results by key diff; retry failed subset only
Endpoint DELETE /feature-flags/:key (feature-flags.controller.ts:54-58)
Success 200 (envelope, data absent/void) — soft delete by _id (feature-flags.service.ts:47-52, base.repository.ts:68-74)
Errors 404 first-guard (findByKey) / second-guard (softDelete returns falsy → 404) (feature-flags.service.ts:48-51); 401
Note key slot retained by unique {tenantId,key} index (feature-flag.schema.ts:26) → re-create may 500 (OQ-4)
Endpoint GET /organizations/:id/feature-flags (organizations.controller.ts:74-79)
Success 200 data: {flagKey: boolean…} from org.metadata.featureFlags (organizations.service.ts:139-142); empty object when unset
Endpoint PATCH /organizations/:id/feature-flags (organizations.controller.ts:81-89)
Body plain Record<string, boolean> (organizations.controller.ts:86) — no DTO, no validation
Semantics full replace : $set: {'metadata.featureFlags': flags} (organizations.service.ts:149-151) — omitted keys are deleted
Success 200 data: {flagKey: boolean…} (updated map)
Errors 404 org missing; 401; (id is org doc id — platform/org-admin context)
Client flow load map → merge locally → PATCH full merged map; diff preview (proposed)
Note metadata is a free-form object (organization.schema.ts:129-130); this surface is not connected to the feature_flags collection or isEnabled() (OQ-2)
Screen Loading Streaming Realtime
list AppSkeleton(list)— (planned) WS topic featureflag.changed (not in 00-shared/07 §8 topic list; no domain event in code — OQ-7); client falls back to TTL refresh
detail skeleton — —
editor save spinner — —
bulk progress bar — —
Screen code UI
any 401 silent refresh; fail → sessionExpired
detail 404 AppEmptyState "Flag not found — deleted?"
editor save 400 field errors from details[]
editor save 5xx snackbar + requestId; form retained
toggle 5xx rollback switch + snackbar
bulk 400 offending rows highlighted
bulk 5xx mid partial results + "retry failed"
delete 404 treat as already-removed
org overlay 404 org missing → settings error state
None — every flag endpoint returns full arrays (no meta). Client renders all rows
(feature-flags.service.ts:10-20); bulk selection is client-side.
Toggles (list + detail): optimistic with rollback (00-shared/06 §3.5); undo = flip again.
Editor save, bulk apply, delete, org overlay PATCH: server-confirm , never optimistic.
Delete has no undo (soft delete; no restore endpoint; re-create caveat OQ-4).