openapi: 3.0.3 info: title: 'Laravel API Documentation' description: 'Mintfit V2 LMS - A modular monolith learning management system with microservice-style boundaries.' version: 1.0.0 servers: - url: 'http://localhost' tags: - name: 'Ad Management' description: '' - name: 'Ad Tracking' description: '' - name: 'Anonymous Sessions' description: '' - name: 'Attempt Management' description: '' - name: 'Block Management' description: '' - name: 'Content Hierarchy' description: '' - name: 'Content History' description: '' - name: 'Content Management' description: '' - name: 'Content Progress' description: '' - name: 'Content Tagging' description: '' - name: Endpoints description: '' - name: Evaluation description: '' - name: 'Global User Management' description: '' - name: Health description: '' - name: Identity description: '' - name: 'Media Management' description: '' - name: MoodleImport description: '' - name: 'Notification Management' description: '' - name: 'Notification Preferences' description: '' - name: 'Pdf Management' description: '' - name: 'Placement Management' description: '' - name: Public description: '' - name: 'Question History' description: '' - name: 'Question Management' description: '' - name: 'Question Tagging' description: '' - name: 'RBAC Management' description: '' - name: Statistics description: '' - name: 'Tag Contexts' description: '' - name: 'Tag Management' description: '' - name: 'Tenant Management' description: '' - name: 'Transfer - Import/Export' description: '' - name: 'User Management' description: '' - name: Verification description: '' paths: /v1/ads: post: summary: 'Create a new ad' operationId: createANewAd description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] '/v1/ads/{id}': put: summary: 'Update ad by ID' operationId: updateAdByID description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] delete: summary: 'Delete ad by ID (soft delete)' operationId: deleteAdByIDsoftDelete description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] parameters: - in: path name: id description: 'The ID of the ad.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/ads/{id}/activate': post: summary: 'Activate an ad' operationId: activateAnAd description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] parameters: - in: path name: id description: 'The ID of the ad.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/ads/{id}/pause': post: summary: 'Pause an ad' operationId: pauseAnAd description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] parameters: - in: path name: id description: 'The ID of the ad.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/ads/{adId}/placements/{placementId}': post: summary: 'Assign ad to placement' operationId: assignAdToPlacement description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] delete: summary: 'Unassign ad from placement' operationId: unassignAdFromPlacement description: '' parameters: [] responses: { } tags: - 'Ad Management' security: [] parameters: - in: path name: adId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: placementId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/ads/track/impression: post: summary: "Track an ad impression (public endpoint)\n\nThis endpoint is called when an ad is displayed to a user.\nThe tracking is processed asynchronously via a queued job." operationId: trackAnAdImpressionpublicEndpointThisEndpointIsCalledWhenAnAdIsDisplayedToAUserTheTrackingIsProcessedAsynchronouslyViaAQueuedJob description: '' parameters: [] responses: { } tags: - 'Ad Tracking' requestBody: required: true content: application/json: schema: type: object properties: ad_id: type: string description: 'The id of an existing record in the ads table.' example: architecto placement_id: type: string description: 'This field is required when placement_key is not present. The id of an existing record in the placements table.' example: architecto nullable: true placement_key: type: string description: 'This field is required when placement_id is not present.' example: architecto nullable: true frontend: type: string description: 'This field is required when placement_key is present.' example: architecto nullable: true tenant_id: type: string description: 'The id of an existing record in the tenants table.' example: architecto nullable: true required: - ad_id security: [] /v1/ads/track/click: post: summary: "Track an ad click (public endpoint)\n\nThis endpoint is called when a user clicks on an ad.\nThe tracking is processed asynchronously via a queued job." operationId: trackAnAdClickpublicEndpointThisEndpointIsCalledWhenAUserClicksOnAnAdTheTrackingIsProcessedAsynchronouslyViaAQueuedJob description: '' parameters: [] responses: { } tags: - 'Ad Tracking' requestBody: required: true content: application/json: schema: type: object properties: ad_id: type: string description: 'The id of an existing record in the ads table.' example: architecto placement_id: type: string description: 'This field is required when placement_key is not present. The id of an existing record in the placements table.' example: architecto nullable: true placement_key: type: string description: 'This field is required when placement_id is not present.' example: architecto nullable: true frontend: type: string description: 'This field is required when placement_key is present.' example: architecto nullable: true tenant_id: type: string description: 'The id of an existing record in the tenants table.' example: architecto nullable: true required: - ad_id security: [] /v1/anonymous-sessions: get: summary: "List anonymous sessions\n\nReturns a paginated list of anonymous sessions with optional filtering.\nRequires `anonymous_sessions.read` permission." operationId: listAnonymousSessionsReturnsAPaginatedListOfAnonymousSessionsWithOptionalFilteringRequiresanonymousSessionsreadPermission description: '' parameters: - in: query name: per_page description: 'Items per page (1-100).' example: 10 required: false schema: type: integer description: 'Items per page (1-100).' example: 10 - in: query name: page description: 'Page number.' example: 1 required: false schema: type: integer description: 'Page number.' example: 1 - in: query name: tenant_id description: 'Filter by tenant ID.' example: 01kav3fbpbpb3wnhtadsmrmt7m required: false schema: type: string description: 'Filter by tenant ID.' example: 01kav3fbpbpb3wnhtadsmrmt7m - in: query name: anonymous_id description: 'Filter by anonymous ID.' example: 01kav3fcmme10dwbeexyqkfndn required: false schema: type: string description: 'Filter by anonymous ID.' example: 01kav3fcmme10dwbeexyqkfndn - in: query name: search description: 'Search in IP address and user agent.' example: '192.168' required: false schema: type: string description: 'Search in IP address and user agent.' example: '192.168' - in: query name: logged_in_at_gte description: 'date Filter sessions after this date.' example: '2026-01-01' required: false schema: type: string description: 'date Filter sessions after this date.' example: '2026-01-01' - in: query name: logged_in_at_lte description: 'date Filter sessions before this date.' example: '2026-12-31' required: false schema: type: string description: 'date Filter sessions before this date.' example: '2026-12-31' - in: query name: is_upgraded description: 'Filter by upgrade status.' example: true required: false schema: type: boolean description: 'Filter by upgrade status.' example: true - in: query name: sort description: 'Sort field (prefix with - for desc).' example: '-logged_in_at' required: false schema: type: string description: 'Sort field (prefix with - for desc).' example: '-logged_in_at' responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Anonymous sessions retrieved successfully' data: data: [] total: 0 per_page: 10 current_page: 1 last_page: 1 properties: success: type: boolean example: true message: type: string example: 'Anonymous sessions retrieved successfully' data: type: object properties: data: type: array example: [] total: type: integer example: 0 per_page: type: integer example: 10 current_page: type: integer example: 1 last_page: type: integer example: 1 tags: - 'Anonymous Sessions' '/v1/anonymous-sessions/{id}': get: summary: "Get anonymous session\n\nReturns a single anonymous session by ID.\nRequires `anonymous_sessions.read` permission." operationId: getAnonymousSessionReturnsASingleAnonymousSessionByIDRequiresanonymousSessionsreadPermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Anonymous session retrieved successfully' data: id: 01kav3fcmme10dwbeexyqkfndn tenant_id: 01kav3fbpbpb3wnhtadsmrmt7m anonymous_id: 01kav3fcmme10dwbeexyqkfndn ip_address: 127.0.0.1 user_agent: Mozilla/5.0 logged_in_at: '2026-03-04T00:00:00+00:00' last_refreshed_at: null refresh_count: 0 upgraded_to_identity_id: null upgraded_at: null expires_at: '2026-04-03T00:00:00+00:00' properties: success: type: boolean example: true message: type: string example: 'Anonymous session retrieved successfully' data: type: object properties: id: type: string example: 01kav3fcmme10dwbeexyqkfndn tenant_id: type: string example: 01kav3fbpbpb3wnhtadsmrmt7m anonymous_id: type: string example: 01kav3fcmme10dwbeexyqkfndn ip_address: type: string example: 127.0.0.1 user_agent: type: string example: Mozilla/5.0 logged_in_at: type: string example: '2026-03-04T00:00:00+00:00' last_refreshed_at: type: string example: null refresh_count: type: integer example: 0 upgraded_to_identity_id: type: string example: null upgraded_at: type: string example: null expires_at: type: string example: '2026-04-03T00:00:00+00:00' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Anonymous session not found' properties: success: type: boolean example: false message: type: string example: 'Anonymous session not found' tags: - 'Anonymous Sessions' parameters: - in: path name: id description: 'The anonymous session ID.' example: 01kav3fcmme10dwbeexyqkfndn required: true schema: type: string /v1/attempts/start: post: summary: 'Start a new attempt' operationId: startANewAttempt description: "Creates a new attempt for an attemptable entity (e.g., quiz_version).\nThe attempt is created in 'in_progress' state.\nThe identity_id is automatically set from the authenticated user." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true message: 'Attempt started successfully' data: id: 01JGATTEMPT000000000001 identity_id: 01JGUSER00000000000001 attemptable_type: quiz_version attemptable_id: 01JGQUIZVERSION0000001 status: in_progress started_at: '2024-01-15T10:30:00Z' submitted_at: null abandoned_at: null total_score: null max_possible_score: null metadata: { } slot_history: [] responses: [] properties: success: type: boolean example: true message: type: string example: 'Attempt started successfully' data: type: object properties: id: type: string example: 01JGATTEMPT000000000001 identity_id: type: string example: 01JGUSER00000000000001 attemptable_type: type: string example: quiz_version attemptable_id: type: string example: 01JGQUIZVERSION0000001 status: type: string example: in_progress started_at: type: string example: '2024-01-15T10:30:00Z' submitted_at: type: string example: null abandoned_at: type: string example: null total_score: type: string example: null max_possible_score: type: string example: null metadata: type: object properties: { } slot_history: type: array example: [] responses: type: array example: [] tags: - 'Attempt Management' requestBody: required: true content: application/json: schema: type: object properties: attemptable_type: type: string description: 'The type of attemptable entity.' example: quiz_version attemptable_id: type: string description: 'The attemptable entity ID (ULID).' example: 01JGQUIZVERSION0000001 metadata: type: object description: 'Optional metadata for the attempt.' example: [] properties: { } required: - attemptable_type - attemptable_id security: [] /v1/attempts/for-attemptable: get: summary: 'Get attempts for attemptable' operationId: getAttemptsForAttemptable description: "Retrieves paginated attempts for a specific identity and attemptable entity.\nUsers can only view their own attempts unless they have the 'attempts.read_all' permission." parameters: - in: query name: identity_id description: 'The identity ID.' example: 01JGUSER00000000000001 required: true schema: type: string description: 'The identity ID.' example: 01JGUSER00000000000001 - in: query name: attemptable_type description: 'The attemptable type.' example: quiz_version required: true schema: type: string description: 'The attemptable type.' example: quiz_version - in: query name: attemptable_id description: 'The attemptable ID.' example: 01JGQUIZVERSION0000001 required: true schema: type: string description: 'The attemptable ID.' example: 01JGQUIZVERSION0000001 - in: query name: per_page description: 'Optional items per page.' example: 10 required: false schema: type: integer description: 'Optional items per page.' example: 10 - in: query name: page description: 'Optional page number.' example: 1 required: false schema: type: integer description: 'Optional page number.' example: 1 responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Attempts retrieved successfully' data: current_page: 1 data: [] total: 0 properties: success: type: boolean example: true message: type: string example: 'Attempts retrieved successfully' data: type: object properties: current_page: type: integer example: 1 data: type: array example: [] total: type: integer example: 0 tags: - 'Attempt Management' requestBody: required: true content: application/json: schema: type: object properties: identity_id: type: string description: 'Must be 26 characters.' example: bngzmiyvdljnikhwaykcmyuwpw nullable: true attemptable_type: type: string description: '' example: architecto attemptable_id: type: string description: 'Must be 26 characters.' example: ngzmiyvdljnikhwaykcmyuwpwl per_page: type: integer description: 'Must be at least 1. Must not be greater than 100.' example: 20 nullable: true page: type: integer description: 'Must be at least 1.' example: 43 nullable: true required: - attemptable_type - attemptable_id security: [] /v1/attempts/record-answer: post: summary: 'Record an answer' operationId: recordAnAnswer description: "Records or updates a user's answer to a question.\nSupports optimistic locking via current_version parameter." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Answer recorded successfully' data: id: 01JGRESPONSE0000000001 attempt_id: 01JGATTEMPT000000000001 slot_option_id: 01JGQUESTION00000000001 answer_snapshot: selected_option_id: b submission_state: answered points_awarded: null is_correct: null response_version: 1 first_responded_at: '2024-01-15T10:31:00Z' last_responded_at: '2024-01-15T10:31:00Z' time_spent_seconds: 45 metadata: null properties: success: type: boolean example: true message: type: string example: 'Answer recorded successfully' data: type: object properties: id: type: string example: 01JGRESPONSE0000000001 attempt_id: type: string example: 01JGATTEMPT000000000001 slot_option_id: type: string example: 01JGQUESTION00000000001 answer_snapshot: type: object properties: selected_option_id: type: string example: b submission_state: type: string example: answered points_awarded: type: string example: null is_correct: type: string example: null response_version: type: integer example: 1 first_responded_at: type: string example: '2024-01-15T10:31:00Z' last_responded_at: type: string example: '2024-01-15T10:31:00Z' time_spent_seconds: type: integer example: 45 metadata: type: string example: null tags: - 'Attempt Management' requestBody: required: true content: application/json: schema: type: object properties: attempt_id: type: string description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 slot_option_id: type: string description: 'The slot option ID (ULID).' example: 01JGQUESTION00000000001 answer_data: type: object description: 'The answer data.' example: selected_option_id: b properties: { } time_spent_seconds: type: integer description: 'Optional time spent on question.' example: 16 current_version: type: integer description: 'Optional current response version for optimistic locking.' example: 16 metadata: type: object description: 'Optional response metadata.' example: [] properties: { } required: - attempt_id - slot_option_id - answer_data security: [] /v1/attempts/record-slot-display: post: summary: 'Record slot display' operationId: recordSlotDisplay description: "Records that a slot/question was displayed to the user.\nUsed for navigation tracking and scheduler context." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Slot display recorded successfully' data: id: 01JGHISTORY00000000001 attempt_id: 01JGATTEMPT000000000001 slot_id: 01JGSLOT000000000000001 slot_option_id: 01JGQUESTION00000000001 displayed_at: '2024-01-15T10:30:30Z' display_sequence: 1 navigation_method: sequential metadata: null properties: success: type: boolean example: true message: type: string example: 'Slot display recorded successfully' data: type: object properties: id: type: string example: 01JGHISTORY00000000001 attempt_id: type: string example: 01JGATTEMPT000000000001 slot_id: type: string example: 01JGSLOT000000000000001 slot_option_id: type: string example: 01JGQUESTION00000000001 displayed_at: type: string example: '2024-01-15T10:30:30Z' display_sequence: type: integer example: 1 navigation_method: type: string example: sequential metadata: type: string example: null tags: - 'Attempt Management' requestBody: required: true content: application/json: schema: type: object properties: attempt_id: type: string description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 slot_id: type: string description: 'The slot ID (ULID).' example: 01JGSLOT000000000000001 slot_option_id: type: string description: 'Optional slot option ID if slot contains a question.' example: architecto navigation_method: type: string description: 'Optional navigation method.' example: sequential metadata: type: object description: 'Optional display metadata.' example: [] properties: { } required: - attempt_id - slot_id security: [] '/v1/attempts/{id}': get: summary: 'Get attempt by ID' operationId: getAttemptByID description: 'Retrieves complete attempt details including all responses and slot history.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Attempt retrieved successfully' data: id: 01JGATTEMPT000000000001 identity_id: 01JGUSER00000000000001 attemptable_type: quiz_version attemptable_id: 01JGQUIZVERSION0000001 status: in_progress started_at: '2024-01-15T10:30:00Z' submitted_at: null abandoned_at: null total_score: null max_possible_score: null metadata: { } slot_history: [] responses: [] properties: success: type: boolean example: true message: type: string example: 'Attempt retrieved successfully' data: type: object properties: id: type: string example: 01JGATTEMPT000000000001 identity_id: type: string example: 01JGUSER00000000000001 attemptable_type: type: string example: quiz_version attemptable_id: type: string example: 01JGQUIZVERSION0000001 status: type: string example: in_progress started_at: type: string example: '2024-01-15T10:30:00Z' submitted_at: type: string example: null abandoned_at: type: string example: null total_score: type: string example: null max_possible_score: type: string example: null metadata: type: object properties: { } slot_history: type: array example: [] responses: type: array example: [] tags: - 'Attempt Management' security: [] delete: summary: '' operationId: deleteV1AttemptsId description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 required: true schema: type: string '/v1/attempts/{id}/submit': post: summary: 'Submit attempt' operationId: submitAttempt description: 'Submits the attempt for grading. Makes the attempt immutable.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Attempt submitted successfully' data: id: 01JGATTEMPT000000000001 status: submitted submitted_at: '2024-01-15T10:45:00Z' properties: success: type: boolean example: true message: type: string example: 'Attempt submitted successfully' data: type: object properties: id: type: string example: 01JGATTEMPT000000000001 status: type: string example: submitted submitted_at: type: string example: '2024-01-15T10:45:00Z' tags: - 'Attempt Management' security: [] parameters: - in: path name: id description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 required: true schema: type: string '/v1/attempts/{id}/abandon': post: summary: 'Abandon attempt' operationId: abandonAttempt description: 'Abandons the attempt. Makes the attempt immutable.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Attempt abandoned successfully' data: id: 01JGATTEMPT000000000001 status: abandoned abandoned_at: '2024-01-15T10:45:00Z' properties: success: type: boolean example: true message: type: string example: 'Attempt abandoned successfully' data: type: object properties: id: type: string example: 01JGATTEMPT000000000001 status: type: string example: abandoned abandoned_at: type: string example: '2024-01-15T10:45:00Z' tags: - 'Attempt Management' security: [] parameters: - in: path name: id description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 required: true schema: type: string '/v1/attempts/{id}/scheduler-context': get: summary: 'Get scheduler runtime context' operationId: getSchedulerRuntimeContext description: 'Returns context data needed by Quiz scheduler drivers.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Scheduler context retrieved successfully' data: used_question_ids: - 01JGQUESTION00000000001 current_score: 0 max_score: 0 answered_count: 1 skipped_count: 0 elapsed_time_seconds: 120 metadata: { } properties: success: type: boolean example: true message: type: string example: 'Scheduler context retrieved successfully' data: type: object properties: used_question_ids: type: array example: - 01JGQUESTION00000000001 items: type: string current_score: type: integer example: 0 max_score: type: integer example: 0 answered_count: type: integer example: 1 skipped_count: type: integer example: 0 elapsed_time_seconds: type: integer example: 120 metadata: type: object properties: { } tags: - 'Attempt Management' security: [] parameters: - in: path name: id description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 required: true schema: type: string '/v1/attempts/{attemptId}/skip/{slotOptionId}': post: summary: 'Skip a question' operationId: skipAQuestion description: 'Marks a question as explicitly skipped.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Question skipped successfully' data: id: 01JGRESPONSE0000000001 attempt_id: 01JGATTEMPT000000000001 slot_option_id: 01JGQUESTION00000000001 answer_snapshot: { } submission_state: skipped points_awarded: null is_correct: null response_version: 1 first_responded_at: '2024-01-15T10:31:00Z' last_responded_at: '2024-01-15T10:31:00Z' time_spent_seconds: null metadata: null properties: success: type: boolean example: true message: type: string example: 'Question skipped successfully' data: type: object properties: id: type: string example: 01JGRESPONSE0000000001 attempt_id: type: string example: 01JGATTEMPT000000000001 slot_option_id: type: string example: 01JGQUESTION00000000001 answer_snapshot: type: object properties: { } submission_state: type: string example: skipped points_awarded: type: string example: null is_correct: type: string example: null response_version: type: integer example: 1 first_responded_at: type: string example: '2024-01-15T10:31:00Z' last_responded_at: type: string example: '2024-01-15T10:31:00Z' time_spent_seconds: type: string example: null metadata: type: string example: null tags: - 'Attempt Management' security: [] parameters: - in: path name: attemptId description: 'The attempt ID (ULID).' example: 01JGATTEMPT000000000001 required: true schema: type: string - in: path name: slotOptionId description: 'The slot option ID (ULID).' example: 01JGQUESTION00000000001 required: true schema: type: string /v1/blocks/schemas: get: summary: "Get all block schemas\n\nReturns JSON schemas for all supported block types.\nSchemas define the structure and validation rules for each block type.\nPublic endpoint - no authentication required." operationId: getAllBlockSchemasReturnsJSONSchemasForAllSupportedBlockTypesSchemasDefineTheStructureAndValidationRulesForEachBlockTypePublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Block schemas retrieved successfully' data: - type: paragraph schema: type: object properties: content: type: string - type: headline schema: type: object properties: content: type: string level: type: integer minimum: 1 maximum: 6 properties: success: type: boolean example: true message: type: string example: 'Block schemas retrieved successfully' data: type: array example: - type: paragraph schema: type: object properties: content: type: string - type: headline schema: type: object properties: content: type: string level: type: integer minimum: 1 maximum: 6 items: type: object properties: type: type: string example: paragraph schema: type: object properties: type: type: string example: object properties: type: object properties: content: type: object properties: type: type: string example: string tags: - 'Block Management' security: [] '/v1/blocks/schemas/{type}': get: summary: "Get schema for specific block type\n\nReturns the JSON schema for a specific block type.\nSupported types: paragraph, headline, image, table.\nPublic endpoint - no authentication required." operationId: getSchemaForSpecificBlockTypeReturnsTheJSONSchemaForASpecificBlockTypeSupportedTypesParagraphHeadlineImageTablePublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Schema retrieved successfully for type: paragraph' data: type: paragraph schema: type: object properties: content: type: string required: - content properties: success: type: boolean example: true message: type: string example: 'Schema retrieved successfully for type: paragraph' data: type: object properties: type: type: string example: paragraph schema: type: object properties: type: type: string example: object properties: type: object properties: content: type: object properties: type: type: string example: string required: type: array example: - content items: type: string 404: description: '' content: application/json: schema: type: object example: success: false message: 'Block type not found: unknown' properties: success: type: boolean example: false message: type: string example: 'Block type not found: unknown' tags: - 'Block Management' security: [] parameters: - in: path name: type description: 'Block type.' example: paragraph required: true schema: type: string /v1/blocks/validate: post: summary: "Validate array of blocks\n\nValidates an array of blocks against their JSON schemas.\nOptionally restrict validation to specific block types.\nReturns validation result with detailed error messages if validation fails.\nPublic endpoint - no authentication required." operationId: validateArrayOfBlocksValidatesAnArrayOfBlocksAgainstTheirJSONSchemasOptionallyRestrictValidationToSpecificBlockTypesReturnsValidationResultWithDetailedErrorMessagesIfValidationFailsPublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'All blocks are valid' data: is_valid: true errors: [] properties: success: type: boolean example: true message: type: string example: 'All blocks are valid' data: type: object properties: is_valid: type: boolean example: true errors: type: array example: [] 400: description: '' content: application/json: schema: type: object example: success: false message: 'Validation failed' errors: is_valid: false errors: - block_index: 0 message: "Block type 'unknown' is not supported" - block_index: 1 field: content message: 'The content field is required' properties: success: type: boolean example: false message: type: string example: 'Validation failed' errors: type: object properties: is_valid: type: boolean example: false errors: type: array example: - block_index: 0 message: "Block type 'unknown' is not supported" - block_index: 1 field: content message: 'The content field is required' items: type: object properties: block_index: type: integer example: 0 message: type: string example: "Block type 'unknown' is not supported" tags: - 'Block Management' requestBody: required: true content: application/json: schema: type: object properties: blocks: type: array description: 'Array of block objects to validate.' example: - type: paragraph content: Hello items: type: string allowed_types: type: array description: 'Optional array of allowed block types. If provided, blocks with other types will fail validation.' example: - paragraph - headline items: type: string required: - blocks security: [] /v1/blocks/transform/html: post: summary: "Transform blocks to HTML\n\nTransforms an array of structured blocks into HTML markup.\nSupports paragraph, headline, image, and table block types.\nPreserves block structure and formatting.\nPublic endpoint - no authentication required." operationId: transformBlocksToHTMLTransformsAnArrayOfStructuredBlocksIntoHTMLMarkupSupportsParagraphHeadlineImageAndTableBlockTypesPreservesBlockStructureAndFormattingPublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Blocks transformed to HTML successfully' data: html: '

