Vana Public API

REST API for accessing courses, quizzes, and learning content. The 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.

v1.0.0OpenAPI 3.0.058 endpointsMIT

Let an agent write the course

Connect your agent and it gets all 58 endpoints as tools, plus a guide to every block type and the traps that fail silently. Point it at an outline and let it build.

Add Vana as an MCP server, once:

claude mcp add --transport http vana https://vana.proposales.com/mcp \ --header "Authorization: Bearer $VANA_KEY"

Create a key on the Account page under API keys, and keep it in VANA_KEY rather than in a file you might commit.

Base URL

https://vana.proposales.com/api

Authentication

API key authentication (format: Bearer vana_xxxxx)

curl -H "Authorization: Bearer vana_…" https://vana.proposales.com/api/projects

Workspaces

The spaces projects live in: your personal one and any teams

  • get/workspacesList your workspacesShow

    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.

    Responses

    • 200Successful responseobject
    • 401UnauthorizedError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/workspaces

Projects

Top-level study spaces

  • get/projectsList all projectsShow

    Every project you can read, across all your workspaces, each with the workspace it belongs to.

    Responses

    • 200Successful responseobject
    • 401UnauthorizedError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects
  • post/projectsCreate a projectShow

    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.

    Request bodyProjectCreate · required

    name*string
    descriptionstring
    workspace_idstring (uuid)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.

    Responses

    • 201CreatedProject
    • 400Invalid request body, or workspace_id missing while you can create in more than one workspaceError
    • 401UnauthorizedError
    • 403You cannot create projects in that workspaceError
    • 404Workspace not foundError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects

    This runs against your real data, not a sandbox.

  • get/projects/{projectId}Get project with full hierarchyShow

    Retrieve a project with all courses, modules, lessons, and blocks

    Parameters

    projectId
    pathstring (uuid)required

    Responses

    • 200Successful responseProject
    • 401Unauthorized
    • 404Project not found

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}

    Fill the path parameters to send.

  • patch/projects/{projectId}Update a projectShow

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyProjectUpdate · required

    namestring
    descriptionstring

    Responses

    • 200UpdatedProject
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/projects/{projectId}Delete a projectShow

    Hides the project and everything in it from every read. Nothing is erased: listDeletedProjects shows it and restoreProject brings it back.

    Parameters

    projectId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Courses

Courses inside a project

  • get/courses/{courseId}Get course detailsShow

    Retrieve a specific course with all modules, lessons, and blocks

    Parameters

    courseId
    pathstring (uuid)required

    Responses

    • 200Successful responseCourse
    • 401Unauthorized
    • 403Unauthorized access to course
    • 404Course not found

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}

    Fill the path parameters to send.

  • patch/courses/{courseId}Update a courseShow

    Parameters

    courseId
    pathstring (uuid)required

    Request bodyCourseUpdate · required

    titlestring
    descriptionstring
    sourcesCourseSource[]The documents this course was built from. Replaces the whole list when given on update.
    positionintegerSort order within the parent. Appended to the end when omitted.

    Responses

    • 200UpdatedCourse
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/courses/{courseId}Delete a courseShow

    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.

    Parameters

    courseId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

  • post/courses/{courseId}/publishPublish a courseShow

    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.

    Parameters

    courseId
    pathstring (uuid)required

    Responses

    • 200PublishedCourse
    • 401UnauthorizedError
    • 404Not found, or not the caller's to editError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}/publish

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • post/courses/{courseId}/unpublishUnpublish a courseShow

    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.

    Parameters

    courseId
    pathstring (uuid)required

    Responses

    • 200UnpublishedCourse
    • 401UnauthorizedError
    • 404Not found, or not the caller's to editError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}/unpublish

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/projects/{projectId}/coursesList courses in a projectShow

    Parameters

    projectId
    pathstring (uuid)required
    limit
    queryinteger— Page size. Default 500, at most 500.
    offset
    queryinteger— Rows to skip. Use the previous page's offset plus its limit while has_more is true.

    Responses

    • 200Courses ordered by positionobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/courses

    Fill the path parameters to send.

  • post/projects/{projectId}/coursesCreate a courseShow

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyCourseCreate · required

    title*string
    descriptionstring
    sourcesCourseSource[]The documents this course was built from. Replaces the whole list when given on update.
    positionintegerSort order within the parent. Appended to the end when omitted.

    Responses

    • 201Course createdCourse
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/courses

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

Modules

