| name | courses |
| description | Full LMS course + quiz REST API for Dataspheres AI. Build a course (modules → lessons → graded quizzes), author quiz questions with correct answers + points, manage enrollment, track per-quiz results and the course gradebook, and issue verifiable certificates. Courses are pages-native — the student view is the course's overview page under /pages/:uri/:slug. |
| argument-hint | [create course | add module/lesson | author quiz | results | enroll | gradebook | certificate] |
Courses (LMS) — Complete REST Reference
A course is pages-native: it is a COURSE Page (the syllabus/overview) anchored to a root DocFolder. Modules are child folders, lessons are TUTORIAL pages, and a quiz is a lesson marked isGradedQuiz whose graded questions are SurveyQuestion rows carrying a correctAnswer + points. There is no separate /courses student namespace — learners read the course at /pages/:uri/:courseSlug (the folder index = overview, each lesson = a page in the pages TOC).
COURSE page (overview/syllabus) → /pages/:uri/:courseSlug
└── Module (folder)
└── Lesson (TUTORIAL page) → /pages/:uri/:lessonSlug
└── Quiz (lesson, isGradedQuiz=true)
└── Questions (correctAnswer + points)
All endpoints below are under /api/v1/dataspheres/:uri/courses. Identify items by id:
coursePageId — the COURSE page id (from list courses / create).
moduleId — a module folder id (from the course TOC).
lessonPageId — a lesson page id; a quiz is just a lesson.
questionId — a quiz question id (from list quiz questions).
Configuration
Load from ~/.dataspheres.env (see the dataspheres-api skill for the full setup):
export $(grep -v '^#' ~/.dataspheres.env | xargs)
Authoring endpoints require MODERATOR+; learner endpoints require PARTICIPANT. Mutating tools carry requiredScope: ingest:pages.
B="$DATASPHERES_BASE_URL/api/v1/dataspheres/$DATASPHERES_DEFAULT_URI/courses"
AUTH="Authorization: Bearer $DATASPHERES_API_KEY"
JSON="Content-Type: application/json"
1. Build the curriculum
CID=$(curl -s -X POST "$B" -H "$AUTH" -H "$JSON" -d '{
"title": "Prompt Engineering Essentials",
"syllabus": "<h2>Prompt Engineering Essentials</h2><p>Treat prompts as specifications.</p>",
"status": "DRAFT", "isPubliclyVisible": false
}' | jq -r '.coursePageId')
MID=$(curl -s -X POST "$B/$CID/modules" -H "$AUTH" -H "$JSON" \
-d '{"title":"Foundations"}' | jq -r '.moduleId')
LID=$(curl -s -X POST "$B/$CID/modules/$MID/lessons" -H "$AUTH" -H "$JSON" \
-d '{"title":"A prompt is a specification","content":"<h3>What you’ll learn</h3><p>…</p>"}' \
| jq -r '.lessonPageId')
curl -s "$B/$CID" -H "$AUTH" | jq '{title, status, modules: [.modules[] | {title, lessons: [.lessons[].title]}]}'
curl -s -X PUT "$B/$CID/modules/reorder" -H "$AUTH" -H "$JSON" -d '{"orderedIds":["mod1","mod2"]}'
curl -s -X PUT "$B/$CID/modules/$MID/lessons/reorder" -H "$AUTH" -H "$JSON" -d '{"orderedIds":["les1","les2"]}'
Publish (and go public)
curl -s -X PUT "$B/$CID" -H "$AUTH" -H "$JSON" \
-d '{"status":"PUBLISHED","isPubliclyVisible":true}'
2. Author a graded quiz
A quiz is a lesson with isGradedQuiz=true. Create the lesson, mark it a quiz with a pass mark, then add questions.
QID_LESSON=$(curl -s -X POST "$B/$CID/modules/$MID/lessons" -H "$AUTH" -H "$JSON" \
-d '{"title":"Check your understanding","isGradedQuiz":true}' | jq -r '.lessonPageId')
curl -s -X PUT "$B/$CID/lessons/$QID_LESSON/quiz-config" -H "$AUTH" -H "$JSON" \
-d '{"isGradedQuiz":true,"passScore":60}'
QUESTION_ID=$(curl -s -X POST "$B/$CID/lessons/$QID_LESSON/questions" -H "$AUTH" -H "$JSON" -d '{
"questionText": "A prompt is best understood as a…",
"answerFormat": "MULTIPLE_CHOICE",
"choices": [{"id":"a","text":"specification of an acceptable answer"},
{"id":"b","text":"casual question"}],
"correctAnswer": ["a"],
"points": 1
}' | jq -r '.questionId')
Read / edit / delete questions (authoring view)
These three keep an authoring tool in sync with what already exists — list returns the correct answers (the learner-facing quiz endpoint strips them).
curl -s "$B/$CID/lessons/$QID_LESSON/questions" -H "$AUTH" \
| jq '{title, passScore, questions: [.questions[] | {id, questionText, answerFormats, correctAnswer, points}]}'
curl -s -X PUT "$B/$CID/questions/$QUESTION_ID" -H "$AUTH" -H "$JSON" -d '{
"questionText": "A prompt is best understood as…",
"answerFormat": "MULTIPLE_CHOICE",
"choices": [{"id":"a","text":"a specification"},{"id":"b","text":"a guess"}],
"correctAnswer": ["a"], "points": 2
}'
curl -s -X PUT "$B/$CID/questions/$QUESTION_ID/grading" -H "$AUTH" -H "$JSON" \
-d '{"correctAnswer":["a"],"points":2}'
curl -s -X DELETE "$B/$CID/questions/$QUESTION_ID" -H "$AUTH"
In the web UI this is the Quiz Editor at /app/:uri/courses/:coursePageId/quiz/:lessonPageId (Questions tab + Results tab).
3. Track learners
curl -s "$B/$CID/lessons/$QID_LESSON/results" -H "$AUTH" \
| jq '{attempts, avgScore, passRate, passScore, results: [.results[] | {name, scorePct, passed}]}'
curl -s "$B/$CID/gradebook" -H "$AUTH" | jq '.rows[:5]'
curl -s "$B/$CID/roster" -H "$AUTH" | jq '.[] | {name, status, progressPercent}'
A learner submits an attempt (autograded → scored → recorded in progress + gradebook; passing the course issues a certificate):
curl -s -X POST "$B/$CID/lessons/$QID_LESSON/submit" -H "$AUTH" -H "$JSON" -d '{
"answers": [{"questionId":"'"$QUESTION_ID"'","selectedChoices":["a"]}]
}' | jq '{scorePct, passed, courseProgress: .courseProgress.progressPercent, certificate: .certificate.serial}'
4. Enrollment
curl -s -X POST "$B/$CID/enroll" -H "$AUTH" -H "$JSON" -d '{}'
curl -s -X POST "$B/$CID/enrollments" -H "$AUTH" -H "$JSON" \
-d '{"email":"learner@example.com"}'
curl -s -X DELETE "$B/$CID/enrollments/$USER_ID" -H "$AUTH"
curl -s "$B/$CID/my-enrollment" -H "$AUTH" | jq '.enrollment'
Progress + prerequisites
curl -s "$B/$CID/progress" -H "$AUTH" | jq '{progressPercent, status}'
curl -s -X POST "$B/$CID/lessons/$LID/complete" -H "$AUTH"
curl -s -X PUT "$B/$CID/lessons/$LID/prerequisite" -H "$AUTH" -H "$JSON" \
-d '{"requiresLessonPageId":"'"$QID_LESSON"'","requiresPass":true}'
5. Certificates
curl -s "$B/$CID/my-certificate" -H "$AUTH" | jq '.certificate'
curl -s "$DATASPHERES_BASE_URL/api/public/courses/certificates/$VERIFY_HASH" | jq '{valid, courseTitle, learner}'
6. Pages-native student view (public reads)
Learners don't use a /courses/* route — they read the course as pages:
curl -s "$DATASPHERES_BASE_URL/api/public/courses/$DATASPHERES_DEFAULT_URI/<courseSlug>" | jq '{title, modules}'
curl -s "$DATASPHERES_BASE_URL/api/public/courses/context/$DATASPHERES_DEFAULT_URI/<anySlug>" | jq '{isCourse, context}'
curl -s "$DATASPHERES_BASE_URL/api/public/courses/$DATASPHERES_DEFAULT_URI/quizzes/<quizLessonId>" | jq
Student URL: ${DATASPHERES_BASE_URL}/pages/<uri>/<courseSlug> (overview, with enroll/progress/certificate chrome and the course TOC sidebar). Instructor: course editor /app/<uri>/courses/<coursePageId>, analytics /courses/<uri>/<courseSlug>/analytics.
Endpoint quick map
| Action | Method + path (under /api/v1/dataspheres/:uri/courses) | Role |
|---|
| List courses | GET / | MODERATOR |
| Create course | POST / | MODERATOR |
| Get TOC | GET /:cid | MODERATOR |
| Update course (cascades visibility) | PUT /:cid | MODERATOR |
| Add module | POST /:cid/modules | MODERATOR |
| Add lesson | POST /:cid/modules/:mid/lessons | MODERATOR |
| Set quiz config | PUT /:cid/lessons/:lid/quiz-config | MODERATOR |
| Add question | POST /:cid/lessons/:lid/questions | MODERATOR |
| List questions (with answers) | GET /:cid/lessons/:lid/questions | MODERATOR |
| Update question | PUT /:cid/questions/:qid | MODERATOR |
| Set question grading | PUT /:cid/questions/:qid/grading | MODERATOR |
| Delete question | DELETE /:cid/questions/:qid | MODERATOR |
| Submit attempt | POST /:cid/lessons/:lid/submit | PARTICIPANT |
| Per-quiz results | GET /:cid/lessons/:lid/results | MODERATOR |
| Gradebook | GET /:cid/gradebook | MODERATOR |
| Roster | GET /:cid/roster | MODERATOR |
| Enroll (self / admin / drop) | POST /:cid/enroll · POST /:cid/enrollments · DELETE /:cid/enrollments/:userId | PARTICIPANT / MODERATOR |
| Progress · complete lesson | GET /:cid/progress · POST /:cid/lessons/:lid/complete | PARTICIPANT |
| Prerequisite | PUT /:cid/lessons/:lid/prerequisite | MODERATOR |
| My / public certificate | GET /:cid/my-certificate · GET /api/public/courses/certificates/:hash | PARTICIPANT / public |
Every one of these is also an ARI tool (same id, e.g. create_course, add_quiz_question, list_quiz_questions, update_quiz_question, delete_quiz_question, get_quiz_results) — ARI invokes them directly from chat. The source of truth is toolMeta in src/server/v1/routes/courses.routes.ts.