Hello world

' properties: success: type: boolean example: true message: type: string example: 'Blocks transformed to HTML successfully' data: type: object properties: html: type: string example: '

Hello world

' 400: description: '' content: application/json: schema: type: object example: success: false message: 'No blocks provided' properties: success: type: boolean example: false message: type: string example: 'No blocks provided' tags: - 'Block Management' requestBody: required: true content: application/json: schema: type: object properties: blocks: type: array description: 'Array of block objects to transform.' example: - type: paragraph content: 'Hello world' items: type: string language: type: string description: 'Optional language code for HTML lang attribute.' example: en required: - blocks security: [] /v1/blocks/transform/markdown: post: summary: "Transform blocks to Markdown\n\nTransforms an array of structured blocks into Markdown format.\nSupports paragraph, headline, image, and table block types.\nPreserves block structure and converts formatting to Markdown syntax.\nPublic endpoint - no authentication required." operationId: transformBlocksToMarkdownTransformsAnArrayOfStructuredBlocksIntoMarkdownFormatSupportsParagraphHeadlineImageAndTableBlockTypesPreservesBlockStructureAndConvertsFormattingToMarkdownSyntaxPublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Blocks transformed to Markdown successfully' data: markdown: "# Title\n\nParagraph text here." properties: success: type: boolean example: true message: type: string example: 'Blocks transformed to Markdown successfully' data: type: object properties: markdown: type: string example: "# Title\n\nParagraph text here." 400: description: '' content: application/json: schema: type: object example: success: false message: 'No blocks provided' properties: success: type: boolean example: false message: type: string example: 'No blocks provided' tags: - 'Block Management' requestBody: required: true content: application/json: schema: type: object properties: blocks: type: array description: 'Array of block objects to transform.' example: - type: headline content: Title level: 1 items: type: string language: type: string description: 'Optional language code for metadata.' example: en required: - blocks security: [] /v1/blocks/transform/plaintext: post: summary: "Transform blocks to plain text\n\nTransforms an array of structured blocks into plain text without formatting.\nSupports paragraph, headline, image, and table block types.\nStrips all formatting and markup, preserving only text content.\nPublic endpoint - no authentication required." operationId: transformBlocksToPlainTextTransformsAnArrayOfStructuredBlocksIntoPlainTextWithoutFormattingSupportsParagraphHeadlineImageAndTableBlockTypesStripsAllFormattingAndMarkupPreservingOnlyTextContentPublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Blocks transformed to plain text successfully' data: text: 'Hello world' properties: success: type: boolean example: true message: type: string example: 'Blocks transformed to plain text successfully' data: type: object properties: text: type: string example: 'Hello world' 400: description: '' content: application/json: schema: type: object example: success: false message: 'No blocks provided' properties: success: type: boolean example: false message: type: string example: 'No blocks provided' tags: - 'Block Management' requestBody: required: true content: application/json: schema: type: object properties: blocks: type: array description: 'Array of block objects to transform.' example: - type: paragraph content: Hello items: type: string language: type: string description: 'Optional language code for metadata.' example: en required: - blocks security: [] /v1/blocks/parse/html: post: summary: "Parse HTML to blocks\n\nParses HTML content into structured block objects.\nConverts common HTML tags (p, h1-h6, img, table) into corresponding block types.\nReturns an array of validated block objects.\nPublic endpoint - no authentication required." operationId: parseHTMLToBlocksParsesHTMLContentIntoStructuredBlockObjectsConvertsCommonHTMLTagspH1H6ImgTableIntoCorrespondingBlockTypesReturnsAnArrayOfValidatedBlockObjectsPublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'HTML parsed to blocks successfully' data: blocks: - type: paragraph content: 'Hello world' - type: headline content: Title level: 1 properties: success: type: boolean example: true message: type: string example: 'HTML parsed to blocks successfully' data: type: object properties: blocks: type: array example: - type: paragraph content: 'Hello world' - type: headline content: Title level: 1 items: type: object properties: type: type: string example: paragraph content: type: string example: 'Hello world' 400: description: '' content: application/json: schema: type: object example: success: false message: 'No HTML content provided' properties: success: type: boolean example: false message: type: string example: 'No HTML content provided' tags: - 'Block Management' requestBody: required: true content: application/json: schema: type: object properties: html: type: string description: 'HTML content to parse.' example: '

