{"openapi":"3.0.0","info":{"title":"Vana Public API","description":"REST API for accessing courses, quizzes, and learning content.\n\nThe API is not versioned in the URL; it only grows. New endpoints, response fields, optional request fields and enum values (such as block types) can appear at any time, so ignore fields you do not recognise and skip enum values you cannot handle. Nothing already in the spec is removed or renamed; replaced parts are marked deprecated and keep working.","version":"1.0.0","contact":{"name":"Vana Support","email":"engineering@proposales.com"},"license":{"name":"MIT"}},"servers":[{"url":"https://vana.proposales.com/api","description":"This deployment"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"custom","description":"API key authentication (format: Bearer vana_xxxxx)"}},"schemas":{"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"key_prefix":{"type":"string","description":"Leading characters of the key. The key itself is never returned after creation."},"last_used_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","key_prefix","created_at"]},"QuestionOption":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"sort_order":{"type":"integer"},"is_correct":{"type":"boolean"}},"required":["id","label","sort_order","is_correct"]},"Question":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"prompt":{"type":"string"},"question_type":{"type":"string","enum":["true_false","single_choice","multiple_choice"]},"difficulty":{"type":"integer","minimum":1,"maximum":5},"options":{"type":"array","items":{"$ref":"#/components/schemas/QuestionOption"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","prompt","question_type","difficulty","options","created_at","updated_at"]},"Block":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["text","image","diagram","video","example","question","quiz","scenario","glossary","interactive"]},"position":{"type":"integer"},"content":{"type":"object","description":"Per-type payload; see BlockCreate.content for the shape each type uses."},"question_role":{"type":"string","enum":["practice","checkpoint","assessment","review"],"nullable":true},"question":{"$ref":"#/components/schemas/Question","nullable":true},"quiz":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"}},"nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","type","position","created_at","updated_at"]},"Lesson":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"position":{"type":"integer"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/Block"},"nullable":true},"published_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was published; null while it is a draft. A learner sees a lesson only when it and its course are both published. New courses and lessons start as drafts."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","position","created_at","updated_at"]},"Module":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"position":{"type":"integer"},"lessons":{"type":"array","items":{"$ref":"#/components/schemas/Lesson"},"nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","title","position","created_at","updated_at"]},"Course":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string"},"position":{"type":"integer"},"modules":{"type":"array","items":{"$ref":"#/components/schemas/Module"},"nullable":true},"published_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was published; null while it is a draft. A learner sees a lesson only when it and its course are both published. New courses and lessons start as drafts."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"sources":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/CourseSource"}}},"required":["id","project_id","title","description","position","created_at","updated_at"]},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"courses":{"type":"array","items":{"$ref":"#/components/schemas/Course"},"nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"workspace":{"$ref":"#/components/schemas/WorkspaceRef"}},"required":["id","name","description","created_at","updated_at"]},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"details":{"type":"object"}},"required":["error"]},"QuizAttemptAnswer":{"type":"object","properties":{"question_id":{"type":"string","format":"uuid"},"selected_option_ids":{"type":"array","items":{"type":"string","format":"uuid"}}},"required":["question_id","selected_option_ids"]},"QuizAttemptSubmission":{"type":"object","properties":{"answers":{"type":"array","items":{"$ref":"#/components/schemas/QuizAttemptAnswer"}}},"required":["answers"]},"QuizAttemptResult":{"type":"object","properties":{"attempt_id":{"type":"string","format":"uuid"},"score_correct":{"type":"integer"},"score_total":{"type":"integer"},"percentage":{"type":"number","format":"float"}},"required":["attempt_id","score_correct","score_total","percentage"]},"CreateApiKeyRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1}},"required":["name"]},"CreateApiKeyResponse":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"key":{"type":"string","description":"The plaintext key. Returned once and never again; only its SHA-256 digest is stored."},"warning":{"type":"string"}}}]},"CourseCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"sources":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/CourseSource"},"description":"The documents this course was built from. Replaces the whole list when given on update."},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."}}},"CourseUpdate":{"type":"object","minProperties":1,"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"sources":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/CourseSource"},"description":"The documents this course was built from. Replaces the whole list when given on update."},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."}}},"ModuleCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."}}},"ModuleUpdate":{"type":"object","minProperties":1,"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."}}},"LessonCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."}}},"LessonUpdate":{"type":"object","minProperties":1,"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."}}},"BlockCreate":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text","image","diagram","video","example","question","quiz","scenario","glossary","interactive"]},"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."},"content":{"type":"object","description":"Per-type payload. Not schema-checked: a shape the renderer does not recognise is stored and the block renders empty, so it never 400s. text/example: {\"body\": \"markdown\"}. scenario: {\"body\", \"reveal\"}. glossary: {\"title\", \"terms\": [{\"term\", \"definition\"}]}. image: {\"source\": {\"type\": \"url\", \"url\"} or {\"type\": \"svg\", \"code\"}, \"alt\", \"caption\"}. video: {\"source\": {\"type\": \"youtube\", \"id\", \"start\"} or {\"type\": \"url\", \"url\"}, \"title\", \"caption\"}. diagram: {\"source\": {\"type\": \"mermaid\", \"code\"}, \"caption\"}. interactive: {\"source\": {\"type\": \"html\", \"code\"}, \"title\", \"caption\", \"height\"}. visual: {\"prompt\", \"status\"}. question/quiz blocks carry no content."},"question_id":{"type":"string","format":"uuid","description":"Required when type is 'question'. Must belong to the same project."},"question_role":{"type":"string","enum":["practice","checkpoint","assessment","review"],"description":"Required when type is 'question'."},"quiz_id":{"type":"string","format":"uuid","description":"Required when type is 'quiz'. Must belong to the same project."}}},"BlockUpdate":{"type":"object","minProperties":1,"description":"type is immutable; delete and recreate the block to change it.","properties":{"position":{"type":"integer","minimum":0,"maximum":32767,"description":"Sort order within the parent. Appended to the end when omitted."},"content":{"type":"object","description":"Per-type payload. Not schema-checked: a shape the renderer does not recognise is stored and the block renders empty, so it never 400s. text/example: {\"body\": \"markdown\"}. scenario: {\"body\", \"reveal\"}. glossary: {\"title\", \"terms\": [{\"term\", \"definition\"}]}. image: {\"source\": {\"type\": \"url\", \"url\"} or {\"type\": \"svg\", \"code\"}, \"alt\", \"caption\"}. video: {\"source\": {\"type\": \"youtube\", \"id\", \"start\"} or {\"type\": \"url\", \"url\"}, \"title\", \"caption\"}. diagram: {\"source\": {\"type\": \"mermaid\", \"code\"}, \"caption\"}. interactive: {\"source\": {\"type\": \"html\", \"code\"}, \"title\", \"caption\", \"height\"}. visual: {\"prompt\", \"status\"}. question/quiz blocks carry no content."},"question_id":{"type":"string","format":"uuid","description":"Required when type is 'question'. Must belong to the same project."},"question_role":{"type":"string","enum":["practice","checkpoint","assessment","review"],"description":"Required when type is 'question'."},"quiz_id":{"type":"string","format":"uuid","description":"Required when type is 'quiz'. Must belong to the same project."}}},"DeleteResult":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}},"Category":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","project_id","name"]},"CategoryWrite":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Unique within the project."}}},"QuestionOptionInput":{"type":"object","required":["label","is_correct"],"properties":{"label":{"type":"string","minLength":1},"is_correct":{"type":"boolean"}}},"QuestionCreate":{"type":"object","required":["category_id","prompt","question_type","difficulty","options"],"properties":{"category_id":{"type":"string","format":"uuid","description":"Required. Must belong to the same project."},"question_family_id":{"type":"string","format":"uuid","nullable":true,"description":"Optional grouping for AI variants. Must belong to the same project."},"prompt":{"type":"string","minLength":1},"question_type":{"type":"string","enum":["true_false","single_choice","multiple_choice"]},"difficulty":{"type":"integer","minimum":1,"maximum":5},"options":{"type":"array","items":{"$ref":"#/components/schemas/QuestionOptionInput"},"description":"Replaces the full set. true_false: exactly 2 options, exactly 1 correct. single_choice: 2+ options, exactly 1 correct. multiple_choice: 2+ options, 2+ correct."}}},"QuestionUpdate":{"type":"object","minProperties":1,"description":"Partial. The merged result is validated, so changing question_type can be rejected because of options you did not send.","properties":{"category_id":{"type":"string","format":"uuid","description":"Required. Must belong to the same project."},"question_family_id":{"type":"string","format":"uuid","nullable":true,"description":"Optional grouping for AI variants. Must belong to the same project."},"prompt":{"type":"string","minLength":1},"question_type":{"type":"string","enum":["true_false","single_choice","multiple_choice"]},"difficulty":{"type":"integer","minimum":1,"maximum":5},"options":{"type":"array","items":{"$ref":"#/components/schemas/QuestionOptionInput"},"description":"Replaces the full set. true_false: exactly 2 options, exactly 1 correct. single_choice: 2+ options, exactly 1 correct. multiple_choice: 2+ options, 2+ correct."}}},"QuestionDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"category_id":{"type":"string","format":"uuid"},"question_family_id":{"type":"string","format":"uuid","nullable":true},"prompt":{"type":"string"},"question_type":{"type":"string","enum":["true_false","single_choice","multiple_choice"]},"difficulty":{"type":"integer"},"options":{"type":"array","items":{"$ref":"#/components/schemas/QuestionOption"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ProjectCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"workspace_id":{"type":"string","format":"uuid","description":"The workspace to create the project in (from list_workspaces). Required when you can create in more than one workspace: ask the user which one rather than choosing for them. Projects in a team workspace are shared with its members."}}},"ProjectUpdate":{"type":"object","minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000}}},"QuizCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"question_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Array order becomes sort_order. Each question must belong to the same project and may appear only once."}}},"QuizUpdate":{"type":"object","minProperties":1,"description":"Sending question_ids replaces the entire set.","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"question_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Array order becomes sort_order. Each question must belong to the same project and may appear only once."}}},"QuizSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"question_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"QuizDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"project_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"questions":{"type":"array","items":{"$ref":"#/components/schemas/QuestionDetail"},"description":"In sort_order."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"WorkspaceRef":{"type":"object","description":"The workspace a project belongs to.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"kind":{"type":"string","enum":["personal","team"]}},"required":["id","name","kind"]},"Workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"kind":{"type":"string","enum":["personal","team"],"description":"personal: only you. team: projects created here are shared with its members."},"role":{"type":"string","enum":["admin","author","learner"]},"can_create_projects":{"type":"boolean"}},"required":["id","name","kind","role","can_create_projects"]},"CourseSource":{"type":"object","description":"A document the course was built from. Use this rather than writing the source into the description.","properties":{"title":{"type":"string","maxLength":200,"description":"The document's name, e.g. \"Proposales Onboarding Hub (Notion)\""},"url":{"type":"string","format":"uri","description":"http(s) link to the document"},"last_edited":{"type":"string","format":"date","description":"When the document was last edited, as of building the course (YYYY-MM-DD)"}},"required":["title"]},"TrashItem":{"type":"object","properties":{"kind":{"type":"string","enum":["course","module","lesson","block","question","quiz","category","question_family"]},"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"context":{"type":["string","null"],"description":"Where it lived, e.g. the course and module of a deleted lesson"},"deleted_at":{"type":"string","format":"date-time"},"deleted_by_name":{"type":["string","null"]}}},"TrashRestore":{"type":"object","required":["kind","id"],"properties":{"kind":{"type":"string","enum":["course","module","lesson","block","question","quiz","category","question_family"]},"id":{"type":"string","format":"uuid"}}},"DeletedProject":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"workspace":{"$ref":"#/components/schemas/WorkspaceRef"},"deleted_at":{"type":"string","format":"date-time"},"deleted_by_name":{"type":["string","null"]}}}}},"security":[{"BearerAuth":[]}],"paths":{"/workspaces":{"get":{"tags":["Workspaces"],"summary":"List your workspaces","description":"The workspaces you belong to, your role in each, and whether you can create projects there. Ask the user which one before creating a project.","operationId":"listWorkspaces","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects":{"get":{"summary":"List all projects","description":"Every project you can read, across all your workspaces, each with the workspace it belongs to.","operationId":"listProjects","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Projects"]},"post":{"tags":["Projects"],"summary":"Create a project","operationId":"createProject","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Invalid request body, or workspace_id missing while you can create in more than one workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"You cannot create projects in that workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Creates a project in a workspace. If the user has not said which workspace, call list_workspaces and ask them before creating. Without workspace_id this fails with 400 when there is more than one workspace to choose from, and lists them."}},"/projects/deleted":{"get":{"tags":["Trash"],"summary":"List deleted projects","description":"Deleted projects you could restore, newest first. Nothing is ever erased; restore one with restoreProject.","operationId":"listDeletedProjects","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/DeletedProject"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{projectId}":{"get":{"summary":"Get project with full hierarchy","description":"Retrieve a project with all courses, modules, lessons, and blocks","operationId":"getProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Project not found"}},"tags":["Projects"]},"patch":{"tags":["Projects"],"summary":"Update a project","operationId":"updateProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Projects"],"summary":"Delete a project","description":"Hides the project and everything in it from every read. Nothing is erased: listDeletedProjects shows it and restoreProject brings it back.","operationId":"deleteProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{projectId}/restore":{"post":{"tags":["Trash"],"summary":"Restore a deleted project","description":"Brings back a deleted project and everything in it.","operationId":"restoreProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Restored","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not deleted, or not yours to restore","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{projectId}/trash":{"get":{"tags":["Trash"],"summary":"List a project's trash","description":"What was deleted in the project, newest first: one entry per delete, not every row it took with it (a deleted course is one entry, not the course plus its modules, lessons and blocks). Needs write access.","operationId":"listTrash","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TrashItem"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not writable by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{projectId}/trash/restore":{"post":{"tags":["Trash"],"summary":"Restore from a project's trash","description":"Brings back one item listed by listTrash, with everything deleted along with it. Things deleted separately before or after stay in the trash. 409 when its parent is still deleted (restore that first; the message names it) or when its name has been taken since.","operationId":"restoreFromTrash","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrashRestore"}}}},"responses":{"200":{"description":"Restored","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"kind":{"type":"string"},"id":{"type":"string","format":"uuid"}}}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not in this project's trash, or project not writable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Parent still deleted, or name taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}":{"get":{"summary":"Get course details","description":"Retrieve a specific course with all modules, lessons, and blocks","operationId":"getCourse","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Course"}}}},"401":{"description":"Unauthorized"},"403":{"description":"Unauthorized access to course"},"404":{"description":"Course not found"}},"tags":["Courses"]},"patch":{"tags":["Courses"],"summary":"Update a course","operationId":"updateCourse","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Course"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Courses"],"summary":"Delete a course","description":"Children are deleted with it. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it.","operationId":"deleteCourse","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}/publish":{"post":{"tags":["Courses"],"summary":"Publish a course","description":"Makes a course visible to learners. Publishing a draft course also publishes every draft lesson in it; on a course that is already published, it publishes the lessons that are still drafts. Not available over MCP: publishing is a person's decision.","operationId":"publishCourse","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Course"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not the caller's to edit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}/unpublish":{"post":{"tags":["Courses"],"summary":"Unpublish a course","description":"Hides a course from learners. Its lessons keep their own state and learners keep their progress, so publishing it again brings back what was out before. Not available over MCP.","operationId":"unpublishCourse","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Unpublished","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Course"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not the caller's to edit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/quizzes/{quizId}/submit":{"post":{"summary":"Submit quiz attempt","description":"Submit answers to a quiz and get scoring","operationId":"submitQuizAttempt","parameters":[{"name":"quizId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizAttemptSubmission"}}}},"responses":{"201":{"description":"Quiz attempt submitted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizAttemptResult"}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"403":{"description":"Unauthorized access to quiz"},"404":{"description":"Quiz not found"}},"tags":["Quizzes"]}},"/api-keys":{"get":{"summary":"List API keys","description":"Get all active API keys for the authenticated user (keys are masked)","operationId":"listApiKeys","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}},"401":{"description":"Unauthorized"}},"tags":["API Keys"]},"post":{"summary":"Create new API key","description":"Mint an additional key using an existing one, for rotation. The first key must be created from the Account page in the web app.","operationId":"createApiKey","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}}},"responses":{"201":{"description":"API key created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"}},"tags":["API Keys"]}},"/api-keys/{keyId}":{"delete":{"summary":"Delete API key","description":"Deactivate an API key","operationId":"deleteApiKey","parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"API key deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"API key not found"}},"tags":["API Keys"]}},"/projects/{projectId}/courses":{"get":{"tags":["Courses"],"summary":"List courses in a project","operationId":"listCourses","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","description":"Page size. Default 500, at most 500.","schema":{"type":"integer","minimum":1,"default":500,"maximum":500}},{"name":"offset","in":"query","description":"Rows to skip. Use the previous page's offset plus its limit while has_more is true.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Courses ordered by position","content":{"application/json":{"schema":{"type":"object","properties":{"courses":{"type":"array","items":{"$ref":"#/components/schemas/Course"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Courses"],"summary":"Create a course","operationId":"createCourse","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseCreate"}}}},"responses":{"201":{"description":"Course created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Course"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/courses/{courseId}/modules":{"get":{"tags":["Modules"],"summary":"List modules in a course","operationId":"listModules","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","description":"Page size. Default 500, at most 500.","schema":{"type":"integer","minimum":1,"default":500,"maximum":500}},{"name":"offset","in":"query","description":"Rows to skip. Use the previous page's offset plus its limit while has_more is true.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Modules ordered by position","content":{"application/json":{"schema":{"type":"object","properties":{"modules":{"type":"array","items":{"$ref":"#/components/schemas/Module"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Modules"],"summary":"Create a module","operationId":"createModule","parameters":[{"name":"courseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleCreate"}}}},"responses":{"201":{"description":"Module created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Module"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/modules/{moduleId}":{"get":{"tags":["Modules"],"summary":"Get a module","operationId":"getModule","parameters":[{"name":"moduleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Module"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Modules"],"summary":"Update a module","operationId":"updateModule","parameters":[{"name":"moduleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Module"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Modules"],"summary":"Delete a module","description":"Children are deleted with it. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it.","operationId":"deleteModule","parameters":[{"name":"moduleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/modules/{moduleId}/lessons":{"get":{"tags":["Lessons"],"summary":"List lessons in a module","operationId":"listLessons","parameters":[{"name":"moduleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","description":"Page size. Default 500, at most 500.","schema":{"type":"integer","minimum":1,"default":500,"maximum":500}},{"name":"offset","in":"query","description":"Rows to skip. Use the previous page's offset plus its limit while has_more is true.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Lessons ordered by position","content":{"application/json":{"schema":{"type":"object","properties":{"lessons":{"type":"array","items":{"$ref":"#/components/schemas/Lesson"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Lessons"],"summary":"Create a lesson","operationId":"createLesson","parameters":[{"name":"moduleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LessonCreate"}}}},"responses":{"201":{"description":"Lesson created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lesson"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/lessons/{lessonId}":{"get":{"tags":["Lessons"],"summary":"Get a lesson","operationId":"getLesson","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lesson"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Lessons"],"summary":"Update a lesson","operationId":"updateLesson","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LessonUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lesson"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Lessons"],"summary":"Delete a lesson","description":"Children are deleted with it. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it.","operationId":"deleteLesson","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/lessons/{lessonId}/publish":{"post":{"tags":["Lessons"],"summary":"Publish a lesson","description":"Makes a draft lesson visible to learners, once its course is published too. Not available over MCP.","operationId":"publishLesson","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lesson"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not the caller's to edit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/lessons/{lessonId}/unpublish":{"post":{"tags":["Lessons"],"summary":"Unpublish a lesson","description":"Turns a lesson back into a draft, hidden from learners. Not available over MCP.","operationId":"unpublishLesson","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Unpublished","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lesson"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not the caller's to edit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/lessons/{lessonId}/blocks":{"get":{"tags":["Blocks"],"summary":"List blocks in a lesson","operationId":"listBlocks","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","description":"Page size. Default 500, at most 500.","schema":{"type":"integer","minimum":1,"default":500,"maximum":500}},{"name":"offset","in":"query","description":"Rows to skip. Use the previous page's offset plus its limit while has_more is true.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Blocks ordered by position","content":{"application/json":{"schema":{"type":"object","properties":{"blocks":{"type":"array","items":{"$ref":"#/components/schemas/Block"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Blocks"],"summary":"Create a block","operationId":"createBlock","parameters":[{"name":"lessonId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockCreate"}}}},"responses":{"201":{"description":"Block created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/blocks/{blockId}":{"get":{"tags":["Blocks"],"summary":"Get a block","operationId":"getBlock","parameters":[{"name":"blockId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Blocks"],"summary":"Update a block","operationId":"updateBlock","parameters":[{"name":"blockId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Blocks"],"summary":"Delete a block","description":"Children are deleted with it. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it.","operationId":"deleteBlock","parameters":[{"name":"blockId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{projectId}/categories":{"get":{"tags":["Categories"],"summary":"List category in a project","operationId":"listCategories","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","description":"Page size. Default 500, at most 500.","schema":{"type":"integer","minimum":1,"default":500,"maximum":500}},{"name":"offset","in":"query","description":"Rows to skip. Use the previous page's offset plus its limit while has_more is true.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Ordered by name","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/Category"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Categories"],"summary":"Create a category","operationId":"createCategory","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryWrite"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/categories/{categoryId}":{"get":{"tags":["Categories"],"summary":"Get a category","operationId":"getCategory","parameters":[{"name":"categoryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Categories"],"summary":"Rename a category","operationId":"updateCategory","parameters":[{"name":"categoryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryWrite"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Categories"],"summary":"Delete a category","operationId":"deleteCategory","parameters":[{"name":"categoryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Fails with 409 while live questions use it. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it."}},"/projects/{projectId}/question-families":{"get":{"tags":["Question families"],"summary":"List question family in a project","operationId":"listQuestionFamilies","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","description":"Page size. Default 500, at most 500.","schema":{"type":"integer","minimum":1,"default":500,"maximum":500}},{"name":"offset","in":"query","description":"Rows to skip. Use the previous page's offset plus its limit while has_more is true.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Ordered by name","content":{"application/json":{"schema":{"type":"object","properties":{"question_families":{"type":"array","items":{"$ref":"#/components/schemas/Category"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Question families"],"summary":"Create a question family","operationId":"createQuestionFamily","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryWrite"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/question-families/{familyId}":{"get":{"tags":["Question families"],"summary":"Get a question family","operationId":"getQuestionFamily","parameters":[{"name":"familyId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Question families"],"summary":"Rename a question family","operationId":"updateQuestionFamily","parameters":[{"name":"familyId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryWrite"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Category"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Question families"],"summary":"Delete a question family","operationId":"deleteQuestionFamily","parameters":[{"name":"familyId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Questions in the family are unlinked from it. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it."}},"/projects/{projectId}/questions":{"get":{"tags":["Questions"],"summary":"List questions in a project","operationId":"listQuestions","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"default":100,"maximum":500}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"category_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter to one category."}],"responses":{"200":{"description":"Newest first","content":{"application/json":{"schema":{"type":"object","properties":{"questions":{"type":"array","items":{"$ref":"#/components/schemas/QuestionDetail"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"Whether another page follows this one."}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Questions"],"summary":"Create a question with its options","description":"Options are supplied inline; a question is never created without a valid set.","operationId":"createQuestion","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionDetail"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/questions/{questionId}":{"get":{"tags":["Questions"],"summary":"Get a question","operationId":"getQuestion","parameters":[{"name":"questionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionDetail"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Questions"],"summary":"Update a question","operationId":"updateQuestion","description":"Sending options replaces the whole set. The merged question is revalidated.","parameters":[{"name":"questionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionDetail"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Questions"],"summary":"Delete a question","description":"Fails with 409 while a lesson block references it. Quiz membership is removed automatically. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it.","operationId":"deleteQuestion","parameters":[{"name":"questionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/projects/{projectId}/quizzes":{"get":{"tags":["Quizzes"],"summary":"List quizzes in a project","operationId":"listQuizzes","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Newest first, with a question count rather than full questions","content":{"application/json":{"schema":{"type":"object","properties":{"quizzes":{"type":"array","items":{"$ref":"#/components/schemas/QuizSummary"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Quizzes"],"summary":"Create a quiz","operationId":"createQuiz","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizDetail"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/quizzes/{quizId}":{"get":{"tags":["Quizzes"],"summary":"Get a quiz with its questions","operationId":"getQuiz","parameters":[{"name":"quizId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Questions in sort_order, with options","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizDetail"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Quizzes"],"summary":"Update a quiz","operationId":"updateQuiz","description":"Sending question_ids replaces the whole set, in array order.","parameters":[{"name":"quizId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizUpdate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuizDetail"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Quizzes"],"summary":"Delete a quiz","description":"Fails with 409 while a lesson block references it. Recorded attempts are kept but hidden with the quiz. Nothing is erased: it goes to the project's trash (listTrash) and can be restored with everything deleted along with it.","operationId":"deleteQuiz","parameters":[{"name":"quizId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not owned by the caller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicts with existing data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"API not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"tags":[{"name":"Workspaces","description":"The spaces projects live in: your personal one and any teams"},{"name":"Projects","description":"Top-level study spaces"},{"name":"Courses","description":"Courses inside a project"},{"name":"Modules","description":"Modules inside a course"},{"name":"Lessons","description":"Lessons inside a module"},{"name":"Blocks","description":"Content units inside a lesson"},{"name":"Quizzes","description":"Quiz attempts and scoring"},{"name":"API Keys","description":"Manage credentials for this API"},{"name":"Questions","description":"Authoring questions and their answer options"},{"name":"Categories","description":"Categories questions are filed under (required on every question)"},{"name":"Question families","description":"Optional grouping used for AI question variants"},{"name":"Trash","description":"Nothing is ever erased. Deletes hide content and stamp who deleted it; these list and restore it."}]}