12 — API Mapping (Organizations Module)
- E1 — Create organization
- E2 — List organizations (tenants)
- E3 — Get organization
- E4 — Update organization
- E5 — Delete organization (soft)
- E6 — Get embedded settings
- E7 — Update embedded settings
- E8 — Get org feature-flag map
- E9 — Update org feature-flag map
- E10 — Standalone settings collection
- E11 — Standalone feature flags
- E12 — Register (first admin)
- E13 — RBAC (invite admin)
- Client contract summary (all screens)
Exact endpoints per screen. Wire contract per 00-shared/07: base
/api/v1, Bearer JWT, success{success:true,message:"OK",data,meta?,timestamp,requestId}, error envelope with codes. Only shapes in code are used.(planned)/(forward-looking)marked.
E1 — Create organization
| Endpoint | POST /api/v1/organizations (organizations.controller.ts:23-27) |
| Guard | none in code today (OQ-4); client restricts to platform admin; JWT+RBAC guard (planned) |
| Request | CreateOrganizationDto (create-organization.dto.ts:57-112) — name required; slug/domain/contact/address/timezone/currency/plan/branding/metadata optional |
| Response | 201 envelope, data = org doc (status forced onboarding — organizations.service.ts:55); no meta |
| Errors | 400 VALIDATION_ERROR; 409 DUPLICATE_RESOURCE (slug/domain, :40-50); 429 RATE_LIMITED; 5xx |
| Client | S8 create form; loading spinner; on success → tenant detail + slug handoff |
| Cache | none (write) |
| Realtime | event OrganizationCreated → in-app queue job org-created-notification (event-queue-map.ts:13) — notification to tenant admins (forward-looking) UI |
E2 — List organizations (tenants)
| Endpoint | GET /api/v1/organizations?page&limit&sort&q (organizations.controller.ts:29-33) |
| Params | page ≥1 default 1; limit 1–100 default 20; sort (-field desc); q → $or regex on name/slug case-insensitive (organizations.service.ts:78-83); default sort -createdAt (:85-87) |
| Response | paginated: data array + meta {page,limit,totalItems,totalPages,hasNext,hasPrevious} (pagination-query.dto.ts:32-54) |
| Errors | 400 (bad params); 429; 5xx |
| Client | S7 tenants list; search debounce 300 ms; infinite scroll on hasNext; pull-to-refresh bypasses cache |
| Cache | client paginated cache sl:{tenantId}:orgs:{query} TTL 5 min (volatile); server Redis cache n/a (no cache key in code for this route) |
| Security note | repo is not tenant-scoped (organizations.repository.ts:15-19) — platform-only gating mandatory (OQ-4) |
E3 — Get organization
| Endpoint | GET /api/v1/organizations/:id (organizations.controller.ts:35-40) |
| Response | 200 envelope, data = org doc |
| Errors | 400 VALIDATION_ERROR (bad ObjectId → CastError mapping http-exception.filter.ts:47-55); 404 RESOURCE_NOT_FOUND (organizations.service.ts:72); cross-tenant id → 404 (no leak) |
| Client | S1/S8 detail; :id resolution: platform = from route; self = OQ-1 ((planned) /organizations/me; stopgap GET /organizations?q=<tenantId slug>) |
| Cache | no client cache (detail views, 00-shared/06 §3.3); stale-while-revalidate OK |
E4 — Update organization
| Endpoint | PATCH /api/v1/organizations/:id (organizations.controller.ts:42-47) |
| Request | UpdateOrganizationDto (update-organization.dto.ts:61-139) — partial; $set merge, version +1 (organizations.repository.ts:49-58) |
| Response | 200 updated doc |
| Errors | 404; 409 slug/domain conflict (organizations.service.ts:107-122); 400 enums invalid |
| Client | S2 form, S8 status/plan switchers, S3 branding save; conflict → inline field error |
| Realtime | OrganizationUpdated interface exists (organization-created.event.ts:10-16) but not emitted — no audit signal (OQ-3) |
E5 — Delete organization (soft)
| Endpoint | DELETE /api/v1/organizations/:id (organizations.controller.ts:49-54) |
| Behaviour | soft delete: isDeleted:true, deletedAt, deletedBy + version (organizations.repository.ts:60-66); returns 200, data = null (void handler) |
| Errors | 404 (organizations.service.ts:158) |
| Client | S8 typed-confirm dialog; snackbar "purged after 30 days"; row removed |
| Purge | worker TENANT_PURGE (tenant-purge.worker.ts:17-47): deletes all models' isDeleted && deletedAt < now−30d, idempotent; enqueue from module (planned) OQ-5; GDPR erasure for users (:27-30,49-56) |
E6 — Get embedded settings
| Endpoint | GET /api/v1/organizations/:id/settings (organizations.controller.ts:56-62) |
| Response | data = org.settings ?? {} — object or {} (never null) |
| Errors | 404 (via findById) |
| Client | S4 form load |
E7 — Update embedded settings
| Endpoint | PATCH /api/v1/organizations/:id/settings (organizations.controller.ts:64-72) |
| Request | UpdateOrganizationSettingsDto (update-organization-settings.dto.ts:4-30) |
| Response | 200 updated org doc (settings replaced wholesale — organizations.service.ts:134) |
| Errors | 404; 400 (workingDays elements must be numbers) |
| Client | S4 "Save all" — always full merged object; wipe risk if partial (OQ-6) |
E8 — Get org feature-flag map
| Endpoint | GET /api/v1/organizations/:id/feature-flags (organizations.controller.ts:74-79) |
| Response | data = Record<string,boolean> (metadata.featureFlags ?? {} — organizations.service.ts:139-142) |
| Client | S5 merge source |
E9 — Update org feature-flag map
| Endpoint | PATCH /api/v1/organizations/:id/feature-flags (organizations.controller.ts:81-89) |
| Request | raw body Record<string,boolean> — no DTO, no whitelist; non-boolean → 400 VALIDATION_ERROR (type check on cast, transform:false for raw body — actually plain body bypasses DTO: values not validated server-side except by schema typing; see OQ note) |
| Response | 200 updated map (full replace 'metadata.featureFlags': flags — organizations.service.ts:144-154) |
| Client | S5 optimistic toggle → full map submit; rollback on error |
E10 — Standalone settings collection
| Endpoints | GET /api/v1/settings?group= (settings.controller.ts:24-29); GET /settings/:key (:31-35); PUT /settings upsert (:37-41); PUT /settings/bulk (:43-47); DELETE /settings/:key (:49-53) — all JwtAuthGuard-protected (:19) |
| Request | UpdateSettingDto — key/value/group/label/description (update-setting.dto.ts:5-26); upsert on {tenantId,key} unique (setting.schema.ts:38) |
| Response | setting doc(s); 404 on missing key (settings.service.ts:19-21) |
| Client | S4 reference tabs (read by group); writes owned by feature modules (cross-module) |
E11 — Standalone feature flags
| Endpoints | GET /api/v1/feature-flags?module= (feature-flags.controller.ts:23-28); GET /feature-flags/enabled (:30-34); GET /feature-flags/:key (:36-40); PUT upsert (:42-46); PUT /bulk (:48-52); DELETE /:key (:54-58) — JwtAuthGuard-protected |
| Request | UpdateFeatureFlagDto — key/enabled (required), label/description/module optional (update-feature-flag.dto.ts:4-27); upsert writes enabled+label only (feature-flag.repository.ts:32-43) |
| Response | flag doc; 404 missing key (feature-flags.service.ts:28-30) |
| Client | S5 catalog (labels/descriptions/module grouping); delete via menu |
E12 — Register (first admin)
| Endpoint | POST /api/v1/auth/register (auth.controller.ts:30-36) — public, @RateLimit auth 5/min (:31) |
| Request | RegisterDto (register.dto.ts:4-29) — firstName/lastName/email/password(≥8)/phone?/tenantId (slug) |
| Response | 201 data {accessToken, refreshToken} (auth.service.ts:95-99); side effects: user created + auth_account, member org_admin, role seeding, UserRegistered event → welcome email (event-queue-map.ts:7) |
| Errors | 409 email exists (auth.service.ts:56-58); 400; 429 |
| Client | onboarding flow; no org-existence check (OQ-2) |
E13 — RBAC (invite admin)
| Endpoints | GET /api/v1/rbac/members (rbac.controller.ts:57-61); POST /rbac/members (:63-67); PATCH /rbac/members/:id (:69-73); DELETE /rbac/members/:id (:75-79); GET /rbac/roles (:27-31) — all org_admin + JwtAuthGuard (:19-23) |
| Request | AddMemberDto {userId, roles[]} |
| Client | S6 list/invite/role change/remove |
Client contract summary (all screens)
| Concern | Rule |
|---|---|
| Auth | Bearer JWT; 401 → single-flight refresh → replay; fail → session expiry (00-shared/06 §3.6) |
| Optimistic | only flag toggles (E9); all other writes show server result (07 §9) |
| Idempotency | PATCH/DELETE retry-safe; no Idempotency-Key support confirmed (B6) |
| Offline | reads from last-good cache + banner; writes blocked (no module offline queue) |
| Pagination | page/limit/sort/q + meta exact (07 §5); infinite scroll driven by hasNext |
| Realtime | WS topics n/a for org config today; (forward-looking) org.branding.updated, org.feature-flags.updated |
| Error mapping | 00-shared/06 §5 table: 400 field errors, 403 hide/deny, 404 empty/not-found, 409 inline conflict, 429 backoff, 5xx generic+requestId |