Hello world

Title

' required: - html security: [] /v1/blocks/parse/markdown: post: summary: "Parse Markdown to blocks\n\nParses Markdown content into structured block objects.\nConverts Markdown syntax (paragraphs, headings, images, tables) into corresponding block types.\nReturns an array of validated block objects.\nPublic endpoint - no authentication required." operationId: parseMarkdownToBlocksParsesMarkdownContentIntoStructuredBlockObjectsConvertsMarkdownSyntaxparagraphsHeadingsImagesTablesIntoCorrespondingBlockTypesReturnsAnArrayOfValidatedBlockObjectsPublicEndpointNoAuthenticationRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Markdown parsed to blocks successfully' data: blocks: - type: headline content: Title level: 1 - type: paragraph content: 'Paragraph text here.' properties: success: type: boolean example: true message: type: string example: 'Markdown parsed to blocks successfully' data: type: object properties: blocks: type: array example: - type: headline content: Title level: 1 - type: paragraph content: 'Paragraph text here.' items: type: object properties: type: type: string example: headline content: type: string example: Title level: type: integer example: 1 400: description: '' content: application/json: schema: type: object example: success: false message: 'No Markdown content provided' properties: success: type: boolean example: false message: type: string example: 'No Markdown content provided' tags: - 'Block Management' requestBody: required: true content: application/json: schema: type: object properties: markdown: type: string description: 'Markdown content to parse.' example: '# Title\n\nParagraph text here.' required: - markdown security: [] '/v1/content/{parentId}/reorder': post: summary: 'Reorder child pages' operationId: reorderChildPages description: '' parameters: [] responses: { } tags: - 'Content Hierarchy' requestBody: required: true content: application/json: schema: type: object properties: ordered_ids: type: array description: 'The id of an existing record in the contents table.' example: - architecto items: type: string required: - ordered_ids security: [] parameters: - in: path name: parentId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/history/compare': post: summary: 'Compare two versions' operationId: compareTwoVersions description: '' parameters: [] responses: { } tags: - 'Content History' requestBody: required: true content: application/json: schema: type: object properties: version1: type: string description: '' example: architecto version2: type: string description: '' example: architecto required: - version1 - version2 security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/history/{historyId}/restore': post: summary: 'Restore a previous version' operationId: restoreAPreviousVersion description: '' parameters: [] responses: { } tags: - 'Content History' security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: historyId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/content: post: summary: 'Create new content page' operationId: createNewContentPage description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] '/v1/content/{id}': put: summary: 'Update content by ID' operationId: updateContentByID description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] delete: summary: 'Delete content by ID (soft delete)' operationId: deleteContentByIDsoftDelete description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/slug/{slug}': put: summary: 'Update content by slug' operationId: updateContentBySlug description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] delete: summary: 'Delete content by slug (soft delete)' operationId: deleteContentBySlugsoftDelete description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] parameters: - in: path name: slug description: 'The slug of the slug.' example: architecto required: true schema: type: string '/v1/content/{id}/publish': post: summary: 'Publish content page' operationId: publishContentPage description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/unpublish': post: summary: 'Unpublish content page (revert to draft)' operationId: unpublishContentPagerevertToDraft description: '' parameters: [] responses: { } tags: - 'Content Management' security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/schedule': post: summary: 'Schedule content for future publication' operationId: scheduleContentForFuturePublication description: '' parameters: [] responses: { } tags: - 'Content Management' requestBody: required: true content: application/json: schema: type: object properties: published_at: type: string description: 'Must be a valid date. Must be a date after now.' example: '2052-05-28' required: - published_at security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/progress/complete': post: summary: 'Mark content page as completed' operationId: markContentPageAsCompleted description: '' parameters: [] responses: { } tags: - 'Content Progress' security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/progress': post: summary: 'Update progress percentage for a content page' operationId: updateProgressPercentageForAContentPage description: '' parameters: [] responses: { } tags: - 'Content Progress' requestBody: required: true content: application/json: schema: type: object properties: progress_percentage: type: integer description: 'Must be at least 0. Must not be greater than 100.' example: 1 required: - progress_percentage security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/content/{id}/tags': post: summary: 'Assign tag(s) to content' operationId: assignTagsToContent description: '' parameters: [] responses: { } tags: - 'Content Tagging' requestBody: required: true content: application/json: schema: type: object properties: tag_slugs: type: array description: '' example: - architecto items: type: string context: type: string description: 'Must not be greater than 50 characters.' example: 'n' nullable: true required: - tag_slugs security: [] delete: summary: 'Remove tag(s) from content' operationId: removeTagsFromContent description: '' parameters: [] responses: { } tags: - 'Content Tagging' requestBody: required: true content: application/json: schema: type: object properties: tag_slugs: type: array description: '' example: - architecto items: type: string context: type: string description: 'Must not be greater than 50 characters.' example: 'n' nullable: true required: - tag_slugs security: [] parameters: - in: path name: id description: 'The ID of the content.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/content/tags/bulk-assign: post: summary: 'Bulk assign tags to multiple contents' operationId: bulkAssignTagsToMultipleContents description: '' parameters: [] responses: { } tags: - 'Content Tagging' requestBody: required: true content: application/json: schema: type: object properties: assignments: type: array description: 'Must have at least 1 items.' example: - [] items: type: object properties: resource_id: type: string description: 'Must be 26 characters.' example: ngzmiyvdljnikhwaykcmyuwpwl tag_slugs: type: array description: '' example: - architecto items: type: string required: - resource_id - tag_slugs context: type: string description: 'Must not be greater than 50 characters.' example: b nullable: true required: - assignments security: [] /livewire/update: post: summary: '' operationId: postLivewireUpdate description: '' parameters: [] responses: { } tags: - Endpoints security: [] /livewire/upload-file: post: summary: '' operationId: postLivewireUploadFile description: '' parameters: [] responses: { } tags: - Endpoints security: [] '/v1/ads/{id}/restore/{historyId}': post: summary: '' operationId: postV1AdsIdRestoreHistoryId description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the ad.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: historyId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/certificate-definitions: post: summary: '' operationId: postV1CertificateDefinitions description: '' parameters: [] responses: { } tags: - Endpoints security: [] '/v1/certificate-definitions/{id}': put: summary: '' operationId: putV1CertificateDefinitionsId description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: '' operationId: deleteV1CertificateDefinitionsId description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the certificate definition.' example: architecto required: true schema: type: string '/v1/certificates/enrollments/{enrollmentId}/issue': post: summary: 'Issue a participation certificate for an enrollment.' operationId: issueAParticipationCertificateForAnEnrollment description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: signed_by: type: string description: 'Must be 26 characters.' example: bngzmiyvdljnikhwaykcmyuwpw nullable: true signed_by_name: type: string description: 'Must not be greater than 255 characters.' example: l nullable: true metadata: type: object description: '' example: null properties: { } nullable: true security: [] parameters: - in: path name: enrollmentId description: '' example: architecto required: true schema: type: string /v1/certificates/issue: post: summary: 'Manually issue a certificate (admin)' operationId: manuallyIssueACertificateadmin description: '' parameters: [] responses: { } tags: - Endpoints security: [] '/v1/certificates/{id}/revoke': post: summary: 'Revoke a certificate (admin)' operationId: revokeACertificateadmin description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: reason: type: string description: 'Must not be greater than 500 characters.' example: b required: - reason security: [] parameters: - in: path name: id description: 'The ID of the certificate.' example: architecto required: true schema: type: string '/v1/certificates/{id}/regenerate-pdf': post: summary: 'Regenerate certificate PDF (admin)' operationId: regenerateCertificatePDFadmin description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the certificate.' example: architecto required: true schema: type: string '/v1/certificates/{id}/recertify': post: summary: 'Re-certify an expired certificate (admin)' operationId: reCertifyAnExpiredCertificateadmin description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: metadata: type: object description: '' example: null properties: { } nullable: true security: [] parameters: - in: path name: id description: 'The ID of the certificate.' example: architecto required: true schema: type: string /v1/courses: post: summary: 'Create a new course' operationId: createANewCourse description: '' parameters: [] responses: { } tags: - Endpoints security: [] '/v1/courses/{id}': put: summary: 'Update a course' operationId: updateACourse description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: 'Delete a course' operationId: deleteACourse description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the course.' example: architecto required: true schema: type: string '/v1/courses/{id}/publish': post: summary: 'Publish a course' operationId: publishACourse description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the course.' example: architecto required: true schema: type: string '/v1/courses/{id}/archive': post: summary: 'Archive a course' operationId: archiveACourse description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the course.' example: architecto required: true schema: type: string '/v1/courses/{id}/activities': post: summary: 'Add activity to course' operationId: addActivityToCourse description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: activity_type: type: string description: '' example: quiz enum: - content - quiz activity_id: type: string description: 'Must be 26 characters.' example: bngzmiyvdljnikhwaykcmyuwpw title: type: object description: '' example: null properties: { } nullable: true description: type: object description: '' example: null properties: { } nullable: true sort_order: type: integer description: 'Must be at least 0.' example: 89 nullable: true is_required: type: boolean description: '' example: false nullable: true is_graded: type: boolean description: '' example: false nullable: true weight: type: number description: 'Must be at least 0.' example: 34 nullable: true unlock_rules: type: object description: '' example: null properties: { } nullable: true required: - activity_type - activity_id security: [] parameters: - in: path name: id description: 'The ID of the course.' example: architecto required: true schema: type: string '/v1/courses/activities/{activityId}': put: summary: 'Update activity' operationId: updateActivity description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: title: type: object description: '' example: null properties: { } nullable: true description: type: object description: '' example: null properties: { } nullable: true sort_order: type: integer description: 'Must be at least 0.' example: 27 nullable: true is_required: type: boolean description: '' example: false nullable: true is_graded: type: boolean description: '' example: false nullable: true weight: type: number description: 'Must be at least 0.' example: 39 nullable: true unlock_rules: type: object description: '' example: null properties: { } nullable: true security: [] delete: summary: 'Delete activity' operationId: deleteActivity description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: activityId description: '' example: architecto required: true schema: type: string '/v1/courses/{id}/activities/reorder': post: summary: 'Reorder activities' operationId: reorderActivities description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: activity_ids: type: array description: 'Must be 26 characters.' example: - bngzmiyvdljnikhwaykcmyuwpw items: type: string required: - activity_ids security: [] parameters: - in: path name: id description: 'The ID of the course.' example: architecto required: true schema: type: string '/v1/enrollments/{enrollmentId}/activities/{activityId}/start': post: summary: 'Start an activity' operationId: startAnActivity description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: enrollmentId description: '' example: architecto required: true schema: type: string - in: path name: activityId description: '' example: architecto required: true schema: type: string '/v1/enrollments/{enrollmentId}/activities/{activityId}/complete': post: summary: 'Complete an activity' operationId: completeAnActivity description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: false content: application/json: schema: type: object properties: score: type: number description: 'Must be at least 0. Must not be greater than 100.' example: 1 nullable: true security: [] parameters: - in: path name: enrollmentId description: '' example: architecto required: true schema: type: string - in: path name: activityId description: '' example: architecto required: true schema: type: string '/v1/enrollments/{id}/suspend': post: summary: 'Suspend enrollment (admin)' operationId: suspendEnrollmentadmin description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the enrollment.' example: architecto required: true schema: type: string '/v1/enrollments/{id}/reactivate': post: summary: 'Reactivate enrollment (admin)' operationId: reactivateEnrollmentadmin description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the enrollment.' example: architecto required: true schema: type: string /dashboard/auth: post: summary: 'Handle password authentication.' operationId: handlePasswordAuthentication description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: password: type: string description: '' example: '|]|{+-' required: - password security: [] /dashboard/logout: post: summary: 'Logout from dashboard.' operationId: logoutFromDashboard description: '' parameters: [] responses: { } tags: - Endpoints security: [] /v1/quizzes: post: summary: '' operationId: postV1Quizzes description: '' parameters: [] responses: { } tags: - Endpoints security: [] '/v1/quizzes/{id}': put: summary: '' operationId: putV1QuizzesId description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: '' operationId: deleteV1QuizzesId description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: id description: 'The ID of the quiz.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/{slug}': put: summary: '' operationId: putV1QuizzesSlug description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: '' operationId: deleteV1QuizzesSlug description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: slug description: 'The slug of the quiz.' example: architecto required: true schema: type: string '/v1/quizzes/{quiz_id}/versions': post: summary: '' operationId: postV1QuizzesQuiz_idVersions description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: quiz_id description: 'The ID of the quiz.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/versions/{version_id}/publish': post: summary: '' operationId: postV1QuizzesVersionsVersion_idPublish description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: version_id description: 'The ID of the version.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/versions/{version_id}/duplicate': post: summary: '' operationId: postV1QuizzesVersionsVersion_idDuplicate description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: version_id description: 'The ID of the version.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/versions/{version_id}': delete: summary: '' operationId: deleteV1QuizzesVersionsVersion_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: version_id description: 'The ID of the version.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/quizzes/versions/compare: post: summary: 'Compare two quiz versions.' operationId: compareTwoQuizVersions description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: old_version_id: type: string description: '' example: architecto new_version_id: type: string description: '' example: architecto required: - old_version_id - new_version_id security: [] '/v1/quizzes/versions/{version_id}/sections': post: summary: '' operationId: postV1QuizzesVersionsVersion_idSections description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: version_id description: 'The ID of the version.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/sections/{section_id}': put: summary: '' operationId: putV1QuizzesSectionsSection_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: '' operationId: deleteV1QuizzesSectionsSection_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: section_id description: 'The ID of the section.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/versions/{version_id}/sections/reorder': post: summary: '' operationId: postV1QuizzesVersionsVersion_idSectionsReorder description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: section_ids: type: array description: 'Must be 26 characters.' example: - bngzmiyvdljnikhwaykcmyuwpw items: type: string required: - section_ids security: [] parameters: - in: path name: version_id description: 'The ID of the version.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/sections/{section_id}/slots': post: summary: '' operationId: postV1QuizzesSectionsSection_idSlots description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: section_id description: 'The ID of the section.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/slots/{slot_id}': put: summary: '' operationId: putV1QuizzesSlotsSlot_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: '' operationId: deleteV1QuizzesSlotsSlot_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: slot_id description: 'The ID of the slot.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/sections/{section_id}/slots/reorder': post: summary: '' operationId: postV1QuizzesSectionsSection_idSlotsReorder description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: slot_ids: type: array description: 'Must be 26 characters.' example: - bngzmiyvdljnikhwaykcmyuwpw items: type: string required: - slot_ids security: [] parameters: - in: path name: section_id description: 'The ID of the section.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/slots/{slot_id}/slot-options/bulk': post: summary: 'Bulk create slot options for a slot.' operationId: bulkCreateSlotOptionsForASlot description: '' parameters: [] responses: { } tags: - Endpoints requestBody: required: true content: application/json: schema: type: object properties: options: type: array description: 'Must have at least 1 items.' example: - [] items: type: object properties: original_question_id: type: string description: 'Must be at least 26 characters. Must not be greater than 26 characters.' example: b points: type: number description: 'Must be at least 0.' example: 39 nullable: true metadata: type: object description: '' example: null properties: { } nullable: true required: - original_question_id required: - options security: [] parameters: - in: path name: slot_id description: 'The ID of the slot.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/slots/{slot_id}/slot-options': post: summary: '' operationId: postV1QuizzesSlotsSlot_idSlotOptions description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: slot_id description: 'The ID of the slot.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/slot-options/{option_id}': put: summary: '' operationId: putV1QuizzesSlotOptionsOption_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] delete: summary: '' operationId: deleteV1QuizzesSlotOptionsOption_id description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: option_id description: 'The ID of the option.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/quizzes/slot-options/{option_id}/refresh': post: summary: '' operationId: postV1QuizzesSlotOptionsOption_idRefresh description: '' parameters: [] responses: { } tags: - Endpoints security: [] parameters: - in: path name: option_id description: 'The ID of the option.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/evaluation/manual-grading/{assignmentId}/grade': post: summary: 'Submit manual grade for assignment.' operationId: submitManualGradeForAssignment description: '' parameters: [] responses: { } tags: - Evaluation requestBody: required: true content: application/json: schema: type: object properties: points_awarded: type: number description: 'Must be at least 0.' example: 27 feedback: type: string description: 'Must not be greater than 10000 characters.' example: 'n' rubric_scores: type: array description: 'Must be at least 0.' example: - 84 items: type: number required: - points_awarded parameters: - in: path name: assignmentId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/evaluation/manual-grading/{assignmentId}/assign': put: summary: 'Assign grading assignment to instructor.' operationId: assignGradingAssignmentToInstructor description: '' parameters: [] responses: { } tags: - Evaluation requestBody: required: true content: application/json: schema: type: object properties: assigned_to: type: string description: '' example: architecto required: - assigned_to parameters: - in: path name: assignmentId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/evaluation/attempts/{attemptId}/regrade': post: summary: 'Regrade a single attempt.' operationId: regradeASingleAttempt description: "Resets scores and re-dispatches grading job.\nUseful for fixing grading errors or updating after config changes." parameters: [] responses: { } tags: - Evaluation parameters: - in: path name: attemptId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/evaluation/quiz-versions/{versionId}/regrade': post: summary: 'Regrade all attempts for a quiz version.' operationId: regradeAllAttemptsForAQuizVersion description: "Batch regrading for all submitted attempts of a quiz version.\nUseful after updating grading configuration." parameters: [] responses: { } tags: - Evaluation parameters: - in: path name: versionId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/evaluations: post: summary: 'Create new evaluation' operationId: createNewEvaluation description: '' parameters: [] responses: { } tags: - Evaluation security: [] '/v1/evaluations/{id}': put: summary: 'Update evaluation' operationId: updateEvaluation description: '' parameters: [] responses: { } tags: - Evaluation security: [] delete: summary: 'Delete evaluation' operationId: deleteEvaluation description: '' parameters: [] responses: { } tags: - Evaluation security: [] parameters: - in: path name: id description: 'The ID of the evaluation.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/global/users: get: summary: 'List all users globally (cross-tenant)' operationId: listAllUsersGloballycrossTenant description: '' parameters: - in: query name: per_page description: 'Number of items per page (1-100). Defaults to 10.' example: 20 required: false schema: type: integer description: 'Number of items per page (1-100). Defaults to 10.' example: 20 - in: query name: page description: 'Current page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Current page number. Defaults to 1.' example: 1 - in: query name: is_anonymous description: 'Filter by anonymous status.' example: false required: false schema: type: boolean description: 'Filter by anonymous status.' example: false - in: query name: search description: 'Search in email, phone, and username.' example: john required: false schema: type: string description: 'Search in email, phone, and username.' example: john - in: query name: created_at_gte description: 'Filter users created on or after date.' example: '2024-01-01' required: false schema: type: string description: 'Filter users created on or after date.' example: '2024-01-01' - in: query name: created_at_lte description: 'Filter users created on or before date.' example: '2024-12-31' required: false schema: type: string description: 'Filter users created on or before date.' example: '2024-12-31' - in: query name: sort description: "Sort field. Prefix with '-' for descending." example: '-created_at' required: false schema: type: string description: "Sort field. Prefix with '-' for descending." example: '-created_at' responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Users retrieved successfully", "data": [...], "meta": {"pagination": {"total": 50, "per_page": 10, "current_page": 1, "last_page": 5}}}' tags: - 'Global User Management' security: [] post: summary: 'Create new user with explicit tenant assignment' operationId: createNewUserWithExplicitTenantAssignment description: '' parameters: [] responses: { } tags: - 'Global User Management' security: [] '/v1/global/users/{id}': put: summary: 'Update user globally (allows tenant transfer)' operationId: updateUserGloballyallowsTenantTransfer description: '' parameters: [] responses: { } tags: - 'Global User Management' security: [] delete: summary: 'Delete user globally (soft delete)' operationId: deleteUserGloballysoftDelete description: '' parameters: [] responses: { } tags: - 'Global User Management' security: [] parameters: - in: path name: id description: 'The ID of the user.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/health: get: summary: 'Check API Health' operationId: checkAPIHealth description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: status: healthy properties: status: type: string example: healthy 503: description: '' content: application/json: schema: type: object example: status: unhealthy properties: status: type: string example: unhealthy tags: - Health security: [] /v1/identity/register: post: summary: "Register new user\n\nCreates a new user account with identity (password).\nRequires at least one identifier (email, phone, or username) plus a password." operationId: registerNewUserCreatesANewUserAccountWithIdentitypasswordRequiresAtLeastOneIdentifieremailPhoneOrUsernamePlusAPassword description: '' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true message: 'Registration successful' data: user: id: 01kav3fcmme10dwbeexyqkfndn email: user@example.com tenant_id: 01kav3fbpbpb3wnhtadsmrmt7m tokens: access_token: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: Bearer expires_in: 86400 properties: success: type: boolean example: true message: type: string example: 'Registration successful' data: type: object properties: user: type: object properties: id: type: string example: 01kav3fcmme10dwbeexyqkfndn email: type: string example: user@example.com tenant_id: type: string example: 01kav3fbpbpb3wnhtadsmrmt7m tokens: type: object properties: access_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: type: string example: Bearer expires_in: type: integer example: 86400 tags: - Identity security: [] /v1/identity/login: post: summary: "Login with credentials\n\nAuthenticate with email/phone/username and password.\nReturns JWT access token (24h) and refresh token (30d).\nRate limited to 10 attempts per hour." operationId: loginWithCredentialsAuthenticateWithEmailphoneusernameAndPasswordReturnsJWTAccessToken24hAndRefreshToken30dRateLimitedTo10AttemptsPerHour description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Login successful' data: user: id: 01kav3fcmme10dwbeexyqkfndn email: user@example.com tokens: access_token: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: Bearer expires_in: 86400 properties: success: type: boolean example: true message: type: string example: 'Login successful' data: type: object properties: user: type: object properties: id: type: string example: 01kav3fcmme10dwbeexyqkfndn email: type: string example: user@example.com tokens: type: object properties: access_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: type: string example: Bearer expires_in: type: integer example: 86400 tags: - Identity security: [] /v1/identity/refresh: post: summary: "Refresh access token\n\nExchange refresh token for new access and refresh tokens.\nImplements OAuth2 token rotation - old refresh token is blacklisted." operationId: refreshAccessTokenExchangeRefreshTokenForNewAccessAndRefreshTokensImplementsOAuth2TokenRotationOldRefreshTokenIsBlacklisted description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Token refreshed successfully' data: access_token: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: Bearer expires_in: 86400 properties: success: type: boolean example: true message: type: string example: 'Token refreshed successfully' data: type: object properties: access_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: type: string example: Bearer expires_in: type: integer example: 86400 tags: - Identity security: [] /v1/identity/anonymous: post: summary: "Anonymous login\n\nGenerate anonymous session token without creating database records.\nReturns a tracking ID and tokens that can be used to link requests across modules.\nUser can later \"upgrade\" to authenticated by registering with this ID." operationId: anonymousLoginGenerateAnonymousSessionTokenWithoutCreatingDatabaseRecordsReturnsATrackingIDAndTokensThatCanBeUsedToLinkRequestsAcrossModulesUserCanLaterupgradeToAuthenticatedByRegisteringWithThisID description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Anonymous session created' data: anonymous_id: 01kav3fcmme10dwbeexyqkfndn tokens: access_token: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: Bearer expires_in: 86400 properties: success: type: boolean example: true message: type: string example: 'Anonymous session created' data: type: object properties: anonymous_id: type: string example: 01kav3fcmme10dwbeexyqkfndn tokens: type: object properties: access_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: type: string example: Bearer expires_in: type: integer example: 86400 tags: - Identity security: [] /v1/identity/anonymous/claim: post: summary: "Claim anonymous account\n\nConverts anonymous session to authenticated account by claiming it with credentials.\nRequires anonymous token in Authorization header.\nCreates User and Identity records with the anonymous ID.\nPreserves data linkage across modules via shared ID." operationId: claimAnonymousAccountConvertsAnonymousSessionToAuthenticatedAccountByClaimingItWithCredentialsRequiresAnonymousTokenInAuthorizationHeaderCreatesUserAndIdentityRecordsWithTheAnonymousIDPreservesDataLinkageAcrossModulesViaSharedID description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Account upgraded successfully' data: user: id: 01kav3fcmme10dwbeexyqkfndn email: user@example.com is_anonymous: false tokens: access_token: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: Bearer expires_in: 86400 properties: success: type: boolean example: true message: type: string example: 'Account upgraded successfully' data: type: object properties: user: type: object properties: id: type: string example: 01kav3fcmme10dwbeexyqkfndn email: type: string example: user@example.com is_anonymous: type: boolean example: false tokens: type: object properties: access_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... refresh_token: type: string example: eyJ0eXAiOiJKV1QiLCJhbGc... token_type: type: string example: Bearer expires_in: type: integer example: 86400 401: description: '' content: application/json: schema: type: object example: success: false message: 'Missing or invalid authorization token' properties: success: type: boolean example: false message: type: string example: 'Missing or invalid authorization token' 409: description: '' content: application/json: schema: type: object example: success: false message: 'Anonymous account has already been upgraded' properties: success: type: boolean example: false message: type: string example: 'Anonymous account has already been upgraded' tags: - Identity security: [] parameters: - in: path name: anonymous description: 'Anonymous identifier (from route: /identity/anonymous/claim)' example: architecto required: true schema: type: string /v1/identity/change-password: post: summary: "Change password\n\nChange password for authenticated user.\nRequires current password for verification.\nProtected route - requires valid access token." operationId: changePasswordChangePasswordForAuthenticatedUserRequiresCurrentPasswordForVerificationProtectedRouteRequiresValidAccessToken description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Password changed successfully' data: null properties: success: type: boolean example: true message: type: string example: 'Password changed successfully' data: type: string example: null tags: - Identity /v1/media: post: summary: 'Upload new media file' operationId: uploadNewMediaFile description: '' parameters: [] responses: { } tags: - 'Media Management' security: [] /v1/media/from-url: post: summary: 'Upload media from URL' operationId: uploadMediaFromURL description: '' parameters: [] responses: { } tags: - 'Media Management' requestBody: required: true content: application/json: schema: type: object properties: url: type: string description: 'Must be a valid URL. Must not be greater than 2048 characters.' example: 'http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html' privacy: type: string description: '' example: identity enum: - public - tenant - identity collection_name: type: string description: 'Must not be greater than 100 characters.' example: i title: type: string description: 'Must not be greater than 255 characters.' example: k description: type: string description: 'Must not be greater than 1000 characters.' example: 'Aut ab provident perspiciatis quo omnis nostrum aut.' alt_text: type: string description: 'Must not be greater than 500 characters.' example: w expires_at: type: string description: 'Must be a valid date.' example: '2026-05-05T11:44:22' required: - url security: [] /v1/media/batch: post: summary: 'Batch upload multiple media files' operationId: batchUploadMultipleMediaFiles description: '' parameters: [] responses: 201: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "3 files uploaded successfully", "data": {"media": [{"id": "01...", ...}], "errors": []}}' tags: - 'Media Management' requestBody: required: true content: application/json: schema: type: object properties: files: type: array description: 'Array of files to upload.' items: type: string format: binary privacy: type: string description: 'Privacy level for all files.' example: tenant collection_name: type: string description: 'nullable Collection name for all files.' example: question-block-images nullable: true titles: type: array description: 'nullable Array of titles (one per file).' example: - 'Image 1' - 'Image 2' items: type: string nullable: true required: - files - privacy security: [] '/v1/media/{id}': patch: summary: 'Update media metadata (file content is immutable)' operationId: updateMediaMetadatafileContentIsImmutable description: '' parameters: [] responses: { } tags: - 'Media Management' security: [] delete: summary: 'Delete media by ID (soft delete)' operationId: deleteMediaByIDsoftDelete description: '' parameters: [] responses: { } tags: - 'Media Management' security: [] parameters: - in: path name: id description: 'The ID of the medium.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/media/bulk-delete: post: summary: 'Bulk delete media files' operationId: bulkDeleteMediaFiles description: '' parameters: [] responses: { } tags: - 'Media Management' requestBody: required: true content: application/json: schema: type: object properties: ids: type: array description: '' example: - architecto items: type: string required: - ids security: [] /v1/media/cleanup: post: summary: 'Cleanup expired media files' operationId: cleanupExpiredMediaFiles description: '' parameters: [] responses: { } tags: - 'Media Management' security: [] /v1/moodle-import: post: summary: 'Import an MBZ file' operationId: importAnMBZFile description: '' parameters: [] responses: { } tags: - MoodleImport security: [] /v1/notifications: post: summary: "Send a notification (single or bulk)\n\nSingle notification:\n{\n \"user_id\": \"ulid\",\n \"category\": \"info\",\n \"template_name\": \"welcome\",\n \"data\": {...}\n}\n\nBulk notifications:\n{\n \"notifications\": [\n {\"user_id\": \"ulid1\", \"category\": \"info\", ...},\n {\"user_id\": \"ulid2\", \"category\": \"urgent\", ...}\n ]\n}" operationId: sendANotificationsingleOrBulkSingleNotificationuserIdulidcategoryinfotemplateNamewelcomedataBulkNotificationsnotificationsuserIdulid1categoryinfouserIdulid2categoryurgent description: '' parameters: [] responses: { } tags: - 'Notification Management' security: [] /v1/notifications/read-all: post: summary: 'Mark all notifications as read for current user' operationId: markAllNotificationsAsReadForCurrentUser description: '' parameters: [] responses: { } tags: - 'Notification Management' security: [] '/v1/notifications/{id}/read': patch: summary: 'Mark notification as read' operationId: markNotificationAsRead description: '' parameters: [] responses: { } tags: - 'Notification Management' security: [] parameters: - in: path name: id description: 'The ID of the notification.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/notifications/preferences: put: summary: 'Update notification preferences' operationId: updateNotificationPreferences description: '' parameters: [] responses: { } tags: - 'Notification Preferences' security: [] delete: summary: 'Delete notification preference (reset to defaults)' operationId: deleteNotificationPreferenceresetToDefaults description: '' parameters: [] responses: { } tags: - 'Notification Preferences' requestBody: required: true content: application/json: schema: type: object properties: category: type: string description: '' example: info enum: - critical - urgent - info - marketing required: - category security: [] /v1/pdf/templates: get: summary: "List all available PDF templates\n\nReturns a list of all PDF templates available for document generation." operationId: listAllAvailablePDFTemplatesReturnsAListOfAllPDFTemplatesAvailableForDocumentGeneration description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: templates: - slug: quiz_certificate name: 'Quiz Certificate' description: ... properties: success: type: boolean example: true data: type: object properties: templates: type: array example: - slug: quiz_certificate name: 'Quiz Certificate' description: ... items: type: object properties: slug: type: string example: quiz_certificate name: type: string example: 'Quiz Certificate' description: type: string example: ... tags: - 'Pdf Management' security: [] '/v1/pdf/generate/{template}': post: summary: "Generate PDF from template\n\nGenerates a PDF document from a template with the provided data.\nReturns the PDF as a base64-encoded string." operationId: generatePDFFromTemplateGeneratesAPDFDocumentFromATemplateWithTheProvidedDataReturnsThePDFAsABase64EncodedString description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: filename: quiz_certificate_2024-01-01_120000.pdf content: base64... size: 12345 contentType: application/pdf generatedAt: '2024-01-01T12:00:00+00:00' mediaId: null properties: success: type: boolean example: true data: type: object properties: filename: type: string example: quiz_certificate_2024-01-01_120000.pdf content: type: string example: base64... size: type: integer example: 12345 contentType: type: string example: application/pdf generatedAt: type: string example: '2024-01-01T12:00:00+00:00' mediaId: type: string example: null tags: - 'Pdf Management' requestBody: required: true content: application/json: schema: type: object properties: data: type: object description: 'The data to populate the template (e.g., {"student_name": "Jane Doe", "quiz_title": "Sample Quiz"}).' example: null properties: { } filename: type: string description: 'optional Custom filename for the generated PDF.' example: architecto format: type: string description: 'optional Paper format (A4, A3, Letter, Legal). Default: A4' example: architecto orientation: type: string description: 'optional Page orientation (portrait, landscape). Default: portrait' example: architecto store: type: boolean description: 'optional Store the PDF in the Media module and return media_id. Default: false' example: false required: - data security: [] parameters: - in: path name: template description: 'The template slug.' example: quiz_certificate required: true schema: type: string '/v1/pdf/download/{template}': post: summary: "Download PDF directly\n\nGenerates a PDF and returns it as a downloadable file instead of a JSON response." operationId: downloadPDFDirectlyGeneratesAPDFAndReturnsItAsADownloadableFileInsteadOfAJSONResponse description: '' parameters: [] responses: { } tags: - 'Pdf Management' requestBody: required: true content: application/json: schema: type: object properties: data: type: object description: 'The data to populate the template (e.g., {"student_name": "Jane Doe", "quiz_title": "Sample Quiz"}).' example: null properties: { } filename: type: string description: 'optional Custom filename for the generated PDF.' example: architecto format: type: string description: 'optional Paper format (A4, A3, Letter, Legal). Default: A4' example: architecto orientation: type: string description: 'optional Page orientation (portrait, landscape). Default: portrait' example: architecto store: type: boolean description: 'optional Store the PDF in the Media module. Default: false' example: false required: - data security: [] parameters: - in: path name: template description: 'The template slug.' example: quiz_certificate required: true schema: type: string /v1/pdf/render: post: summary: "Render PDF from custom HTML\n\nGenerates a PDF from raw HTML provided in the request.\nThe HTML is processed through Blade, allowing variable substitution." operationId: renderPDFFromCustomHTMLGeneratesAPDFFromRawHTMLProvidedInTheRequestTheHTMLIsProcessedThroughBladeAllowingVariableSubstitution description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: filename: custom_2024-01-01_120000.pdf content: base64... size: 12345 contentType: application/pdf generatedAt: '2024-01-01T12:00:00+00:00' mediaId: null properties: success: type: boolean example: true data: type: object properties: filename: type: string example: custom_2024-01-01_120000.pdf content: type: string example: base64... size: type: integer example: 12345 contentType: type: string example: application/pdf generatedAt: type: string example: '2024-01-01T12:00:00+00:00' mediaId: type: string example: null tags: - 'Pdf Management' requestBody: required: true content: application/json: schema: type: object properties: html: type: string description: 'The HTML template (max 500KB).' example: '