Modules inside a course

  • get/courses/{courseId}/modulesList modules in a courseShow

    Parameters

    courseId
    pathstring (uuid)required
    limit
    queryinteger— Page size. Default 500, at most 500.
    offset
    queryinteger— Rows to skip. Use the previous page's offset plus its limit while has_more is true.

    Responses

    • 200Modules ordered by positionobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}/modules

    Fill the path parameters to send.

  • post/courses/{courseId}/modulesCreate a moduleShow

    Parameters

    courseId
    pathstring (uuid)required

    Request bodyModuleCreate · required

    title*string
    positionintegerSort order within the parent. Appended to the end when omitted.

    Responses

    • 201Module createdModule
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/courses/{courseId}/modules

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/modules/{moduleId}Get a moduleShow

    Parameters

    moduleId
    pathstring (uuid)required

    Responses

    • 200SuccessModule
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/modules/{moduleId}

    Fill the path parameters to send.

  • patch/modules/{moduleId}Update a moduleShow

    Parameters

    moduleId
    pathstring (uuid)required

    Request bodyModuleUpdate · required

    titlestring
    positionintegerSort order within the parent. Appended to the end when omitted.

    Responses

    • 200UpdatedModule
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/modules/{moduleId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/modules/{moduleId}Delete a moduleShow

    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.

    Parameters

    moduleId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/modules/{moduleId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Lessons

Lessons inside a module

  • get/modules/{moduleId}/lessonsList lessons in a moduleShow

    Parameters

    moduleId
    pathstring (uuid)required
    limit
    queryinteger— Page size. Default 500, at most 500.
    offset
    queryinteger— Rows to skip. Use the previous page's offset plus its limit while has_more is true.

    Responses

    • 200Lessons ordered by positionobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/modules/{moduleId}/lessons

    Fill the path parameters to send.

  • post/modules/{moduleId}/lessonsCreate a lessonShow

    Parameters

    moduleId
    pathstring (uuid)required

    Request bodyLessonCreate · required

    title*string
    positionintegerSort order within the parent. Appended to the end when omitted.

    Responses

    • 201Lesson createdLesson
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/modules/{moduleId}/lessons

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/lessons/{lessonId}Get a lessonShow

    Parameters

    lessonId
    pathstring (uuid)required

    Responses

    • 200SuccessLesson
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}

    Fill the path parameters to send.

  • patch/lessons/{lessonId}Update a lessonShow

    Parameters

    lessonId
    pathstring (uuid)required

    Request bodyLessonUpdate · required

    titlestring
    positionintegerSort order within the parent. Appended to the end when omitted.

    Responses

    • 200UpdatedLesson
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/lessons/{lessonId}Delete a lessonShow

    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.

    Parameters

    lessonId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

  • post/lessons/{lessonId}/publishPublish a lessonShow

    Makes a draft lesson visible to learners, once its course is published too. Not available over MCP.

    Parameters

    lessonId
    pathstring (uuid)required

    Responses

    • 200PublishedLesson
    • 401UnauthorizedError
    • 404Not found, or not the caller's to editError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}/publish

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • post/lessons/{lessonId}/unpublishUnpublish a lessonShow

    Turns a lesson back into a draft, hidden from learners. Not available over MCP.

    Parameters

    lessonId
    pathstring (uuid)required

    Responses

    • 200UnpublishedLesson
    • 401UnauthorizedError
    • 404Not found, or not the caller's to editError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}/unpublish

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

Blocks

Content units inside a lesson

  • get/lessons/{lessonId}/blocksList blocks in a lessonShow

    Parameters

    lessonId
    pathstring (uuid)required
    limit
    queryinteger— Page size. Default 500, at most 500.
    offset
    queryinteger— Rows to skip. Use the previous page's offset plus its limit while has_more is true.

    Responses

    • 200Blocks ordered by positionobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}/blocks

    Fill the path parameters to send.

  • post/lessons/{lessonId}/blocksCreate a blockShow

    Parameters

    lessonId
    pathstring (uuid)required

    Request bodyBlockCreate · required

    type*string"text" | "image" | "diagram" | "video" | "example" | "question" | "quiz" | "scenario" | "glossary" | "interactive"
    positionintegerSort order within the parent. Appended to the end when omitted.
    contentobjectPer-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_idstring (uuid)Required when type is 'question'. Must belong to the same project.
    question_rolestringRequired when type is 'question'. "practice" | "checkpoint" | "assessment" | "review"
    quiz_idstring (uuid)Required when type is 'quiz'. Must belong to the same project.

    Responses

    • 201Block createdBlock
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/lessons/{lessonId}/blocks

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/blocks/{blockId}Get a blockShow

    Parameters

    blockId
    pathstring (uuid)required

    Responses

    • 200SuccessBlock
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/blocks/{blockId}

    Fill the path parameters to send.

  • patch/blocks/{blockId}Update a blockShow

    Parameters

    blockId
    pathstring (uuid)required

    Request bodyBlockUpdate · required

    positionintegerSort order within the parent. Appended to the end when omitted.
    contentobjectPer-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_idstring (uuid)Required when type is 'question'. Must belong to the same project.
    question_rolestringRequired when type is 'question'. "practice" | "checkpoint" | "assessment" | "review"
    quiz_idstring (uuid)Required when type is 'quiz'. Must belong to the same project.

    Responses

    • 200UpdatedBlock
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/blocks/{blockId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/blocks/{blockId}Delete a blockShow

    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.

    Parameters

    blockId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/blocks/{blockId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Quizzes

Quiz attempts and scoring

  • post/quizzes/{quizId}/submitSubmit quiz attemptShow

    Submit answers to a quiz and get scoring

    Parameters

    quizId
    pathstring (uuid)required

    Request bodyQuizAttemptSubmission · required

    answers*QuizAttemptAnswer[]

    Responses

    • 201Quiz attempt submitted successfullyQuizAttemptResult
    • 400Invalid request
    • 401Unauthorized
    • 403Unauthorized access to quiz
    • 404Quiz not found

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/quizzes/{quizId}/submit

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/projects/{projectId}/quizzesList quizzes in a projectShow

    Parameters

    projectId
    pathstring (uuid)required

    Responses

    • 200Newest first, with a question count rather than full questionsobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/quizzes

    Fill the path parameters to send.

  • post/projects/{projectId}/quizzesCreate a quizShow

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyQuizCreate · required

    title*string
    question_idsstring (uuid)[]Array order becomes sort_order. Each question must belong to the same project and may appear only once.

    Responses

    • 201CreatedQuizDetail
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/quizzes

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/quizzes/{quizId}Get a quiz with its questionsShow

    Parameters

    quizId
    pathstring (uuid)required

    Responses

    • 200Questions in sort_order, with optionsQuizDetail
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/quizzes/{quizId}

    Fill the path parameters to send.

  • patch/quizzes/{quizId}Update a quizShow

    Sending question_ids replaces the whole set, in array order.

    Parameters

    quizId
    pathstring (uuid)required

    Request bodyQuizUpdate · required

    titlestring
    question_idsstring (uuid)[]Array order becomes sort_order. Each question must belong to the same project and may appear only once.

    Responses

    • 200UpdatedQuizDetail
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/quizzes/{quizId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/quizzes/{quizId}Delete a quizShow

    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.

    Parameters

    quizId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/quizzes/{quizId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

API Keys

Manage credentials for this API

  • get/api-keysList API keysShow

    Get all active API keys for the authenticated user (keys are masked)

    Responses

    • 200Successful responseobject
    • 401Unauthorized

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/api-keys
  • post/api-keysCreate new API keyShow

    Mint an additional key using an existing one, for rotation. The first key must be created from the Account page in the web app.

    Request bodyCreateApiKeyRequest · required

    name*string

    Responses

    • 201API key created successfullyCreateApiKeyResponse
    • 400Invalid request
    • 401Unauthorized

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/api-keys

    This runs against your real data, not a sandbox.

  • delete/api-keys/{keyId}Delete API keyShow

    Deactivate an API key

    Parameters

    keyId
    pathstring (uuid)required

    Responses

    • 200API key deleted successfullyobject
    • 401Unauthorized
    • 404API key not found

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/api-keys/{keyId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Questions

Authoring questions and their answer options

  • get/projects/{projectId}/questionsList questions in a projectShow

    Parameters

    projectId
    pathstring (uuid)required
    limit
    queryinteger
    offset
    queryinteger
    category_id
    querystring (uuid)— Filter to one category.

    Responses

    • 200Newest firstobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/questions

    Fill the path parameters to send.

  • post/projects/{projectId}/questionsCreate a question with its optionsShow

    Options are supplied inline; a question is never created without a valid set.

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyQuestionCreate · required

    category_id*string (uuid)Required. Must belong to the same project.
    question_family_idstring (uuid) | nullOptional grouping for AI variants. Must belong to the same project.
    prompt*string
    question_type*string"true_false" | "single_choice" | "multiple_choice"
    difficulty*integer
    options*QuestionOptionInput[]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.

    Responses

    • 201CreatedQuestionDetail
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/questions

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/questions/{questionId}Get a questionShow

    Parameters

    questionId
    pathstring (uuid)required

    Responses

    • 200SuccessQuestionDetail
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/questions/{questionId}

    Fill the path parameters to send.

  • patch/questions/{questionId}Update a questionShow

    Sending options replaces the whole set. The merged question is revalidated.

    Parameters

    questionId
    pathstring (uuid)required

    Request bodyQuestionUpdate · required

    category_idstring (uuid)Required. Must belong to the same project.
    question_family_idstring (uuid) | nullOptional grouping for AI variants. Must belong to the same project.
    promptstring
    question_typestring"true_false" | "single_choice" | "multiple_choice"
    difficultyinteger
    optionsQuestionOptionInput[]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.

    Responses

    • 200UpdatedQuestionDetail
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/questions/{questionId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/questions/{questionId}Delete a questionShow

    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.

    Parameters

    questionId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/questions/{questionId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Categories

Categories questions are filed under (required on every question)

  • get/projects/{projectId}/categoriesList category in a projectShow

    Parameters

    projectId
    pathstring (uuid)required
    limit
    queryinteger— Page size. Default 500, at most 500.
    offset
    queryinteger— Rows to skip. Use the previous page's offset plus its limit while has_more is true.

    Responses

    • 200Ordered by nameobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/categories

    Fill the path parameters to send.

  • post/projects/{projectId}/categoriesCreate a categoryShow

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyCategoryWrite · required

    name*stringUnique within the project.

    Responses

    • 201CreatedCategory
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/categories

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/categories/{categoryId}Get a categoryShow

    Parameters

    categoryId
    pathstring (uuid)required

    Responses

    • 200SuccessCategory
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/categories/{categoryId}

    Fill the path parameters to send.

  • patch/categories/{categoryId}Rename a categoryShow

    Parameters

    categoryId
    pathstring (uuid)required

    Request bodyCategoryWrite · required

    name*stringUnique within the project.

    Responses

    • 200UpdatedCategory
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/categories/{categoryId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/categories/{categoryId}Delete a categoryShow

    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.

    Parameters

    categoryId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/categories/{categoryId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Question families

Optional grouping used for AI question variants

  • get/projects/{projectId}/question-familiesList question family in a projectShow

    Parameters

    projectId
    pathstring (uuid)required
    limit
    queryinteger— Page size. Default 500, at most 500.
    offset
    queryinteger— Rows to skip. Use the previous page's offset plus its limit while has_more is true.

    Responses

    • 200Ordered by nameobject
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/question-families

    Fill the path parameters to send.

  • post/projects/{projectId}/question-familiesCreate a question familyShow

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyCategoryWrite · required

    name*stringUnique within the project.

    Responses

    • 201CreatedCategory
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/question-families

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/question-families/{familyId}Get a question familyShow

    Parameters

    familyId
    pathstring (uuid)required

    Responses

    • 200SuccessCategory
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/question-families/{familyId}

    Fill the path parameters to send.

  • patch/question-families/{familyId}Rename a question familyShow

    Parameters

    familyId
    pathstring (uuid)required

    Request bodyCategoryWrite · required

    name*stringUnique within the project.

    Responses

    • 200UpdatedCategory
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/question-families/{familyId}

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • delete/question-families/{familyId}Delete a question familyShow

    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.

    Parameters

    familyId
    pathstring (uuid)required

    Responses

    • 200DeletedDeleteResult
    • 401UnauthorizedError
    • 404Not found, or not owned by the callerError
    • 409Conflicts with existing dataError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/question-families/{familyId}

    Fill the path parameters to send.

    This deletes real data in your account, and cascades to anything beneath it.

Trash

Nothing is ever erased. Deletes hide content and stamp who deleted it; these list and restore it.

  • get/projects/deletedList deleted projectsShow

    Deleted projects you could restore, newest first. Nothing is ever erased; restore one with restoreProject.

    Responses

    • 200Successful responseobject
    • 401UnauthorizedError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/deleted
  • post/projects/{projectId}/restoreRestore a deleted projectShow

    Brings back a deleted project and everything in it.

    Parameters

    projectId
    pathstring (uuid)required

    Responses

    • 200Restoredobject
    • 401UnauthorizedError
    • 404Not deleted, or not yours to restoreError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/restore

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.

  • get/projects/{projectId}/trashList a project's trashShow

    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.

    Parameters

    projectId
    pathstring (uuid)required

    Responses

    • 200Successful responseobject
    • 401UnauthorizedError
    • 404Not found, or not writable by the callerError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/trash

    Fill the path parameters to send.

  • post/projects/{projectId}/trash/restoreRestore from a project's trashShow

    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.

    Parameters

    projectId
    pathstring (uuid)required

    Request bodyTrashRestore · required

    kind*string"course" | "module" | "lesson" | "block" | "question" | "quiz" | "category" | "question_family"
    id*string (uuid)

    Responses

    • 200Restoredobject
    • 400Invalid request bodyError
    • 401UnauthorizedError
    • 404Not in this project's trash, or project not writableError
    • 409Parent still deleted, or name takenError
    • 503API not configuredError

    Try it

    Kept for this tab only, shared with the other endpoints on this page, and sent only to /api on this origin. Create one on the Account page.

    /api/projects/{projectId}/trash/restore

    Fill the path parameters to send.

    This runs against your real data, not a sandbox.