{"openapi":"3.0.0","paths":{"/api/v1/health":{"get":{"operationId":"HealthController_check","parameters":[],"responses":{"200":{"description":""}},"summary":"Health check","tags":["health"]}},"/api/v1/auth/login":{"post":{"operationId":"AuthController_login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginDto"}}}},"responses":{"200":{"description":"Returns access token, profile, user_type, permissions"},"401":{"description":"Invalid credentials"}},"summary":"Admin panel login: SUPER_ADMIN/ADMIN (root) and SUB_ADMIN (tenant). Uses subdomain when SUB_DOMAIN_LOGIN_ACTIVE=true, else x-org-slug header fallback.","tags":["auth"]}},"/api/v1/auth/consultant/login":{"post":{"operationId":"AuthController_consultantLogin","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsultantLoginDto"}}}},"responses":{"200":{"description":"Returns access & refresh tokens, profile"},"401":{"description":"Invalid credentials"}},"summary":"Consultant-only mobile login (Tenant DB). org_slug optional in body.","tags":["auth"]}},"/api/v1/auth/forgot-password/request-otp":{"post":{"description":"Root users (admin / super-admin): email only. Consultants: email + org_slug. Validates active account. When env LIVE_OTP=true, a random code is emailed (SMTP). When false, STATIC_OTP is used and no email is sent.","operationId":"AuthController_forgotPasswordRequestOtp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequestOtpDto"}}}},"responses":{"200":{"description":""}},"summary":"Request password reset OTP","tags":["auth"]}},"/api/v1/auth/forgot-password/verify-otp":{"post":{"operationId":"AuthController_forgotPasswordVerifyOtp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordVerifyOtpDto"}}}},"responses":{"200":{"description":""}},"summary":"Verify OTP and receive a short-lived reset token","tags":["auth"]}},"/api/v1/auth/reset-password":{"post":{"operationId":"AuthController_resetPasswordWithToken","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordWithTokenDto"}}}},"responses":{"200":{"description":""}},"summary":"Set new password using reset_token from verify-otp","tags":["auth"]}},"/api/v1/auth/password":{"post":{"operationId":"AuthController_changePassword","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Change password (works for both admins and consultants)","tags":["auth"]}},"/api/v1/auth/refresh":{"post":{"operationId":"AuthController_refresh","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenDto"}}}},"responses":{"201":{"description":""}},"summary":"Refresh access token for consultants (send x-org-slug header)","tags":["auth"]}},"/api/v1/auth/logout":{"post":{"description":"Works for every user type. Super-admins (no org context) and clients that only hold an access token can call this without a body — the current session is resolved from the access token when available.","operationId":"AuthController_logout","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Logout and revoke the current session / refresh token","tags":["auth"]}},"/api/v1/auth/sessions":{"get":{"operationId":"AuthController_listSessions","parameters":[],"responses":{"200":{"description":"Active sessions retrieved successfully"}},"security":[{"bearer":[]}],"summary":"List active sessions/devices for current user","tags":["auth"]}},"/api/v1/auth/logout-device":{"post":{"operationId":"AuthController_logoutDevice","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutDeviceDto"}}}},"responses":{"200":{"description":"Device session revoked successfully"},"404":{"description":"Active session not found"}},"security":[{"bearer":[]}],"summary":"Force logout a specific active device/session","tags":["auth"]}},"/api/v1/auth/me":{"get":{"description":"Returns the signed-in user plus organisation context: orgName, orgSlug, orgImageUrl, timezone, datetime_format, and a nested organisation object (when applicable).","operationId":"AuthController_getMe","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get current user profile","tags":["auth"]}},"/api/v1/auth/profile":{"patch":{"description":"Root users (SUPER_ADMIN / ADMIN / SUB_ADMIN): updates full_name and mobile on master DB. CONSULTANT: updates tenant user; requires org_slug on JWT (consultant login). employee_id and region apply only to consultants.","operationId":"AuthController_updateProfile","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update own profile","tags":["auth"]}},"/api/v1/auth/profile-image":{"post":{"description":"Multipart field name: file (max 5MB, jpeg/png/webp/gif). Works for every user type (SUPER_ADMIN / ADMIN / SUB_ADMIN / CONSULTANT) — all are stored on master users.profile_image_url and no org slug header is required. Returns a profile_image_url served via GET /media/profile-image/:userId.","operationId":"AuthController_uploadProfileImage","parameters":[],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload profile image","tags":["auth"]}},"/api/v1/media/presign":{"post":{"operationId":"MediaController_presign","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaPresignDto"}}}},"responses":{"200":{"description":"Returns upload_url, headers, and expires_at"}},"security":[{"bearer":[]}],"summary":"Get a pre-signed S3 upload URL for media","tags":["media"]}},"/api/v1/media/org-logo/super-admin/{orgId}":{"post":{"operationId":"MediaController_uploadOrgLogoBySuperAdmin","parameters":[{"name":"orgId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Logo image (max 5MB)"}},"required":["file"]}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload organisation logo (Super Admin)","tags":["media"]}},"/api/v1/media/org-logo/admin":{"post":{"operationId":"MediaController_uploadOrgLogoByAdmin","parameters":[{"name":"org_id","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Logo image (max 5MB)"}},"required":["file"]}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload own organisation logo (Admin)","tags":["media"]}},"/api/v1/media/org-branding/{assetType}/admin":{"post":{"operationId":"MediaController_uploadOrgBrandingAsset","parameters":[{"name":"assetType","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Branding asset image (max 5MB)"}},"required":["file"]}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload organisation branding asset (thumbnail/favicon)","tags":["media"]}},"/api/v1/media/org-logo/{orgId}":{"get":{"operationId":"MediaController_viewOrganisationLogo","parameters":[{"name":"orgId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"View organisation logo by org id (public)","tags":["media"]}},"/api/v1/media/profile-image/{userId}":{"get":{"operationId":"MediaController_viewProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"View a user profile image by user id (public)","tags":["media"]}},"/api/v1/media/files/{mediaId}/logo-temp":{"get":{"operationId":"MediaController_viewTempLogo","parameters":[{"name":"mediaId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"View temporary pre-org logo (public)","tags":["media"]}},"/api/v1/media/files/{mediaId}":{"get":{"description":"Requires auth and access checks. For no-header access use signed URL endpoint.","operationId":"MediaController_downloadFile","parameters":[{"name":"mediaId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Download inspection media (authenticated)","tags":["media"]}},"/api/v1/media/files/{mediaId}/signed-url":{"get":{"description":"Returns a URL with a short-lived token that can be opened without auth headers.","operationId":"MediaController_getSignedUrl","parameters":[{"name":"mediaId","required":true,"in":"path","schema":{"type":"string"}},{"name":"expires_in","required":false,"in":"query","schema":{"default":3600,"maximum":86400}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get a signed temporary URL for media (for mobile image views)","tags":["media"]}},"/api/v1/media/files/{mediaId}/view":{"get":{"description":"Pass ?token=... from signed-url endpoint. For mobile image views and browser access.","operationId":"MediaController_viewFileWithToken","parameters":[{"name":"mediaId","required":true,"in":"path","schema":{"type":"string"}},{"name":"token","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"View media with signed token (no auth header needed)","tags":["media"]}},"/api/v1/upload/image":{"post":{"description":"Send multipart field `file` plus `inspection_id` and `field_key`. Stores binary in S3 or local disk; DB must only persist the returned `url` (never base64).","operationId":"UploadController_uploadImage","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","inspection_id","field_key"],"properties":{"file":{"type":"string","format":"binary"},"inspection_id":{"type":"string","format":"uuid"},"field_key":{"type":"string"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload inspection image (multipart); returns URL for field storage","tags":["upload"]}},"/api/v1/upload/base64":{"post":{"description":"Accepts a base64 data URL and stores it as media, returning the full URL and media_id.","operationId":"UploadController_uploadBase64","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string"},"inspection_id":{"type":"string"},"field_key":{"type":"string"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload image from base64 payload","tags":["upload"]}},"/api/v1/upload/base64-batch":{"post":{"description":"Accepts an array of base64 data URLs tied to an inspection/field and stores each one. Returns an array of { url, media_id } entries so callers can persist accessible URLs (never the raw base64).","operationId":"UploadController_uploadBase64Batch","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["inspection_id","files"],"properties":{"inspection_id":{"type":"string","format":"uuid"},"field_key":{"type":"string"},"files":{"type":"array","description":"Array of base64 data URLs (e.g. \"data:image/png;base64,...\")","items":{"type":"string"}}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload multiple base64 images in one request","tags":["upload"]}},"/api/v1/upload/images":{"post":{"description":"Send multipart field `files` (one or many) plus `inspection_id` and `field_key`. Each file is persisted to S3/local storage and the response returns accessible URLs.","operationId":"UploadController_uploadImages","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files","inspection_id","field_key"],"properties":{"files":{"type":"array","items":{"type":"string","format":"binary"}},"inspection_id":{"type":"string","format":"uuid"},"field_key":{"type":"string"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload multiple inspection images (multipart)","tags":["upload"]}},"/api/v1/mobile/kpis":{"get":{"description":"Counts for the logged-in consultant. Default: current ISO week (Monday–Sunday, org timezone). `period=overall`: all-time counts. `start_date` + `end_date`: custom range on scheduled_date and submitted_at.","operationId":"MobileController_consultantKpis","parameters":[{"name":"period","required":false,"in":"query","description":"Preset window. Default `this_week`. `overall` returns all-time counts (ignores start/end).","schema":{"enum":["this_week","overall"],"type":"string"}},{"name":"start_date","required":false,"in":"query","description":"YYYY-MM-DD inclusive. Must be sent with end_date (overrides period presets).","schema":{"example":"2026-05-01","type":"string"}},{"name":"end_date","required":false,"in":"query","description":"YYYY-MM-DD inclusive. Must be sent with start_date.","schema":{"example":"2026-05-31","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant KPI statistics","tags":["mobile"]}},"/api/v1/mobile/dashboard":{"get":{"description":"Single response for consultants: assigned inspection items (with linked order details), published templates (summary), asset-type categories, inspection summary and recent items. Requires consultant JWT and tenant context (org_slug).","operationId":"MobileController_dashboard","parameters":[{"name":"inspection_items_limit","required":false,"in":"query","schema":{"default":50,"maximum":100}},{"name":"templates_limit","required":false,"in":"query","schema":{"default":40,"maximum":80}},{"name":"inspections_recent_limit","required":false,"in":"query","schema":{"default":25,"maximum":50}},{"name":"submitted_limit","required":false,"in":"query","schema":{"default":25,"maximum":100}},{"name":"submitted_offset","required":false,"in":"query","schema":{"default":0,"minimum":0}},{"name":"submitted_page","required":false,"in":"query","description":"1-based page number for submitted_items pagination. Convenience alternative to submitted_offset; offset = (page - 1) × submitted_limit. Ignored if submitted_offset is also provided.","schema":{"default":1,"minimum":1}},{"name":"submitted_search","required":false,"in":"query","description":"Search term (applies only when status=submitted; searches inspection_items.data)","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Optional filter (case-insensitive). `overdue` — items whose scheduled date+time window has passed in the organisation timezone (live SQL; uses `inspection_items_limit`, not submitted_* pagination). Inspection statuses: `SUBMITTED`, `APPROVED`, `REJECTED`, or combined `SUBMITTED|APPROVED|REJECTED` (comma also supported). When omitted, default list behaviour is unchanged.","schema":{"type":"string"}},{"name":"upcoming_days","required":false,"in":"query","description":"Echoed in `inspection_items.meta` for client-side “upcoming” grouping (calendar days after today in org timezone; aligns with GET /consultant/dashboard). Default 7.","schema":{"default":7,"minimum":1,"maximum":366}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Mobile app bootstrap payload","tags":["mobile"]}},"/api/v1/mobile/tenants":{"get":{"description":"Public endpoint for mobile app pre-login. Returns active organisations with org_slug so user can select tenant before consultant login.","operationId":"MobilePublicController_listTenants","parameters":[],"responses":{"200":{"description":""}},"summary":"List active tenants for mobile login","tags":["mobile"]}},"/api/v1/mobile/tenants/review":{"get":{"operationId":"MobilePublicController_listTenantsForReview","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List tenants for review (includes inactive organisations)","tags":["mobile"]}},"/api/v1/mobile/legal/{key}":{"get":{"description":"No authentication. Use the same URLs from the mobile app and from the admin/organisation web app (e.g. login or settings screens). Global prefix is `API_PREFIX` (default `api/v1`). Examples: `GET /api/v1/mobile/legal/terms?org_slug=<slug>`, `GET /api/v1/mobile/legal/privacy?org_slug=<slug>`. `key` is `terms` or `privacy` (anything else is treated as terms).","operationId":"MobilePublicController_getLegalContent","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}},{"name":"org_slug","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"summary":"Public Terms & Privacy (mobile + admin web)","tags":["mobile"]},"put":{"operationId":"MobilePublicController_upsertLegalContent","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertLegalContentDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Create/update legal content","tags":["mobile"]},"post":{"description":"Same as PUT. Example: `POST /api/v1/mobile/legal/terms`.","operationId":"MobilePublicController_createLegalContent","parameters":[{"name":"key","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertLegalContentDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Alias: create/update legal content","tags":["mobile"]}},"/api/v1/consultant/dashboard":{"get":{"description":"Mobile-optimized normalized payload with today/upcoming inspection items, in-progress inspections, and minimal deduplicated templates. Upcoming items are limited to scheduled dates within `upcoming_days` calendar days after today (org timezone); default 7.","operationId":"ConsultantDashboardController_getDashboard","parameters":[{"name":"updated_after","required":true,"in":"query","schema":{"type":"string"}},{"name":"upcoming_days","required":false,"in":"query","description":"Calendar-day window after today for the \"upcoming\" bucket (1–366). Default 7.","schema":{"default":7,"minimum":1,"maximum":366}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Unified consultant dashboard snapshot","tags":["mobile"]}},"/api/v1/consultant/templates":{"get":{"description":"Returns templates assigned to this consultant (via inspection items/inspections) with compact basic info and full definition for offline usage.","operationId":"ConsultantDashboardController_getAssignedTemplates","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant assigned templates for offline sync","tags":["mobile"]}},"/api/v1/consultant/orders":{"get":{"description":"Status filter: upcoming | in_progress | active. Returns only orders tied to this consultant. `upcoming` respects the same `upcoming_days` window as GET /consultant/dashboard.","operationId":"ConsultantDashboardController_getOrders","parameters":[{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"upcoming_days","required":false,"in":"query","schema":{"default":7,"minimum":1,"maximum":366}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant orders by mobile status","tags":["mobile"]}},"/api/v1/consultant/inspections":{"get":{"description":"Status filter: upcoming | in_progress | active. Returns only inspections owned by this consultant. `upcoming` respects `upcoming_days` (default 7).","operationId":"ConsultantDashboardController_getInspections","parameters":[{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"upcoming_days","required":false,"in":"query","schema":{"default":7,"minimum":1,"maximum":366}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant inspections by mobile status","tags":["mobile"]}},"/api/v1/inspections":{"post":{"operationId":"InspectionsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInspectionDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Ensure inspection exists for an inspection item","tags":["inspections"]},"get":{"operationId":"InspectionsController_findAll","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"consultant_id","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"submitted_by_role","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List inspections (paginated, searchable)","tags":["inspections"]}},"/api/v1/inspections/{id}/consultant":{"patch":{"operationId":"InspectionsController_assignConsultant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignInspectionConsultantDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Assign or re-assign consultant to inspection","tags":["inspections"]},"delete":{"operationId":"InspectionsController_revokeConsultant","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Revoke consultant assignment (inspection becomes unassigned)","tags":["inspections"]}},"/api/v1/inspections/{id}/inspection-fields":{"get":{"operationId":"InspectionsController_listInspectionFieldSnapshots","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List full-form JSON snapshots from `inspection_fields` (append-only, includes template_version_id)","tags":["inspections"]}},"/api/v1/inspections/{id}":{"get":{"operationId":"InspectionsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get inspection by ID with all fields and media","tags":["inspections"]},"delete":{"operationId":"InspectionsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft-delete an inspection","tags":["inspections"]}},"/api/v1/inspections/admin/inspection/{id}":{"get":{"operationId":"InspectionsController_findOneAdminWithTemplate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get admin inspection with full template sections/steps/groups/fields and field values","tags":["inspections"]}},"/api/v1/inspections/admin/order/{orderId}/inspections":{"get":{"operationId":"InspectionsController_findSubmittedByOrderAdmin","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}},{"name":"type","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"page","required":true,"in":"query","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get submitted inspections by order ID, grouped under the order hierarchy","tags":["inspections"]}},"/api/v1/inspections/{id}/fields":{"patch":{"description":"**Canonical single-field PATCH body** (stored under `field_data.fields[field_key]` as `{ value, datetime }` when `datetime` is present):\n\n```json\n{\n  \"field_key\": \"f1111111-1111-4111-8111-111111111101:f1111111-1111-4111-8111-111111111201\",\n  \"value\": \"SW1A 1AA\",\n  \"datetime\": \"2026-04-30 11:22:00\"\n}\n```\n\n**Bulk:** JSON array of the same objects, or `{ \"data\": [ ... ] }`.\n\n**Merge:** `{ \"fields\": { \"<field_key>\": <value> }, \"expected_field_data_version\"?: n }` (no per-field `datetime` in that shape).\n\nEach change creates an `inspection_versions` row. Image upload: `{ \"image_base64\": \"...\", \"content_type\": \"image/png\", \"filename\": \"x.png\" }` → file in `media_files`, value stores `{ url, media_id }`.","operationId":"InspectionsController_updateFields","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Single `UpdateFieldDto`, an array of them, `{ data: UpdateFieldDto[] }`, or merge `{ fields: {...} }`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UpdateFieldDto"},{"type":"array","items":{"$ref":"#/components/schemas/UpdateFieldDto"}},{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UpdateFieldDto"}}}},{"type":"object","required":["fields"],"properties":{"fields":{"type":"object","additionalProperties":true,"description":"field_key → value map"},"expected_field_data_version":{"type":"number"}}}]},"examples":{"canonicalPatch":{"summary":"Single PATCH body (object, not array)","value":{"field_key":"f1111111-1111-4111-8111-111111111101:f1111111-1111-4111-8111-111111111201","value":"SW1A 1AA","datetime":"2026-04-30 11:22:00"}},"single":{"summary":"Legacy single field","value":{"field_key":"exterior_condition","value":"Poor","datetime":"2026-04-30 11:22:00"}},"bulkWrapped":{"summary":"Bulk with data wrapper","value":{"data":[{"field_key":"fld_104097ee-dd28-49f3-9e2b-b78057d8f011","value":"test","datetime":"2026-04-30 11:22:00"},{"field_key":"fld_1a46065c-813f-411a-9c0e-44536e03489d","value":"test","datetime":"2026-04-30 11:23:00"}]}},"bulk":{"summary":"Multiple fields","value":[{"field_key":"exterior_condition","value":"Poor","datetime":"2026-04-30 11:22:00"},{"field_key":"roof_condition","value":"Good","datetime":"2026-04-30 11:23:00"}]},"templateKeys":{"summary":"Template-aligned keys","value":[{"field_key":"f1111111-1111-4111-8111-111111111101:f1111111-1111-4111-8111-111111111201","value":"SW1A 1AA","datetime":"2026-04-30 11:22:00"}]}}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update inspection field(s) — single or bulk","tags":["inspections"]}},"/api/v1/inspections/{id}/fields/resolve-conflict":{"post":{"operationId":"InspectionsController_resolveFieldConflict","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveFieldConflictBodyDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Resolve a field conflict after 409 (choose server, client, or merge)","tags":["inspections"]}},"/api/v1/inspections/{id}/fields/test-conflict":{"post":{"operationId":"InspectionsController_testConflict","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"DEV ONLY: force a 409 field conflict using stale expected_field_data_version","tags":["inspections"]}},"/api/v1/inspections/{id}/status":{"patch":{"operationId":"InspectionsController_updateStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStatusDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update inspection status (state machine)","tags":["inspections"]}},"/api/v1/inspections/{id}/history":{"get":{"operationId":"InspectionsController_getHistory","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get inspection version history (use /versions for full API)","tags":["inspections"]}},"/api/v1/inspections/{id}/submit":{"post":{"operationId":"InspectionsController_submit","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"submitted_at":{"type":"string","format":"date-time"},"submittedAt":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time"},"datetime":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Submit inspection for review","tags":["inspections"]}},"/api/v1/inspections/{id}/check-in":{"post":{"operationId":"InspectionsController_checkIn","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"started_at":{"type":"string","format":"date-time"},"start_time":{"type":"string","format":"date-time"},"datetime":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Check in to start the inspection","tags":["inspections"]}},"/api/v1/inspections/{id}/approve":{"post":{"description":"Admins may approve any inspection. Consultants may approve only inspections assigned to them (`consultant_id` matches their user id).","operationId":"InspectionsController_approve","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Approve submitted inspection","tags":["inspections"]}},"/api/v1/inspections/{id}/reject":{"post":{"description":"Admins may reject any inspection. Consultants may reject only inspections assigned to them (`consultant_id` matches their user id).","operationId":"InspectionsController_reject","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reject submitted inspection","tags":["inspections"]}},"/api/v1/reports":{"get":{"operationId":"ReportsController_findAll","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all reports (paginated)","tags":["reports"]}},"/api/v1/reports/inspection/{inspectionId}":{"get":{"operationId":"ReportsController_findByInspection","parameters":[{"name":"inspectionId","required":true,"in":"path","description":"Inspection UUID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get report by inspection ID","tags":["reports"]}},"/api/v1/reports/inspection/{inspectionId}/regenerate":{"post":{"operationId":"ReportsController_regenerate","parameters":[{"name":"inspectionId","required":true,"in":"path","description":"Inspection UUID","schema":{"format":"uuid","type":"string"}}],"responses":{"202":{"description":""}},"security":[{"bearer":[]}],"summary":"Regenerate the PDF report for an inspection (soft-deletes the previous report row and re-queues generation)","tags":["reports"]}},"/api/v1/reports/inspection/{inspectionId}/download":{"get":{"operationId":"ReportsController_downloadInspectionReport","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Download the latest inspection report PDF by inspection id.","tags":["reports"]}},"/api/v1/reports/order/{orderId}":{"get":{"operationId":"ReportsController_findByOrder","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get the latest full-order report row for an order","tags":["reports"]}},"/api/v1/reports/order/{orderId}/consultants":{"get":{"operationId":"ReportsController_listConsultantReports","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List the per-consultant order-report rows for a given order (one per assigned consultant).","tags":["reports"]}},"/api/v1/reports/order/{orderId}/consultant/{consultantId}":{"get":{"operationId":"ReportsController_findByOrderForConsultant","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}},{"name":"consultantId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get the latest personalised order report for one consultant under an order.","tags":["reports"]}},"/api/v1/reports/order/{orderId}/generate":{"post":{"operationId":"ReportsController_generateOrder","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":""}},"security":[{"bearer":[]}],"summary":"Manually queue generation of the full-order PDF (covers every inspection-item under the order, including unsubmitted ones).","tags":["reports"]}},"/api/v1/reports/order/{orderId}/regenerate":{"post":{"operationId":"ReportsController_regenerateOrder","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":""}},"security":[{"bearer":[]}],"summary":"Hard-replace the existing order report and re-queue generation.","tags":["reports"]}},"/api/v1/reports/{reportId}/download":{"get":{"operationId":"ReportsController_download","parameters":[{"name":"reportId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Download the PDF for a generated report. Always streams the file bytes through the API so CORS headers are preserved.","tags":["reports"]}},"/api/v1/reports/order/{orderId}/download":{"get":{"operationId":"ReportsController_downloadOrderReport","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Download the latest order-report PDF by order id (full, or per-consultant via ?consultant_id=).","tags":["reports"]}},"/api/v1/reports/order/{orderId}/mine/download":{"get":{"operationId":"ReportsController_downloadMyOrderReport","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Download the caller's personalised order report. For admins, falls back to the full report.","tags":["reports"]}},"/api/v1/notifications":{"get":{"operationId":"NotificationsController_findAll","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List notifications for current user (paginated)","tags":["notifications"]}},"/api/v1/notifications/unread-count":{"get":{"operationId":"NotificationsController_getUnreadCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnreadCountResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Get unread notification count","tags":["notifications"]}},"/api/v1/notifications/{id}/delivered":{"patch":{"operationId":"NotificationsController_markAsDelivered","parameters":[{"name":"id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Confirm push notification was delivered to the device","tags":["notifications"]}},"/api/v1/notifications/{id}/read":{"patch":{"operationId":"NotificationsController_markAsRead","parameters":[{"name":"id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Mark notification as read","tags":["notifications"]}},"/api/v1/notifications/read-all":{"patch":{"operationId":"NotificationsController_markAllAsRead","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Mark all notifications as read","tags":["notifications"]}},"/api/v1/notifications/delivery-status":{"get":{"operationId":"NotificationsController_getDeliveryStatus","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Unread count broken down by delivery status","tags":["notifications"]}},"/api/v1/notifications/admin-panel/alerts":{"get":{"operationId":"NotificationsController_listAdminAlerts","parameters":[{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List recent admin-panel alerts (sync failures, etc.)","tags":["notifications"]}},"/api/v1/orders/{orderId}/items":{"post":{"operationId":"InspectionItemsController_create","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInspectionItemDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Add a single inspection item to an order","tags":["Inspection Items"]},"get":{"operationId":"InspectionItemsController_findAll","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all items for an order","tags":["Inspection Items"]}},"/api/v1/orders/{orderId}/items/bulk":{"post":{"operationId":"InspectionItemsController_bulkCreate","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCreateInspectionItemsDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Bulk-create inspection items by quantity","tags":["Inspection Items"]}},"/api/v1/orders/{orderId}/items/csv-import":{"post":{"operationId":"InspectionItemsController_csvImport","parameters":[{"name":"orderId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CsvImportDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Import inspection items from CSV data","tags":["Inspection Items"]}},"/api/v1/orders/{orderId}/items/{id}":{"get":{"operationId":"InspectionItemsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get a specific inspection item","tags":["Inspection Items"]},"patch":{"operationId":"InspectionItemsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInspectionItemDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update an inspection item","tags":["Inspection Items"]},"delete":{"operationId":"InspectionItemsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft-delete an inspection item","tags":["Inspection Items"]}},"/api/v1/orders/{orderId}/items/{id}/status":{"patch":{"operationId":"InspectionItemsController_updateStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update inspection item status","tags":["Inspection Items"]}},"/api/v1/orders/{orderId}/items/{id}/reassign":{"post":{"operationId":"InspectionItemsController_reassign","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReassignInspectionItemDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reassign an inspection item to a different consultant","tags":["Inspection Items"]}},"/api/v1/inspection-items/bulk-reassign":{"post":{"operationId":"InspectionItemsBulkController_bulkReassign","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReassignDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Bulk reassign multiple inspection items","tags":["Inspection Items — Bulk Operations"]}},"/api/v1/template-mappings":{"post":{"operationId":"TemplateMappingsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateMappingDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Map published template to asset/service combination","tags":["template-mappings"]},"get":{"operationId":"TemplateMappingsController_findAll","parameters":[{"name":"asset_id","required":false,"in":"query","schema":{"type":"string"}},{"name":"service_id","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get template mappings","tags":["template-mappings"]}},"/api/v1/super-admin/overview":{"get":{"description":"Master DB only: `kpis` (organisation and root-user counts), `org_growth` (last six months), `role_distribution` for charts.","operationId":"SuperAdminDashboardController_platformOverview","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Platform-wide dashboard metrics","tags":["super-admin/dashboard"]}},"/api/v1/super-admin/organisations":{"get":{"description":"Returns organisation list with admin count, consultant count, and DB health.","operationId":"SuperAdminDashboardController_listOrganisations","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all organisations with summary stats (paginated)","tags":["super-admin/dashboard"]}},"/api/v1/super-admin/organisations/{orgRef}/dashboard":{"get":{"description":"Returns organisation details, root user counts, and tenant data statistics.","operationId":"SuperAdminDashboardController_getDashboard","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Full dashboard for a specific organisation","tags":["super-admin/dashboard"]}},"/api/v1/super-admin/organisations/{orgRef}/consultants":{"get":{"operationId":"SuperAdminDashboardController_listConsultants","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List consultants for an organisation (tenant DB, paginated)","tags":["super-admin/dashboard"]}},"/api/v1/super-admin/organisations/{orgRef}/inspection-items":{"get":{"operationId":"SuperAdminDashboardController_listInspectionItems","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List inspection items for an organisation","tags":["super-admin/dashboard"]}},"/api/v1/super-admin/organisations/{orgRef}/inspections":{"get":{"operationId":"SuperAdminDashboardController_listInspections","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List inspections for an organisation (tenant DB, paginated)","tags":["super-admin/dashboard"]}},"/api/v1/super-admin/tenants/{orgRef}/admins":{"get":{"operationId":"SuperAdminTenantAdminsController_listAdmins","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"include_deleted","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List Admin users for an organisation (root DB, paginated)","tags":["super-admin/tenant-admins"]},"post":{"operationId":"SuperAdminTenantAdminsController_createAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSuperAdminTenantAdminDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create an Admin user in root DB for a tenant organisation","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/{orgRef}/admins/{userId}/reset-password":{"post":{"operationId":"SuperAdminTenantAdminsController_resetAdminPassword","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetSuperAdminTenantAdminPasswordDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reset Admin password (root DB)","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/{orgRef}/admins/{userId}/force-logout":{"post":{"operationId":"SuperAdminTenantAdminsController_forceLogoutAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout Admin (blocklist)","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/{orgRef}/admins/{userId}/activate":{"patch":{"operationId":"SuperAdminTenantAdminsController_activateAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Activate Admin account","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/{orgRef}/admins/{userId}/deactivate":{"patch":{"operationId":"SuperAdminTenantAdminsController_deactivateAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Deactivate Admin account","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/{orgRef}/admins/{userId}/suspend":{"patch":{"operationId":"SuperAdminTenantAdminsController_suspendAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Suspend Admin account","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/{orgRef}/admins/{userId}":{"get":{"operationId":"SuperAdminTenantAdminsController_getAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get one Admin by id","tags":["super-admin/tenant-admins"]},"patch":{"operationId":"SuperAdminTenantAdminsController_updateAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSuperAdminTenantAdminDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update Admin (profile, status, optional new password)","tags":["super-admin/tenant-admins"]},"delete":{"operationId":"SuperAdminTenantAdminsController_deleteAdmin","parameters":[{"name":"orgRef","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft-delete Admin","tags":["super-admin/tenant-admins"]}},"/api/v1/super-admin/tenants/media/upload":{"post":{"operationId":"TenantsController_uploadPreOrgMedia","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Logo image (max 5MB)"}},"required":["file"]}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Upload logo before org creation (pre-organisation)","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants":{"post":{"operationId":"TenantsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTenantDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Provision a new tenant organisation","tags":["super-admin/tenants"]},"get":{"description":"Supports pagination via `page` and `limit` query params, and search via `search` (matches name/slug/domain). Omit `limit` to return the full list. Credential objects never expose encrypted or plain database passwords.","operationId":"TenantsController_findAll","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all organisations","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/options/timezones":{"get":{"operationId":"TenantsController_timezoneAndDateFormatOptions","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Master timezone list","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/options/datetime-formats":{"get":{"operationId":"TenantsController_datetimeFormatOptions","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Configured datetime format options","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/{id}":{"get":{"description":"Credential objects never expose encrypted or plain database passwords.","operationId":"TenantsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get organisation details","tags":["super-admin/tenants"]},"patch":{"operationId":"TenantsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTenantDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update organisation settings","tags":["super-admin/tenants"]},"delete":{"description":"Soft-disables the org (`is_active = false`) and evicts the cached DB connection. The tenant DB is preserved — call `PATCH /:id/reactivate` to bring it back online.","operationId":"TenantsController_deactivate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Deactivate organisation","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/{slug}/health":{"get":{"operationId":"TenantsController_healthCheck","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Check tenant database health","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/{id}/reactivate":{"patch":{"description":"Flips `is_active` back to `true` and clears any stale cached DB connection so the next tenant request re-resolves credentials. Idempotent if the org is already active. Hard-deleted orgs cannot be reactivated.","operationId":"TenantsController_reactivate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Reactivate a previously deactivated organisation","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/sync-schemas":{"post":{"description":"Ensures entity schema is consistent on all active tenant databases.","operationId":"TenantsController_syncAllSchemas","parameters":[],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Synchronise schema across all tenant databases","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/migrate-all":{"post":{"description":"Runs in parallel with bounded concurrency and writes status into tenant_credentials.","operationId":"TenantsController_migrateAll","parameters":[{"name":"concurrency","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Run schema migration/sync for all tenant databases","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/health-all":{"get":{"operationId":"TenantsController_healthCheckAll","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Health check for all tenant databases","tags":["super-admin/tenants"]}},"/api/v1/super-admin/tenants/{id}/hard":{"delete":{"description":"Deactivates sessions/tokens, evicts pool, optionally drops tenant DB, and deactivates root records.","operationId":"TenantsController_hardDelete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"drop_db","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Hard delete organisation (optional DB drop)","tags":["super-admin/tenants"]}},"/api/v1/tenants/options/timezones":{"get":{"operationId":"TenantOptionsController_timezones","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Timezone list for super admin/admin/sub-admin","tags":["tenants/options"]}},"/api/v1/tenants/options/datetime-formats":{"get":{"operationId":"TenantOptionsController_datetimeFormats","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Datetime format options for super admin/admin/sub-admin","tags":["tenants/options"]}},"/api/v1/tenants/options/country-codes":{"get":{"operationId":"TenantOptionsController_countryCodes","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Country code options for consultant phone inputs","tags":["tenants/options"]}},"/api/v1/platform/organisations":{"post":{"operationId":"PlatformOrganisationsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTenantDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create organisation (alias of super-admin/tenants)","tags":["platform/organisations"]},"get":{"description":"Supports pagination via `page` and `limit` query params, and search via `search` (matches name/slug/domain). Omit `limit` to return the full list.","operationId":"PlatformOrganisationsController_findAll","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List organisations (alias)","tags":["platform/organisations"]}},"/api/v1/platform/organisations/{id}":{"get":{"operationId":"PlatformOrganisationsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get organisation (alias)","tags":["platform/organisations"]}},"/api/v1/organisation/settings":{"patch":{"description":"Root **ADMIN** or tenant **ADMIN** JWT with `tenant_id` (organisation id). Send only fields to change. Requires `x-org-slug` (or subdomain) to match the token when not using strict subdomain login alone.","operationId":"OrganisationAdminSettingsController_patchSettings","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchOrganisationAdminSettingsDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update organisation datetime, timezone, retention, and domain","tags":["organisation"]}},"/api/v1/organisation/integration-configs":{"get":{"description":"Organisation is resolved from the `x-org-slug` request header (admin header org selector). Org **ADMIN** / **SUB_ADMIN** may only access their own slug. **Super admin** may use any slug.","operationId":"OrganisationAdminSettingsController_getIntegrationConfigs","parameters":[{"name":"x-org-slug","required":true,"in":"header","description":"Organisation slug from admin header selector","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get organisation integration keys","tags":["organisation"]},"patch":{"description":"Replaces `organisations.integration_configs` for the organisation identified by `x-org-slug`.","operationId":"OrganisationAdminSettingsController_updateIntegrationConfigs","parameters":[{"name":"x-org-slug","required":true,"in":"header","description":"Organisation slug from admin header selector","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrganisationIntegrationConfigDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update organisation integration keys","tags":["organisation"]}},"/api/v1/consultants":{"post":{"operationId":"ConsultantsController_create[0]","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConsultantDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create tenant user (CONSULTANT, SUB_ADMIN, or ADMIN) in tenant DB","tags":["tenant-users"]},"get":{"operationId":"ConsultantsController_findAll[0]","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["ACTIVE","SUSPENDED","DEACTIVATED"]}},{"name":"role","required":false,"in":"query","description":"Single role or comma-separated roles. Example: ADMIN,SUB_ADMIN","schema":{"example":"ADMIN,SUB_ADMIN","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List consultants (paginated, searchable)","tags":["tenant-users"]}},"/api/v1/tenant-users":{"post":{"operationId":"ConsultantsController_create[1]","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConsultantDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create tenant user (CONSULTANT, SUB_ADMIN, or ADMIN) in tenant DB","tags":["tenant-users"]},"get":{"operationId":"ConsultantsController_findAll[1]","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["ACTIVE","SUSPENDED","DEACTIVATED"]}},{"name":"role","required":false,"in":"query","description":"Single role or comma-separated roles. Example: ADMIN,SUB_ADMIN","schema":{"example":"ADMIN,SUB_ADMIN","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List consultants (paginated, searchable)","tags":["tenant-users"]}},"/api/v1/consultants/import":{"post":{"description":"CSV must have header: full_name,email,mobile,country_code,password (optional: employee_id,region). Duplicates are skipped.","operationId":"ConsultantsController_importCsv[0]","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"CSV file (max 2MB, max 500 rows)"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Bulk import consultants via CSV","tags":["tenant-users"]}},"/api/v1/tenant-users/import":{"post":{"description":"CSV must have header: full_name,email,mobile,country_code,password (optional: employee_id,region). Duplicates are skipped.","operationId":"ConsultantsController_importCsv[1]","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"CSV file (max 2MB, max 500 rows)"}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Bulk import consultants via CSV","tags":["tenant-users"]}},"/api/v1/consultants/meta/country-codes":{"get":{"description":"Returns global dial-code options. Super admins may call without X-Org-Slug; tenant admins are scoped via JWT or header.","operationId":"ConsultantsController_countryCodes[0]","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Country code options for consultant phone numbers","tags":["tenant-users"]}},"/api/v1/tenant-users/meta/country-codes":{"get":{"description":"Returns global dial-code options. Super admins may call without X-Org-Slug; tenant admins are scoped via JWT or header.","operationId":"ConsultantsController_countryCodes[1]","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Country code options for consultant phone numbers","tags":["tenant-users"]}},"/api/v1/consultants/all/sessions":{"get":{"operationId":"ConsultantsController_listAllUserSessions[0]","parameters":[{"name":"role","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List active sessions for all tenant users (CONSULTANT/SUB_ADMIN/ADMIN)","tags":["tenant-users"]}},"/api/v1/tenant-users/all/sessions":{"get":{"operationId":"ConsultantsController_listAllUserSessions[1]","parameters":[{"name":"role","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List active sessions for all tenant users (CONSULTANT/SUB_ADMIN/ADMIN)","tags":["tenant-users"]}},"/api/v1/consultants/{id}/permissions":{"get":{"operationId":"ConsultantsController_getPermissions[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get tenant user permissions (CONSULTANT/SUB_ADMIN/ADMIN)","tags":["tenant-users"]},"post":{"operationId":"ConsultantsController_assignPermissions[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignTenantUserPermissionsDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Assign tenant permissions to tenant user (CONSULTANT, SUB_ADMIN, or ADMIN) using tenant_user_permissions","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/permissions":{"get":{"operationId":"ConsultantsController_getPermissions[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get tenant user permissions (CONSULTANT/SUB_ADMIN/ADMIN)","tags":["tenant-users"]},"post":{"operationId":"ConsultantsController_assignPermissions[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignTenantUserPermissionsDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Assign tenant permissions to tenant user (CONSULTANT, SUB_ADMIN, or ADMIN) using tenant_user_permissions","tags":["tenant-users"]}},"/api/v1/consultants/{id}/summary":{"get":{"operationId":"ConsultantsController_consultantSummary[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant summary — profile and inspection outcome statistics","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/summary":{"get":{"operationId":"ConsultantsController_consultantSummary[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant summary — profile and inspection outcome statistics","tags":["tenant-users"]}},"/api/v1/consultants/{id}/submitted-inspections":{"get":{"operationId":"ConsultantsController_consultantSubmittedInspections[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"period","required":false,"in":"query","description":"Filter submitted inspections by submission date","schema":{"default":"30d","type":"string","enum":["7d","30d","90d","all"]}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Paginated submitted inspections for a consultant (period filter)","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/submitted-inspections":{"get":{"operationId":"ConsultantsController_consultantSubmittedInspections[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"period","required":false,"in":"query","description":"Filter submitted inspections by submission date","schema":{"default":"30d","type":"string","enum":["7d","30d","90d","all"]}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Paginated submitted inspections for a consultant (period filter)","tags":["tenant-users"]}},"/api/v1/consultants/{id}":{"get":{"operationId":"ConsultantsController_findOne[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get consultant by ID","tags":["tenant-users"]},"patch":{"operationId":"ConsultantsController_update[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateConsultantDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update consultant profile","tags":["tenant-users"]},"delete":{"operationId":"ConsultantsController_remove[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft delete consultant","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}":{"get":{"operationId":"ConsultantsController_findOne[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get consultant by ID","tags":["tenant-users"]},"patch":{"operationId":"ConsultantsController_update[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateConsultantDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update consultant profile","tags":["tenant-users"]},"delete":{"operationId":"ConsultantsController_remove[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft delete consultant","tags":["tenant-users"]}},"/api/v1/consultants/{id}/reset-password":{"post":{"operationId":"ConsultantsController_resetPassword[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetConsultantPasswordDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reset consultant password","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/reset-password":{"post":{"operationId":"ConsultantsController_resetPassword[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetConsultantPasswordDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reset consultant password","tags":["tenant-users"]}},"/api/v1/consultants/{id}/force-logout":{"post":{"operationId":"ConsultantsController_forceLogout[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout consultant","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/force-logout":{"post":{"operationId":"ConsultantsController_forceLogout[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout consultant","tags":["tenant-users"]}},"/api/v1/consultants/{id}/sessions":{"get":{"operationId":"ConsultantsController_listUserSessions[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List active sessions/devices for tenant user (ADMIN/SUB_ADMIN/CONSULTANT)","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/sessions":{"get":{"operationId":"ConsultantsController_listUserSessions[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List active sessions/devices for tenant user (ADMIN/SUB_ADMIN/CONSULTANT)","tags":["tenant-users"]}},"/api/v1/consultants/{id}/force-logout-session":{"post":{"operationId":"ConsultantsController_forceLogoutSession[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutDeviceDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout a specific active session/device for tenant user","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/force-logout-session":{"post":{"operationId":"ConsultantsController_forceLogoutSession[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutDeviceDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout a specific active session/device for tenant user","tags":["tenant-users"]}},"/api/v1/consultants/{id}/activate":{"patch":{"operationId":"ConsultantsController_activate[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Activate consultant account","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/activate":{"patch":{"operationId":"ConsultantsController_activate[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Activate consultant account","tags":["tenant-users"]}},"/api/v1/consultants/{id}/deactivate":{"patch":{"operationId":"ConsultantsController_deactivate[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Deactivate consultant account","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/deactivate":{"patch":{"operationId":"ConsultantsController_deactivate[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Deactivate consultant account","tags":["tenant-users"]}},"/api/v1/consultants/{id}/suspend":{"patch":{"operationId":"ConsultantsController_suspend[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuspendConsultantDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Suspend consultant account","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/suspend":{"patch":{"operationId":"ConsultantsController_suspend[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuspendConsultantDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Suspend consultant account","tags":["tenant-users"]}},"/api/v1/consultants/{id}/activity-logs":{"get":{"operationId":"ConsultantsController_activityLogs[0]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant activity logs (paginated)","tags":["tenant-users"]}},"/api/v1/tenant-users/{id}/activity-logs":{"get":{"operationId":"ConsultantsController_activityLogs[1]","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant activity logs (paginated)","tags":["tenant-users"]}},"/api/v1/users":{"post":{"operationId":"UsersController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create root user (ADMIN or SUPER_ADMIN)","tags":["users"]},"get":{"operationId":"UsersController_findAll","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"role","required":false,"in":"query","description":"Filter by role(s). Comma-separated: ADMIN, SUB_ADMIN, SUPER_ADMIN, CONSULTANT. Alias of `user_type` for the admin UI.","schema":{"example":"ADMIN,SUB_ADMIN","type":"string"}},{"name":"user_type","required":false,"in":"query","description":"Filter by user type (legacy alias of `role`)","schema":{"example":"ADMIN","type":"string"}},{"name":"tenant_id","required":false,"in":"query","description":"Filter by tenant/organisation id","schema":{"example":"org-uuid","type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by account status","schema":{"example":"ACTIVE","type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List admin users (hierarchy-filtered, paginated). Tenant scope can be supplied via the `X-Org-Slug` header (preferred for FE/mobile), `org_slug` query param, or `tenant_id` query (legacy). Org Admins are automatically scoped to their own tenant.","tags":["users"]}},"/api/v1/users/super-admins":{"get":{"operationId":"UsersController_listSuperAdmins","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"is_root","required":false,"in":"query","description":"Filter only root master-admin rows","schema":{"example":true,"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List super admins (Super Admin only)","tags":["users"]}},"/api/v1/users/hierarchy/root-tenants":{"get":{"operationId":"UsersController_listRootWithTenants","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"is_root","required":false,"in":"query","description":"Filter only root master-admin rows","schema":{"example":true,"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Hierarchy list: root admins first, then tenant sub-admins","tags":["users"]}},"/api/v1/users/{id}/summary":{"get":{"operationId":"UsersController_consultantSummary","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Consultant summary (tenant user) — profile and inspection statistics","tags":["users"]}},"/api/v1/users/{id}/submitted-inspections":{"get":{"operationId":"UsersController_consultantSubmittedInspections","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"period","required":false,"in":"query","description":"Filter submitted inspections by submission date","schema":{"default":"30d","type":"string","enum":["7d","30d","90d","all"]}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Paginated submitted inspections for a tenant consultant","tags":["users"]}},"/api/v1/users/{id}":{"get":{"operationId":"UsersController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get user details with permissions","tags":["users"]},"patch":{"operationId":"UsersController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"tenant_id","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update user. Pass `permission_ids` to also replace assigned permissions in one call (auto-routes to tenant_user_permissions for tenant SUB_ADMIN/CONSULTANT targets). `tenant_id` (query or body) is an optional scope hint for super-admin edits against a specific organisation.","tags":["users"]},"delete":{"operationId":"UsersController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft delete user","tags":["users"]}},"/api/v1/users/{id}/suspend":{"patch":{"operationId":"UsersController_suspend","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Suspend a user (sets status=SUSPENDED and blocks active sessions)","tags":["users"]}},"/api/v1/users/{id}/activate":{"patch":{"operationId":"UsersController_activate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Activate a user (sets status=ACTIVE and removes session block)","tags":["users"]}},"/api/v1/users/{id}/deactivate":{"patch":{"operationId":"UsersController_deactivate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Deactivate a user (sets status=DEACTIVATED and blocks active sessions)","tags":["users"]}},"/api/v1/users/{id}/reset-password":{"post":{"operationId":"UsersController_resetPassword","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetUserPasswordDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reset password (sets temporary flag)","tags":["users"]}},"/api/v1/users/{id}/permissions":{"post":{"operationId":"UsersController_assignPermissions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignPermissionsDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Assign permissions. Auto-routes to tenant_user_permissions when target is a tenant SUB_ADMIN/CONSULTANT (so the org-admin UI can keep using a single endpoint).","tags":["users"]},"get":{"operationId":"UsersController_getUserPermissions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get user permissions","tags":["users"]}},"/api/v1/users/{id}/permission-preset":{"post":{"operationId":"UsersController_assignPermissionPreset","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignPermissionPresetDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Assign permissions from a tenant permission preset to a user","tags":["users"]}},"/api/v1/users/{id}/sessions":{"get":{"operationId":"UsersController_listUserSessions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List active sessions for a user. Sessions live on the tenant DB; the id may be a master users.id or a tenant_users.id (resolved by email mirror). Org scope from JWT or X-Org-Slug header.","tags":["users"]}},"/api/v1/users/{id}/force-logout":{"post":{"operationId":"UsersController_forceLogoutUser","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout a user: revokes all active sessions/refresh tokens for the user on the tenant DB and adds a Redis blocklist entry.","tags":["users"]}},"/api/v1/users/{id}/force-logout-session":{"post":{"operationId":"UsersController_forceLogoutUserSession","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutDeviceDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Force logout one specific session/device for a user. Other devices of the same user remain logged in.","tags":["users"]}},"/api/v1/super-admin":{"get":{"operationId":"SuperAdminController_list","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"is_root","required":false,"in":"query","description":"Filter only root master-admin rows","schema":{"example":true,"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List super admins (alias endpoint)","tags":["super-admin"]}},"/api/v1/permissions":{"post":{"operationId":"PermissionsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePermissionDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create permission (Super Admin only)","tags":["permissions"]},"get":{"operationId":"PermissionsController_findAll","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"module","required":false,"in":"query","description":"Filter by permission module (e.g. services)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List permissions (paginated, searchable)","tags":["permissions"]}},"/api/v1/permissions/{id}":{"get":{"operationId":"PermissionsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get permission by ID","tags":["permissions"]},"patch":{"operationId":"PermissionsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePermissionDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update permission","tags":["permissions"]},"delete":{"operationId":"PermissionsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Delete permission","tags":["permissions"]}},"/api/v1/permissions/seed":{"post":{"operationId":"PermissionsController_seed","parameters":[],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Seed default permissions (idempotent)","tags":["permissions"]}},"/api/v1/templates":{"post":{"operationId":"TemplatesController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create a new template","tags":["templates"]},"get":{"operationId":"TemplatesController_findAll","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all templates (paginated, searchable)","tags":["templates"]}},"/api/v1/templates/field-definitions":{"get":{"operationId":"TemplatesController_fieldDefinitions","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get standard template field definitions","tags":["templates"]}},"/api/v1/templates/{id}":{"get":{"operationId":"TemplatesController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get template by ID","tags":["templates"]},"patch":{"operationId":"TemplatesController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update template","tags":["templates"]},"delete":{"operationId":"TemplatesController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Delete template","tags":["templates"]}},"/api/v1/templates/{id}/versions":{"get":{"operationId":"TemplatesController_listVersions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all versions of a template","tags":["templates"]}},"/api/v1/templates/{id}/version-management":{"get":{"operationId":"TemplatesController_versionManagement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Admin version management payload (active + list + template details)","tags":["templates"]}},"/api/v1/templates/{id}/versions/latest":{"get":{"operationId":"TemplatesController_latestVersion","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get latest published template version","tags":["templates"]}},"/api/v1/templates/{id}/versions/{version_id}":{"get":{"operationId":"TemplatesController_versionDetail","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"version_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get template version details","tags":["templates"]}},"/api/v1/templates/{id}/versions/{version_id}/restore":{"post":{"operationId":"TemplatesController_restoreVersion","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"version_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmTemplateVersionSwitchDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Restore old template version as new version","tags":["templates"]}},"/api/v1/templates/{id}/versions/{version_id}/activate":{"patch":{"operationId":"TemplatesController_activateVersion","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"version_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmTemplateVersionSwitchDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Activate any existing template version","tags":["templates"]}},"/api/v1/sync/initial":{"get":{"operationId":"SyncController_initial","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Full hydration after login: orders, inspection_items, template_versions, inspections, notifications","tags":["sync"]}},"/api/v1/sync/pull":{"get":{"operationId":"SyncController_pull","parameters":[{"name":"since","required":false,"in":"query","description":"ISO 8601 timestamp of last sync","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Incremental pull — same shape as /sync/initial","tags":["sync"]}},"/api/v1/sync/batch":{"post":{"operationId":"SyncController_batch","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncBatchDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Push outbound mutations from device (mobile client contract)","tags":["sync"]}},"/api/v1/sync/push":{"post":{"operationId":"SyncController_push","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncPushDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Push offline changes from device with conflict detection (admin/advanced)","tags":["sync"]}},"/api/v1/sync/pull-advanced":{"post":{"operationId":"SyncController_pullAdvanced","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncPullDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Pull server changes since last sync (fields, inspection_items, conflicts, versions) — advanced","tags":["sync"]}},"/api/v1/sync/session":{"post":{"operationId":"SyncController_session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncSessionDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Unified offline sync: push all offline edits AND pull server changes in one call. Non-conflicting fields apply silently; only conflicting fields are returned (with rich metadata) for user review.","tags":["sync"]}},"/api/v1/sync/session/resolve":{"post":{"operationId":"SyncController_resolveSession","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncSessionResolveDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Resolve conflicts returned by /sync/session at the field level. ACCEPT = take server value; REJECT = push my client value to the server.","tags":["sync"]}},"/api/v1/sync/history":{"get":{"operationId":"SyncController_history","parameters":[{"name":"limit","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get sync event history for current user","tags":["sync"]}},"/api/v1/sync/stale-check":{"get":{"operationId":"SyncController_staleCheck","parameters":[{"name":"since","required":true,"in":"query","description":"ISO 8601 timestamp of last known sync state","schema":{"type":"string"}},{"name":"inspection_ids","required":false,"in":"query","description":"Comma-separated list of inspection IDs the client knows about","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Check which inspections have newer server data since client last sync (call before push)","tags":["sync"]}},"/api/v1/sync/conflicts/{inspectionId}":{"get":{"operationId":"SyncController_conflicts","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get conflict history for an inspection","tags":["sync"]}},"/api/v1/inspections/{id}/conflicts":{"get":{"operationId":"ConflictsController_listForInspection","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"enum":["OPEN","RESOLVED"],"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List conflict rows for an inspection (admin dashboard surface)","tags":["conflicts"]}},"/api/v1/conflicts":{"get":{"operationId":"ConflictsController_listAll","parameters":[{"name":"status","required":false,"in":"query","schema":{"enum":["OPEN","RESOLVED"],"type":"string"}},{"name":"page","required":false,"in":"query","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List conflicts across the tenant (admin dashboard \"open conflicts\" tab)","tags":["conflicts"]}},"/api/v1/inspections/{inspectionId}/versions":{"get":{"operationId":"VersionsController_getHistory","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}},{"name":"page","required":true,"in":"query","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get version history for an inspection","tags":["versions"]}},"/api/v1/inspections/{inspectionId}/versions/reconstruct/{versionNumber}":{"get":{"operationId":"VersionsController_reconstruct","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}},{"name":"versionNumber","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Reconstruct inspection state at a specific version number","tags":["versions"]}},"/api/v1/inspections/{inspectionId}/versions/compare/{versionA}/{versionB}":{"get":{"operationId":"VersionsController_compare","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}},{"name":"versionA","required":true,"in":"path","schema":{"type":"number"}},{"name":"versionB","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Compare two versions and see the diff","tags":["versions"]}},"/api/v1/inspections/{inspectionId}/versions/rollback":{"post":{"operationId":"VersionsController_rollback","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RollbackDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Rollback inspection to a previous version","tags":["versions"]}},"/api/v1/inspections/{inspectionId}/versions/checkpoint":{"post":{"operationId":"VersionsController_checkpoint","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckpointDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create a named checkpoint (full snapshot) of current state","tags":["versions"]}},"/api/v1/inspections/{inspectionId}/versions/{versionId}":{"get":{"operationId":"VersionsController_getDetail","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}},{"name":"versionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get details of a specific version","tags":["versions"]}},"/api/v1/ai/analyze-image":{"post":{"description":"Send `image_url` as HTTPS or app-relative `/api/.../media/files/{id}`. Validates media belongs to `inspection_id`. Does not overwrite field values; poll GET `/ai/jobs/:id` then accept or reject the suggestion.","operationId":"AiController_analyzeImage","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeImageDto"},"examples":{"byAbsoluteUrl":{"summary":"Image by HTTPS URL","description":"Full URL pointing at inspection media (`/media/files/{id}` on your API host). Must belong to `inspection_id`.","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"lift_machine_room_photo","image_url":"https://api.example.org/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765","field_label":"Lift machine room","field_type":"image_input","context":{"checklist":["ropes","brake","controller"]}}},"byRelativeApiPath":{"summary":"Image by app-relative path","description":"Resolved with API_PUBLIC_URL (or forwarded as-is to clients that prepend the host). Must belong to the inspection.","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"lift_car_interior_photo","image_url":"/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765","field_label":"Lift car interior","field_type":"image_input","context":{}}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Queue AI image analysis for an inspection field (async)","tags":["ai"]}},"/api/v1/ai/jobs":{"post":{"description":"`IMAGE_ANALYSIS`: `input_url`, `image_base64`, or both (prefers Base64). By default **`wait_for_completion` is true** for image jobs: the response is the finished job row (`DONE` / `FAILED`) with `fieldSuggestion` and `result` filled. Set `wait_for_completion: false` for immediate `PENDING` + poll GET `/ai/jobs/:id`. `VOICE_TRANSCRIPTION`: **`audio_base64`**. `FIELD_SUGGESTION`: `target_fields`. Other job types stay async unless `wait_for_completion: true`.","operationId":"AiController_createJob","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAiJobDto"},"examples":{"byUrlOnly":{"summary":"IMAGE_ANALYSIS — URL only (worker downloads image)","description":"Worker normalizes relative paths, downloads bytes, sends inline to Gemini.","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"lift_door_clearance_photo","job_type":"IMAGE_ANALYSIS","input_url":"/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765","field_context":{"asset_type":"lift","note":"Field context JSON is passed to the model"}}},"byBase64Only":{"summary":"IMAGE_ANALYSIS — Base64 only (worker uses inline payload)","description":"Use `data:mime;base64,...` or raw base64 with `mime_type`. Image is persisted and `agent_image_url` attached when possible.","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"lift_safety_photo","job_type":"IMAGE_ANALYSIS","mime_type":"image/png","image_base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==","field_context":{}}},"byUrlAndBase64":{"summary":"IMAGE_ANALYSIS — Both (worker prefers Base64)","description":"When both are set, Gemini receives `image_base64`; URL is retained on the job for traceability.","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"combined_example","job_type":"IMAGE_ANALYSIS","input_url":"https://api.example.org/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765","mime_type":"image/png","image_base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="}},"voiceMp3OrMpegAsDataUri":{"summary":"VOICE_TRANSCRIPTION — MP3 / MPEG (data URI)","description":"Mobile often produces `Blob`/`File` Base64 → prefix with MIME so the worker skips `audio_mime_type`.","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"voice_inspection_notes","job_type":"VOICE_TRANSCRIPTION","audio_base64":"data:audio/mpeg;base64,REPLACE_WITH_ENCODER_OUTPUT_BASE64","target_fields":["transcription_notes"],"field_context":{"recorder":"mobile_data_uri_mpeg"}}},"voiceRawBase64PlusMimeWebmOrMp3":{"summary":"VOICE_TRANSCRIPTION — raw Base64 + audio_mime_type","description":"Alternative for clients that omit the `data:` prefix: pair raw Base64 bytes with MIME (`audio/webm`, `audio/mpeg`, `audio/mp4`, `audio/wav`).","value":{"inspection_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","field_key":"voice_notes","job_type":"VOICE_TRANSCRIPTION","audio_mime_type":"audio/webm","audio_base64":"PASTE_RAW_BASE64_FROM_DEVICE_NO_data_PREFIX_MediaRecorder_Or_NativeEncoderOutput","target_fields":["transcription_notes"],"field_context":{}}}}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create an AI analysis job (image / voice / generic)","tags":["ai"]}},"/api/v1/ai/image-analysis":{"post":{"description":"Runs dynamic template field detection for each image. Default `wait_for_completion: true` waits for the worker and returns `results` per image. Set `wait_for_completion: false` to get `jobId` and poll GET `/ai/jobs/:id` (see `result` and `field_suggestion`).","operationId":"AiController_multiImageAnalysis","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMultiImageAiAnalysisDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Multi-image inspection analysis (sync by default)","tags":["ai"]}},"/api/v1/ai/jobs/{id}/suggestion/accept":{"post":{"description":"For multi-field image analysis, send **`field_id`** (template field id). When only one field is suggested, `field_id` may be omitted. Applies the suggested **value** to `inspection.field_data` for that field only.","operationId":"AiController_acceptSuggestion","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestionFieldAcceptDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Accept AI suggestion for one field (or whole legacy job)","tags":["ai"]}},"/api/v1/ai/jobs/{id}/suggestion/reject":{"post":{"description":"For multi-field responses, send **`field_id`**. Optional **`reason`** is stored on the field row and in audit logs.","operationId":"AiController_rejectSuggestion","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestionRejectDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reject AI suggestion for one field (or whole legacy job)","tags":["ai"]}},"/api/v1/ai/jobs/{id}/suggestion/manual":{"post":{"description":"Updates the inspection field and marks the matching AI suggestion row(s) as **MANUALLY_EDITED**. Send **`field_id`** and **`value`** (same shape as inspection field PATCH).","operationId":"AiController_manualFieldSuggestion","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestionFieldManualDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Apply a manual value for a field while resolving an AI suggestion","tags":["ai"]}},"/api/v1/ai/jobs/{id}":{"get":{"description":"Returns `{ id, status, field_suggestion, suggestion_status, result, error_message, error_code, completed_at, queued_at }`. For **IMAGE_ANALYSIS** when `status` is **DONE**, `result` / `field_suggestion` use `{ results: [{ imageUrl, fields: [{ fieldId, label, type, value, suggestion_status, user_value_snapshot, acted_at, acted_by, reject_reason, manual_value }] }] }`. Per-field **`suggestion_status`**: `PENDING` | `ACCEPTED` | `REJECTED` | `MANUALLY_EDITED`. Job-level **`suggestion_status`**: `PENDING_REVIEW` while any field is pending, `ALL_FIELDS_REVIEWED` when every suggested field has been decided, or legacy `ACCEPTED` / `REJECTED` for older single-blob jobs. ADMIN/SUB_ADMIN see any job in their tenant; CONSULTANT only sees jobs for inspections assigned to them.","operationId":"AiController_getJob","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get AI job status (poll this to drive the UI thinking/done/failed state)","tags":["ai"]}},"/api/v1/ai/inspections/{inspectionId}/jobs":{"get":{"operationId":"AiController_getJobsByInspection","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get AI jobs for inspection","tags":["ai"]}},"/api/v1/ai/inspections/{inspectionId}/chat-history":{"get":{"operationId":"AiController_getInspectionChatHistory","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"AI chat / interaction history for an inspection (messages + uploaded image refs)","tags":["ai"]}},"/api/v1/ai/inspections/{inspectionId}/action-audits":{"get":{"operationId":"AiController_getInspectionActionAudits","parameters":[{"name":"inspectionId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"AI action audit log (accepted / rejected / manually edited) for an inspection","tags":["ai"]}},"/api/v1/audit":{"get":{"operationId":"AuditController_findAll","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"entity_type","required":true,"in":"query","schema":{"type":"string"}},{"name":"actor_id","required":true,"in":"query","schema":{"type":"string"}},{"name":"action","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List audit logs (paginated, searchable)","tags":["audit"]}},"/api/v1/audit/{entityType}/{entityId}":{"get":{"operationId":"AuditController_findByEntity","parameters":[{"name":"entityType","required":true,"in":"path","schema":{"type":"string"}},{"name":"entityId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get audit log for specific entity","tags":["audit"]}},"/api/v1/device-token":{"post":{"operationId":"DeviceTokenController_register","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeviceTokenDto"}}}},"responses":{"200":{"description":"Token registered"}},"security":[{"bearer":[]}],"summary":"Register a push notification device token (FCM)","tags":["device-token"]},"delete":{"operationId":"DeviceTokenController_unregister","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Remove the device token for current user","tags":["device-token"]}},"/api/v1/asset-types":{"post":{"operationId":"AssetTypesController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAssetTypeDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create asset type","tags":["asset-types"]},"get":{"operationId":"AssetTypesController_findAll","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}},{"name":"service_id","required":false,"in":"query","description":"Filter asset types by service id","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List asset types","tags":["asset-types"]}},"/api/v1/asset-types/{id}":{"get":{"operationId":"AssetTypesController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get asset type by id","tags":["asset-types"]},"patch":{"operationId":"AssetTypesController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetTypeDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update asset type","tags":["asset-types"]},"delete":{"operationId":"AssetTypesController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Delete asset type","tags":["asset-types"]}},"/api/v1/asset-types/{id}/templates":{"post":{"operationId":"AssetTypesController_mapTemplate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapTemplateToAssetTypeDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Map template to asset type","tags":["asset-types"]}},"/api/v1/asset-types/{id}/templates/{templateId}":{"delete":{"operationId":"AssetTypesController_unmapTemplate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"templateId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Remove template-asset type mapping","tags":["asset-types"]}},"/api/v1/services":{"post":{"operationId":"ServicesController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create service","tags":["services"]},"get":{"operationId":"ServicesController_findAll","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (1-based). Defaults to 1.","schema":{"minimum":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size. Omit this parameter to return the full dataset without pagination.","schema":{"minimum":1,"maximum":100,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List services for current organisation","tags":["services"]}},"/api/v1/services/{id}":{"get":{"operationId":"ServicesController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get service by id","tags":["services"]},"patch":{"operationId":"ServicesController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateServiceDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update service","tags":["services"]},"delete":{"operationId":"ServicesController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Deactivate and soft-delete service","tags":["services"]}},"/api/v1/services/{id}/assets":{"get":{"operationId":"ServicesController_findAssets","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List assets linked to a service","tags":["services"]}},"/api/v1/orders":{"post":{"operationId":"OrdersController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create a new order","tags":["Orders"]},"get":{"operationId":"OrdersController_findAll","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all orders with pagination and filters","tags":["Orders"]}},"/api/v1/orders/{id}":{"get":{"operationId":"OrdersController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get order by ID with items and progress","tags":["Orders"]},"patch":{"operationId":"OrdersController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrderDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update order details","tags":["Orders"]},"delete":{"operationId":"OrdersController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Soft-delete an order","tags":["Orders"]}},"/api/v1/orders/{id}/publish":{"post":{"operationId":"OrdersController_publish","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Publish a draft order — creates inspections, locks template versions","tags":["Orders"]}},"/api/v1/orders/{id}/duplicate":{"post":{"operationId":"OrdersController_duplicate","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Duplicate an order as a new draft","tags":["Orders"]}},"/api/v1/orders/{id}/progress":{"get":{"operationId":"OrdersController_progress","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Recalculate and return order progress","tags":["Orders"]}},"/api/v1/order-config":{"post":{"operationId":"OrderConfigController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderConfigDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create an order configuration rule (global or per item type)","tags":["Order Configuration"]},"get":{"operationId":"OrderConfigController_findAll","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all order configuration rules","tags":["Order Configuration"]}},"/api/v1/order-config/global":{"get":{"operationId":"OrderConfigController_findGlobal","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get or create the global (default) order config","tags":["Order Configuration"]}},"/api/v1/order-config/{id}":{"get":{"operationId":"OrderConfigController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get order config by ID","tags":["Order Configuration"]},"patch":{"operationId":"OrderConfigController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrderConfigDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update an order configuration rule","tags":["Order Configuration"]},"delete":{"operationId":"OrderConfigController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Delete an order configuration rule","tags":["Order Configuration"]}},"/api/v1/permission-presets/modules":{"get":{"operationId":"PermissionPresetsController_getModules","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get all module definitions with available permission levels","tags":["Permission Presets"]}},"/api/v1/permission-presets":{"post":{"operationId":"PermissionPresetsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePermissionPresetDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create a named permission preset","tags":["Permission Presets"]},"get":{"operationId":"PermissionPresetsController_findAll","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all permission presets","tags":["Permission Presets"]}},"/api/v1/permission-presets/{id}":{"get":{"operationId":"PermissionPresetsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get a permission preset by ID","tags":["Permission Presets"]},"patch":{"operationId":"PermissionPresetsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePermissionPresetDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update a permission preset","tags":["Permission Presets"]},"delete":{"operationId":"PermissionPresetsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Delete a permission preset","tags":["Permission Presets"]}},"/api/v1/billing/status":{"get":{"operationId":"BillingController_status","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Billing placeholder status endpoint","tags":["billing"]}},"/api/v1/clients":{"post":{"operationId":"ClientsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientDto"}}}},"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Create client","tags":["clients"]},"get":{"operationId":"ClientsController_findAll","parameters":[{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search keyword (name, email, etc.)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List clients","tags":["clients"]}},"/api/v1/clients/{id}":{"get":{"operationId":"ClientsController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Get client by ID","tags":["clients"]},"patch":{"operationId":"ClientsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateClientDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Update client","tags":["clients"]},"delete":{"operationId":"ClientsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Delete client (soft)","tags":["clients"]}},"/api/v1/admin/dashboard/summary":{"get":{"description":"Org admins receive `{ dashboard }` with tenant KPIs, charts, and sync health. Super admins receive the same `dashboard` object (tenant metrics when `X-Org-Slug` is set, otherwise an empty tenant snapshot) plus `super_admin_dashboard` with platform counts, organisation growth, top-organisation trends, and inspection leaders. Super admins may call this endpoint without `X-Org-Slug` for the platform overview.","operationId":"AdminDashboardController_summary","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Dashboard metrics (Admin / Sub-admin / Super admin)","tags":["admin/dashboard"]}},"/api/v1/admin/dashboard/inspection-summary":{"get":{"description":"Counts linked inspections whose `created_at` (UTC calendar date) falls within `from` and `to` (inclusive, YYYY-MM-DD).","operationId":"AdminDashboardController_inspectionSummary","parameters":[{"name":"from","required":true,"in":"query","schema":{"type":"string"}},{"name":"to","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Inspection status breakdown for a date range","tags":["admin/dashboard"]}},"/api/v1/admin/dashboard/last-updated":{"get":{"operationId":"AdminDashboardController_lastUpdated","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Timestamp of most recent change across orders, inspections, and conflict logs — for efficient dashboard polling","tags":["admin/dashboard"]}},"/api/v1/template-previews/email/{template}":{"get":{"operationId":"TemplatePreviewsController_previewEmail","parameters":[{"name":"template","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Preview email template HTML in browser","tags":["template-previews"]}},"/api/v1/template-previews/report/email":{"get":{"operationId":"TemplatePreviewsController_previewReportEmail","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Preview report email HTML in browser","tags":["template-previews"]}},"/api/v1/template-previews/report/pdf":{"get":{"operationId":"TemplatePreviewsController_previewReportPdf","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Download sample report PDF preview","tags":["template-previews"]}}},"info":{"title":"InspectPro API","description":"AI-Powered Multi-Tenant Inspection Platform — Backend API. Send **X-Org-Slug** (or subdomain) to identify the tenant. Super admins must send **X-Org-Slug** when calling tenant APIs so the backend connects to the selected organisation database.","version":"1.0","contact":{}},"tags":[{"name":"health","description":""},{"name":"auth","description":""},{"name":"platform/organisations","description":""},{"name":"super-admin/tenants","description":""},{"name":"super-admin/tenant-admins","description":""},{"name":"consultants","description":""},{"name":"orders","description":""},{"name":"Inspection Items","description":""},{"name":"inspections","description":""},{"name":"templates","description":""},{"name":"sync","description":""},{"name":"notifications","description":""},{"name":"reports","description":""},{"name":"ai","description":""},{"name":"versions","description":""},{"name":"audit","description":""}],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"},"org-slug":{"type":"apiKey","in":"header","name":"X-Org-Slug"}},"schemas":{"LoginDto":{"type":"object","properties":{"email":{"type":"string","example":"admin@demo-org.com","description":"Email for SUPER_ADMIN / ADMIN"},"password":{"type":"string","example":"Admin@123"},"fcm_token":{"type":"string","example":"fcm-device-token-string..."},"device_id":{"type":"string","example":"ios-iphone-15-pro"},"device_name":{"type":"string","example":"John iPhone"},"device_type":{"type":"string","example":"iOS"},"app_version":{"type":"string","example":"2.1.0"}},"required":["email","password"]},"ConsultantLoginDto":{"type":"object","properties":{"email":{"type":"string","example":"john@demo-org.com","description":"Tenant user email (CONSULTANT or SUB_ADMIN)."},"password":{"type":"string","example":"Consultant@123"},"org_slug":{"type":"string","example":"demo-org","description":"Optional organisation slug. When omitted, the tenant is resolved automatically from the user email."},"fcm_token":{"type":"string","example":"fcm-device-token-string..."},"device_id":{"type":"string","example":"android-pixel-8"},"device_name":{"type":"string","example":"Pixel 8"},"device_type":{"type":"string","example":"Android"},"app_version":{"type":"string","example":"2.1.0"}},"required":["email","password"]},"ForgotPasswordRequestOtpDto":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com"},"org_slug":{"type":"string","example":"demo-org","description":"Organisation slug — required for consultant (tenant) accounts; omit for admin / super-admin."}},"required":["email"]},"ForgotPasswordVerifyOtpDto":{"type":"object","properties":{"challenge_id":{"type":"string","description":"Returned from request-otp"},"otp":{"type":"string","example":"123456"}},"required":["challenge_id","otp"]},"ResetPasswordWithTokenDto":{"type":"object","properties":{"reset_token":{"type":"string"},"new_password":{"type":"string","example":"NewSecurePassword1!"}},"required":["reset_token","new_password"]},"ChangePasswordDto":{"type":"object","properties":{"current_password":{"type":"string","example":"OldPassword123!"},"new_password":{"type":"string","example":"NewPassword456!"}},"required":["current_password","new_password"]},"RefreshTokenDto":{"type":"object","properties":{"refresh_token":{"type":"string"},"fcm_token":{"type":"string","example":"fcm-device-token-string..."},"device_id":{"type":"string","example":"android-pixel-8"},"device_name":{"type":"string","example":"Pixel 8"},"device_type":{"type":"string","example":"Android"},"app_version":{"type":"string","example":"2.1.0"}},"required":["refresh_token"]},"LogoutDto":{"type":"object","properties":{"refresh_token":{"type":"string","description":"Refresh token to revoke. Optional: super-admins (and clients that only hold an access token) can call logout without it."},"session_id":{"type":"string","example":"c7b51d3e-8186-470e-b79a-c3f0d6db0b0a"}}},"LogoutDeviceDto":{"type":"object","properties":{"session_id":{"type":"string","description":"Active session id to revoke"}},"required":["session_id"]},"UpdateProfileDto":{"type":"object","properties":{"full_name":{"type":"string","example":"Jane Admin"},"mobile":{"type":"string","example":"+441234567890"},"country_code":{"type":"string","example":"+44"},"employee_id":{"type":"string","example":"EMP-001"},"region":{"type":"string","example":"North Region"},"biometric_enabled":{"type":"boolean","description":"Enable or disable biometric authentication (consultants only)."},"profile_image_url":{"type":"string","description":"Optional avatar URL/path for root users. Send empty string to clear. Prefer POST /auth/profile-image for uploads."}}},"MediaPresignDto":{"type":"object","properties":{"inspection_id":{"type":"string","example":"uuid-of-inspection"},"field_id":{"type":"string","example":"photo_field_1"},"filename":{"type":"string","example":"photo.jpg"},"content_type":{"type":"string","example":"image/jpeg"}},"required":["inspection_id","field_id","filename","content_type"]},"UpsertLegalContentDto":{"type":"object","properties":{"org_slug":{"type":"string","description":"Organisation slug","example":"demo-org"},"content":{"type":"string","description":"Terms/Privacy markdown or HTML content"}},"required":["org_slug","content"]},"UpdateFieldDto":{"type":"object","properties":{"field_key":{"type":"string","description":"Stable key for this answer. Use template `section_id:field_id` (e.g. `f1111111-1111-4111-8111-111111111101:f1111111-1111-4111-8111-111111111201`) or legacy keys like `exterior_condition`."},"value":{"type":"object","description":"Answer payload. Persisted under `field_data.fields[field_key]`. When **`datetime`** is sent, the stored JSON is **`{ \"value\": <resolved>, \"datetime\": \"<string>\" }`**; otherwise the stored value is `value` alone (after media/base64 resolution). Images: upload via `POST /upload/image` first."},"datetime":{"type":"string","description":"Optional client timestamp for this edit (e.g. `2026-04-30 11:22:00`). When set, `field_data.fields[field_key]` is stored as `{ value, datetime }` so it matches other field updates and conflict logic."},"expected_field_data_version":{"type":"number","description":"Optimistic concurrency: must match current `field_data_version` on the inspection when set."},"expected_field_version":{"type":"number","description":"Deprecated (row-based fields). Ignored."},"expected_last_modified_at":{"type":"string","description":"Deprecated (row-based fields). Ignored."},"change_id":{"type":"string","description":"Optional client-generated change id."}},"required":["field_key","value"]},"CreateInspectionDto":{"type":"object","properties":{"inspection_item_id":{"type":"string","description":"Inspection item id; template and order context come from the item."}},"required":["inspection_item_id"]},"AssignInspectionConsultantDto":{"type":"object","properties":{"consultant_id":{"type":"string","description":"Target user id; must have role CONSULTANT"}},"required":["consultant_id"]},"ResolveFieldConflictBodyDto":{"type":"object","properties":{"field_key":{"type":"string"},"resolution":{"type":"string","enum":["server","client","merge","accept","reject"]},"client_value":{"type":"object","description":"Required for `client` and `merge` when setting a new value."},"expected_field_data_version":{"type":"number","description":"Must match current inspection `field_data_version`."}},"required":["field_key","resolution","expected_field_data_version"]},"UpdateStatusDto":{"type":"object","properties":{"status":{"type":"string","enum":["SCHEDULED","IN_PROGRESS","SUBMITTED","APPROVED","REJECTED"]},"reason":{"type":"string"}},"required":["status"]},"UnreadCountResponseDto":{"type":"object","properties":{"count":{"type":"number","example":3}},"required":["count"]},"CreateInspectionItemDto":{"type":"object","properties":{"asset_type_id":{"type":"string","description":"Asset type id; drives active template mapping."},"title":{"type":"string","description":"Display title for the item."},"consultant_id":{"type":"string","description":"Consultant user id; optional at creation, assign later via PATCH."},"assigned_to":{"type":"string","description":"Deprecated; use consultant_id."},"scheduled_date":{"type":"string","description":"Scheduled date for this inspection item."},"scheduled_time":{"type":"string","description":"Scheduled time for this inspection item. Accepts a single time (HH, HH:MM, HH:MM:SS) or a range (e.g. 13-14, 09:30-10:30)."},"notes":{"type":"string"},"metadata":{"type":"object"}},"required":["asset_type_id","scheduled_date","scheduled_time"]},"BulkCreateInspectionItemsDto":{"type":"object","properties":{"asset_type_id":{"type":"string","description":"Asset type id; drives active template mapping."},"quantity":{"type":"number"},"consultant_id":{"type":"string"},"assigned_to":{"type":"string"},"scheduled_date":{"type":"string","description":"Scheduled date for this inspection item."},"scheduled_time":{"type":"string","description":"Scheduled time for this inspection item. Accepts a single time (HH, HH:MM, HH:MM:SS) or a range (e.g. 13-14, 09:30-10:30)."}},"required":["asset_type_id","quantity","scheduled_date","scheduled_time"]},"CsvImportItemDto":{"type":"object","properties":{"asset_type_id":{"type":"string","description":"Asset type id; drives active template mapping."},"title":{"type":"string","description":"Display title for the item."},"consultant_id":{"type":"string"},"assigned_to":{"type":"string"},"scheduled_date":{"type":"string","description":"Scheduled date for this inspection item."},"scheduled_time":{"type":"string","description":"Scheduled time for this inspection item. Accepts a single time (HH, HH:MM, HH:MM:SS) or a range (e.g. 13-14, 09:30-10:30)."},"notes":{"type":"string"}},"required":["asset_type_id","scheduled_date","scheduled_time"]},"CsvImportDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CsvImportItemDto"}}},"required":["items"]},"UpdateInspectionItemDto":{"type":"object","properties":{"title":{"type":"string","description":"Display title for the item."},"consultant_id":{"type":"string","description":"Assign or change consultant (admin)."},"assigned_to":{"type":"string","description":"Deprecated; use consultant_id."},"scheduled_date":{"type":"string","description":"Scheduled date for this inspection item."},"scheduled_time":{"type":"string","description":"Scheduled time for this inspection item. Accepts a single time (HH, HH:MM, HH:MM:SS) or a range (e.g. 13-14, 09:30-10:30)."},"notes":{"type":"string"},"metadata":{"type":"object"}},"required":["scheduled_date","scheduled_time"]},"ReassignInspectionItemDto":{"type":"object","properties":{"consultant_id":{"type":"string"}},"required":["consultant_id"]},"BulkReassignDto":{"type":"object","properties":{"item_ids":{"type":"array","items":{"type":"string"}},"consultant_id":{"type":"string"}},"required":["item_ids","consultant_id"]},"CreateTemplateMappingDto":{"type":"object","properties":{"asset_type_id":{"type":"string"},"template_id":{"type":"string"}},"required":["asset_type_id","template_id"]},"CreateSuperAdminTenantAdminDto":{"type":"object","properties":{"email":{"type":"string","example":"admin@company.com"},"full_name":{"type":"string","example":"Jane Admin"},"tenant_id":{"type":"string","description":"Must match this organisation’s tenant_id in the master DB. Omit to default to the organisation tenant_id.","example":"uuid-of-tenant"},"mobile":{"type":"string","example":"+441234567890"},"country_code":{"type":"string","example":"+44"},"password":{"type":"string","example":"SecurePass123!","description":"Stored only as a bcrypt hash (same pipeline as tenant login).","minLength":8},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","DEACTIVATED"],"default":"ACTIVE"},"employee_id":{"type":"string","example":"EMP-1001"},"region":{"type":"string","example":"North"},"biometric_enabled":{"type":"boolean","description":"Enable biometric authentication."}},"required":["email","full_name","mobile","country_code","password"]},"ResetSuperAdminTenantAdminPasswordDto":{"type":"object","properties":{"new_password":{"type":"string","example":"NewTemp@123456","description":"New password; stored hashed only. Existing access is invalidated.","minLength":8}},"required":["new_password"]},"UpdateSuperAdminTenantAdminDto":{"type":"object","properties":{"email":{"type":"string"},"full_name":{"type":"string"},"mobile":{"type":"string"},"country_code":{"type":"string","example":"+44"},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","DEACTIVATED"]},"password":{"type":"string","description":"When set, replaces the stored password hash (same bcrypt + app salts as login).","minLength":8},"employee_id":{"type":"string","example":"EMP-1001"},"region":{"type":"string","example":"North"},"biometric_enabled":{"type":"boolean","description":"Enable biometric authentication."}}},"CreateTenantDto":{"type":"object","properties":{"orgName":{"type":"string","example":"Ansal Properties"},"orgSlug":{"type":"string","example":"ansal-properties"},"logo_url":{"type":"string","example":"https://cdn.example.com/orgs/ansal/logo.svg"},"logo_media_id":{"type":"string","example":"f47ac10b-58cc-4372-a567-0e02b2c3d479","description":"Media ID from POST /super-admin/media/upload. If provided, overrides logo_url."},"domain_url":{"type":"string","example":"https://ansal.example.com"},"default_timezone":{"type":"object","example":"America/Chicago","description":"IANA timezone. If omitted/null, defaults to Asia/Kolkata."},"default_datetime_format":{"type":"string","example":"DD-MM-YYYY hh:mm A","enum":["YYYY-MM-DD HH:mm:ss","YYYY-MM-DD hh:mm A","DD-MM-YYYY HH:mm:ss","DD-MM-YYYY hh:mm A","MM/DD/YYYY HH:mm:ss","MM/DD/YYYY hh:mm A","DD/MM/YYYY HH:mm:ss","DD/MM/YYYY hh:mm A","DD MMM YYYY HH:mm:ss","DD MMM YYYY hh:mm A","MMM DD, YYYY HH:mm:ss","MMM DD, YYYY hh:mm A"],"description":"Organisation-level preferred datetime format. Date-only views are derived automatically."},"data_retention_years":{"type":"number","example":3,"description":"Optional retention period in years."},"autoCreateDb":{"type":"boolean","example":true,"description":"When true, creates the tenant database on the master server, generates a dedicated DB user/password, syncs schema, and stores the password only in encrypted form. When false, provide dbHost, dbName, dbUser, and dbPassword."},"dbHost":{"type":"string","example":"localhost"},"dbPort":{"type":"number","example":5432,"description":"Default: 5432 (PostgreSQL)"},"dbName":{"type":"string","example":"ansal_db"},"dbUser":{"type":"string","example":"ansal_user"},"dbPassword":{"type":"string","example":"secure_password"},"dbSsl":{"type":"boolean","default":false},"initial_admin_full_name":{"type":"string","example":"Org Super Admin","description":"Optional initial tenant ADMIN full name created during provisioning."},"initial_admin_email":{"type":"string","example":"org-admin@example.com","description":"Optional initial tenant ADMIN email created during provisioning."},"initial_admin_mobile":{"type":"string","example":"+44 7700 900999","description":"Optional initial tenant ADMIN mobile number."}},"required":["orgName","orgSlug"]},"UpdateTenantDto":{"type":"object","properties":{"orgName":{"type":"string","example":"Updated Name 1","description":"Same key as create tenant (`orgName`)."},"orgSlug":{"type":"string","example":"updated-org-slug"},"logo_url":{"type":"string","example":"https://cdn.example.com/orgs/updated/logo.svg","description":"Logo URL. Accepts an external URL or the temp-logo URL returned by POST /super-admin/tenants/media/upload (which is converted into a permanent org logo). Send an empty string to clear."},"domain_url":{"type":"string","example":"https://updated-org.example.com"},"default_timezone":{"type":"object","example":"America/Chicago","description":"IANA timezone. If omitted/null, defaults to Asia/Kolkata."},"default_datetime_format":{"type":"string","example":"DD-MM-YYYY hh:mm A","enum":["YYYY-MM-DD HH:mm:ss","YYYY-MM-DD hh:mm A","DD-MM-YYYY HH:mm:ss","DD-MM-YYYY hh:mm A","MM/DD/YYYY HH:mm:ss","MM/DD/YYYY hh:mm A","DD/MM/YYYY HH:mm:ss","DD/MM/YYYY hh:mm A","DD MMM YYYY HH:mm:ss","DD MMM YYYY hh:mm A","MMM DD, YYYY HH:mm:ss","MMM DD, YYYY hh:mm A"],"description":"Organisation-level preferred datetime format. Date-only views are derived automatically."},"data_retention_years":{"type":"number","example":3,"description":"Optional retention period in years."},"plan":{"type":"string"},"is_active":{"type":"boolean"}}},"PatchOrganisationAdminSettingsDto":{"type":"object","properties":{"default_timezone":{"type":"object","example":"Europe/London","description":"IANA timezone. Must exist in master timezone catalog."},"default_datetime_format":{"type":"string","example":"DD-MM-YYYY hh:mm A","enum":["YYYY-MM-DD HH:mm:ss","YYYY-MM-DD hh:mm A","DD-MM-YYYY HH:mm:ss","DD-MM-YYYY hh:mm A","MM/DD/YYYY HH:mm:ss","MM/DD/YYYY hh:mm A","DD/MM/YYYY HH:mm:ss","DD/MM/YYYY hh:mm A","DD MMM YYYY HH:mm:ss","DD MMM YYYY hh:mm A","MMM DD, YYYY HH:mm:ss","MMM DD, YYYY hh:mm A"],"description":"Organisation-level preferred datetime format."},"data_retention_years":{"type":"number","example":2,"description":"Data retention period in years."},"domain_url":{"type":"string","example":"https://new-org1.example.com"}}},"OrganisationIntegrationConfigItemDto":{"type":"object","properties":{"label":{"type":"string","example":"Map key"},"type":{"type":"string","example":"map","enum":["map","aiKey"]},"value":{"type":"string","example":"123546"}},"required":["label","type","value"]},"UpdateOrganisationIntegrationConfigDto":{"type":"object","properties":{"items":{"example":[{"label":"Map key","type":"map","value":"123546"},{"label":"Gemini key","type":"aiKey","value":"123546"}],"type":"array","items":{"$ref":"#/components/schemas/OrganisationIntegrationConfigItemDto"}}},"required":["items"]},"CreateConsultantDto":{"type":"object","properties":{"full_name":{"type":"string","example":"John Doe"},"email":{"type":"string","example":"john@example.com"},"mobile":{"type":"string","example":"+911234567890"},"country_code":{"type":"string","example":"+91"},"employee_id":{"type":"string","example":"EMP001"},"region":{"type":"string","example":"North"},"password":{"type":"string","example":"TempPass123!"},"role":{"type":"string","enum":["CONSULTANT","SUB_ADMIN","ADMIN"],"description":"Tenant role. Defaults to CONSULTANT."},"permission_ids":{"description":"Permission IDs to assign to this tenant user (recommended). For `role: SUB_ADMIN`, omit or send `[]` with no `exclude_permissions` to copy the creating admin's (or sub-admin's) permission set.","type":"array","items":{"type":"string"}},"exclude_permissions":{"description":"Optional permission names/ids to exclude from role-default permissions. Alias supported: excludePermissions.","type":"array","items":{"type":"string"}}},"required":["full_name","email","mobile","country_code","password"]},"UpdateConsultantDto":{"type":"object","properties":{"full_name":{"type":"string"},"email":{"type":"string"},"mobile":{"type":"string"},"country_code":{"type":"string","example":"+91"},"employee_id":{"type":"string"},"role":{"type":"string","enum":["CONSULTANT","SUB_ADMIN","ADMIN"],"description":"Tenant role."},"permission_ids":{"description":"Permission IDs to replace for this tenant user.","type":"array","items":{"type":"string"}},"region":{"type":"string"},"biometric_enabled":{"type":"boolean","description":"Enable biometric authentication for this consultant."},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","DEACTIVATED"]}}},"AssignTenantUserPermissionsDto":{"type":"object","properties":{"permission_ids":{"description":"Replace user permissions with this exact list of permission IDs.","default":[],"type":"array","items":{"type":"string"}}}},"ResetConsultantPasswordDto":{"type":"object","properties":{"new_password":{"type":"string","example":"NewTemp@123","description":"New password; user must sign in again"}},"required":["new_password"]},"SuspendConsultantDto":{"type":"object","properties":{"reason":{"type":"string","description":"Reason shown to the consultant in email"}}},"ModulePermissionDto":{"type":"object","properties":{"module":{"type":"string","example":"orders","description":"Permission module name from permissions table."},"level":{"type":"string","enum":["READ","WRITE","ADMIN"],"example":"WRITE"}},"required":["module","level"]},"CreateUserDto":{"type":"object","properties":{"full_name":{"type":"string","example":"John Doe"},"email":{"type":"string","example":"john@example.com"},"mobile":{"type":"string","example":"+911234567890"},"country_code":{"type":"string","example":"+91"},"password":{"type":"string","example":"TempPass123!"},"user_type":{"type":"string","enum":["SUPER_ADMIN","ADMIN","SUB_ADMIN","CONSULTANT"],"example":"ADMIN","description":"Root user type. Use `role` as an alias when omitted (e.g. role=CONSULTANT)."},"role":{"type":"string","description":"Alias for `user_type` (e.g. CONSULTANT, SUB_ADMIN, ADMIN). Supports the admin UI `role` field.","example":"CONSULTANT"},"sub_admin_profile":{"type":"string","enum":["APPROVER","VIEWER"],"description":"Required for SUB_ADMIN: Approver (approve+view) or Viewer (view only)."},"tenant_id":{"type":"string","example":"uuid-of-organisation","description":"Organisation / tenant ID (required for ADMIN)"},"permission_ids":{"description":"Optional. When non-empty, assigns exactly these permission IDs (granter must already hold each grant). When omitted or `[]`, the new user inherits the creating user's permissions. When omitted with module_permissions/role/exclude, those fields are resolved instead.","type":"array","items":{"type":"string"}},"module_permissions":{"description":"Optional module-wise permission levels at creation. READ => read, WRITE => read+write, ADMIN => read+write+admin. Ignored if permission_ids is set.","type":"array","items":{"$ref":"#/components/schemas/ModulePermissionDto"}},"exclude_permission_ids":{"description":"Optional. Removes permissions from the role/default-derived base set. Ignored if permission_ids or module_permissions is provided.","type":"array","items":{"type":"string"}},"parent_id":{"type":"string","example":"uuid-of-parent-user","description":"Parent user ID for hierarchy (auto-set to creator when omitted)"},"region":{"type":"string","example":"North","description":"Consultant region (tenant user mirror)."},"employee_id":{"type":"string","example":"EMP-1001","description":"Consultant employee id (tenant user mirror)."},"biometric_enabled":{"type":"boolean","description":"Enable biometric authentication (consultants and mobile app users)."}},"required":["full_name","email","password","user_type","sub_admin_profile"]},"UpdateUserDto":{"type":"object","properties":{"full_name":{"type":"string","example":"Jane Doe"},"email":{"type":"string","example":"jane@example.com"},"mobile":{"type":"string","example":"+911234567890"},"country_code":{"type":"string","example":"+91"},"user_type":{"type":"string","enum":["SUPER_ADMIN","ADMIN","SUB_ADMIN","CONSULTANT"]},"role":{"type":"string","description":"Alias for `user_type` on update payloads from the admin UI."},"sub_admin_profile":{"type":"string","enum":["APPROVER","VIEWER"],"description":"Only for SUB_ADMIN accounts."},"status":{"type":"string","enum":["ACTIVE","SUSPENDED","DEACTIVATED"]},"employee_id":{"type":"string","example":"EMP-1001","description":"Consultant employee id (tenant user mirror)."},"region":{"type":"string","example":"North","description":"Consultant region (tenant user mirror)."},"biometric_enabled":{"type":"boolean","description":"Enable biometric authentication (consultants and mobile app users)."},"permission_ids":{"description":"Optional. When provided, replaces the user's assigned permissions. Send `[]` to revoke all. Routing matches `POST /users/:id/permissions` — master users hit `user_permissions`; tenant SUB_ADMIN/CONSULTANT targets are forwarded to `tenant_user_permissions`. Granter ownership / RBAC ceiling is still enforced.","type":"array","items":{"type":"string"}},"tenant_id":{"type":"string","description":"Optional. Tenant slug or id of the user being edited. Useful when a super-admin updates a SUB_ADMIN/CONSULTANT in a specific organisation."}}},"ResetUserPasswordDto":{"type":"object","properties":{"new_password":{"type":"string","example":"NewTemp@123"}},"required":["new_password"]},"AssignPermissionsDto":{"type":"object","properties":{"permission_ids":{"example":["permission-uuid-1","permission-uuid-2"],"description":"Replaces assigned permissions. Grants you do not hold are rejected; revokes are always allowed. RBAC ceiling applies to rbac:* permissions.","type":"array","items":{"type":"string"}}},"required":["permission_ids"]},"AssignPermissionPresetDto":{"type":"object","properties":{"preset_id":{"type":"string","example":"preset-uuid","description":"Permission preset ID from tenant permission_presets table."}},"required":["preset_id"]},"CreatePermissionDto":{"type":"object","properties":{"name":{"type":"string","example":"consultants:create"},"description":{"type":"string","example":"Allows creating consultant users"},"module":{"type":"string","example":"consultants"},"action":{"type":"string","example":"create"}},"required":["name","module","action"]},"UpdatePermissionDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"module":{"type":"string"},"action":{"type":"string"}}},"TemplateFieldValidationDto":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"},"minLength":{"type":"number"},"maxLength":{"type":"number"},"pattern":{"type":"string","description":"Regex pattern string"}}},"RenderInputDto":{"type":"object","properties":{"id":{"type":"string","description":"Stable ID; generated by the client (e.g. ri_<uuid>)"},"type":{"type":"string","enum":["text","textarea","number","email","tel","url","date","time","datetime","checkbox","radio","dropdown","password","toggle","image","multi_file_upload","signature","gps","repeatable","repeatable_components","table","label"]},"label":{"type":"string"},"required":{"type":"boolean"},"placeholder":{"type":"string"},"options":{"default":[],"type":"array","items":{"type":"string"}},"gpsMapPicker":{"type":"boolean","description":"Enable map picker for GPS inputs"}},"required":["type","label"]},"FieldRuleDto":{"type":"object","properties":{"id":{"type":"string"},"sourceFieldId":{"type":"string"},"operator":{"type":"string","enum":["equals","not_equals","contains","greater_than","less_than"]},"value":{"type":"string"},"thenAction":{"type":"string","enum":["show","require","show_group","hide","hide_group","show_input"]},"targetGroupId":{"type":"string","description":"Target group ID for show_group / hide_group actions"},"renderInputs":{"description":"Per-row input schema for repeatable fields (used with thenAction: show_input)","type":"array","items":{"$ref":"#/components/schemas/RenderInputDto"}}},"required":["id","thenAction"]},"TemplateFieldDto":{"type":"object","properties":{"id":{"type":"string","description":"Omitted or duplicate IDs are reassigned on save"},"label":{"type":"string"},"type":{"type":"string","enum":["text","textarea","number","email","tel","url","date","time","datetime","checkbox","radio","dropdown","password","toggle","image","multi_file_upload","signature","gps","repeatable","repeatable_components","table","label"]},"required":{"type":"boolean"},"placeholder":{"type":"string"},"validation":{"$ref":"#/components/schemas/TemplateFieldValidationDto"},"options":{"default":[],"type":"array","items":{"type":"string"}},"gpsMapPicker":{"type":"boolean","description":"Enable map picker for GPS fields"},"rules":{"default":[],"type":"array","items":{"$ref":"#/components/schemas/FieldRuleDto"}},"itemType":{"type":"string","description":"Sub-item type for repeatable fields (e.g. \"text\")"},"fields":{"description":"Nested fields for repeatable field type","type":"array","items":{"$ref":"#/components/schemas/TemplateFieldDto"}},"tableHeading":{"type":"string","description":"Heading/title for table field type"},"tableColumns":{"description":"Table column definitions for table field type","type":"array","items":{"$ref":"#/components/schemas/TemplateFieldDto"}},"table_rows":{"type":"array","description":"Same as `tableRows` — use one or the other","items":{"type":"object","additionalProperties":true}},"tableRows":{"type":"array","description":"Table rows data for table field type (preset rows / default row labels keyed by column id). Alternate key: `table_rows`.","items":{"type":"object","additionalProperties":true}},"tableRepeatRowsInView":{"type":"boolean","description":"Repeat table rows in view for table field type"},"repeatableComponents":{"description":"Nested component fields for `repeatable_components` payloads (frontend naming).","type":"array","items":{"$ref":"#/components/schemas/TemplateFieldDto"}}},"required":["label","type","required"]},"TemplateGroupDto":{"type":"object","properties":{"id":{"type":"string","description":"Omitted or duplicate IDs are reassigned on save"},"title":{"type":"string","description":"Optional in drafts. Required when the template is published (see is_published)."},"description":{"type":"string"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/TemplateFieldDto"}}},"required":["fields"]},"TemplateStepDto":{"type":"object","properties":{"id":{"type":"string","description":"Omitted or duplicate IDs are reassigned on save"},"title":{"type":"string","description":"Optional in drafts. Required when the template is published (see is_published)."},"description":{"type":"string"},"groups":{"description":"Groups containing fields (preferred format)","type":"array","items":{"$ref":"#/components/schemas/TemplateGroupDto"}},"fields":{"description":"Direct fields (legacy format, auto-wrapped in a default group)","type":"array","items":{"$ref":"#/components/schemas/TemplateFieldDto"}}}},"TemplateSectionDto":{"type":"object","properties":{"id":{"type":"string","description":"Omitted or duplicate IDs are reassigned on save"},"title":{"type":"string","description":"Optional in drafts. Required when the template is published (see is_published)."},"description":{"type":"string"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/TemplateStepDto"}}},"required":["steps"]},"TemplateCreateMappingDto":{"type":"object","properties":{"asset_type_id":{"type":"string"}}},"CreateTemplateDto":{"type":"object","properties":{"name":{"type":"string","example":"Building Inspection Template"},"description":{"type":"string"},"is_published":{"type":"boolean","default":false},"asset_type_id":{"type":"string","description":"Optional legacy field. Template-to-asset linkage should use template_mappings."},"sections":{"description":"Sections with nested steps and fields","type":"array","items":{"$ref":"#/components/schemas/TemplateSectionDto"}},"mapping":{"description":"Optional direct mapping payload. Allowed only when is_published=true.","allOf":[{"$ref":"#/components/schemas/TemplateCreateMappingDto"}]}},"required":["name","sections"]},"ConfirmTemplateVersionSwitchDto":{"type":"object","properties":{"confirm_switch":{"type":"boolean","description":"Must be true to acknowledge possible data loss when activating/restoring another template version.","example":true}},"required":["confirm_switch"]},"UpdateTemplateDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"is_published":{"type":"boolean"},"asset_type_id":{"type":"string"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/TemplateSectionDto"}},"mapping":{"description":"Mapping payload required when publishing template via update.","allOf":[{"$ref":"#/components/schemas/TemplateCreateMappingDto"}]}}},"SyncBatchDto":{"type":"object","properties":{"mutations":{"type":"array","description":"Array of mutation payloads from the device","items":{"type":"object"}},"expected_versions":{"type":"object","description":"Expected fieldDataVersion per inspection_id — used to detect stale state before applying mutations","additionalProperties":{"type":"number"}},"device_id":{"type":"string","description":"Device identifier that produced these mutations"}},"required":["mutations"]},"FieldChangeDto":{"type":"object","properties":{"change_id":{"type":"string","description":"Unique change ID for idempotency"},"inspection_id":{"type":"string","description":"Inspection this change belongs to"},"field_key":{"type":"string","description":"The specific field key being updated"},"value":{"type":"object","description":"New field value (any JSON)"},"client_modified_at":{"type":"string","description":"Timestamp when client made this change (ISO 8601)"},"datetime":{"type":"string","description":"Optional wall-clock datetime for this field edit (e.g. \"2026-04-30 11:22:00\"). When set, the stored JSON is `{ \"value\": <resolved>, \"datetime\": \"<string>\" }` to match PATCH /inspections/:id/fields behavior."},"change_source":{"type":"string","default":"CONSULTANT","description":"Source of the change"}},"required":["change_id","inspection_id","field_key","value","client_modified_at"]},"SyncPushDto":{"type":"object","properties":{"last_sync_at":{"type":"string","description":"Last known sync timestamp (ISO 8601)"},"changes":{"description":"Array of field-level changes to push","type":"array","items":{"$ref":"#/components/schemas/FieldChangeDto"}},"device_id":{"type":"string","description":"Unique device identifier"},"expected_versions":{"type":"object","description":"Expected `field_data_version` per inspection_id from the client's last successful sync. Used with the same version-gate rules as PATCH /inspections/:id/fields.","additionalProperties":{"type":"number"}}},"required":["last_sync_at","changes","device_id"]},"SyncPullDto":{"type":"object","properties":{"last_sync_at":{"type":"string","description":"Last sync timestamp to pull changes after (ISO 8601)"},"device_id":{"type":"string","description":"Unique device identifier"}},"required":["last_sync_at","device_id"]},"SyncSessionDto":{"type":"object","properties":{"device_id":{"type":"string","description":"Unique device identifier"},"fcm_token":{"type":"string","description":"FCM token for this device (same value registered at login). When set, the server resolves `device_id` from `user_device_tokens` for conflict detection."},"last_sync_at":{"type":"string","description":"Last successful sync timestamp (ISO 8601)"},"field_changes":{"description":"Field-level edits made while offline. Each entry is one (inspection, field) update.","type":"array","items":{"$ref":"#/components/schemas/FieldChangeDto"}},"mutations":{"type":"array","description":"Offline mutations (same contract as POST /sync/batch). Applied before field_changes in this endpoint; mutation sort order is create → status → fields → submit → approve/reject.\n\nValid JSON example:\n`[\n  { \"inspection_id\": \"<uuid>\", \"status\": \"IN_PROGRESS\", \"started_at\": \"2026-05-20T10:55:00.000Z\" },\n  { \"inspection_id\": \"<uuid>\", \"status\": \"SUBMITTED\", \"submitted_at\": \"2026-05-20T11:55:00.000Z\" },\n  { \"inspection_id\": \"<uuid>\", \"status\": \"APPROVED\", \"reason\": \"optional note\" }\n]`\n\nDo not combine `status: \"IN_PROGRESS\"` with submit-only keys (`submitted_at` / `completed_at`) in the same object—the server treats explicit IN_PROGRESS as a start mutation. Use separate objects for start vs submit.","items":{"type":"object"}},"expected_versions":{"type":"object","description":"Expected `field_data_version` per inspection_id from the client's last successful sync (same semantics as PATCH field save). When provided and the server version is newer with a different field value, a field-level conflict is recorded instead of overwriting.","additionalProperties":{"type":"number"}},"base_field_data_version":{"type":"number","description":"Deprecated / server-derived. Accepted for backwards compatibility but ignored — the server derives field & inspection versions from its own state and from `last_sync_at`."}},"required":["device_id","last_sync_at"]},"SyncConflictResolutionDto":{"type":"object","properties":{"conflict_id":{"type":"string","description":"conflict_log row id returned by /sync/session"},"decision":{"type":"string","enum":["ACCEPT","REJECT"],"description":"ACCEPT = take the server value (discard my edit). REJECT = keep my client value and push it to the server."}},"required":["conflict_id","decision"]},"SyncSessionResolveDto":{"type":"object","properties":{"resolutions":{"type":"array","items":{"$ref":"#/components/schemas/SyncConflictResolutionDto"}},"device_id":{"type":"string","description":"Device identifier (for audit)"}},"required":["resolutions"]},"RollbackDto":{"type":"object","properties":{"target_version_number":{"type":"number","description":"Version number to rollback to","example":5}},"required":["target_version_number"]},"CreateCheckpointDto":{"type":"object","properties":{"label":{"type":"string","description":"Label for the checkpoint","example":"Before final review"}},"required":["label"]},"AnalyzeImageDto":{"type":"object","properties":{"inspection_id":{"type":"string"},"field_key":{"type":"string","description":"Target inspection field key"},"image_url":{"type":"string","description":"Media for this inspection: full `https://...` URL, or path starting with `/` (combined with `API_PUBLIC_URL`). Must match a `media_files` row for `inspection_id` (see `/media/files/{id}` or configured S3 key).","example":"/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765"},"field_label":{"type":"string"},"field_type":{"type":"string","example":"select"},"context":{"type":"object","description":"Extra context for the model (e.g. allowed options)"}},"required":["inspection_id","field_key","image_url"]},"CreateAiJobDto":{"type":"object","properties":{"inspection_id":{"type":"string","description":"Inspection this job belongs to"},"field_key":{"type":"string","example":"exterior_photo","description":"Target inspection field key. Optional for IMAGE_ANALYSIS dynamic mode (system derives fields from inspection/template)."},"job_type":{"type":"string","enum":["IMAGE_ANALYSIS","TEXT_ANALYSIS","VOICE_ANALYSIS","VOICE_TRANSCRIPTION","FIELD_SUGGESTION"]},"input_url":{"type":"string","description":"Inspection image reference: absolute `https://...` URL, or app-relative path (e.g. `/api/v1/media/files/{id}`). Normalized with `API_PUBLIC_URL` before the worker downloads. For `IMAGE_ANALYSIS`, supply this and/or `image_base64`; the worker prefers `image_base64` when both are set.","example":"/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765"},"image_base64":{"type":"string","description":"`data:image/jpeg;base64,...` or raw Base64 bytes; optional `mime_type` when raw. Persisted when possible (`agent_image_url` on context). Preferred over `input_url` when both sent.","example":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="},"audio_base64":{"type":"string","description":"**VOICE_TRANSCRIPTION** — required for that job type. Mobile: send raw Base64 recorder output with `audio_mime_type`, or **`data:audio/mpeg;base64,...`**, **`data:audio/webm;base64,...`**, etc. Persisted when possible as `agent_audio_url` in job context.","example":"data:audio/webm;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQ..."},"mime_type":{"type":"string","description":"MIME for **IMAGE_ANALYSIS** when `image_base64` is raw (no `data:` prefix). Typical: `image/jpeg`, `image/png`.","example":"image/jpeg"},"audio_mime_type":{"type":"string","description":"MIME for **VOICE_TRANSCRIPTION** when `audio_base64` is raw Base64 (no `data:` prefix). Examples: `audio/mpeg` (MP3), `audio/webm`, `audio/mp4`, `audio/wav`, `audio/flac`. If payload is `data:audio/...;base64,...`, MIME is read from the prefix.","example":"audio/mpeg","enum":["audio/mpeg","audio/webm","audio/mp4","audio/wav","audio/flac","audio/aac"]},"target_fields":{"example":["field_a","field_b"],"type":"array","items":{"type":"string"}},"field_context":{"type":"object","description":"Optional context passed to the model (labels, options, etc.)"},"wait_for_completion":{"type":"boolean","description":"**IMAGE_ANALYSIS**: when omitted or `true`, the API waits for the worker and returns the job row with `status` DONE/FAILED, `fieldSuggestion`, and `result` populated. Set `false` to return immediately as PENDING and poll GET `/ai/jobs/:id`. **Other job types**: defaults to async; set `true` to wait for completion.","default":true}},"required":["inspection_id","job_type"]},"MultiImageInputDto":{"type":"object","properties":{"input_url":{"type":"string","description":"HTTPS or app-relative media URL (same rules as /ai/jobs).","example":"/api/v1/media/files/f7e8d9c0-b1a2-4321-8765-fedcba098765"},"image_base64":{"type":"string","description":"Raw base64 or data:image/...;base64,..."},"mime_type":{"type":"string","example":"image/jpeg"}}},"CreateMultiImageAiAnalysisDto":{"type":"object","properties":{"inspection_id":{"type":"string"},"type":{"type":"string","enum":["IMAGE_ANALYSIS","TEXT","VOICE_ANALYSIS"],"default":"IMAGE_ANALYSIS","description":"Analysis mode. `IMAGE_ANALYSIS` (default) expects `images`, `TEXT` expects `text`, `VOICE_ANALYSIS` expects `audio_file`."},"images":{"description":"Required when `type` is omitted or set to `IMAGE_ANALYSIS`. Each entry must include `input_url` and/or `image_base64`.","type":"array","items":{"$ref":"#/components/schemas/MultiImageInputDto"}},"text":{"type":"string","description":"Required when `type` is `TEXT`. Free-form user text that will be mapped to inspection template fields."},"audio_file":{"type":"string","description":"Required when `type` is `VOICE_ANALYSIS`. Audio payload as raw Base64 or `data:audio/...;base64,...`. Aliases: `audio_base64`.","example":"data:audio/webm;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQ..."},"audio_base64":{"type":"string","description":"Alias for `audio_file` (same shape)."},"audio_mime_type":{"type":"string","description":"MIME for `audio_file`/`audio_base64` when sent as raw Base64 (no `data:` prefix). Examples: `audio/mpeg`, `audio/webm`.","example":"audio/mpeg"},"wait_for_completion":{"type":"boolean","default":true,"description":"When true (default), the API waits for the worker to finish and returns `results`. When false, returns `job_id` and `status: QUEUED` — poll GET `/ai/jobs/:id` (see `result` / `field_suggestion`)."},"field_context":{"type":"object","description":"Optional extra context for prompts."}},"required":["inspection_id"]},"SuggestionFieldAcceptDto":{"type":"object","properties":{"field_id":{"type":"string","description":"Template field id (`fieldId` in `field_suggestion.results[].fields[]`). Required when multiple fields are suggested."}}},"SuggestionRejectDto":{"type":"object","properties":{"field_id":{"type":"string","description":"When the AI response lists multiple fields, send the template field id to reject that row only."},"reason":{"type":"string"}}},"SuggestionFieldManualDto":{"type":"object","properties":{"field_id":{"type":"string","description":"Template field id to update"},"value":{"type":"object","description":"New value for the inspection field (same shape as PATCH inspection field body)"}},"required":["field_id","value"]},"RegisterDeviceTokenDto":{"type":"object","properties":{"platform":{"type":"string","example":"fcm"},"token":{"type":"string","example":"fcm-device-token-string..."},"app_version":{"type":"string","example":"1.0.0"},"device_name":{"type":"string","example":"John iPhone 15"},"device_type":{"type":"string","example":"iOS"},"device_id":{"type":"string","example":"android-pixel-8"},"session_id":{"type":"string","example":"c7b51d3e-8186-470e-b79a-c3f0d6db0b0a"}},"required":["platform","token"]},"CreateAssetTypeDto":{"type":"object","properties":{"name":{"type":"string","example":"Residential"},"description":{"type":"string","example":"Residential property inspection templates"},"service_id":{"type":"string","example":"6cbf8b7f-9e4d-4017-a5be-0c6876f7e6cd"},"slug":{"type":"string","example":"residential"},"label_pattern":{"type":"string","example":"Flat {N}"},"id_prefix":{"type":"string","example":"FLAT"},"default_template_id":{"type":"string"},"icon":{"type":"string","example":"home"},"metadata":{"type":"object","example":{"industry":"property","requires_cert":true}}},"required":["name","service_id"]},"UpdateAssetTypeDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"is_active":{"type":"boolean"},"service_id":{"type":"string"},"slug":{"type":"string"},"label_pattern":{"type":"string"},"id_prefix":{"type":"string"},"default_template_id":{"type":"string"},"icon":{"type":"string"},"metadata":{"type":"object"}}},"MapTemplateToAssetTypeDto":{"type":"object","properties":{"template_id":{"type":"string"}},"required":["template_id"]},"CreateServiceDto":{"type":"object","properties":{"name":{"type":"string","example":"Fire Safety"},"slug":{"type":"string","example":"fire-safety"},"description":{"type":"string","example":"Service line for fire safety inspection operations"},"is_active":{"type":"boolean","default":true},"metadata":{"type":"object","example":{"region":"north","color":"red"}}},"required":["name"]},"UpdateServiceDto":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"is_active":{"type":"boolean"},"metadata":{"type":"object"}}},"CreateOrderItemDto":{"type":"object","properties":{"asset_type_id":{"type":"string","description":"Asset type id that this inspection item is for."},"title":{"type":"string","description":"Display title for the item."},"consultant_id":{"type":"string","description":"Consultant to assign this item to (optional)."},"assigned_to":{"type":"string","description":"Deprecated; use consultant_id."},"scheduled_date":{"type":"string","description":"Scheduled date for this inspection item."},"scheduled_time":{"type":"string","description":"Scheduled time for this inspection item (single time or range string)."},"notes":{"type":"string"},"metadata":{"type":"object"}},"required":["asset_type_id","scheduled_date","scheduled_time"]},"CreateOrderDto":{"type":"object","properties":{"title":{"type":"string"},"client_name":{"type":"string"},"client_id":{"type":"string"},"save_client":{"type":"boolean","description":"When true, creates/updates a client record from provided client fields."},"client_contact_email":{"type":"string"},"client_contact_phone":{"type":"string"},"site_address":{"type":"string"},"client_address":{"type":"string","description":"Client address (also persisted in Client module when save_client=true)."},"site_lat":{"type":"number"},"site_lng":{"type":"number"},"order_time":{"type":"string","description":"Order time (HH:mm or ISO datetime string)."},"priority":{"type":"string","enum":["low","medium","high"]},"notes":{"type":"string"},"metadata":{"type":"object"},"items":{"description":"Inspection items for this order. At least one item is required; orders are created in a published (scheduled) status.","type":"array","items":{"$ref":"#/components/schemas/CreateOrderItemDto"}}},"required":["title","client_name","items"]},"UpdateOrderItemDto":{"type":"object","properties":{"id":{"type":"string","description":"Existing inspection item id. Omit to create a new item."},"asset_type_id":{"type":"string","description":"Asset type id that this inspection item is for."},"title":{"type":"string","description":"Display title for the item."},"consultant_id":{"type":"string","description":"Consultant to assign this item to (optional)."},"assigned_to":{"type":"string","description":"Deprecated; use consultant_id."},"scheduled_date":{"type":"string","description":"Scheduled date for this inspection item."},"scheduled_time":{"type":"string","description":"Scheduled time for this inspection item (single time or range string)."},"notes":{"type":"string"},"metadata":{"type":"object"}},"required":["asset_type_id","scheduled_date","scheduled_time"]},"UpdateOrderDto":{"type":"object","properties":{"title":{"type":"string"},"client_name":{"type":"string"},"client_id":{"type":"string"},"save_client":{"type":"boolean"},"client_contact_email":{"type":"string"},"client_contact_phone":{"type":"string"},"site_address":{"type":"string"},"client_address":{"type":"string","description":"Client address (also persisted in Client module when save_client=true)."},"site_lat":{"type":"number"},"site_lng":{"type":"number"},"order_time":{"type":"string","description":"Order time (HH:mm or ISO datetime string)."},"priority":{"type":"string","enum":["low","medium","high"]},"notes":{"type":"string"},"metadata":{"type":"object"},"items":{"description":"Optional inspection-item edits in the same request. Include item id to update existing item; omit id to create a new item.","type":"array","items":{"$ref":"#/components/schemas/UpdateOrderItemDto"}}}},"CreateOrderConfigDto":{"type":"object","properties":{"item_type_id":{"type":"string"},"default_item_count":{"type":"number"},"allow_mixed_item_types":{"type":"boolean"},"enforce_single_template":{"type":"boolean"},"allow_mixed_consultants":{"type":"boolean"},"min_items_per_order":{"type":"number"},"max_items_per_order":{"type":"number"},"auto_create_consolidated_report":{"type":"boolean"},"label_pattern_override":{"type":"string"}}},"UpdateOrderConfigDto":{"type":"object","properties":{"item_type_id":{"type":"string"},"default_item_count":{"type":"number"},"allow_mixed_item_types":{"type":"boolean"},"enforce_single_template":{"type":"boolean"},"allow_mixed_consultants":{"type":"boolean"},"min_items_per_order":{"type":"number"},"max_items_per_order":{"type":"number"},"auto_create_consolidated_report":{"type":"boolean"},"label_pattern_override":{"type":"string"}}},"CreatePermissionPresetDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"role":{"type":"string","enum":["ADMIN","SUB_ADMIN","VIEWER","APPROVER"],"description":"Optional standard role this preset represents."},"permissions":{"type":"object","description":"Map of module name to permission level (read/write/admin)"}},"required":["name","permissions"]},"UpdatePermissionPresetDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"role":{"type":"string","enum":["ADMIN","SUB_ADMIN","VIEWER","APPROVER"]},"permissions":{"type":"object"}}},"CreateClientDto":{"type":"object","properties":{"name":{"type":"string"},"contact_email":{"type":"string"},"contact_phone":{"type":"string"},"country_code":{"type":"string","description":"Dial code for contact_phone, e.g. \"+91\""},"client_address":{"type":"string"}},"required":["name"]},"UpdateClientDto":{"type":"object","properties":{"name":{"type":"string"},"contact_email":{"type":"string"},"contact_phone":{"type":"string"},"country_code":{"type":"string","description":"Dial code for contact_phone, e.g. \"+91\""},"client_address":{"type":"string"}}}}}}