{{ $title }}

' data: type: object description: 'optional Variables for Blade rendering (e.g., {"title": "My Document"}).' example: null properties: { } filename: type: string description: 'optional Custom filename for the generated PDF.' example: architecto format: type: string description: 'optional Paper format (A4, A3, Letter, Legal). Default: A4' example: architecto orientation: type: string description: 'optional Page orientation (portrait, landscape). Default: portrait' example: architecto store: type: boolean description: 'optional Store the PDF in the Media module and return media_id. Default: false' example: false required: - html security: [] /v1/placements: post: summary: 'Create a new placement' operationId: createANewPlacement description: '' parameters: [] responses: { } tags: - 'Placement Management' security: [] '/v1/placements/{id}': put: summary: 'Update placement by ID' operationId: updatePlacementByID description: '' parameters: [] responses: { } tags: - 'Placement Management' security: [] delete: summary: 'Delete placement by ID (soft delete)' operationId: deletePlacementByIDsoftDelete description: '' parameters: [] responses: { } tags: - 'Placement Management' security: [] parameters: - in: path name: id description: 'The ID of the placement.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/tenants/lookup/{slug}': get: summary: "Public tenant lookup by slug (for login flow)\n\nReturns minimal tenant info (id, slug, name) without requiring authentication.\nThis endpoint is used during login to resolve a tenant slug to its ID." operationId: publicTenantLookupBySlugforLoginFlowReturnsMinimalTenantInfoidSlugNameWithoutRequiringAuthenticationThisEndpointIsUsedDuringLoginToResolveATenantSlugToItsID description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 01ARZ3... slug: acme-corp name: 'Acme Corp' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 01ARZ3... slug: type: string example: acme-corp name: type: string example: 'Acme Corp' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Tenant not found' properties: success: type: boolean example: false message: type: string example: 'Tenant not found' tags: - Public security: [] parameters: - in: path name: slug description: 'The slug of the lookup.' example: architecto required: true schema: type: string /v1/questions/history/compare: get: summary: 'Compare two versions' operationId: compareTwoVersions description: '' parameters: - in: query name: old description: 'ID of the old history record.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string description: 'ID of the old history record.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV - in: query name: new description: 'ID of the new history record.' example: 01ARZ4NDEKTSV4RRFFQ69G5FAV required: true schema: type: string description: 'ID of the new history record.' example: 01ARZ4NDEKTSV4RRFFQ69G5FAV responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Version comparison completed", "data": {"old_version": {...}, "new_version": {...}, "differences": {...}}}' 404: description: '' content: application/json: schema: type: object example: success: false message: 'History record not found' properties: success: type: boolean example: false message: type: string example: 'History record not found' tags: - 'Question History' requestBody: required: true content: application/json: schema: type: object properties: old: type: string description: 'Must be 26 characters.' example: bngzmiyvdljnikhwaykcmyuwpw new: type: string description: 'Must be 26 characters.' example: lvqwrsitcpscqldzsnrwtujwvl required: - old - new security: [] '/v1/questions/{id}/history': get: summary: 'Get version history for a question' operationId: getVersionHistoryForAQuestion description: '' parameters: - in: query name: per_page description: 'Number of history records per page. Defaults to 20.' example: 20 required: false schema: type: integer description: 'Number of history records per page. Defaults to 20.' example: 20 - in: query name: page description: 'Current page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Current page number. Defaults to 1.' example: 1 responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question history retrieved successfully", "data": [...], "meta": {"pagination": {...}}}' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Question not found' properties: success: type: boolean example: false message: type: string example: 'Question not found' tags: - 'Question History' security: [] parameters: - in: path name: id description: 'The ID of the question.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/questions/{id}/history/{historyId}': get: summary: 'Get specific version from history' operationId: getSpecificVersionFromHistory description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question version retrieved successfully", "data": {...}}' 404: description: '' content: application/json: schema: type: object example: success: false message: 'History record not found' properties: success: type: boolean example: false message: type: string example: 'History record not found' tags: - 'Question History' security: [] parameters: - in: path name: id description: 'The ID of the question.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: historyId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/questions/{id}/restore/{historyId}': post: summary: 'Restore question to a previous version (owner only)' operationId: restoreQuestionToAPreviousVersionownerOnly description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question restored successfully", "data": {...}}' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Only the owner tenant can restore this question' properties: success: type: boolean example: false message: type: string example: 'Only the owner tenant can restore this question' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Question or history record not found' properties: success: type: boolean example: false message: type: string example: 'Question or history record not found' tags: - 'Question History' requestBody: required: false content: application/json: schema: type: object properties: version_note: type: string description: 'nullable Note describing this restoration.' example: 'Restored from version before error' security: [] parameters: - in: path name: id description: 'The ID of the question.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: historyId description: '' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/questions: get: summary: 'List all questions with filters' operationId: listAllQuestionsWithFilters description: '' parameters: - in: query name: per_page description: 'Number of items per page (1-100). Defaults to 15.' example: 20 required: false schema: type: integer description: 'Number of items per page (1-100). Defaults to 15.' example: 20 - in: query name: page description: 'Current page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Current page number. Defaults to 1.' example: 1 - in: query name: type description: 'Filter by question type.' example: multiple_choice required: false schema: type: string description: 'Filter by question type.' example: multiple_choice - in: query name: is_public description: 'Filter by public status.' example: true required: false schema: type: boolean description: 'Filter by public status.' example: true - in: query name: search description: 'Search in title (multilingual).' example: math required: false schema: type: string description: 'Search in title (multilingual).' example: math - in: query name: created_at_gte description: 'Filter questions created on or after date.' example: '2024-01-01' required: false schema: type: string description: 'Filter questions created on or after date.' example: '2024-01-01' - in: query name: created_at_lte description: 'Filter questions created on or before date.' example: '2024-12-31' required: false schema: type: string description: 'Filter questions created on or before date.' example: '2024-12-31' - in: query name: tags description: 'Comma-separated tag slugs.' example: 'difficulty:hard,topic:math' required: false schema: type: string description: 'Comma-separated tag slugs.' example: 'difficulty:hard,topic:math' - in: query name: tag_mode description: "Tag matching mode: 'and' (all tags) or 'or' (any tag). Defaults to 'or'." example: and required: false schema: type: string description: "Tag matching mode: 'and' (all tags) or 'or' (any tag). Defaults to 'or'." example: and - in: query name: sort description: "Sort field. Prefix with '-' for descending." example: '-created_at' required: false schema: type: string description: "Sort field. Prefix with '-' for descending." example: '-created_at' - in: query name: include_public description: 'Include public questions from other tenants. Defaults to true.' example: true required: false schema: type: boolean description: 'Include public questions from other tenants. Defaults to true.' example: true responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Questions retrieved successfully", "data": [...], "meta": {"pagination": {...}}}' tags: - 'Question Management' security: [] post: summary: 'Create new question' operationId: createNewQuestion description: '' parameters: [] responses: 201: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question created successfully", "data": {...}}' tags: - 'Question Management' requestBody: required: true content: multipart/form-data: schema: type: object properties: title: type: array description: 'Multilingual title (JSON object with language codes as keys).' example: - architecto items: type: string prompt: type: array description: 'Multilingual prompt (JSON object with language codes as keys).' example: - architecto items: type: string type: type: string description: 'Question type.' example: multiple_choice is_public: type: boolean description: 'nullable Make question public to all tenants. Defaults to false.' example: true instructions: type: array description: 'nullable Array of block objects.' example: - architecto items: type: string feedbacks: type: array description: 'nullable Array of block objects.' example: - architecto items: type: string media_id: type: string description: 'nullable Media attachment ID (mutually exclusive with media_file and media_url).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV media_file: type: string format: binary description: 'nullable Direct file upload (mutually exclusive with media_id and media_url).' media_url: type: string description: 'nullable URL to download image from (mutually exclusive with media_id and media_file).' example: 'https://example.com/image.jpg' logic: type: array description: 'nullable Custom logic/scoring rules.' example: - architecto items: type: string version_note: type: string description: 'nullable Note describing this version.' example: 'Initial version' required: - title - prompt - type security: [] /v1/questions/public: get: summary: 'List all public questions (across all tenants)' operationId: listAllPublicQuestionsacrossAllTenants description: '' parameters: - in: query name: per_page description: 'Number of items per page. Defaults to 15.' example: 20 required: false schema: type: integer description: 'Number of items per page. Defaults to 15.' example: 20 - in: query name: page description: 'Current page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Current page number. Defaults to 1.' example: 1 responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Public questions retrieved successfully", "data": [...], "meta": {"pagination": {...}}}' tags: - 'Question Management' security: [] /v1/questions/bulk-delete: post: summary: 'Bulk delete questions (owner only)' operationId: bulkDeleteQuestionsownerOnly description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Bulk delete completed' data: deleted_count: 5 errors: [] properties: success: type: boolean example: true message: type: string example: 'Bulk delete completed' data: type: object properties: deleted_count: type: integer example: 5 errors: type: array example: [] tags: - 'Question Management' requestBody: required: true content: application/json: schema: type: object properties: ids: type: array description: 'Array of question IDs to delete.' example: - 01ARZ3... - 01ARZ4... items: type: string required: - ids security: [] '/v1/questions/{id}': get: summary: 'Get question by ID' operationId: getQuestionByID description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question retrieved successfully", "data": {...}}' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Question not found' properties: success: type: boolean example: false message: type: string example: 'Question not found' tags: - 'Question Management' security: [] put: summary: 'Update question by ID (owner only)' operationId: updateQuestionByIDownerOnly description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question updated successfully", "data": {...}}' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Only the owner tenant can modify this question' properties: success: type: boolean example: false message: type: string example: 'Only the owner tenant can modify this question' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Question not found' properties: success: type: boolean example: false message: type: string example: 'Question not found' tags: - 'Question Management' requestBody: required: false content: multipart/form-data: schema: type: object properties: title: type: array description: 'nullable Multilingual title.' example: - architecto items: type: string prompt: type: array description: 'nullable Multilingual prompt.' example: - architecto items: type: string type: type: string description: 'nullable Question type.' example: architecto is_public: type: boolean description: 'nullable Public visibility flag.' example: false instructions: type: array description: 'nullable Array of block objects.' example: - architecto items: type: string feedbacks: type: array description: 'nullable Array of block objects.' example: - architecto items: type: string media_id: type: string description: 'nullable Media attachment ID (mutually exclusive with media_file and media_url).' example: architecto media_file: type: string format: binary description: 'nullable Direct file upload (mutually exclusive with media_id and media_url).' media_url: type: string description: 'nullable URL to download image from (mutually exclusive with media_id and media_file).' example: 'http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html' logic: type: array description: 'nullable Custom logic/scoring rules.' example: - architecto items: type: string version_note: type: string description: 'nullable Note describing this version.' example: 'Fixed typo in title' security: [] delete: summary: 'Delete question by ID (soft delete, owner only)' operationId: deleteQuestionByIDsoftDeleteOwnerOnly description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question deleted successfully", "data": {...}}' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Only the owner tenant can delete this question' properties: success: type: boolean example: false message: type: string example: 'Only the owner tenant can delete this question' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Question not found' properties: success: type: boolean example: false message: type: string example: 'Question not found' tags: - 'Question Management' requestBody: required: false content: application/json: schema: type: object properties: version_note: type: string description: 'nullable Note describing this deletion.' example: 'Removed outdated content' security: [] parameters: - in: path name: id description: 'The ID of the question.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/questions/{id}/duplicate': post: summary: 'Duplicate a question' operationId: duplicateAQuestion description: '' parameters: [] responses: 201: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Question duplicated successfully", "data": {...}}' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Question not found' properties: success: type: boolean example: false message: type: string example: 'Question not found' tags: - 'Question Management' requestBody: required: false content: application/json: schema: type: object properties: overrides: type: array description: 'nullable Fields to override in the duplicate.' example: - architecto items: type: string security: [] parameters: - in: path name: id description: 'The ID of the question.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/questions/{id}/tags': post: summary: 'Assign tag(s) to question' operationId: assignTagsToQuestion description: '' parameters: [] responses: { } tags: - 'Question Tagging' requestBody: required: true content: application/json: schema: type: object properties: tag_slugs: type: array description: '' example: - architecto items: type: string context: type: string description: 'Must not be greater than 50 characters.' example: 'n' nullable: true required: - tag_slugs security: [] delete: summary: 'Remove tag(s) from question' operationId: removeTagsFromQuestion description: '' parameters: [] responses: { } tags: - 'Question Tagging' requestBody: required: true content: application/json: schema: type: object properties: tag_slugs: type: array description: '' example: - architecto items: type: string context: type: string description: 'Must not be greater than 50 characters.' example: 'n' nullable: true required: - tag_slugs security: [] parameters: - in: path name: id description: 'The ID of the question.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/questions/tags/bulk-assign: post: summary: 'Bulk assign tags to multiple questions' operationId: bulkAssignTagsToMultipleQuestions description: '' parameters: [] responses: { } tags: - 'Question Tagging' requestBody: required: true content: application/json: schema: type: object properties: assignments: type: array description: 'Must have at least 1 items.' example: - [] items: type: object properties: resource_id: type: string description: 'Must be 26 characters.' example: ngzmiyvdljnikhwaykcmyuwpwl tag_slugs: type: array description: '' example: - architecto items: type: string required: - resource_id - tag_slugs context: type: string description: 'Must not be greater than 50 characters.' example: b nullable: true required: - assignments security: [] /v1/rbac/me/permissions: get: summary: "Get my effective permissions\n\nReturns the current authenticated user's effective permissions after grant/deny resolution.\nPublic endpoint - no rbac.manage permission required.\nSuperadmin users have all permissions automatically." operationId: getMyEffectivePermissionsReturnsTheCurrentAuthenticatedUsersEffectivePermissionsAfterGrantdenyResolutionPublicEndpointNoRbacmanagePermissionRequiredSuperadminUsersHaveAllPermissionsAutomatically description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Effective permissions retrieved' data: - users.read - users.create - tenants.read - rbac.manage properties: success: type: boolean example: true message: type: string example: 'Effective permissions retrieved' data: type: array example: - users.read - users.create - tenants.read - rbac.manage items: type: string tags: - 'RBAC Management' security: [] /v1/rbac/me/role: get: summary: "Get my role\n\nReturns the current authenticated user's assigned role.\nIf no role is explicitly assigned, returns the tenant's default role.\nPublic endpoint - no rbac.manage permission required." operationId: getMyRoleReturnsTheCurrentAuthenticatedUsersAssignedRoleIfNoRoleIsExplicitlyAssignedReturnsTheTenantsDefaultRolePublicEndpointNoRbacmanagePermissionRequired description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Role retrieved' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: Administrator slug: admin description: 'Full system access' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Role retrieved' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: Administrator slug: type: string example: admin description: type: string example: 'Full system access' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV tags: - 'RBAC Management' security: [] /v1/rbac/permissions: get: summary: "List all permissions\n\nReturns all permissions in the tenant's RBAC system.\nRequires rbac.manage permission." operationId: listAllPermissionsReturnsAllPermissionsInTheTenantsRBACSystemRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Permissions retrieved' data: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: users.read name: 'View Users' description: 'View user list and details' resource: users action: read tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Permissions retrieved' data: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: users.read name: 'View Users' description: 'View user list and details' resource: users action: read tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: type: string example: users.read name: type: string example: 'View Users' description: type: string example: 'View user list and details' resource: type: string example: users action: type: string example: read tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' tags: - 'RBAC Management' post: summary: "Create new permission\n\nCreates a new permission in the tenant's RBAC system.\nThe slug must be unique within the tenant.\nRequires rbac.manage permission." operationId: createNewPermissionCreatesANewPermissionInTheTenantsRBACSystemTheSlugMustBeUniqueWithinTheTenantRequiresRbacmanagePermission description: '' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true message: 'Permission created successfully' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: courses.publish name: 'Publish Courses' description: 'Allows publishing courses to students' resource: courses action: publish tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Permission created successfully' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: type: string example: courses.publish name: type: string example: 'Publish Courses' description: type: string example: 'Allows publishing courses to students' resource: type: string example: courses action: type: string example: publish tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 400: description: '' content: application/json: schema: type: object example: success: false message: 'Permission with this slug already exists' properties: success: type: boolean example: false message: type: string example: 'Permission with this slug already exists' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' tags: - 'RBAC Management' requestBody: required: true content: application/json: schema: type: object properties: slug: type: string description: 'Permission slug (lowercase, dots/underscores allowed).' example: courses.publish name: type: string description: 'Permission display name.' example: 'Publish Courses' description: type: string description: 'Description of what this permission allows.' example: 'Allows publishing courses to students' resource: type: string description: 'Resource this permission applies to.' example: courses action: type: string description: 'Action this permission allows.' example: publish required: - slug - name - resource - action '/v1/rbac/permissions/{id}': get: summary: "Get permission by ID\n\nReturns a single permission by its ID.\nRequires rbac.manage permission." operationId: getPermissionByIDReturnsASinglePermissionByItsIDRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Permission retrieved' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: users.create name: 'Create Users' description: 'Create new user accounts' resource: users action: create tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Permission retrieved' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: type: string example: users.create name: type: string example: 'Create Users' description: type: string example: 'Create new user accounts' resource: type: string example: users action: type: string example: create tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Permission not found' properties: success: type: boolean example: false message: type: string example: 'Permission not found' tags: - 'RBAC Management' put: summary: "Update permission\n\nUpdates an existing permission. The slug cannot be changed after creation.\nRequires rbac.manage permission." operationId: updatePermissionUpdatesAnExistingPermissionTheSlugCannotBeChangedAfterCreationRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Permission updated successfully' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: users.create name: 'Create Users' description: 'Create new user accounts' resource: users action: create tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Permission updated successfully' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV slug: type: string example: users.create name: type: string example: 'Create Users' description: type: string example: 'Create new user accounts' resource: type: string example: users action: type: string example: create tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Permission not found' properties: success: type: boolean example: false message: type: string example: 'Permission not found' tags: - 'RBAC Management' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Permission display name.' example: 'Create Users' description: type: string description: 'Description of what this permission allows.' example: 'Create new user accounts' resource: type: string description: 'Resource this permission applies to.' example: users action: type: string description: 'Action this permission allows.' example: create required: - name - resource - action delete: summary: "Delete permission\n\nDeletes a permission from the tenant's RBAC system.\nCannot delete permissions that are currently assigned to roles.\nRequires rbac.manage permission." operationId: deletePermissionDeletesAPermissionFromTheTenantsRBACSystemCannotDeletePermissionsThatAreCurrentlyAssignedToRolesRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Permission deleted successfully' data: null properties: success: type: boolean example: true message: type: string example: 'Permission deleted successfully' data: type: string example: null 400: description: '' content: application/json: schema: type: object example: success: false message: 'Cannot delete permission: currently assigned to roles' properties: success: type: boolean example: false message: type: string example: 'Cannot delete permission: currently assigned to roles' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Permission not found' properties: success: type: boolean example: false message: type: string example: 'Permission not found' tags: - 'RBAC Management' parameters: - in: path name: id description: 'Permission ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /v1/rbac/roles: get: summary: "List all roles\n\nReturns all roles in the tenant's RBAC system.\nRequires rbac.manage permission." operationId: listAllRolesReturnsAllRolesInTheTenantsRBACSystemRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Roles retrieved' data: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: Administrator slug: admin description: 'Full system access' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Roles retrieved' data: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: Administrator slug: admin description: 'Full system access' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: Administrator slug: type: string example: admin description: type: string example: 'Full system access' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' tags: - 'RBAC Management' post: summary: "Create new role\n\nCreates a new role in the tenant's RBAC system.\nThe slug must be unique within the tenant.\nOnly one role can be marked as default per tenant.\nSuperadmin roles bypass all permission checks.\nRequires rbac.manage permission." operationId: createNewRoleCreatesANewRoleInTheTenantsRBACSystemTheSlugMustBeUniqueWithinTheTenantOnlyOneRoleCanBeMarkedAsDefaultPerTenantSuperadminRolesBypassAllPermissionChecksRequiresRbacmanagePermission description: '' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true message: 'Role created successfully' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: 'Content Manager' slug: content-manager description: 'Manages course content and media' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Role created successfully' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: 'Content Manager' slug: type: string example: content-manager description: type: string example: 'Manages course content and media' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 400: description: '' content: application/json: schema: type: object example: success: false message: 'Role with this slug already exists' properties: success: type: boolean example: false message: type: string example: 'Role with this slug already exists' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' tags: - 'RBAC Management' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Role display name.' example: 'Content Manager' slug: type: string description: 'Role slug (lowercase, hyphens/underscores allowed).' example: content-manager description: type: string description: 'Description of this role.' example: 'Manages course content and media' is_default: type: boolean description: 'Whether this is the default role for new users.' example: false is_superadmin: type: boolean description: 'Whether this role bypasses all permission checks.' example: false required: - name - slug - is_default - is_superadmin '/v1/rbac/roles/{id}': get: summary: "Get role by ID\n\nReturns a single role by its ID, including granted and denied permissions.\nRequires rbac.manage permission." operationId: getRoleByIDReturnsASingleRoleByItsIDIncludingGrantedAndDeniedPermissionsRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Role retrieved' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: Administrator slug: admin description: 'Full system access' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV granted_permissions: - users.read - users.create denied_permissions: [] properties: success: type: boolean example: true message: type: string example: 'Role retrieved' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: Administrator slug: type: string example: admin description: type: string example: 'Full system access' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV granted_permissions: type: array example: - users.read - users.create items: type: string denied_permissions: type: array example: [] 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Role not found' properties: success: type: boolean example: false message: type: string example: 'Role not found' tags: - 'RBAC Management' put: summary: "Update role\n\nUpdates an existing role. The slug cannot be changed after creation.\nSetting is_default to true will unset any other default role.\nRequires rbac.manage permission." operationId: updateRoleUpdatesAnExistingRoleTheSlugCannotBeChangedAfterCreationSettingIsDefaultToTrueWillUnsetAnyOtherDefaultRoleRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Role updated successfully' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: 'Content Manager' slug: content-manager description: 'Manages course content and media' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Role updated successfully' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: 'Content Manager' slug: type: string example: content-manager description: type: string example: 'Manages course content and media' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Role not found' properties: success: type: boolean example: false message: type: string example: 'Role not found' tags: - 'RBAC Management' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Role display name.' example: 'Content Manager' description: type: string description: 'Description of this role.' example: 'Manages course content and media' is_default: type: boolean description: 'Whether this is the default role for new users.' example: false is_superadmin: type: boolean description: 'Whether this role bypasses all permission checks.' example: false required: - name - is_default - is_superadmin delete: summary: "Delete role\n\nDeletes a role from the tenant's RBAC system.\nCannot delete the default role or roles currently assigned to users.\nRequires rbac.manage permission." operationId: deleteRoleDeletesARoleFromTheTenantsRBACSystemCannotDeleteTheDefaultRoleOrRolesCurrentlyAssignedToUsersRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Role deleted successfully' data: null properties: success: type: boolean example: true message: type: string example: 'Role deleted successfully' data: type: string example: null 400: description: '' content: application/json: schema: type: object example: success: false message: 'Cannot delete default role or role assigned to users' properties: success: type: boolean example: false message: type: string example: 'Cannot delete default role or role assigned to users' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Role not found' properties: success: type: boolean example: false message: type: string example: 'Role not found' tags: - 'RBAC Management' parameters: - in: path name: id description: 'Role ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /v1/rbac/roles/default: get: summary: "Get default role\n\nReturns the tenant's default role (assigned to new users automatically).\nReturns null if no default role is set.\nOnly one default role can exist per tenant.\nRequires rbac.manage permission." operationId: getDefaultRoleReturnsTheTenantsDefaultRoleassignedToNewUsersAutomaticallyReturnsNullIfNoDefaultRoleIsSetOnlyOneDefaultRoleCanExistPerTenantRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Default role retrieved' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: 'Default User' slug: default description: 'Default permissions for new users' is_default: true is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'Default role retrieved' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: 'Default User' slug: type: string example: default description: type: string example: 'Default permissions for new users' is_default: type: boolean example: true is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' tags: - 'RBAC Management' '/v1/rbac/roles/{roleId}/permissions/sync': put: summary: "Sync role permissions\n\nSynchronizes a role's granted and denied permissions.\nPermissions not in either list will be removed from the role.\nExplicit grants override denies (if a permission appears in both lists, it will be granted).\nRequires rbac.manage permission." operationId: syncRolePermissionsSynchronizesARolesGrantedAndDeniedPermissionsPermissionsNotInEitherListWillBeRemovedFromTheRoleExplicitGrantsOverrideDeniesifAPermissionAppearsInBothListsItWillBeGrantedRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Role permissions synced successfully' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: 'Content Manager' slug: content-manager description: 'Manages course content' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV granted_permissions: - courses.read - courses.create denied_permissions: - courses.delete properties: success: type: boolean example: true message: type: string example: 'Role permissions synced successfully' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: 'Content Manager' slug: type: string example: content-manager description: type: string example: 'Manages course content' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV granted_permissions: type: array example: - courses.read - courses.create items: type: string denied_permissions: type: array example: - courses.delete items: type: string 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Role not found or permission not found' properties: success: type: boolean example: false message: type: string example: 'Role not found or permission not found' tags: - 'RBAC Management' requestBody: required: false content: application/json: schema: type: object properties: grant_permission_ids: type: array description: 'Array of permission IDs to grant.' example: - 01ARZ3NDEKTSV4RRFFQ69G5FAV - 01ARZ3NDEKTSV4RRFFQ69G5FAW items: type: string deny_permission_ids: type: array description: 'Array of permission IDs to explicitly deny.' example: - 01ARZ3NDEKTSV4RRFFQ69G5FAX items: type: string parameters: - in: path name: roleId description: 'Role ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /v1/rbac/users/assign-role: post: summary: "Assign role to user\n\nAssigns a role to a user (identity) in the tenant.\nIf the user already has a role assignment, it will be updated.\nRequires rbac.manage permission." operationId: assignRoleToUserAssignsARoleToAUseridentityInTheTenantIfTheUserAlreadyHasARoleAssignmentItWillBeUpdatedRequiresRbacmanagePermission description: '' parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true message: 'Role assigned successfully' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAY identity_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV role_id: 01ARZ3NDEKTSV4RRFFQ69G5FAW tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAZ assigned_by: 01ARZ3NDEKTSV4RRFFQ69G5FAA assigned_at: '2024-01-15T10:30:00Z' properties: success: type: boolean example: true message: type: string example: 'Role assigned successfully' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAY identity_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV role_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAW tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAZ assigned_by: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAA assigned_at: type: string example: '2024-01-15T10:30:00Z' 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Identity not found or role not found' properties: success: type: boolean example: false message: type: string example: 'Identity not found or role not found' tags: - 'RBAC Management' requestBody: required: true content: application/json: schema: type: object properties: identity_id: type: string description: 'Identity ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV role_id: type: string description: 'Role ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAW required: - identity_id - role_id '/v1/rbac/users/{identityId}/revoke-role': delete: summary: "Revoke user's role\n\nRevokes (removes) the role assignment from a user.\nThe user will fall back to the tenant's default role if one exists.\nRequires rbac.manage permission." operationId: revokeUsersRoleRevokesremovesTheRoleAssignmentFromAUserTheUserWillFallBackToTheTenantsDefaultRoleIfOneExistsRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Role revoked successfully' data: null properties: success: type: boolean example: true message: type: string example: 'Role revoked successfully' data: type: string example: null 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'User has no role assignment' properties: success: type: boolean example: false message: type: string example: 'User has no role assignment' tags: - 'RBAC Management' parameters: - in: path name: identityId description: 'Identity ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string '/v1/rbac/users/{identityId}/role': get: summary: "Get user's role\n\nReturns the role assigned to a specific user.\nIf no role is explicitly assigned, returns the tenant's default role.\nRequires rbac.manage permission." operationId: getUsersRoleReturnsTheRoleAssignedToASpecificUserIfNoRoleIsExplicitlyAssignedReturnsTheTenantsDefaultRoleRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'User role retrieved' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: 'Content Manager' slug: content-manager description: 'Manages course content' is_default: false is_superadmin: false tenant_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV properties: success: type: boolean example: true message: type: string example: 'User role retrieved' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV name: type: string example: 'Content Manager' slug: type: string example: content-manager description: type: string example: 'Manages course content' is_default: type: boolean example: false is_superadmin: type: boolean example: false tenant_id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Identity not found' properties: success: type: boolean example: false message: type: string example: 'Identity not found' tags: - 'RBAC Management' parameters: - in: path name: identityId description: 'Identity ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string '/v1/rbac/users/{identityId}/effective-permissions': get: summary: "Get user's effective permissions\n\nReturns the effective permissions for a specific user after grant/deny resolution.\nTakes into account the user's assigned role and its permission grants/denies.\nSuperadmin users will have all permissions.\nRequires rbac.manage permission." operationId: getUsersEffectivePermissionsReturnsTheEffectivePermissionsForASpecificUserAfterGrantdenyResolutionTakesIntoAccountTheUsersAssignedRoleAndItsPermissionGrantsdeniesSuperadminUsersWillHaveAllPermissionsRequiresRbacmanagePermission description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'User effective permissions retrieved' data: - courses.read - courses.create - courses.update - media.read properties: success: type: boolean example: true message: type: string example: 'User effective permissions retrieved' data: type: array example: - courses.read - courses.create - courses.update - media.read items: type: string 403: description: '' content: application/json: schema: type: object example: success: false message: 'Insufficient permissions' properties: success: type: boolean example: false message: type: string example: 'Insufficient permissions' 404: description: '' content: application/json: schema: type: object example: success: false message: 'Identity not found' properties: success: type: boolean example: false message: type: string example: 'Identity not found' tags: - 'RBAC Management' parameters: - in: path name: identityId description: 'Identity ID (ULID, 26 characters).' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /v1/statistics/record: post: summary: "Record a usage event directly\n\nRecords a usage event. Events are queued for async processing." operationId: recordAUsageEventDirectlyRecordsAUsageEventEventsAreQueuedForAsyncProcessing description: '' parameters: [] responses: { } tags: - Statistics security: [] /v1/tags/contexts: post: summary: 'Create context' operationId: createContext description: '' parameters: [] responses: { } tags: - 'Tag Contexts' security: [] '/v1/tags/contexts/{context}': patch: summary: 'Update context' operationId: updateContext description: '' parameters: [] responses: { } tags: - 'Tag Contexts' security: [] delete: summary: 'Delete context' operationId: deleteContext description: '' parameters: [] responses: { } tags: - 'Tag Contexts' security: [] parameters: - in: path name: context description: 'The context.' example: architecto required: true schema: type: string /v1/tags: post: summary: 'Create new tag' operationId: createNewTag description: '' parameters: [] responses: { } tags: - 'Tag Management' security: [] /v1/tags/bulk-delete: post: summary: 'Bulk delete tags' operationId: bulkDeleteTags description: '' parameters: [] responses: { } tags: - 'Tag Management' requestBody: required: true content: application/json: schema: type: object properties: ids: type: array description: 'Must be 26 characters.' example: - bngzmiyvdljnikhwaykcmyuwpw items: type: string force: type: boolean description: '' example: false nullable: true required: - ids security: [] '/v1/tags/{id}': patch: summary: 'Update tag' operationId: updateTag description: '' parameters: [] responses: { } tags: - 'Tag Management' security: [] delete: summary: 'Delete tag' operationId: deleteTag description: '' parameters: [] responses: { } tags: - 'Tag Management' security: [] parameters: - in: path name: id description: 'The ID of the tag.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string /v1/tenants: get: summary: 'List all tenants (public - no auth required)' operationId: listAllTenantspublicNoAuthRequired description: '' parameters: - in: query name: per_page description: 'Number of items per page (1-100). Defaults to 10.' example: 20 required: false schema: type: integer description: 'Number of items per page (1-100). Defaults to 10.' example: 20 - in: query name: page description: 'Current page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Current page number. Defaults to 1.' example: 1 - in: query name: enabled description: 'Filter by enabled status.' example: true required: false schema: type: boolean description: 'Filter by enabled status.' example: true - in: query name: search description: 'Search in name, slug, and domain.' example: acme required: false schema: type: string description: 'Search in name, slug, and domain.' example: acme - in: query name: created_at_gte description: 'Filter tenants created on or after date.' example: '2024-01-01' required: false schema: type: string description: 'Filter tenants created on or after date.' example: '2024-01-01' - in: query name: created_at_lte description: 'Filter tenants created on or before date.' example: '2024-12-31' required: false schema: type: string description: 'Filter tenants created on or before date.' example: '2024-12-31' - in: query name: sort description: "Sort field. Prefix with '-' for descending." example: '-created_at' required: false schema: type: string description: "Sort field. Prefix with '-' for descending." example: '-created_at' responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Tenants retrieved successfully", "data": [...], "meta": {"pagination": {"total": 50, "per_page": 10, "current_page": 1, "last_page": 5}}}' tags: - 'Tenant Management' security: [] post: summary: 'Create new tenant with initial admin user.' operationId: createNewTenantWithInitialAdminUser description: '' parameters: [] responses: 201: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Tenant and admin user created successfully", "data": {"tenant": {...}, "admin": {"email": "admin@acme.com", "generated_password": "kR8mP2vN9qT3wX7z"}}}' tags: - 'Tenant Management' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Tenant name.' example: 'Acme Corp' slug: type: string description: 'Tenant slug.' example: acme domain: type: string description: 'nullable Tenant domain.' example: acme.com description: type: string description: 'nullable Tenant description.' example: 'Eius et animi quos velit et.' settings: type: array description: 'nullable Tenant settings.' example: - architecto items: type: string enabled: type: boolean description: 'nullable Tenant enabled status. Defaults to true.' example: false admin_email: type: string description: 'Admin user email.' example: admin@acme.com admin_password: type: string description: 'nullable Admin password (min 8 chars). If omitted, a secure password is generated and returned.' example: architecto required: - name - slug - admin_email security: [] '/v1/tenants/{id}': get: summary: 'Get tenant by ID (public - no auth required)' operationId: getTenantByIDpublicNoAuthRequired description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"id": "01ARZ3...", "name": "Acme Corp", ...}' 404: description: '' content: application/json: schema: type: object example: message: 'Tenant not found' properties: message: type: string example: 'Tenant not found' tags: - 'Tenant Management' security: [] put: summary: 'Update tenant by ID' operationId: updateTenantByID description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"id": "01ARZ3...", "name": "Updated Corp", ...}' 404: description: '' content: application/json: schema: type: object example: message: 'Tenant not found' properties: message: type: string example: 'Tenant not found' tags: - 'Tenant Management' security: [] delete: summary: 'Delete tenant by ID (soft delete)' operationId: deleteTenantByIDsoftDelete description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"id": "01ARZ3...", "deleted_at": "2025-12-03T10:30:00Z", ...}' 404: description: '' content: application/json: schema: type: object example: message: 'Tenant not found' properties: message: type: string example: 'Tenant not found' tags: - 'Tenant Management' security: [] parameters: - in: path name: id description: 'The ID of the tenant.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/tenants/{slug}': get: summary: 'Get tenant by slug (public - no auth required)' operationId: getTenantBySlugpublicNoAuthRequired description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"id": "01ARZ3...", "name": "Acme Corp", "slug": "acme-corp", ...}' 404: description: '' content: application/json: schema: type: object example: message: 'Tenant not found' properties: message: type: string example: 'Tenant not found' tags: - 'Tenant Management' security: [] put: summary: 'Update tenant by slug' operationId: updateTenantBySlug description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"id": "01ARZ3...", "name": "Updated Corp", ...}' 404: description: '' content: application/json: schema: type: object example: message: 'Tenant not found' properties: message: type: string example: 'Tenant not found' tags: - 'Tenant Management' security: [] delete: summary: 'Delete tenant by slug (soft delete)' operationId: deleteTenantBySlugsoftDelete description: '' parameters: [] responses: 200: description: '' content: text/plain: schema: type: string example: '{"id": "01ARZ3...", "deleted_at": "2025-12-03T10:30:00Z", ...}' 404: description: '' content: application/json: schema: type: object example: message: 'Tenant not found' properties: message: type: string example: 'Tenant not found' tags: - 'Tenant Management' security: [] parameters: - in: path name: slug description: 'The slug of the tenant.' example: architecto required: true schema: type: string /v1/transfer/export/preview: post: summary: "Preview export\n\nReturns counts of entities that would be exported without creating the file.\nIncludes resolved dependencies." operationId: previewExportReturnsCountsOfEntitiesThatWouldBeExportedWithoutCreatingTheFileIncludesResolvedDependencies description: '' parameters: [] responses: { } tags: - 'Transfer - Import/Export' requestBody: required: true content: application/json: schema: type: object properties: entity_types: type: array description: 'Entity types to export.' example: - questions - quizzes items: type: string format: type: string description: 'Export format (default: json).' example: json filters: type: object description: 'Optional filters per entity type.' example: [] properties: { } include_media_files: type: boolean description: 'Include media files in export (default: true).' example: false required: - entity_types security: [] /v1/transfer/export: post: summary: "Execute export\n\nCreates a ZIP package with selected entities and returns download information." operationId: executeExportCreatesAZIPPackageWithSelectedEntitiesAndReturnsDownloadInformation description: '' parameters: [] responses: { } tags: - 'Transfer - Import/Export' requestBody: required: true content: application/json: schema: type: object properties: entity_types: type: array description: 'Entity types to export.' example: - questions - quizzes items: type: string format: type: string description: 'Export format (default: json).' example: json filters: type: object description: 'Optional filters per entity type.' example: [] properties: { } include_media_files: type: boolean description: 'Include media files in export (default: true).' example: false export_name: type: string description: 'Optional name for the export.' example: architecto required: - entity_types security: [] /v1/transfer/import/validate: post: summary: "Validate import file\n\nValidates the uploaded ZIP file without importing.\nReturns validation result with entity counts and any errors.\nIn upsert/skip modes, shows breakdown of existing vs new entities." operationId: validateImportFileValidatesTheUploadedZIPFileWithoutImportingReturnsValidationResultWithEntityCountsAndAnyErrorsInUpsertskipModesShowsBreakdownOfExistingVsNewEntities description: '' parameters: [] responses: { } tags: - 'Transfer - Import/Export' requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: 'The ZIP file to validate.' mode: type: string description: 'Import mode: create, upsert, or skip (default: create).' example: architecto required: - file security: [] /v1/transfer/import: post: summary: "Execute import\n\nImports entities from the uploaded ZIP file." operationId: executeImportImportsEntitiesFromTheUploadedZIPFile description: '' parameters: [] responses: { } tags: - 'Transfer - Import/Export' requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: 'The ZIP file to import.' mode: type: string description: 'Import mode: create, upsert, or skip (default: create).' example: architecto required: - file security: [] /v1/users/me: delete: summary: 'Delete the currently authenticated user account' operationId: deleteTheCurrentlyAuthenticatedUserAccount description: '' parameters: [] responses: { } tags: - 'User Management' security: [] /v1/users: get: summary: 'List all users in tenant' operationId: listAllUsersInTenant description: '' parameters: - in: query name: per_page description: 'Number of items per page (1-100). Defaults to 10.' example: 20 required: false schema: type: integer description: 'Number of items per page (1-100). Defaults to 10.' example: 20 - in: query name: page description: 'Current page number. Defaults to 1.' example: 1 required: false schema: type: integer description: 'Current page number. Defaults to 1.' example: 1 - in: query name: is_anonymous description: 'Filter by anonymous status.' example: false required: false schema: type: boolean description: 'Filter by anonymous status.' example: false - in: query name: search description: 'Search in email, phone, and username.' example: john required: false schema: type: string description: 'Search in email, phone, and username.' example: john - in: query name: created_at_gte description: 'Filter users created on or after date.' example: '2024-01-01' required: false schema: type: string description: 'Filter users created on or after date.' example: '2024-01-01' - in: query name: created_at_lte description: 'Filter users created on or before date.' example: '2024-12-31' required: false schema: type: string description: 'Filter users created on or before date.' example: '2024-12-31' - in: query name: sort description: "Sort field. Prefix with '-' for descending." example: '-created_at' required: false schema: type: string description: "Sort field. Prefix with '-' for descending." example: '-created_at' responses: 200: description: '' content: text/plain: schema: type: string example: '{"success": true, "message": "Users retrieved successfully", "data": [...], "meta": {"pagination": {"total": 50, "per_page": 10, "current_page": 1, "last_page": 5}}}' tags: - 'User Management' security: [] post: summary: 'Create new user in tenant' operationId: createNewUserInTenant description: '' parameters: [] responses: { } tags: - 'User Management' security: [] '/v1/users/{id}': put: summary: 'Update user by ID within tenant' operationId: updateUserByIDWithinTenant description: '' parameters: [] responses: { } tags: - 'User Management' security: [] delete: summary: 'Delete user by ID (soft delete) within tenant' operationId: deleteUserByIDsoftDeleteWithinTenant description: '' parameters: [] responses: { } tags: - 'User Management' security: [] parameters: - in: path name: id description: 'The ID of the user.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/v1/verification/{handleType}/initiate': post: summary: "Initiate verification for any handle type\n\nSends a 6-digit verification code to the authenticated user's handle (email, phone, etc.)." operationId: initiateVerificationForAnyHandleTypeSendsA6DigitVerificationCodeToTheAuthenticatedUsersHandleemailPhoneEtc description: '' parameters: [] responses: { } tags: - Verification security: [] parameters: - in: path name: handleType description: 'The handle type to verify (email, phone).' example: email required: true schema: type: string '/v1/verification/{handleType}/verify': post: summary: "Verify handle with code\n\nVerifies the user's handle (email, phone, etc.) using the provided 6-digit code." operationId: verifyHandleWithCodeVerifiesTheUsersHandleemailPhoneEtcUsingTheProvided6DigitCode description: '' parameters: [] responses: { } tags: - Verification requestBody: required: true content: application/json: schema: type: object properties: code: type: string description: 'The 6-digit verification code.' example: '123456' required: - code security: [] parameters: - in: path name: handleType description: 'The handle type to verify (email, phone).' example: email required: true schema: type: string '/v1/verification/{handleType}/resend': post: summary: "Resend verification code\n\nResends a new verification code to the user's handle (email, phone, etc.)." operationId: resendVerificationCodeResendsANewVerificationCodeToTheUsersHandleemailPhoneEtc description: '' parameters: [] responses: { } tags: - Verification security: [] parameters: - in: path name: handleType description: 'The handle type to verify (email, phone).' example: email required: true schema: type: string /v1/verification/password-reset/initiate: post: summary: "Initiate password reset\n\nSends a password reset link to the user's email. No authentication required.\nAlways returns success to prevent user enumeration." operationId: initiatePasswordResetSendsAPasswordResetLinkToTheUsersEmailNoAuthenticationRequiredAlwaysReturnsSuccessToPreventUserEnumeration description: '' parameters: [] responses: { } tags: - Verification security: [] /v1/verification/password-reset/verify: post: summary: "Verify password reset token\n\nValidates a password reset token without consuming it.\nUsed by frontend to show password reset form." operationId: verifyPasswordResetTokenValidatesAPasswordResetTokenWithoutConsumingItUsedByFrontendToShowPasswordResetForm description: '' parameters: [] responses: { } tags: - Verification security: [] /v1/verification/password-reset/complete: post: summary: "Complete password reset\n\nResets the user's password using the provided token and new password.\nToken is consumed and cannot be reused." operationId: completePasswordResetResetsTheUsersPasswordUsingTheProvidedTokenAndNewPasswordTokenIsConsumedAndCannotBeReused description: '' parameters: [] responses: { } tags: - Verification security: []