{"openapi":"3.1.0","info":{"title":"OpenAPI definition","version":"v0"},"servers":[{"url":"/"}],"tags":[{"name":"auth","description":"Login, MFA, refresh token rotation, password management"},{"name":"users","description":"User management: CRUD and search"},{"name":"permissions","description":"Permission catalog"},{"name":"me","description":"Self-service: current user profile"},{"name":"roles","description":"Role management: CRUD and search"}],"paths":{"/api/users/{id}/roles":{"get":{"tags":["users"],"summary":"Get user roles","description":"Returns all roles assigned to the user.\n","operationId":"getRoles","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RoleResponse"}}}}}}},"put":{"tags":["users"],"summary":"Replace user roles","description":"Replaces all role assignments for the user with the given list. Send an empty list to\nremove all roles. Each role must exist and belong to the same tenant as the user.\nDuplicate role IDs cause 400.\n","operationId":"setRoles","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"type":"string","format":"uuid"}}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid, duplicate, or cross-tenant role ID","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Replaced","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RoleResponse"}}}}}}}},"/api/users/{id}/permissions":{"get":{"tags":["users"],"summary":"Get user direct permissions","description":"Returns all direct permission grants for the user (not including permissions inherited\nfrom roles).\n","operationId":"getPermissions","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserPermissionResponse"}}}}}}},"put":{"tags":["users"],"summary":"Replace user direct permissions","description":"Replaces all direct permission grants for the user with the given list. Send an empty\nlist to remove all direct permissions.\n\nEach entry specifies `permissionName`, a required single `app` slug (e.g. `\"adm\"`,\n`\"wiki\"`), and an optional `server` array of server slugs. The admin UI sends one\nentry per app tab, so the same `(permissionName, server)` tuple may legitimately\nappear in multiple entries with different `app` values (e.g. `PERMIT_ALL` from the\n`adm` and `wiki` tabs) — the backend merges them into a single stored row with a\ncolon-joined app scope (`\"adm:wiki\"`). The `app` dimension has no wildcard: the\nliteral `\"all\"` is rejected, every entry must use a real app slug.\n\n`server` is omitted or `null` for permissions with `has_server_dim=false`.\nFor server-ful permissions, pass an array of slugs (e.g. `[\"x500\",\"x20\"]`); the\nbackend stores one row per server. Duplicate slugs in `server` are silently collapsed.\n\nThe app scope must be a subset of `permissions_catalog.app` for the referenced\npermission. Unknown permissions or app-subset violations cause 400.\n","operationId":"setPermissions","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PermissionEntry"}}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid permission entry or app-subset violation","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Replaced","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserPermissionResponse"}}}}}}}},"/api/users/roles/{id}/permissions":{"get":{"tags":["roles"],"summary":"Get role permissions","description":"Returns all permissions assigned to the role.\n","operationId":"getPermissions_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Role not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RolePermissionResponse"}}}}}}},"put":{"tags":["roles"],"summary":"Replace role permissions","description":"Replaces all permissions for the role with the given list. Send an empty list to remove all.\n\nEach entry specifies `permissionName`, a required single `app` slug (e.g. `\"adm\"`,\n`\"wiki\"`), and an optional `server` array of server slugs. The admin UI sends one\nentry per app tab, so the same `(permissionName, server)` tuple may legitimately\nappear in multiple entries with different `app` values (e.g. `PERMIT_ALL` from the\n`adm` and `wiki` tabs) — the backend merges them into a single stored row with a\ncolon-joined app scope (`\"adm:wiki\"`). The `app` dimension has no wildcard: the\nliteral `\"all\"` is rejected, every entry must use a real app slug.\n\n`server` is omitted or `null` for permissions with `has_server_dim=false`.\nFor server-ful permissions, pass an array of slugs (e.g. `[\"x500\",\"x20\"]`); the\nbackend stores one row per server. Duplicate slugs in `server` are silently collapsed.\n\nThe app scope must be a subset of `permissions_catalog.app` for the referenced\npermission. Unknown permissions or app-subset violations cause 400.\n","operationId":"setPermissions_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PermissionEntry"}}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid permission entry or app-subset violation","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Role not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Replaced","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RolePermissionResponse"}}}}}}}},"/api/users/{id}/totp/reset":{"post":{"tags":["users"],"summary":"Admin TOTP reset","description":"Clears the user's TOTP secret and verified flag. The user will be prompted to set up\nMFA again on their next login.\n","operationId":"adminResetTotp","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"TOTP reset"}}}},"/api/users/{id}/password/reset":{"post":{"tags":["users"],"summary":"Admin password reset","description":"Invalidates the user's current password, sets force_password_change = true, and sends\na password reset email. The user must use the email link to set a new password.\n","operationId":"adminResetPassword","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Reset triggered"}}}},"/api/users/roles":{"get":{"tags":["roles"],"summary":"Search roles in the current tenant","description":"Paginated list with optional case-insensitive substring match on name (all locales).\nSort accepts: id, name.\n","operationId":"search","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/Pageable"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Unsupported sort property","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PagedModelRoleResponse"}}}}}},"post":{"tags":["roles"],"summary":"Create a role","description":"Creates a per-tenant role. Name must have at least one non-blank locale value.\n","operationId":"create","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRoleRequest"}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_CREATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RoleResponse"}}}}}}},"/api/users/auth/refresh":{"post":{"tags":["auth"],"summary":"Rotate refresh token","description":"Verifies the refresh JWT, marks the presented token used, and issues a new access +\nrefresh pair. Presenting an already-used refresh token triggers reuse detection: every\nactive refresh token for that user is revoked and the caller receives 401.\n","operationId":"refresh","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"401":{"description":"Invalid, expired, revoked, or reused refresh token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Rotated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}}}}},"/api/users/auth/password/set":{"post":{"tags":["auth"],"summary":"Set password with token","description":"Consumes a single-use token and sets a new password. The token comes from an email\nlink — this endpoint is shared by the self-service reset flow and the admin-create\nwelcome flow, the consume semantics are identical. No JWT required.\n","operationId":"setPassword","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordRequest"}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid, expired, or already used token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Password set"}}}},"/api/users/auth/password/reset-request":{"post":{"tags":["auth"],"summary":"Request password reset email","description":"Sends a password reset email to the given address. Always returns 200 regardless of\nwhether the email exists — no user enumeration.\n","operationId":"resetRequest","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Request accepted (email may or may not exist)"}}}},"/api/users/auth/password/change":{"post":{"tags":["auth"],"summary":"Change own password","description":"Authenticated user changes their own password. Requires a valid JWT and the current\npassword. Clears force_password_change on success.\n","operationId":"changePassword","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"401":{"description":"Invalid current password or no JWT","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error or weak password","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Password changed"}}}},"/api/users/auth/mfa":{"post":{"tags":["auth"],"summary":"Verify MFA (TOTP code)","description":"Verifies the TOTP code against the MFA challenge from login. On first-time setup,\nmarks the user's TOTP as verified. Returns a fresh access + refresh token pair.\n","operationId":"verifyMfa","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyRequest"}}},"required":true},"responses":{"401":{"description":"Invalid challenge, expired, wrong code, or deactivated user","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"MFA verified, tokens issued","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}}}}},"/api/users/auth/logout":{"post":{"tags":["auth"],"summary":"Logout (revoke current session)","description":"Revokes all refresh tokens in the caller's session chain. The session is identified\nby the `sid` claim in the access token. Rows are soft-revoked (revoked_at set, not\ndeleted) for audit purposes. Requires a valid access token.\n","operationId":"logout","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"No or invalid access token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Session revoked"}}}},"/api/users/auth/login":{"post":{"tags":["auth"],"summary":"Authenticate with email + password","description":"Validates credentials against the tenant resolved from the infra-injected headers.\nReturns either tokens (no MFA) or an MFA challenge. All credential failure modes\nreturn the same 401 to prevent user enumeration.\n\nResponse variants (check which fields are non-null):\n- accessToken + refreshToken present → direct login (no MFA)\n- mfaToken present → MFA required, call POST /auth/mfa with TOTP code\n- mfaToken + totpSecret + otpAuthUri present → first-time MFA setup\n","operationId":"login","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"401":{"description":"Invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Authenticated or MFA challenge","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}}}}},"/api/users/auth/code/issue":{"post":{"tags":["auth"],"summary":"Issue cross-app auth code (temporary phase 1 flow)","description":"Issues a short-lived one-time auth code for the currently authenticated user.\nThe issuing app hands the code to the target app out-of-band (e.g. via query\nstring on a browser navigation). The target app then calls\nPOST /auth/code/exchange to obtain its own access + refresh token pair.\n\nRequires a valid access token — the user id is taken from the JWT, no request\nbody is needed. UPSERTs on user_id, so at most one active code per user exists\nat any time; a repeat call atomically invalidates the previous code.\n\nTEMPORARY: this endpoint is a phase 1 bridge between existing apps and will be\nremoved once the IdP SPA login flow ships (see SPEC-cross-auth-backend-spec.md).\n","operationId":"issueCode","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"No or invalid access token","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Code issued","content":{"*/*":{"schema":{"$ref":"#/components/schemas/IssueCodeResponse"}}}}}}},"/api/users/auth/code/exchange":{"post":{"tags":["auth"],"summary":"Exchange cross-app auth code for tokens","description":"Atomically consumes a one-time auth code (issued via POST /auth/code/issue)\nand returns a fresh access + refresh token pair for the code's owner. Public\nendpoint — no JWT required. All failure modes (missing, expired, already\nconsumed, user deactivated, cross-tenant misuse) return the same 401 to\nprevent probing.\n\nThe returned tokens are scoped to the tenant resolved from the request's\nTenant-Id / Tenant-Slug headers — which must match the user's tenant,\notherwise the code is rejected.\n","operationId":"exchangeCode","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeCodeRequest"}}},"required":true},"responses":{"401":{"description":"Invalid, expired, or already consumed code","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Tokens issued","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}}}}},"/api/users/":{"get":{"tags":["users"],"summary":"Search users in the current tenant","description":"Paginated list with optional case-insensitive substring match on email/name and an\noptional active filter. Sort accepts: id, email, name, lastLoginAt.\n","operationId":"search_1","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"active","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/Pageable"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Unsupported sort property","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PagedModelUserResponse"}}}}}},"post":{"tags":["users"],"summary":"Create a user","description":"Creates a user in the current tenant with an unusable password hash and\nforce_password_change = true. The new account cannot log in until the password-reset\nemail flow (Trench B) gives the user a way to set their own password.\n","operationId":"create_1","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_CREATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Email already exists in the tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}}}},"/api/users/{id}":{"get":{"tags":["users"],"summary":"Get user by id","operationId":"getById","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}}},"delete":{"tags":["users"],"summary":"Delete a user","description":"Hard delete; cascades remove the user's refresh tokens, direct permission grants and\nrole assignments via database foreign keys.\n","operationId":"delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_DELETE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Deleted"}}},"patch":{"tags":["users"],"summary":"Update user (partial)","description":"Only fields present in the request body are applied. At least one of email, name,\nor active must be provided.\n","operationId":"update","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing USERS_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error or no changes","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Email already exists in the tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Updated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}}}},"/api/users/roles/{id}":{"get":{"tags":["roles"],"summary":"Get role by id","operationId":"getById_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_READ permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RoleResponse"}}}}}},"delete":{"tags":["roles"],"summary":"Delete a role","description":"Hard delete; cascades remove the role's permission assignments via database foreign keys.\n","operationId":"delete_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_DELETE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Deleted"}}},"patch":{"tags":["roles"],"summary":"Update role (partial)","description":"Only fields present in the request body are applied. At least one of name or description\nmust be provided.\n","operationId":"update_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRoleRequest"}}},"required":true},"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Missing ROLES_UPDATE permission","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Validation error or no changes","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not found or belongs to another tenant","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"Updated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RoleResponse"}}}}}}},"/api/users/permissions":{"get":{"tags":["permissions"],"summary":"List permission catalog","description":"Returns all permissions defined in the platform catalog.\n","operationId":"list","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PermissionResponse"}}}}}}}},"/api/users/me":{"get":{"tags":["me"],"summary":"Get current user profile","description":"Returns the authenticated caller's own profile. No permission check — any user with a\nvalid JWT can read their own data. Returns 401 if no JWT is present.\n","operationId":"me","parameters":[{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"No valid JWT","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"User no longer exists","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}}}},"/api/users/.well-known/jwks.json":{"get":{"tags":["JWKS"],"summary":"JSON Web Key Set — public keys for JWT verification","operationId":"jwks","parameters":[{"name":"tenant_id","in":"query","description":"Filter keys by tenant id","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Tenant-Id","in":"header","description":"Tenant UUID — omit for common (platform) scope","required":false,"schema":{"type":"string","format":"uuid"},"example":"019cd67b-8a66-7e72-8cf2-e1ad073d9f33"},{"name":"Tenant-Slug","in":"header","description":"Tenant slug — omit for common (platform) scope","required":false,"schema":{"type":"string"},"example":"bohpts"},{"name":"Server-Id","in":"header","description":"Server UUID","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"Server-Slug","in":"header","description":"Server slug","required":false,"schema":{"type":"string"}}],"responses":{"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"JWK Set","content":{"*/*":{"schema":{"type":"object","additionalProperties":{}}}}}}}}},"components":{"schemas":{"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"RoleResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"object","additionalProperties":{"type":"string"}},"description":{"type":"object","additionalProperties":{"type":"string"}}}},"PermissionEntry":{"type":"object","properties":{"permissionName":{"type":"string"},"app":{"type":"string"},"server":{"type":"array","items":{"type":"string"}}}},"UserPermissionResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"permissionName":{"type":"string"},"app":{"type":"string"},"server":{"type":"string"}}},"RolePermissionResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"permissionName":{"type":"string"},"app":{"type":"string"},"server":{"type":"string"}}},"CreateRoleRequest":{"type":"object","properties":{"name":{"type":"object","additionalProperties":{"type":"string"}},"description":{"type":"object","additionalProperties":{"type":"string"}}},"required":["name"]},"RefreshRequest":{"type":"object","properties":{"refreshToken":{"type":"string","minLength":1}},"required":["refreshToken"]},"TokenResponse":{"type":"object","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"}}},"SetPasswordRequest":{"type":"object","properties":{"token":{"type":"string","minLength":1},"newPassword":{"type":"string","maxLength":2147483647,"minLength":8}},"required":["newPassword","token"]},"PasswordResetRequest":{"type":"object","properties":{"email":{"type":"string","minLength":1}},"required":["email"]},"ChangePasswordRequest":{"type":"object","properties":{"currentPassword":{"type":"string","minLength":1},"newPassword":{"type":"string","maxLength":2147483647,"minLength":8}},"required":["currentPassword","newPassword"]},"MfaVerifyRequest":{"type":"object","properties":{"mfaToken":{"type":"string","format":"uuid"},"code":{"type":"string","minLength":1}},"required":["code","mfaToken"]},"LoginRequest":{"type":"object","properties":{"email":{"type":"string","minLength":1},"password":{"type":"string","minLength":1}},"required":["email","password"]},"LoginResponse":{"type":"object","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"},"mfaToken":{"type":"string","format":"uuid"},"totpSecret":{"type":"string"},"otpAuthUri":{"type":"string"}}},"IssueCodeResponse":{"type":"object","properties":{"code":{"type":"string","format":"uuid"}}},"ExchangeCodeRequest":{"type":"object","properties":{"code":{"type":"string","format":"uuid"}},"required":["code"]},"CreateUserRequest":{"type":"object","properties":{"email":{"type":"string","maxLength":256,"minLength":0},"name":{"type":"string","maxLength":256,"minLength":0}},"required":["email","name"]},"UserResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"name":{"type":"string"},"active":{"type":"boolean"},"forcePasswordChange":{"type":"boolean"},"lastLoginAt":{"type":"string","format":"date-time"}}},"UpdateUserRequest":{"type":"object","properties":{"email":{"type":"string","maxLength":256,"minLength":0},"name":{"type":"string","maxLength":256,"minLength":0},"active":{"type":"boolean"}}},"UpdateRoleRequest":{"type":"object","properties":{"name":{"type":"object","additionalProperties":{"type":"string"}},"description":{"type":"object","additionalProperties":{"type":"string"}}}},"Pageable":{"type":"object","properties":{"page":{"type":"integer","format":"int32","minimum":0},"size":{"type":"integer","format":"int32","minimum":1},"sort":{"type":"array","items":{"type":"string"}}}},"PageMetadata":{"type":"object","properties":{"size":{"type":"integer","format":"int64"},"number":{"type":"integer","format":"int64"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int64"}}},"PagedModelRoleResponse":{"type":"object","properties":{"content":{"type":"array","items":{"$ref":"#/components/schemas/RoleResponse"}},"page":{"$ref":"#/components/schemas/PageMetadata"}}},"PermissionResponse":{"type":"object","properties":{"name":{"type":"string"},"domain":{"type":"string"},"description":{"type":"object","additionalProperties":{"type":"string"}},"hasServerDim":{"type":"boolean"},"ns":{"type":"string"},"app":{"type":"string"}}},"PagedModelUserResponse":{"type":"object","properties":{"content":{"type":"array","items":{"$ref":"#/components/schemas/UserResponse"}},"page":{"$ref":"#/components/schemas/PageMetadata"}}}}}}