Skip to content

API Reference

This section documents the API endpoints available in the PhonoLab backend.

Lesson API

Front-End

Method Endpoint Description
GET /lesson/ Get all lessons
GET /lesson/<lesson_id> Get a lesson by ID
GET /lesson/vowel/<vowel_id> Get a lesson by vowel ID
GET /quiz/ Get all quiz items
GET /quiz/<quiz_id> Get a quiz item by ID
GET /quiz/vowel/<vowel_id> Get all quiz items for a specific vowel

lesson

create_all_lessons

Create lessons for all vowels that don't have lessons yet.

Source code in src/api/lesson.py
@lesson_bp.route("/create-all", methods=["POST"])
def create_all_lessons():
    """Create lessons for all vowels that don't have lessons yet."""
    try:
        count, error = create_lessons_for_all_vowels()

        if error:
            return error_response(error, 400)

        return success_response(
            f"Created {count} new lessons",
            {"count": count}
        )
    except Exception as e:
        return error_response(f"Error creating lessons: {str(e)}")

create_new_lesson

Create a new lesson.

Source code in src/api/lesson.py
@lesson_bp.route("/", methods=["POST"])
def create_new_lesson():
    """Create a new lesson."""
    try:
        data = request.get_json()
        if not data or 'vowel_id' not in data:
            return error_response("Vowel ID is required", 400)

        vowel_id = data['vowel_id']
        lesson, error = create_lesson(vowel_id)

        if error:
            return error_response(error, 400)

        formatted_lesson = format_lesson_http(lesson)
        return jsonify(formatted_lesson), 201
    except (ValueError, TypeError) as e:
        return error_response(f"Error formatting lesson: {str(e)}")
    except Exception as e:
        return error_response(f"Error creating lesson: {str(e)}")

delete_existing_lesson

Delete a lesson.

Source code in src/api/lesson.py
@lesson_bp.route("/<int:lesson_id>", methods=["DELETE"])
def delete_existing_lesson(lesson_id):
    """Delete a lesson."""
    try:
        error = delete_lesson(lesson_id)

        if error:
            return error_response(error, 400)

        return success_response("Lesson deleted successfully")
    except Exception as e:
        return error_response(f"Error deleting lesson: {str(e)}")

fetch_all_lessons

Get all lessons.

Returns:

Name Type Description
JSON

A list of formatted lesson objects.

Errors: 400: Error formatting lessons 500: Unexpected server error

Source code in src/api/lesson.py
@lesson_bp.route("/", methods=["GET"])
def fetch_all_lessons():
    """
    Get all lessons.

    Returns:
        JSON: A list of formatted lesson objects.
    Errors:
        400: Error formatting lessons
        500: Unexpected server error
    """
    try:
        lessons = get_all_lessons()
        formatted_lessons = format_lessons_http(lessons)
        return jsonify(formatted_lessons)
    except (ValueError, TypeError) as e:
        return error_response(f"Error formatting lessons: {str(e)}")
    except Exception as e:
        logging.exception("Unexpected error in fetch_all_lessons")
        return error_response(f"Unexpected error: {str(e)}")

fetch_lesson_by_id

Get a lesson by ID.

Source code in src/api/lesson.py
@lesson_bp.route("/<int:lesson_id>", methods=["GET"])
def fetch_lesson_by_id(lesson_id):
    """Get a lesson by ID."""
    try:
        lesson = get_lesson_by_id(lesson_id)
        if not lesson:
            return error_response(f"Lesson with ID {lesson_id} not found", 404)

        formatted_lesson = format_lesson_http(lesson)
        if not formatted_lesson:
            return error_response("Could not format lesson response", 500)

        return jsonify(formatted_lesson)
    except (ValueError, TypeError) as e:
        return error_response(f"Error formatting lesson: {str(e)}")
    except Exception as e:
        return error_response(f"Error retrieving lesson: {str(e)}")

fetch_lesson_by_vowel_id

Get a lesson by vowel ID.

