Remove AUDIO_CLIP from the stimulus component catalog and use AUDIO_PROMPT for all question-side audio. Update integration, practice, and Postman docs accordingly. Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
Practice Creation API Guide (Lesson Scope)
This guide provides the full step-by-step API process to create a lesson practice when using:
- system-defined question types (
MCQ,TRUE_FALSE,SHORT_ANSWER,AUDIO) - dynamic question types (
DYNAMICwithquestion_type_definition_id+dynamic_payload)
All endpoints below are relative to /api/v1 and require bearer authentication.
Standard Response Envelope
Most successful responses follow:
{
"message": "Human-readable message",
"data": {},
"success": true,
"status_code": 200,
"metadata": null
}
Most errors follow:
{
"message": "Error summary",
"error": "Detailed reason"
}
Required Permissions
At minimum, your role should have:
questions.createquestion_sets.createquestion_set_items.addpractices.create
If you create/update dynamic definitions:
questions.updatequestions.delete(if you also delete definitions)
End-to-End Flow
- (Optional) Upload media assets
- Create question(s):
- system-defined path, or
- dynamic path (definition + question)
- Create
PRACTICEquestion set - Add question(s) to the set
- Create lesson practice linked to that set
- Verify under lesson
Step 0 (Optional): Upload Media
Use this when question content references audio/image/PDF URLs (e.g. dynamic IMAGE, AUDIO_PROMPT, or PDF_ATTACHMENT stimulus).
Endpoint
POST /files/upload (multipart form-data)
Form fields
file: binarymedia_type:image,audio,video, orpdf(PDF is stored in MinIO; response includes presignedurlandobject_key)
Example success response (shape)
{
"message": "Media uploaded successfully",
"data": {
"url": "https://your-host/static/uploads/audio/abc.mp3",
"object_key": "audio/abc.mp3"
},
"success": true,
"status_code": 201
}
Common errors
400invalid media type/content type500upload/storage failure
Capture and reuse:
data.url(or equivalent resolved file URL)
Step 1A: Create System-Defined Question(s)
Endpoint
POST /questions
Request example (MCQ)
{
"question_text": "Choose the correct sentence.",
"question_type": "MCQ",
"difficulty_level": "EASY",
"points": 1,
"status": "PUBLISHED",
"options": [
{ "option_text": "He go to school.", "is_correct": false },
{ "option_text": "He goes to school.", "is_correct": true }
]
}
Request example (SHORT_ANSWER)
{
"question_text": "Write one sentence using the word 'improve'.",
"question_type": "SHORT_ANSWER",
"difficulty_level": "MEDIUM",
"points": 2,
"status": "PUBLISHED",
"short_answers": [
{ "acceptable_answer": "I want to improve my English.", "match_type": "CASE_INSENSITIVE" }
]
}
Example success response (shape)
{
"message": "Question created successfully",
"data": {
"id": 456,
"question_text": "Choose the correct sentence.",
"question_type": "MCQ",
"status": "PUBLISHED"
},
"success": true,
"status_code": 201
}
Common errors
400validation/body errors500create failure
Capture:
data.idasquestion_id
Step 1B: Dynamic Question Path
If you use dynamic questions, follow these sub-steps.
1B.1 Validate component-kind selection (optional but recommended)
Endpoint
POST /questions/validate-question-type-definition
Request
{
"stimulus_component_kinds": ["QUESTION_TEXT", "AUDIO_PROMPT", "IMAGE"],
"response_component_kinds": ["AUDIO_RESPONSE", "TEXT_INPUT"]
}
Success response
{
"message": "Question type definition is valid",
"data": { "valid": true }
}
Error response example
{
"message": "Invalid question type definition",
"error": "response: unknown component kind \"AUDIO_PROMPT\""
}
1B.2 Create or reuse a dynamic type definition
Endpoint
POST /questions/type-definitions
Request
{
"key": "dialogue_audio_avatar_v1",
"display_name": "Dialogue Audio + Avatar",
"description": "Question text + prompt audio + two avatar images, with audio/text answer",
"stimulus_component_kinds": ["QUESTION_TEXT", "AUDIO_PROMPT", "IMAGE"],
"response_component_kinds": ["AUDIO_RESPONSE", "TEXT_INPUT"],
"stimulus_schema": [
{ "id": "question_text", "kind": "QUESTION_TEXT", "required": true },
{ "id": "prompt_audio", "kind": "AUDIO_PROMPT", "required": true },
{ "id": "speaker_a_avatar", "kind": "IMAGE", "required": true },
{ "id": "speaker_b_avatar", "kind": "IMAGE", "required": true }
],
"response_schema": [
{ "id": "answer_audio", "kind": "AUDIO_RESPONSE", "required": true },
{ "id": "answer_text", "kind": "TEXT_INPUT", "required": true }
],
"status": "ACTIVE"
}
Success response example
{
"message": "Question type definition created",
"data": {
"id": 123,
"key": "dialogue_audio_avatar_v1",
"status": "ACTIVE"
}
}
Common errors
400invalid schema/kinds/mapping500unexpected persistence errors
Capture:
data.idasquestion_type_definition_id
1B.3 Create dynamic question
Endpoint
POST /questions
Request
{
"question_type": "DYNAMIC",
"question_type_definition_id": 123,
"difficulty_level": "MEDIUM",
"points": 2,
"status": "PUBLISHED",
"dynamic_payload": {
"stimulus": [
{ "id": "question_text", "kind": "QUESTION_TEXT", "value": "Respond to the conversation." },
{ "id": "prompt_audio", "kind": "AUDIO_PROMPT", "value": "https://cdn.example.com/audio/prompt-1.mp3" },
{ "id": "speaker_a_avatar", "kind": "IMAGE", "value": "https://cdn.example.com/images/a.webp" },
{ "id": "speaker_b_avatar", "kind": "IMAGE", "value": "https://cdn.example.com/images/b.webp" }
],
"response": [
{ "id": "answer_audio", "kind": "AUDIO_RESPONSE", "value": { "instructions": "Record your answer" } },
{ "id": "answer_text", "kind": "TEXT_INPUT", "value": { "placeholder": "Type your answer" } }
]
}
}
Success response example
{
"message": "Question created successfully",
"data": {
"id": 789,
"question_type": "DYNAMIC",
"question_type_definition_id": 123,
"status": "PUBLISHED"
},
"success": true,
"status_code": 201
}
Common errors
400missing/invaliddynamic_payload400missingquestion_type_definition_id500persistence failure
Capture:
data.idasquestion_id
Step 2: Create PRACTICE Question Set
Endpoint
POST /question-sets
Request
{
"title": "Lesson 12 - Practice Set",
"description": "Question set for lesson-level practice",
"set_type": "PRACTICE",
"owner_type": "LESSON",
"owner_id": 12,
"status": "PUBLISHED",
"shuffle_questions": false
}
Success response example
{
"message": "Question set created successfully",
"data": {
"id": 55,
"title": "Lesson 12 - Practice Set",
"set_type": "PRACTICE",
"owner_type": "LESSON",
"owner_id": 12,
"status": "PUBLISHED"
},
"success": true,
"status_code": 201
}
Common errors
400invalid input500create failure
Capture:
data.idasset_id
Step 3: Add Question(s) to Set
Run this once per question_id.
Endpoint
POST /question-sets/:setId/questions
Request
{
"question_id": 456,
"display_order": 1
}
Success response example
{
"message": "Question added to set successfully",
"data": {
"id": 901,
"set_id": 55,
"question_id": 456,
"display_order": 1
},
"success": true,
"status_code": 201
}
Common errors
400invalidsetIdor body500link/create failure
Step 4: Create Lesson Practice
This creates the practice record scoped to lesson.
Endpoint
POST /practices
Request
title is optional; omit it or use an empty string to create a practice without a display title (stored as empty).
Include publish_status: DRAFT to hide the practice from subscribed learners until you set it to PUBLISHED (via create or PUT /practices/:id). Omit the field or send PUBLISHED to go live immediately (backward compatible).
{
"parent_kind": "LESSON",
"parent_id": 12,
"title": "Lesson 12 Conversation Drill",
"story_description": "A short two-speaker scenario.",
"story_image": "https://cdn.example.com/images/story.webp",
"question_set_id": 55,
"quick_tips": "Listen carefully before answering.",
"publish_status": "DRAFT"
}
Success response example
{
"message": "Practice created successfully",
"data": {
"id": 37,
"parent_kind": "LESSON",
"parent_id": 12,
"title": "Lesson 12 Conversation Drill",
"question_set_id": 55
},
"success": true,
"status_code": 201
}
Common errors
400validation failed / invalid parent kind404lesson not found404question set not found500create failure
Capture:
data.idaspractice_id
Step 5: Verify Practice Under Lesson
Endpoint
GET /lessons/:id/practices
Example:
GET /lessons/12/practices
Success response example
{
"message": "Practices retrieved successfully",
"data": {
"practices": [
{
"id": 37,
"parent_kind": "LESSON",
"parent_id": 12,
"title": "Lesson 12 Conversation Drill",
"question_set_id": 55
}
],
"total_count": 1,
"limit": 20,
"offset": 0
},
"success": true,
"status_code": 200
}
Optional Learner Completion Step
Endpoint
POST /progress/practices/:id/complete
Use practice_id as :id for current behavior.
Success response example
{
"message": "Practice completed",
"success": true,
"status_code": 200
}
Common errors
403sequence gating violation404practice not found500completion/persistence failure
Quick Checklist (IDs to Carry Forward)
- From question create:
question_id - From dynamic definition create (if used):
question_type_definition_id - From question set create:
set_id - From practice create:
practice_id
Notes and Pitfalls
- For dynamic questions,
question_typemust beDYNAMIC. - For dynamic questions, both
question_type_definition_idanddynamic_payloadare required. AUDIO_PROMPTis stimulus-side; response-side audio usesAUDIO_RESPONSE.question_set_idinPOST /practicesmust reference an existing set.- For lesson practice always use:
parent_kind = "LESSON"parent_id = <lesson_id>
- Publish questions and question set (
status = "PUBLISHED") if learners must complete immediately.