{"openapi":"3.1.0","info":{"title":"Vignette API","description":"This API is automatically generated by SpringDoc and is used to document the endpoints of the Vignette Java application.\nIt was originally created by Titouan Johanny, under the supervision of teacher <a href='mailto:louis.edouard.lafontant@umontreal.ca'>Louis-Edouard Lafontant</a>.\n\nAuthentication model:\n  - Public endpoints do not require authentication\n  - Protected endpoints require a Bearer JWT\n  - Browser-based flows may also rely on session cookies and CSRF\n\nRecommended integration mode for API clients:\n  - use Authorization: Bearer <token>\n","termsOfService":"https://github.com/Titiplex/vignette","contact":{"name":"Titouan Johanny","url":"https://github.com/Titiplex","email":"johannytitouan@gmail.com"},"license":{"name":"GNU","url":"https://www.gnu.org/licenses/gpl-3.0.en.html"},"version":"1.0 (BETA)","summary":"Automated REST OpenAPI documentation for Vignette"},"servers":[{"url":"http://localhost:8081","description":"Local development server"}],"tags":[{"name":"Admin","description":"Global administration endpoints."},{"name":"Audio","description":"Endpoint for fetching and managing audio files and associated metadata."},{"name":"Auth","description":"Endpoints for user authentication, identity and registration."},{"name":"Community","description":"Endpoints for community interactions around languages and media.\n\nThis includes:\n- threaded discussions attached to a language or audio clip\n- accreditation requests for community contribution roles\n- accreditation review and direct grant flows\n"},{"name":"Language","description":"Endpoints for listing and retrieving language data."},{"name":"Scenario","description":"Endpoints for creating, retrieving, listing, and deleting scenarios."},{"name":"Thumbnail","description":"Endpoints for managing thumbnail images associated with scenarios, including listing, uploading, and retrieving thumbnail content."},{"name":"User","description":"Endpoints for managing user profiles, including viewing and updating private profiles, and accessing public profiles."}],"paths":{"/api/admin/overview":{"get":{"tags":["Admin"],"summary":"Get admin overview","description":"Access: Authenticated users with role ADMIN only.\n\nRequires authentication. Required role: ADMIN.\n\nReturns global counters for the administration dashboard.","operationId":"overview","responses":{"200":{"description":"Overview retrieved successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminOverview"}}}},"403":{"description":"Admin privileges required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"ADMIN","x-authorization-rule":"Requires authentication. Required role: ADMIN."}},"/api/admin/scenarios":{"get":{"tags":["Admin"],"summary":"List scenarios for administration","description":"Access: Authenticated users with role ADMIN only.\n\nRequires authentication. Required role: ADMIN.\n\nReturns all scenarios for administration purposes, including drafts.","operationId":"listScenarios","responses":{"200":{"description":"Scenarios retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Scenario"}}}}},"403":{"description":"Admin privileges required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"ADMIN","x-authorization-rule":"Requires authentication. Required role: ADMIN."}},"/api/admin/users":{"get":{"tags":["Admin"],"summary":"List users for administration","description":"Access: Authenticated users with role ADMIN only.\n\nRequires authentication. Required role: ADMIN.\n\nReturns all users for administration purposes.","operationId":"listUsers","responses":{"200":{"description":"Users retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminUserRow"}}}}},"403":{"description":"Admin privileges required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"ADMIN","x-authorization-rule":"Requires authentication. Required role: ADMIN."}},"/api/admin/scenarios/{id}/visibility":{"patch":{"tags":["Admin"],"summary":"Update scenario visibility","description":"Access: Authenticated users with role ADMIN only.\n\nRequires authentication. Required role: ADMIN.\n\nUpdates scenario visibility for administration purposes. Admin only.","operationId":"updateScenarioVisibility","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScenarioVisibilityRequest"}}},"required":true},"responses":{"200":{"description":"Scenario visibility updated successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Scenario"}}}},"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Admin privileges required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"ADMIN","x-authorization-rule":"Requires authentication. Required role: ADMIN."}},"/api/admin/users/{id}/roles":{"patch":{"tags":["Admin"],"summary":"Update user roles","description":"Access: Authenticated users with role ADMIN only.\n\nRequires authentication. Required role: ADMIN.\n\nReplaces the roles of a given user. Admin only.","operationId":"updateUserRoles","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRolesRequest"}}},"required":true},"responses":{"200":{"description":"User roles updated successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserRow"}}}},"400":{"description":"Invalid request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Admin privileges required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"ADMIN","x-authorization-rule":"Requires authentication. Required role: ADMIN."}},"/api/audios/{audioId}":{"delete":{"tags":["Audio"],"summary":"Deletes an audio file.","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: AUDIO.\nOwnership parameter: audioId.\n\nAccess: Resource owner or ADMIN only.\n\nRequires authentication. Authorization: related resource owner or ADMIN only.\n\nDeletes the audio file associated to the given ID. One must have the appropriate permissions (owner/manager of the audio or admin).","operationId":"delete","parameters":[{"name":"audioId","in":"path","description":"ID of the audio file to delete","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"Audio successfully deleted"},"404":{"description":"Audio not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden : not the owner","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Requires authentication. Authorization: related resource owner or ADMIN only.","x-owner-resource":"AUDIO","x-owner-param":"audioId"}},"/api/languages/{langId}/audios":{"get":{"tags":["Audio"],"summary":"List audio files for a language","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns all audio files associated with a language.","operationId":"listByLanguage","parameters":[{"name":"langId","in":"path","description":"ID of the language to retrieve the audio files for","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully retrieved audio list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AudioRow"}},"examples":{"Example Audio List":{"description":"Example Audio List","value":[{"id":1,"title":"Audio 1","idx":1,"mime":"png"},{"id":2,"title":"Audio 2","idx":2,"mime":"png"}]}}}}},"404":{"description":"Language not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/thumbnails/{thumbId}/audios":{"get":{"tags":["Audio"],"summary":"List audio files for a thumbnail","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns all audio files associated with a thumbnail.","operationId":"list","parameters":[{"name":"thumbId","in":"path","description":"ID of the thumbnail to retrieve the audio files for","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Successfully retrieved audio list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AudioRow"}},"examples":{"Example Audio List":{"description":"Example Audio List","value":[{"id":1,"title":"Audio 1","idx":1,"mime":"png"},{"id":2,"title":"Audio 2","idx":2,"mime":"png"}]}}}}},"404":{"description":"Thumbnail not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."},"post":{"tags":["Audio"],"summary":"Uploads an audio file.","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: THUMBNAIL.\nOwnership parameter: thumbId.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nUploads an audio file and associates it with a thumbnail.\n\nRequest content type:\n - multipart/form-data","operationId":"upload","parameters":[{"name":"thumbId","in":"path","description":"ID of the thumbnail to associate the audio file with","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"title","in":"query","description":"Title of the audio file","required":false,"schema":{"type":"string","default":""}},{"name":"idx","in":"query","description":"Index of the audio file (order of the audio among all audios associated with a thumbnail.","required":false,"schema":{"type":"integer","format":"int32","minimum":1},"example":1},{"name":"markerX","in":"query","description":"X-coordinate of the marker.","required":false,"schema":{"type":"number","format":"double"}},{"name":"markerY","in":"query","description":"Y-coordinate of the marker.","required":false,"schema":{"type":"number","format":"double"}},{"name":"markerLabel","in":"query","description":"Label of the marker.","required":false,"schema":{"type":"string","default":""}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"audio":{"type":"string","format":"binary","description":"Audio file to upload."}},"required":["audio"]}}}},"responses":{"201":{"description":"Audio uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAudioResponse"}}}},"400":{"description":"Invalid input","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CreateAudioResponse"}}}},"403":{"description":"Access denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CreateAudioResponse"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"THUMBNAIL","x-owner-param":"thumbId"}},"/api/audios/{id}/content":{"get":{"tags":["Audio"],"summary":"Retrieves the content of an audio file.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns the audio file content, with appropriate MIME type and caching headers.","operationId":"content","parameters":[{"name":"id","in":"path","description":"ID of the audio file to retrieve","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Audio content retrieved successfully","content":{"audio/*":{"schema":{"type":"string"}}}},"404":{"description":"Audio not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/audios/{audioId}/marker":{"patch":{"tags":["Audio"],"summary":"Update an audio marker.","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: AUDIO.\nOwnership parameter: audioId.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nUpdates the marker for an audio, could it be modifying it or creating it for the first time.","operationId":"updateMarker","parameters":[{"name":"audioId","in":"path","description":"ID of the audio file whose marker is to be updated","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"description":"New marker information.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMarkerRequest"}}},"required":true},"responses":{"200":{"description":"Marker updated successfully"},"403":{"description":"Access denied","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Audio not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required"}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"AUDIO","x-owner-param":"audioId"}},"/api/auth/login":{"post":{"tags":["Auth"],"summary":"Authenticate a user.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nAuthenticates a user with username and password.\n\nReturns a JWT token for authenticated API access.\nThis login flow also establishes a server-side authenticated session.","operationId":"login","requestBody":{"description":"Username and password to authenticate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"User authenticated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"},"examples":{"Login response":{"description":"Login response","value":{"token":"eyJhbGciOiJSUzI1NiIs...","expiresIn":3600}}}}}},"401":{"description":"Invalid credentials : username or password incorrect","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"400":{"description":"Missing or malformed request body","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/auth/me":{"get":{"tags":["Auth"],"summary":"Get the current authenticated user","description":"Access: Authenticated users only.\n\nRequires authentication.\n\nReturns the current authenticated user and granted roles.","operationId":"me","responses":{"200":{"description":"Information fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"description":"User not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Authenticated user not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"AUTHENTICATED","x-authorization-rule":"Requires authentication."}},"/api/auth/logout":{"post":{"tags":["Auth"],"summary":"Logout the current user.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nLogs out the current user.\n\nThis endpoint invalidates the current server-side session and clears the security context.","operationId":"logout","responses":{"204":{"description":"User logged out successfully"},"200":{"description":"User logged out successfully"}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/auth/refresh":{"post":{"tags":["Auth"],"summary":"Refresh the current access token","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nIssues a new JWT access token for the current authenticated session.\n\nThis endpoint is meant to be called with the existing server-side session\ncookie, not with an Authorization bearer token.","operationId":"refresh","responses":{"200":{"description":"A new access token was issued successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"No authenticated session available","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/auth/register":{"post":{"tags":["Auth"],"summary":"Registers a new user.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nRegisters a new user and returns the created account identifier.","operationId":"register","requestBody":{"description":"User registration details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Registered successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"},"examples":{"Register response":{"description":"Register response","value":{"id":12,"username":"alice"}}}}}},"400":{"description":"Invalid input : missing required fields, password too short, or malformed data","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflict : username or email already exists","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/community/discussions":{"get":{"tags":["Community"],"summary":"List discussion messages","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns all messages attached to a specific discussion target.\n\nA discussion target is identified by:\n- targetType: the type of object being discussed\n- targetId: the identifier of that object\n\nCurrent supported target types include language-level and audio-level discussions.","operationId":"listDiscussions","parameters":[{"name":"targetType","in":"query","description":"Type of target being discussed","required":true,"schema":{"type":"string","enum":["LANGUAGE","AUDIO"]},"example":"LANGUAGE"},{"name":"targetId","in":"query","description":"Identifier of the target being discussed","required":true,"schema":{"type":"string"},"example":"chuj"}],"responses":{"200":{"description":"Discussion messages retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscussionMessageDto"}}}},"400":{"description":"Invalid discussion target type or target id","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Discussion target does not exist","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."},"post":{"tags":["Community"],"summary":"Create a discussion message","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nCreates a new message attached to a discussion target.\n\nThe message may optionally reply to an existing message by providing parentMessageId.\nThe authenticated user becomes the message author.","operationId":"createDiscussion","requestBody":{"description":"New discussion message payload.\n\nExample:\n- create a top-level message on a language\n- or reply to an existing message with parentMessageId\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDiscussionMessageRequest"},"examples":{"Create discussion message":{"description":"Create discussion message","value":{"targetType":"LANGUAGE","targetId":"chuj","parentMessageId":null,"contributionType":"GLOSS","content":"I think this gloss should be revised."}}}}},"required":true},"responses":{"200":{"description":"Discussion message created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscussionMessageDto"}}}},"400":{"description":"Invalid request body, unknown target, invalid parent message, or blank content","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Authenticated but not allowed for this operation"}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/community/accreditation-requests":{"get":{"tags":["Community"],"summary":"List accreditation requests","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nLists accreditation requests for a given scope.\n\nAccess rules:\n- GLOBAL scope: admin only\n- SCENARIO scope: admin or scenario owner","operationId":"listAccreditationRequests","parameters":[{"name":"permissionType","in":"query","description":"Permission of accreditation request to receive","required":true,"schema":{"type":"string","enum":["COMMUNITY_REVIEW","LANGUAGE_EDIT","SCENARIO_EDIT","SCENARIO_MODERATE"]},"example":"LANGUAGE_EDIT"},{"name":"scopeType","in":"query","description":"Scope of accreditation requests to retrieve","required":true,"schema":{"type":"string","enum":["GLOBAL","SCENARIO","LANGUAGE","LANGUAGE_FAMILY"]},"example":"SCENARIO"},{"name":"targetId","in":"query","description":"target identifier for scopeType","required":false,"schema":{"type":"string"},"example":12}],"responses":{"200":{"description":"Accreditation requests retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccreditationRequestDto"}}}},"400":{"description":"Invalid scope or missing scenarioId","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Not allowed to read accreditation requests for this scope","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."},"post":{"tags":["Community"],"summary":"Create an accreditation request","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nCreates a new accreditation request for the authenticated user.\n\nThis can be:\n- a global accreditation request\n- or a scenario-scoped accreditation request\n\nScenario-scoped requests must include scenarioId.","operationId":"createAccreditationRequest","requestBody":{"description":"Accreditation request payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccreditationRequestBody"},"examples":{"Scenario accreditation request":{"description":"Scenario accreditation request","value":{"permissionType":"SCENARIO_EDIT","scopeType":"SCENARIO","targetId":"12","motivation":"I can help organize and maintain this scenario."}}}}},"required":true},"responses":{"200":{"description":"Accreditation request created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccreditationRequestDto"}}}},"400":{"description":"Invalid scope, missing scenarioId, blank motivation, or duplicate pending request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Authenticated but not allowed for this operation"}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/community/accreditations":{"get":{"tags":["Community"],"summary":"List granted accreditations","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nLists granted accreditations for a given scope.\n\nAccess rules:\n- GLOBAL scope: admin only\n- SCENARIO scope: admin or scenario owner","operationId":"listAccreditations","parameters":[{"name":"permissionType","in":"query","description":"Permissions of accreditations to receive","required":true,"schema":{"type":"string","enum":["COMMUNITY_REVIEW","LANGUAGE_EDIT","SCENARIO_EDIT","SCENARIO_MODERATE"]},"example":"LANGUAGE_EDIT"},{"name":"scopeType","in":"query","description":"Scope of accreditations to retrieve","required":true,"schema":{"type":"string","enum":["GLOBAL","SCENARIO","LANGUAGE","LANGUAGE_FAMILY"]},"example":"SCENARIO"},{"name":"targetId","in":"query","description":"target identifier for scopeType","required":false,"schema":{"type":"string"},"example":12}],"responses":{"200":{"description":"Granted accreditations retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityAccreditationDto"}}}},"400":{"description":"Invalid scope or missing scenarioId","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Not allowed to list accreditations for this scope","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."},"post":{"tags":["Community"],"summary":"Grant an accreditation directly","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nGrants an accreditation directly to a user without going through a review request flow.\n\nAccess rules:\n- GLOBAL scope: admin only\n- SCENARIO scope: admin or scenario owner","operationId":"grantAccreditation","requestBody":{"description":"Direct accreditation grant payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantAccreditationBody"},"examples":{"Grant scenario accreditation":{"description":"Grant scenario accreditation","value":{"userId":15,"permissionType":"SCENARIO_MODERATE","scopeType":"SCENARIO","targetId":"12","note":"Trusted moderator for this scenario."}}}}},"required":true},"responses":{"200":{"description":"Accreditation granted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommunityAccreditationDto"}}}},"400":{"description":"Invalid scope, missing scenarioId, or unknown target user","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Not allowed to grant accreditation for this scope","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/community/accreditation-requests/{requestId}/review":{"post":{"tags":["Community"],"summary":"Review an accreditation request","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nApproves or rejects an existing accreditation request.\n\nAccess rules:\n- GLOBAL scope request: admin only\n- SCENARIO scope request: admin or scenario owner","operationId":"reviewAccreditationRequest","parameters":[{"name":"requestId","in":"path","description":"Identifier of the accreditation request to review","required":true,"schema":{"type":"integer","format":"int64"},"example":42}],"requestBody":{"description":"Review decision payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewAccreditationRequestBody"},"examples":{"Approve request":{"description":"Approve request","value":{"approved":true,"reviewNote":"Approved for scenario moderation."}}}}},"required":true},"responses":{"200":{"description":"Accreditation request reviewed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccreditationRequestDto"}}}},"400":{"description":"Unknown request or request already reviewed","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Not allowed to review this accreditation request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/languages/{id}":{"get":{"tags":["Language"],"summary":"Get a language by ID.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns a language object with its details based on the provided ID.","operationId":"getOne","parameters":[{"name":"id","in":"path","description":"ID of the language to retrieve","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Language retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Language"}}}},"404":{"description":"Language not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."},"put":{"tags":["Language"],"summary":"Update a language","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nUpdates editable language metadata.\n\nRequires either:\n- ROLE_ADMIN\n- or a LANGUAGE_EDIT accreditation matching:\n  - GLOBAL\n  - the specific language\n  - or the language family of the target language","operationId":"updateLanguage","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLanguageRequest"}}},"required":true},"responses":{"200":{"description":"Language updated successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Language"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"User is not allowed to edit this language","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Language not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/languages/{id}/permissions/me":{"get":{"tags":["Language"],"summary":"Get current user language permissions","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nReturns whether the authenticated user can edit the specified language.","operationId":"myPermissions","parameters":[{"name":"id","in":"path","description":"ID of the language to check permissions for","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Permissions resolved successfully","content":{"*/*":{"schema":{"$ref":"#/components/schemas/LanguagePermissionsDto"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Authenticated but not allowed for this operation"}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/languages/{id}/family":{"get":{"tags":["Language"],"summary":"List languages by family","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns a list of languages in a specific language family.","operationId":"getFamily","parameters":[{"name":"id","in":"path","description":"ID of the language family to retrieve languages for.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Languages retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LanguageRowDto"}}}},"404":{"description":"Family not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/languages":{"get":{"tags":["Language"],"summary":"Lists available languages.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns a paginated list of languages, including their details.","operationId":"list_1","parameters":[{"name":"q","in":"query","description":"Search query to filter languages","required":false,"schema":{"type":"string","default":"\"\""}},{"name":"page","in":"query","description":"Page number to retrieve.","required":true,"schema":{"type":"integer","minimum":0},"example":15},{"name":"size","in":"query","description":"Size of the page to retrieve; i.e. the number of languages to retrieve for a call.","required":true,"schema":{"type":"integer","default":50,"minimum":1},"example":25}],"responses":{"200":{"description":"List retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModel"}}}},"400":{"description":"Invalid page number or size","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/languages/{id}/scenarios":{"get":{"tags":["Language"],"summary":"Retrieves scenarios associated with a language.","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns a list of scenarios associated with a specific language.","operationId":"getOneScenarios","parameters":[{"name":"id","in":"path","description":"ID of the language to retrieve scenarios for","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Scenarios successfully retrieved.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Scenario"}}}}},"404":{"description":"Language not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/languages/options":{"get":{"tags":["Language"],"summary":"Search for a language","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns a paginated list of language options based on a search query.\nUseful when needing minimal information to see, especially when querying such as in search motors.","operationId":"options","parameters":[{"name":"q","in":"query","description":"The query to search for.","required":false,"schema":{"type":"string","default":"\"\""},"example":"fre"},{"name":"page","in":"query","description":"The page to return among all the results for the search.","required":false,"schema":{"type":"integer","default":0,"minimum":0}},{"name":"size","in":"query","description":"The number of language per page to return.","required":false,"schema":{"type":"integer","default":50,"minimum":1}}],"responses":{"200":{"description":"Language options retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedModel"}}}},"400":{"description":"Invalid pagination parameters","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/scenarios":{"get":{"tags":["Scenario"],"summary":"List all scenarios","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nRetrieves a list of all scenarios in the system, ordered by creation date (most recent first).","operationId":"listAll","responses":{"200":{"description":"Scenarios retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Scenario"}}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."},"post":{"tags":["Scenario"],"summary":"Create a new scenario","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nCreates a new scenario for the authenticated user.","operationId":"create","requestBody":{"description":"Scenario creation details including title, description, and language ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioRequest"}}},"required":true},"responses":{"201":{"description":"Scenario created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateScenarioResponse"},"examples":{"Created Scenario Response":{"description":"Created Scenario Response","value":{"id":42}}}}}},"400":{"description":"Invalid input : missing required fields, unknown language ID, or duplicate scenario","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"User not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Access denied : USER role required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Language not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflict : scenario with same title, language, and author already exists","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/scenarios/{id}":{"get":{"tags":["Scenario"],"summary":"Get a scenario by ID","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nRetrieves detailed information about a specific scenario including its thumbnails and metadata.","operationId":"getOne_1","parameters":[{"name":"id","in":"path","description":"ID of the scenario to retrieve","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Scenario retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scenario"}}}},"404":{"description":"Scenario not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."},"delete":{"tags":["Scenario"],"summary":"Delete a scenario","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: SCENARIO.\nOwnership parameter: id.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nDeletes a scenario and all its associated data (thumbnails, audios, etc.).","operationId":"delete_1","parameters":[{"name":"id","in":"path","description":"ID of the scenario to delete","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"Scenario deleted successfully"},"401":{"description":"User not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden : user is not the owner or admin","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Scenario not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"SCENARIO","x-owner-param":"id"}},"/api/scenarios/mine":{"get":{"tags":["Scenario"],"summary":"List my scenarios","description":"Access: Authenticated users with role USER only.\n\nRequires authentication. Required role: USER.\n\nReturns all scenarios authored by the current authenticated user, including drafts.","operationId":"listMine","responses":{"200":{"description":"User scenarios retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Scenario"}}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Authenticated but not allowed for this operation"}},"security":[{"bearerAuth":[]}],"x-access-level":"USER","x-authorization-rule":"Requires authentication. Required role: USER."}},"/api/scenarios/{id}/publish":{"post":{"tags":["Scenario"],"summary":"Publish a scenario","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: SCENARIO.\nOwnership parameter: id.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nPublishes a scenario and makes it visible to public users.\n\nIf the scenario was never published before, the publication timestamp is set.\nRequires scenario ownership or admin privileges.","operationId":"publish","parameters":[{"name":"id","in":"path","description":"ID of the scenario to publish","required":true,"schema":{"type":"integer","format":"int64"},"example":12}],"responses":{"200":{"description":"Scenario published successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scenario"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"User is not allowed to publish this scenario","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Scenario not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"SCENARIO","x-owner-param":"id"}},"/api/scenarios/{id}/metadata":{"patch":{"tags":["Scenario"],"summary":"Update scenario metadata","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: SCENARIO.\nOwnership parameter: id.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nUpdates editable scenario metadata such as title and description.\n\nRequires scenario ownership or admin privileges.","operationId":"updateMetadata","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScenarioMetadataRequest"}}},"required":true},"responses":{"200":{"description":"Scenario metadata updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scenario"}}}},"400":{"description":"Invalid metadata payload","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"User is not allowed to edit this scenario","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Scenario not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"SCENARIO","x-owner-param":"id"}},"/api/scenarios/{id}/storyboard":{"patch":{"tags":["Scenario"],"summary":"Update storyboard settings","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: SCENARIO.\nOwnership parameter: id.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nUpdates storyboard display settings for a scenario.\n\nEditable fields include:\n- layoutMode\n- preset\n- columns\n\nRequires scenario ownership or admin privileges.","operationId":"updateStoryboard","parameters":[{"name":"id","in":"path","description":"ID of the scenario to update","required":true,"schema":{"type":"integer","format":"int64"},"example":12}],"requestBody":{"description":"Storyboard settings to update","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScenarioStoryboardRequest"},"examples":{"Storyboard update":{"description":"Storyboard update","value":{"layoutMode":"PRESET","preset":"GRID_3","columns":3}}}}},"required":true},"responses":{"200":{"description":"Storyboard settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scenario"}}}},"400":{"description":"Invalid storyboard settings","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"User is not allowed to edit this scenario","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Scenario not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"SCENARIO","x-owner-param":"id"}},"/api/thumbnails/{id}/content":{"get":{"tags":["Thumbnail"],"summary":"Get thumbnail image content","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns the raw thumbnail content with the appropriate MIME type and cache headers.","operationId":"content_1","parameters":[{"name":"id","in":"path","description":"ID of the thumbnail to retrieve","required":true,"schema":{"type":"integer","format":"int64"},"example":1}],"responses":{"200":{"description":"Thumbnail content retrieved successfully","content":{"image/*":{"schema":{"type":"string"}}}},"404":{"description":"Thumbnail not found with the specified ID","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}},"/api/scenarios/{scenarioId}/thumbnails":{"get":{"tags":["Thumbnail"],"summary":"List thumbnails for a scenario","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns all thumbnails associated with a specific scenario, ordered by their index.","operationId":"list_2","parameters":[{"name":"scenarioId","in":"path","description":"ID of the scenario to retrieve thumbnails for.","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Thumbnails retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ThumbnailRowDto"}},"examples":{"Example Thumbnail List":{"description":"Example Thumbnail List","value":[{"id":1,"title":"Scene 1","idx":1},{"id":2,"title":"Scene 2","idx":2}]}}}}},"404":{"description":"Scenario not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."},"post":{"tags":["Thumbnail"],"summary":"Upload a thumbnail image","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: SCENARIO.\nOwnership parameter: scenarioId.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nUploads a thumbnail image for a scenario.\n\nRequest content type:\n - multipart/form-data","operationId":"upload_1","parameters":[{"name":"scenarioId","in":"path","description":"ID of the scenario to associate the thumbnail with","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"title","in":"query","description":"Optional title for the thumbnail","required":false,"schema":{"type":"string","default":""},"example":"Introduction Scene"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"binary","description":"Image file to upload (JPEG, PNG, etc.)"}},"required":["image"]}}}},"responses":{"201":{"description":"Thumbnail uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"},"examples":{"Upload Success Response":{"description":"Upload Success Response","value":{"id":42}}}}}},"400":{"description":"Invalid input : missing or empty image file","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"User not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden : user is not the scenario owner or admin","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Scenario not found with the specified ID","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"Unsupported media type : invalid image format","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error : I/O error during image processing","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"SCENARIO","x-owner-param":"scenarioId"}},"/api/thumbnails/{id}/layout":{"patch":{"tags":["Thumbnail"],"summary":"Update thumbnail layout","description":"Access: Resource owner or ADMIN only.\n\nProtected resource: THUMBNAIL.\nOwnership parameter: id.\n\nAccess: Public endpoint.\n\nPublic endpoint.\n\nUpdates the persisted layout position of a thumbnail inside a storyboard.\n\nEditable fields include:\n- gridColumn\n- gridRow\n- gridColumnSpan\n- gridRowSpan\n\nRequires scenario ownership or admin privileges.","operationId":"updateLayout","parameters":[{"name":"id","in":"path","description":"ID of the thumbnail to update","required":true,"schema":{"type":"integer","format":"int64"},"example":7}],"requestBody":{"description":"New layout values for the thumbnail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateThumbnailLayoutRequest"},"examples":{"Thumbnail layout update":{"description":"Thumbnail layout update","value":{"gridColumn":2,"gridRow":1,"gridColumnSpan":1,"gridRowSpan":2}}}}},"required":true},"responses":{"200":{"description":"Thumbnail layout updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThumbnailRowDto"}}}},"400":{"description":"Invalid layout request","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Authentication required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"User is not allowed to edit this thumbnail layout","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Thumbnail or parent scenario not found","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"OWNER_OR_ADMIN","x-authorization-rule":"Public endpoint.","x-owner-resource":"THUMBNAIL","x-owner-param":"id"}},"/api/users/me/profile":{"get":{"tags":["User"],"summary":"Get my profile","description":"Access: Authenticated users only.\n\nRequires authentication.\n\nReturns the complete private profile of the current authenticated user.","operationId":"myProfile","responses":{"200":{"description":"Profile retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"},"examples":{"User Profile Example":{"description":"User Profile Example","value":{"id":1,"username":"john_doe","email":"john@example.com","displayName":"John Doe","bio":"Linguist and researcher","institution":"University Example","researchInterests":"Phonetics, Syntax","profilePublic":true,"roles":["ROLE_USER"],"academyAffiliations":"Academy of Sciences"}}}}}},"401":{"description":"User not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Authenticated user not found in database","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"AUTHENTICATED","x-authorization-rule":"Requires authentication."},"put":{"tags":["User"],"summary":"Update my profile","description":"Access: Authenticated users only.\n\nRequires authentication.\n\nUpdates the private profile of the current authenticated user.","operationId":"updateMyProfile","requestBody":{"description":"Profile update details including display name, bio, institution, research interests, visibility, and affiliations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserProfileRequest"},"examples":{"Profile Update Example":{"description":"Profile Update Example","value":{"displayName":"Dr. John Doe","bio":"Senior linguist specializing in phonetics","institution":"MIT Linguistics Department","researchInterests":"Phonetics, Phonology, Syntax","profilePublic":true,"academyAffiliations":"International Phonetic Association"}}}}},"required":true},"responses":{"200":{"description":"Profile updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}},"400":{"description":"Invalid input : malformed request data","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"User not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Authenticated user not found in database","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[{"bearerAuth":[]}],"x-access-level":"AUTHENTICATED","x-authorization-rule":"Requires authentication."}},"/api/users/{id}/profile":{"get":{"tags":["User"],"summary":"Get public user profile","description":"Access: Public endpoint.\n\nPublic Endpoint, no authentication required.\n\nReturns the public profile of a user.","operationId":"publicProfile","parameters":[{"name":"id","in":"path","description":"ID of the user whose public profile needs to be retrieved","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Public profile retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicUserProfileResponse"},"examples":{"Public Profile Example":{"description":"Public Profile Example","value":{"id":5,"username":"jane_smith","displayName":"Dr. Jane Smith","bio":"Syntax researcher","institution":"Harvard University","researchInterests":"Syntax, Semantics","roles":["ROLE_USER"],"academyAffiliations":"Linguistic Society of America"}}}}}},"404":{"description":"User not found or profile is not public","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[],"x-access-level":"PUBLIC","x-authorization-rule":"Public Endpoint, no authentication required."}}},"components":{"schemas":{"UpdateUserProfileRequest":{"type":"object","description":"Request body for updating user profile.","properties":{"displayName":{"type":"string","description":"Display name of the user.","example":"StrangeUser"},"bio":{"type":"string","description":"Biography of the user."},"institution":{"type":"string","description":"Institution affiliation of the user."},"researchInterests":{"type":"string","description":"Research interests of the user."},"profilePublic":{"type":"boolean","description":"Whether the profile is public or not."},"academyAffiliations":{"type":"array","description":"Roles of the user.","examples":["ROLE_ADMIN","ROLE_USER"],"items":{"type":"string"},"uniqueItems":true}}},"UserProfileResponse":{"type":"object","description":"Response containing user profile information.","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"email":{"type":"string"},"displayName":{"type":"string"},"bio":{"type":"string"},"institution":{"type":"string"},"researchInterests":{"type":"string"},"profilePublic":{"type":"boolean"},"roles":{"type":"array","items":{"type":"string"},"uniqueItems":true},"academyAffiliations":{"type":"array","items":{"type":"string"},"uniqueItems":true}}},"ApiError":{"type":"object","description":"Standard API error response","properties":{"timestamp":{"type":"string","example":"2026-03-18T14:00:00Z"},"status":{"type":"integer","format":"int32","example":400},"error":{"type":"string","example":"Bad Request"},"message":{"type":"string","example":"username required"},"path":{"type":"string","example":"/api/auth/register"}}},"UpdateLanguageRequest":{"type":"object","properties":{"name":{"type":"string"},"level":{"type":"string"},"bookkeeping":{"type":"boolean"},"iso639P3code":{"type":"string"},"latitude":{"type":"number","format":"float"},"longitude":{"type":"number","format":"float"},"countryIds":{"type":"string"},"familyId":{"type":"string"},"parentId":{"type":"string"},"description":{"type":"string"},"markupDescription":{"type":"string"}}},"Language":{"type":"object","description":"Language information","properties":{"id":{"type":"string","description":"Language ID","example":"1234ung"},"name":{"type":"string","description":"Language name","example":"English"},"level":{"type":"string","description":"Language level","examples":["Dialect","Language","Family"]},"bookkeeping":{"type":"boolean","description":"Is book-kept. Indicates if a particular classification isn't accepted anymore because of reanalysing its classification."},"iso639P3code":{"type":"string","description":"ISO 639 code"},"latitude":{"type":"number","format":"float","description":"Latitude"},"longitude":{"type":"number","format":"float","description":"Longitude"},"countryIds":{"type":"string","description":"Country in which the language is/was present IDs"},"description":{"type":"string","description":"Description of the language"},"markupDescription":{"type":"string","description":"Markup description of the language"},"familyId":{"type":"string","description":"Description of the family to which the language belongs."},"familyName":{"type":"string","description":"Name of the family to which the language belongs."},"parentId":{"type":"string","description":"ID of the parent language/group/family."},"parentName":{"type":"string","description":"Name of the parent language/group/family."}}},"CreateAudioResponse":{"type":"object","description":"Response after successful audio file creation.","properties":{"id":{"type":"integer","format":"int64","description":"ID of the created audio file","example":42}}},"CreateScenarioRequest":{"type":"object","description":"Request body for creating a new scenario.","properties":{"title":{"type":"string","description":"Title of the new scenario.","example":"My new scenario"},"description":{"type":"string","description":"Description of the new scenario.","example":"This is a new scenario."},"languageId":{"type":"string","description":"ID of the language to use for the scenario.","example":"1234ung"}}},"CreateScenarioResponse":{"type":"object","description":"Response for creating a new scenario","properties":{"id":{"type":"integer","format":"int64","description":"ID of the created scenario","example":42}}},"UploadResponse":{"type":"object","description":"Response after successful thumbnail upload","properties":{"id":{"type":"integer","format":"int64","description":"ID of the uploaded thumbnail","example":42}}},"Scenario":{"type":"object","description":"Scenario information","properties":{"id":{"type":"integer","format":"int64","description":"Scenario ID (unique)."},"title":{"type":"string","description":"Scenario title.","example":"My scenario"},"description":{"type":"string","description":"Scenario description.","example":"This is a scenario."},"languageId":{"type":"string","description":"ID of the language used in the scenario.","example":"1234ung"},"authorUsername":{"type":"string","description":"Username of the author of the scenario."},"createdAt":{"type":"string","format":"date-time","description":"Creation date of the scenario."},"visibilityStatus":{"type":"string","description":"Publication status.","example":"DRAFT"},"publishedAt":{"type":"string","format":"date-time","description":"Publication timestamp."},"storyboardLayoutMode":{"type":"string","description":"Storyboard layout mode.","example":"PRESET"},"storyboardPreset":{"type":"string","description":"Storyboard preset.","example":"GRID_3"},"storyboardColumns":{"type":"integer","format":"int32","description":"Storyboard columns.","example":3}}},"CreateDiscussionMessageRequest":{"type":"object","properties":{"targetType":{"type":"string","enum":["LANGUAGE","AUDIO"]},"targetId":{"type":"string"},"parentMessageId":{"type":"integer","format":"int64"},"contributionType":{"type":"string","enum":["GENERAL","TRANSCRIPTION","TRANSLATION","GLOSS","INTERPRETATION"]},"content":{"type":"string"}}},"DiscussionMessageDto":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"targetType":{"type":"string","enum":["LANGUAGE","AUDIO"]},"targetId":{"type":"string"},"parentMessageId":{"type":"integer","format":"int64"},"authorId":{"type":"integer","format":"int64"},"authorUsername":{"type":"string"},"contributionType":{"type":"string","enum":["GENERAL","TRANSCRIPTION","TRANSLATION","GLOSS","INTERPRETATION"]},"content":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"GrantAccreditationBody":{"type":"object","properties":{"userId":{"type":"integer","format":"int64"},"permissionType":{"type":"string","enum":["COMMUNITY_REVIEW","LANGUAGE_EDIT","SCENARIO_EDIT","SCENARIO_MODERATE"]},"scopeType":{"type":"string","enum":["GLOBAL","SCENARIO","LANGUAGE","LANGUAGE_FAMILY"]},"targetId":{"type":"string"},"note":{"type":"string"}}},"CommunityAccreditationDto":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"userId":{"type":"integer","format":"int64"},"username":{"type":"string"},"permissionType":{"type":"string","enum":["COMMUNITY_REVIEW","LANGUAGE_EDIT","SCENARIO_EDIT","SCENARIO_MODERATE"]},"scopeType":{"type":"string","enum":["GLOBAL","SCENARIO","LANGUAGE","LANGUAGE_FAMILY"]},"targetId":{"type":"string"},"grantedByUserId":{"type":"integer","format":"int64"},"grantedAt":{"type":"string","format":"date-time"},"note":{"type":"string"}}},"CreateAccreditationRequestBody":{"type":"object","properties":{"permissionType":{"type":"string","enum":["COMMUNITY_REVIEW","LANGUAGE_EDIT","SCENARIO_EDIT","SCENARIO_MODERATE"]},"scopeType":{"type":"string","enum":["GLOBAL","SCENARIO","LANGUAGE","LANGUAGE_FAMILY"]},"targetId":{"type":"string"},"motivation":{"type":"string"}}},"AccreditationRequestDto":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"requestedByUserId":{"type":"integer","format":"int64"},"requestedByUsername":{"type":"string"},"permissionType":{"type":"string","enum":["COMMUNITY_REVIEW","LANGUAGE_EDIT","SCENARIO_EDIT","SCENARIO_MODERATE"]},"scopeType":{"type":"string","enum":["GLOBAL","SCENARIO","LANGUAGE","LANGUAGE_FAMILY"]},"targetId":{"type":"string"},"motivation":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"reviewedByUserId":{"type":"integer","format":"int64"},"reviewNote":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"reviewedAt":{"type":"string","format":"date-time"}}},"ReviewAccreditationRequestBody":{"type":"object","properties":{"approved":{"type":"boolean"},"reviewNote":{"type":"string"}}},"RegisterRequest":{"type":"object","description":"Request body for user registration.","properties":{"username":{"type":"string","description":"Username of the new user."},"email":{"type":"string","description":"Email address of the new user.","example":"name@address.domain"},"password":{"type":"string","description":"Password of the new user."}}},"RegisterResponse":{"type":"object","description":"Response after user registration.","properties":{"id":{"type":"integer","format":"int64","description":"User ID (unique)."},"username":{"type":"string","description":"Username of the registered user."}}},"LoginResponse":{"type":"object","description":"Response after user login.","properties":{"accessToken":{"type":"string","description":"JWT access token."},"expiresInSeconds":{"type":"integer","format":"int64","description":"JWT expiration time in seconds."}}},"LoginRequest":{"type":"object","description":"Request body for user login.","properties":{"username":{"type":"string","description":"Username of the user."},"password":{"type":"string","description":"Password of the user."}}},"UpdateThumbnailLayoutRequest":{"type":"object","description":"Thumbnail storyboard placement update payload","properties":{"gridColumn":{"type":"integer","format":"int32","description":"Grid column start"},"gridRow":{"type":"integer","format":"int32","description":"Grid row start"},"gridColumnSpan":{"type":"integer","format":"int32","description":"Grid column span"},"gridRowSpan":{"type":"integer","format":"int32","description":"Grid row span"}}},"ThumbnailRowDto":{"type":"object","description":"Thumbnail row information with storyboard metadata","properties":{"id":{"type":"integer","format":"int64","description":"Unique thumbnail identifier"},"title":{"type":"string","description":"Title of the thumbnail","example":"Introduction Scene"},"idx":{"type":"integer","format":"int32","description":"Index/order of the thumbnail in the scenario","example":1},"gridColumn":{"type":"integer","format":"int32","description":"Storyboard column start"},"gridRow":{"type":"integer","format":"int32","description":"Storyboard row start"},"gridColumnSpan":{"type":"integer","format":"int32","description":"Storyboard column span"},"gridRowSpan":{"type":"integer","format":"int32","description":"Storyboard row span"},"imageWidth":{"type":"integer","format":"int32","description":"Stored image width"},"imageHeight":{"type":"integer","format":"int32","description":"Stored image height"}}},"UpdateScenarioStoryboardRequest":{"type":"object","description":"Scenario storyboard settings update payload","properties":{"layoutMode":{"type":"string","description":"Storyboard layout mode","example":"PRESET"},"preset":{"type":"string","description":"Storyboard preset key","example":"GRID_3"},"columns":{"type":"integer","format":"int32","description":"Storyboard column count","example":3}}},"UpdateScenarioMetadataRequest":{"type":"object","description":"Editable metadata for a scenario.","properties":{"title":{"type":"string","description":"Scenario title.","example":"My revised scenario"},"description":{"type":"string","description":"Scenario description.","example":"Updated description for the scenario."}}},"UpdateMarkerRequest":{"type":"object","description":"Request body for updating marker position.","properties":{"markerX":{"type":"number","format":"double","default":"null","description":"the x-coordinate of the marker (null if not set)","maximum":100,"minimum":0},"markerY":{"type":"number","format":"double","default":"null","description":"the y-coordinate of the marker (null if not set)","maximum":100,"minimum":0},"markerLabel":{"type":"string","default":"\"\"","description":"the label for the marker (can be null or blank if not set)"}}},"UpdateUserRolesRequest":{"type":"object","description":"Payload to replace a user's granted roles.","properties":{"roles":{"type":"array","description":"Role names to assign to the user.","items":{"type":"string"},"uniqueItems":true}}},"AdminUserRow":{"type":"object","description":"User row for administration listing.","properties":{"id":{"type":"integer","format":"int64","description":"User ID.","example":5},"username":{"type":"string","description":"Username.","example":"alice"},"email":{"type":"string","description":"Email.","example":"alice@example.com"},"displayName":{"type":"string","description":"Display name.","example":"Alice Martin"},"roles":{"type":"array","description":"Granted roles.","items":{"type":"string"},"uniqueItems":true},"profilePublic":{"type":"boolean","description":"Public profile flag.","example":true}}},"UpdateScenarioVisibilityRequest":{"type":"object","description":"Payload to update scenario visibility.","properties":{"visibilityStatus":{"type":"string","description":"Target visibility status.","example":"PUBLISHED"}}},"PublicUserProfileResponse":{"type":"object","description":"Response containing public user profile information.","properties":{"id":{"type":"integer","format":"int64","description":"User ID (unique)."},"username":{"type":"string","description":"Username of the user."},"displayName":{"type":"string","description":"Display name of the user, can be different from the username and doesn't serve as id."},"bio":{"type":"string","description":"Self-written biography of the user.","example":"I obtained my masters' degree in Université de Montréal"},"institution":{"type":"string","description":"Institutions where the user is affiliated.","example":"University of Montréal, MIT, Oxford University"},"researchInterests":{"type":"string","description":"Research interests of the user.","example":"Diachronical phonology in Mayan languages."},"roles":{"type":"array","description":"Roles of the user.","examples":["ROLE_ADMIN","ROLE_USER"],"items":{"type":"string"},"uniqueItems":true},"academyAffiliations":{"type":"array","description":"Academy affiliations of the user.","examples":["Member of Team Chuj","Writer at revue of Languages"],"items":{"type":"string"},"uniqueItems":true}}},"AudioRow":{"type":"object","description":"Audio file Metadata.","properties":{"id":{"type":"integer","format":"int64","description":"Audio file ID (unique)."},"title":{"type":"string","default":"\"\"","description":"Audio file title.","example":"My audio file"},"idx":{"type":"integer","format":"int32","description":"Index of the audio file among all audios associated to the corresponding thumbnail.","example":5,"minimum":1},"mime":{"type":"string","description":"MIME type of the audio file.","example":"audio/mpeg"},"markerX":{"type":"number","format":"double","description":"X axis of the audio marker.","example":15.8},"markerY":{"type":"number","format":"double","description":"Y axis of the audio marker.","example":12.3},"markerLabel":{"type":"string","description":"Label of the audio marker.","example":"Person number 1"}}},"PageMetadata":{"type":"object","properties":{"size":{"type":"integer","format":"int64"},"number":{"type":"integer","format":"int64"},"totalElements":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int64"}}},"PagedModel":{"type":"object","properties":{"content":{"type":"array","items":{}},"page":{"$ref":"#/components/schemas/PageMetadata"}}},"LanguagePermissionsDto":{"type":"object","properties":{"canEdit":{"type":"boolean"}}},"LanguageRowDto":{"type":"object","description":"DTO for a single language row or description.","properties":{"id":{"type":"string","description":"Language ID","example":"1234ung"},"name":{"type":"string","description":"Language name","example":"English"},"level":{"type":"string","description":"Language level","examples":["Dialect","Language","Family"]},"family":{"type":"string","description":"Name of the family to which the language belongs."},"parent":{"type":"string","description":"Name of the parent language/group/family."}}},"MeResponse":{"type":"object","description":"Response containing user self information.","properties":{"id":{"type":"integer","format":"int64","description":"User ID (unique)."},"username":{"type":"string","description":"Username of the user."},"roles":{"type":"array","description":"Roles of the user.","examples":["ROLE_USER","ROLE_ADMIN"],"items":{"type":"string"}}}},"AdminOverview":{"type":"object","description":"Global administration overview counters.","properties":{"userCount":{"type":"integer","format":"int64","description":"Total number of users.","example":12},"scenarioCount":{"type":"integer","format":"int64","description":"Total number of scenarios.","example":37},"publishedScenarioCount":{"type":"integer","format":"int64","description":"Number of published scenarios.","example":18},"draftScenarioCount":{"type":"integer","format":"int64","description":"Number of draft/private scenarios.","example":19}}}},"securitySchemes":{"bearerAuth":{"type":"http","description":"JWT Bearer authentication.\n\nHow to obtain a token:\n  - Call POST /api/auth/login with username and password\n  - Copy the token from the response body\n\nHow to use it:\n  - Send header: Authorization: Bearer <token>\n\nNotes:\n  - Tokens currently expire after 1 hour\n  - Roles are stored in the JWT 'roles' claim\n","name":"Authorization","scheme":"bearer","bearerFormat":"JWT"}}}}