Skip to content

Build a course with the API

This guide creates a course, adds a chapter, adds a quiz lesson, reads it back, edits it safely, and finally deletes the course — using nothing but an API key.

You need a key with Edit on Courses, Units (API: chapters) and Lessons (how to get one), stored in YOSHUKO_API_KEY.

Each call returns the object it created, including its id, which the next call uses.

1. Create a course

The slug becomes part of the course's web address, so it must be unique in your organisation.

curl -sS -X POST "https://www-dev.yoshuko.com/api/v1/agent/courses/" \
  -H "Authorization: Bearer $YOSHUKO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Intro to Watercolour", "slug": "intro-to-watercolour"}'
course = requests.post(
    f"{BASE}/api/v1/agent/courses/",
    headers=HEADERS,
    json={"title": "Intro to Watercolour", "slug": "intro-to-watercolour"},
    timeout=30,
).json()

The response is 201 Created with the new course, including its id. Your plan's course limit applies — the call is refused with an explanation if you are at it.

2. Add a chapter

curl -sS -X POST "https://www-dev.yoshuko.com/api/v1/agent/courses/$COURSE_ID/chapters/" \
  -H "Authorization: Bearer $YOSHUKO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Unit 1: Materials", "position": 1}'

3. Add a quiz lesson

A lesson's content_type says what kind it is (lesson, quiz, assignment …) and content holds the body. For a quiz, that is the questions:

{
  "title": "Knowledge check",
  "content_type": "quiz",
  "position": 1,
  "content": {
    "title": "Knowledge check",
    "passing_score": 50,
    "questions": [{
      "id": "q1",
      "type": "multiple_choice",
      "prompt_html": "<p>Which paper weight suits wet washes?</p>",
      "points": 1,
      "options": [
        {"id": "a", "html": "300 gsm", "is_correct": true},
        {"id": "b", "html": "80 gsm", "is_correct": false}
      ]
    }]
  }
}

POST it to /api/v1/agent/courses/{course_id}/chapters/{chapter_id}/lessons/. It is validated by exactly the same rules as the course editor, so a malformed quiz is refused with 400 and a message naming the field.

4. Edit a lesson without overwriting anyone

People may be editing the same course in the studio while your agent works. To make sure you never silently overwrite their change, every lesson edit must send back the updated_at value you last read:

curl -sS -X PATCH "https://www-dev.yoshuko.com/api/v1/agent/courses/$COURSE_ID/chapters/$CHAPTER_ID/lessons/$LESSON_ID/" \
  -H "Authorization: Bearer $YOSHUKO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"title\": \"Quick check\", \"updated_at\": \"$UPDATED_AT\"}"

If someone changed the lesson after you read it, you get 409 with "code": "stale_lesson" and the lesson as it is now in lesson. Re-apply your change to that version and send it again with its updated_at.

The whole thing, runnable

Copy either script, set YOSHUKO_API_KEY, and run it. It creates a course, a chapter and a quiz, reads the quiz back, edits it, and deletes the course again so your catalogue is left as it was.

#!/usr/bin/env bash
set -euo pipefail
BASE="https://www-dev.yoshuko.com/api/v1/agent"
AUTH="Authorization: Bearer $YOSHUKO_API_KEY"
JSON="Content-Type: application/json"
SLUG="api-demo-$(date +%s)"

COURSE_ID=$(curl -sSf -X POST "$BASE/courses/" -H "$AUTH" -H "$JSON" \
  -d "{\"title\": \"API demo course\", \"slug\": \"$SLUG\"}" | jq -r .id)
echo "course  $COURSE_ID"

CHAPTER_ID=$(curl -sSf -X POST "$BASE/courses/$COURSE_ID/chapters/" -H "$AUTH" -H "$JSON" \
  -d '{"title": "Unit 1", "position": 1}' | jq -r .id)
echo "chapter $CHAPTER_ID"

LESSON=$(curl -sSf -X POST "$BASE/courses/$COURSE_ID/chapters/$CHAPTER_ID/lessons/" \
  -H "$AUTH" -H "$JSON" -d '{
    "title": "Knowledge check", "content_type": "quiz", "position": 1,
    "content": {"title": "Knowledge check", "passing_score": 50, "questions": [{
      "id": "q1", "type": "multiple_choice", "prompt_html": "<p>2 + 2?</p>", "points": 1,
      "options": [{"id": "a", "html": "4", "is_correct": true},
                  {"id": "b", "html": "5", "is_correct": false}]}]}}')
LESSON_ID=$(echo "$LESSON" | jq -r .id)
UPDATED_AT=$(echo "$LESSON" | jq -r .updated_at)
echo "lesson  $LESSON_ID"

curl -sSf -X PATCH "$BASE/courses/$COURSE_ID/chapters/$CHAPTER_ID/lessons/$LESSON_ID/" \
  -H "$AUTH" -H "$JSON" -d "{\"title\": \"Quick check\", \"updated_at\": \"$UPDATED_AT\"}" \
  | jq '{title, content_type, questions: (.content.questions | length)}'

curl -sSf -X DELETE "$BASE/courses/$COURSE_ID/" -H "$AUTH"
echo "deleted $COURSE_ID"

import os
import time

import requests

BASE = "https://www-dev.yoshuko.com/api/v1/agent"
HEADERS = {"Authorization": f"Bearer {os.environ['YOSHUKO_API_KEY']}"}


def call(method, path, **kwargs):
    response = requests.request(method, BASE + path, headers=HEADERS, timeout=30, **kwargs)
    response.raise_for_status()
    return response.json() if response.content else None


course = call("POST", "/courses/", json={
    "title": "API demo course", "slug": f"api-demo-{int(time.time())}"})
try:
    chapter = call("POST", f"/courses/{course['id']}/chapters/",
                   json={"title": "Unit 1", "position": 1})
    lessons = f"/courses/{course['id']}/chapters/{chapter['id']}/lessons/"
    lesson = call("POST", lessons, json={
        "title": "Knowledge check", "content_type": "quiz", "position": 1,
        "content": {"title": "Knowledge check", "passing_score": 50, "questions": [{
            "id": "q1", "type": "multiple_choice", "prompt_html": "<p>2 + 2?</p>",
            "points": 1, "options": [{"id": "a", "html": "4", "is_correct": True},
                                     {"id": "b", "html": "5", "is_correct": False}]}]},
    })
    edited = call("PATCH", f"{lessons}{lesson['id']}/",
                  json={"title": "Quick check", "updated_at": lesson["updated_at"]})
    print(edited["title"], edited["content_type"], len(edited["content"]["questions"]))
finally:
    call("DELETE", f"/courses/{course['id']}/")
    print("deleted", course["id"])

Reference

Every field and response for these endpoints: Courses, Chapters, Lessons.