Source code in src/api/lesson.py
@lesson_bp.route("/vowel/<string:vowel_id>", methods=["GET"])
def fetch_lesson_by_vowel_id(vowel_id):
    """Get a lesson by vowel ID."""
    try:
        lesson = get_lesson_by_vowel_id(vowel_id)
        if not lesson:
            return error_response(f"Lesson for vowel {vowel_id} not found", 404)

        formatted_lesson = format_lesson_http(lesson)
        if not formatted_lesson:
            return error_response("Could not format lesson response", 500)

        return jsonify(formatted_lesson)
    except (ValueError, TypeError) as e:
        return error_response(f"Error formatting lesson: {str(e)}")
    except Exception as e:
        return error_response(f"Error retrieving lesson: {str(e)}")

update_existing_lesson

Update an existing lesson.

Source code in src/api/lesson.py
@lesson_bp.route("/<int:lesson_id>", methods=["PUT"])
def update_existing_lesson(lesson_id):
    """Update an existing lesson."""
    try:
        data = request.get_json()
        if not data or 'vowel_id' not in data:
            return error_response("Vowel ID is required", 400)

        vowel_id = data['vowel_id']
        lesson, error = update_lesson(lesson_id, vowel_id)

        if error:
            return error_response(error, 400)

        formatted_lesson = format_lesson_http(lesson)
        return jsonify(formatted_lesson)
    except (ValueError, TypeError) as e:
        return error_response(f"Error formatting lesson: {str(e)}")
    except Exception as e:
        return error_response(f"Error updating lesson: {str(e)}")

Response Formats

Success Response

Success Response Format
{
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    // Operation-specific data
  }
}

Error Response

Error Response Format
{
  "status": "error",
  "message": "Error message describing what went wrong",
  "error": {
    "code": 404,
    "details": "Additional error details if available"
  }
}

Data

Lesson Object Format
{
  "id": 1,
  "vowel": {
    "id": "v1",
    "phoneme": "i",
    "name": "Long E",
    "ipa_example": "iː",
    "color_code": "#FF5733",
    "audio_url": "/audio/vowels/long_e.mp3",
    "description": "The long E vowel sound",
    "mouth_image_url": "/images/mouth/long_e.png"
  },
  "lesson_card": {
    "pronounced": "as 'ee' in 'see'",
    "common_spellings": ["ee", "ea", "e", "ie", "ei"],
    "lips": "Spread wide",
    "tongue": "High and forward in the mouth",
    "example_words": [
      {"word": "see", "ipa": "siː"},
      {"word": "meet", "ipa": "miːt"},
      {"word": "piece", "ipa": "piːs"}
    ]
  }
}

Quiz API

quiz

get_quiz

Retrieves a quiz by its ID.

Source code in src/api/quiz.py
@quiz_bp.route("/<int:quiz_id>", methods=["GET"])
def get_quiz(quiz_id):
    """
    Retrieves a quiz by its ID.
    """
    formatted = get_formatted_quiz_by_id(quiz_id)
    if not formatted:
        return error_response("Quiz not found", 404)
    return success_response("Quiz retrieved", {"quiz": formatted})

Response Formats

Success Response

Success Response Format
{
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    // Operation-specific data
  }
}

Error Response

Error Response Format
{
  "status": "error",
  "message": "Error message describing what went wrong",
  "error": {
    "code": 404,
    "details": "Additional error details if available"
  }
}

Data

Quiz Item Object Format
{
  "id": 1,
  "prompt_word": "sheep",
  "prompt_ipa": "ʃiːp",
  "prompt_audio_url": "/audio/quiz/sheep.mp3",
  "feedback_correct": "Great job! You identified the correct vowel sound.",
  "feedback_incorrect": "Not quite. Listen to the difference again.",
  "options": [
    {
      "id": 1,
      "word": "ship",
      "ipa": "ʃɪp",
      "audio_url": "/audio/quiz/ship.mp3",
      "is_correct": false,
      "language": "en"
    },
    {
      "id": 2,
      "word": "sheep",
      "ipa": "ʃiːp",
      "audio_url": "/audio/quiz/sheep.mp3",
      "is_correct": true,
      "language": "en"
    }
  ]
}