docs: add instructor api v2 ADR/OpenAPI spec for course info and grading (#37743)
This commit is contained in:
committed by
GitHub
parent
ca78895edf
commit
2c53232c5d
@@ -0,0 +1,66 @@
|
||||
Instructor Course Information API Specification
|
||||
-----------------------------------------------
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
**Accepted** *2025-10-30*
|
||||
|
||||
Context
|
||||
=======
|
||||
|
||||
The instructor dashboard requires comprehensive course metadata, enrollment statistics, user
|
||||
permissions, and navigation configuration. This information was previously scattered across
|
||||
multiple endpoints, requiring multiple round-trip requests and complex client-side data
|
||||
aggregation for MFEs.
|
||||
|
||||
Decisions
|
||||
=========
|
||||
|
||||
#. **Consolidated Course Metadata Endpoint**
|
||||
|
||||
Create ``GET /api/instructor/v2/courses/{course_id}`` that returns comprehensive course
|
||||
information in a single request, including course identity, timing, enrollment statistics,
|
||||
user permissions, dashboard tab configuration, and operational information.
|
||||
|
||||
#. **Permission-Based Tab Configuration**
|
||||
|
||||
Server-side logic determines which dashboard tabs the current user can access based on
|
||||
their roles, course features, and system configuration. Tabs are returned with URLs
|
||||
pointing to the appropriate MFE routes.
|
||||
|
||||
#. **Serializer-Based Business Logic**
|
||||
|
||||
Use Django REST Framework serializers (``CourseInformationSerializer``) to encapsulate
|
||||
all business logic for data gathering, permission checks, enrollment queries, and
|
||||
formatting. Keep views thin.
|
||||
|
||||
#. **OpenAPI Specification**
|
||||
|
||||
Maintain an OpenAPI specification at ``../references/instructor-v2-course-info-spec.yaml`` to guide implementation. This static specification serves as a reference during development, but ``/api-docs/`` is the source of truth for what is actually deployed. Once implementation is complete and the endpoints are live in ``/api-docs/``, the static spec file will be deleted to avoid maintaining outdated documentation.
|
||||
|
||||
Consequences
|
||||
============
|
||||
|
||||
Positive
|
||||
~~~~~~~~
|
||||
|
||||
* Single request replaces multiple round-trips, reducing latency for MFE page loads
|
||||
* Centralized business logic ensures consistent permission checks and data formatting
|
||||
* Simplified client code with all course information available in one call
|
||||
* OpenAPI specification enables type-safe client generation
|
||||
|
||||
Negative
|
||||
~~~~~~~~
|
||||
|
||||
* Larger response payload (though offset by eliminating multiple requests)
|
||||
* Some over-fetching when clients don't need all information
|
||||
* Permission-based data prevents simple course-level caching
|
||||
* Enrollment queries and permission checks run on every request
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
* OpenAPI Specification: ``../references/instructor-v2-course-info-spec.yaml``
|
||||
* Implementation: ``lms/djangoapps/instructor/views/api_v2.py``
|
||||
* Live API Documentation: ``/api-docs/``
|
||||
@@ -0,0 +1,77 @@
|
||||
Instructor Grading API Specification
|
||||
-------------------------------------
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
**Draft** (=> **Provisional**)
|
||||
|
||||
Context
|
||||
=======
|
||||
|
||||
The instructor dashboard is being migrated to a Micro-Frontend (MFE) architecture, which requires RESTful API endpoints. The current implementation provides grading operations (reset attempts, rescore, override scores, delete state) through legacy endpoints.
|
||||
|
||||
The MFE migration requires a modern, RESTful API with consistent URL patterns, clear synchronous vs asynchronous behavior, comprehensive task monitoring, and proper documentation. These operations need to support both single-learner (synchronous) and all-learners (asynchronous) execution models.
|
||||
|
||||
Decisions
|
||||
=========
|
||||
|
||||
#. **RESTful Resource-Oriented Design**
|
||||
|
||||
Use resource-oriented URLs: ``/api/instructor/v2/courses/{course_key}/{problem}/grading/{resource}``
|
||||
|
||||
Use appropriate HTTP methods:
|
||||
|
||||
* ``GET`` for read operations (learner info, problem metadata, task status)
|
||||
* ``POST`` for actions (reset attempts, rescore)
|
||||
* ``PUT`` for replacements (score overrides)
|
||||
* ``DELETE`` for removals (delete learner state)
|
||||
|
||||
#. **Synchronous vs Asynchronous Execution**
|
||||
|
||||
* Operations targeting a single learner (with ``learner`` parameter) execute synchronously
|
||||
and return ``200 OK`` with immediate results (< 5s typical)
|
||||
* Operations targeting all learners (no ``learner`` parameter) queue a background task
|
||||
and return ``202 Accepted`` with task tracking information
|
||||
* Provide task status endpoint: ``GET /api/instructor/v2/courses/{course_key}/tasks/{task_id}``
|
||||
|
||||
#. **Clear Operation Semantics**
|
||||
|
||||
* **Reset Attempts**: Resets counter to zero, preserves answers/state
|
||||
* **Delete State**: Permanently removes all learner data (requires ``learner`` parameter)
|
||||
* **Rescore**: Re-evaluates submissions with current grading logic (supports ``only_if_higher``)
|
||||
* **Override Score**: Manually sets specific score (requires ``learner`` parameter)
|
||||
|
||||
#. **Consistent Response Formats**
|
||||
|
||||
* Synchronous operations return ``SyncOperationResult`` with success, learner, problem_location, message
|
||||
* Asynchronous operations return ``AsyncOperationResult`` with task_id, status_url, scope
|
||||
* Task status responses include task_id, state, progress, result/error, timestamps
|
||||
|
||||
#. **OpenAPI Specification**
|
||||
|
||||
Maintain an OpenAPI specification at ``../references/instructor-v2-grading-api-spec.yaml`` to guide implementation. This static specification serves as a reference during development, but ``/api-docs/`` is the source of truth for what is actually deployed. Once implementation is complete and the endpoints are live in ``/api-docs/``, the static spec file will be deleted to avoid maintaining outdated documentation.
|
||||
|
||||
Consequences
|
||||
============
|
||||
|
||||
Positive
|
||||
~~~~~~~~
|
||||
|
||||
* Consistent URL patterns and response formats make the API predictable
|
||||
* Explicit sync/async behavior allows proper UI feedback
|
||||
* OpenAPI specification enables automated validation, testing, and type-safe client generation
|
||||
* Resource-oriented design makes it easy to add new operations
|
||||
|
||||
Negative
|
||||
~~~~~~~~
|
||||
|
||||
* Existing clients using legacy endpoints need to be updated
|
||||
* Dual maintenance during transition period
|
||||
* Developers familiar with legacy endpoints need to learn new patterns
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
* OpenAPI Specification: ``../references/instructor-v2-grading-api-spec.yaml``
|
||||
* Live API Documentation: ``/api-docs/``
|
||||
@@ -0,0 +1,484 @@
|
||||
swagger: '2.0'
|
||||
info:
|
||||
title: Instructor Dashboard API v2
|
||||
version: 2.0.0
|
||||
description: |
|
||||
REST API for instructor dashboard operations.
|
||||
|
||||
**Design Principles:**
|
||||
- RESTful resource-oriented URLs
|
||||
- Query parameters for filtering operations
|
||||
- Clear separation between read and write operations
|
||||
- Consistent error handling
|
||||
|
||||
host: courses.example.com
|
||||
basePath: /
|
||||
schemes:
|
||||
- https
|
||||
|
||||
securityDefinitions:
|
||||
JWTAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: Authorization
|
||||
description: JWT token authentication. Header format depends on JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] setting (default is 'JWT <token>').
|
||||
|
||||
security:
|
||||
- JWTAuth: []
|
||||
|
||||
tags:
|
||||
- name: Course
|
||||
description: Course metadata and configuration
|
||||
- name: Tasks
|
||||
description: Background task monitoring
|
||||
|
||||
paths:
|
||||
/api/instructor/v2/courses/{course_id}:
|
||||
get:
|
||||
tags:
|
||||
- Course
|
||||
summary: Get course metadata
|
||||
description: |
|
||||
Retrieve comprehensive course metadata including enrollment counts, dashboard configuration,
|
||||
permissions, and navigation sections.
|
||||
operationId: getCourseMetadata
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseId'
|
||||
responses:
|
||||
200:
|
||||
description: Course metadata retrieved successfully
|
||||
schema:
|
||||
$ref: '#/definitions/CourseInformation'
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
/api/instructor/v2/courses/{course_id}/instructor_tasks:
|
||||
get:
|
||||
tags:
|
||||
- Tasks
|
||||
summary: List instructor tasks
|
||||
description: |
|
||||
List instructor tasks for a course with optional filtering by problem location and student.
|
||||
|
||||
**Task States:**
|
||||
- `PENDING`: Task is queued but not yet started
|
||||
- `PROGRESS`: Task is currently executing
|
||||
- `SUCCESS`: Task finished successfully
|
||||
- `FAILURE`: Task encountered an error
|
||||
- `REVOKED`: Task was cancelled
|
||||
operationId: listInstructorTasks
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseId'
|
||||
- name: problem_location_str
|
||||
in: query
|
||||
description: Filter tasks to a specific problem location (usage key)
|
||||
required: false
|
||||
type: string
|
||||
x-example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem"
|
||||
- name: unique_student_identifier
|
||||
in: query
|
||||
description: Filter tasks to specific student (requires problem_location_str). Can be username or email.
|
||||
required: false
|
||||
type: string
|
||||
x-example: "john_harvard"
|
||||
responses:
|
||||
200:
|
||||
description: Task list retrieved successfully
|
||||
schema:
|
||||
$ref: '#/definitions/InstructorTaskList'
|
||||
examples:
|
||||
application/json:
|
||||
tasks:
|
||||
- task_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
task_type: "rescore_problem"
|
||||
task_state: "PROGRESS"
|
||||
status: "In progress"
|
||||
created: "2024-01-15T10:30:00Z"
|
||||
task_input: '{"problem_url": "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1"}'
|
||||
task_output: null
|
||||
duration_sec: "45"
|
||||
task_message: "Processing 75 of 150 students"
|
||||
requester: "staff"
|
||||
- task_id: "b2c3d4e5-f6a7-8901-bcde-f1234567890a"
|
||||
task_type: "reset_problem_attempts"
|
||||
task_state: "SUCCESS"
|
||||
status: "Complete"
|
||||
created: "2024-01-15T10:00:00Z"
|
||||
task_input: '{"problem_url": "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw2"}'
|
||||
task_output: '{"total": 150, "succeeded": 150, "failed": 0}'
|
||||
duration_sec: "120"
|
||||
task_message: "Reset attempts for 150 students"
|
||||
requester: "instructor"
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
parameters:
|
||||
CourseId:
|
||||
name: course_id
|
||||
in: path
|
||||
required: true
|
||||
description: Course identifier in format `course-v1:{org}+{course}+{run}`
|
||||
type: string
|
||||
pattern: '^course-v1:[^/+]+(\\+[^/+]+)+(\\+[^/]+)$'
|
||||
x-example: "course-v1:edX+DemoX+Demo_Course"
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Bad request - Invalid parameters or malformed request
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "INVALID_PARAMETER"
|
||||
message: "Invalid course key format"
|
||||
|
||||
Unauthorized:
|
||||
description: Unauthorized - Authentication required
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "AUTHENTICATION_REQUIRED"
|
||||
message: "You must be authenticated to access this endpoint"
|
||||
|
||||
Forbidden:
|
||||
description: Forbidden - Insufficient permissions
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "PERMISSION_DENIED"
|
||||
message: "You do not have instructor permissions for this course"
|
||||
|
||||
NotFound:
|
||||
description: Not found - Resource does not exist
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "RESOURCE_NOT_FOUND"
|
||||
message: "The specified resource does not exist"
|
||||
|
||||
definitions:
|
||||
CourseInformation:
|
||||
type: object
|
||||
description: Comprehensive course information including metadata, enrollment statistics, permissions, and dashboard configuration
|
||||
required:
|
||||
- course_id
|
||||
- display_name
|
||||
- org
|
||||
- course_number
|
||||
- pacing
|
||||
- has_started
|
||||
- has_ended
|
||||
- total_enrollment
|
||||
- enrollment_counts
|
||||
- num_sections
|
||||
- permissions
|
||||
- tabs
|
||||
- disable_buttons
|
||||
properties:
|
||||
course_id:
|
||||
type: string
|
||||
description: Course run key
|
||||
example: "course-v1:edX+DemoX+Demo_Course"
|
||||
display_name:
|
||||
type: string
|
||||
description: Course display name
|
||||
example: "Demonstration Course"
|
||||
org:
|
||||
type: string
|
||||
description: Organization identifier
|
||||
example: "edX"
|
||||
course_number:
|
||||
type: string
|
||||
description: Course number
|
||||
example: "DemoX"
|
||||
enrollment_start:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Enrollment start date (ISO 8601 with timezone)
|
||||
example: "2013-02-05T00:00:00Z"
|
||||
x-nullable: true
|
||||
enrollment_end:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Enrollment end date (ISO 8601 with timezone)
|
||||
example: "2024-12-31T23:59:59Z"
|
||||
x-nullable: true
|
||||
start:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Course start date (ISO 8601 with timezone)
|
||||
example: "2013-02-05T05:00:00Z"
|
||||
x-nullable: true
|
||||
end:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Course end date (ISO 8601 with timezone)
|
||||
example: "2024-12-31T23:59:59Z"
|
||||
x-nullable: true
|
||||
pacing:
|
||||
type: string
|
||||
enum:
|
||||
- self
|
||||
- instructor
|
||||
description: Course pacing type
|
||||
example: "instructor"
|
||||
has_started:
|
||||
type: boolean
|
||||
description: Whether the course has started based on current time
|
||||
example: true
|
||||
has_ended:
|
||||
type: boolean
|
||||
description: Whether the course has ended based on current time
|
||||
example: false
|
||||
total_enrollment:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: Total number of enrollments across all modes
|
||||
example: 150
|
||||
enrollment_counts:
|
||||
type: object
|
||||
description: Enrollment count breakdown by mode
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
minimum: 0
|
||||
audit:
|
||||
type: integer
|
||||
minimum: 0
|
||||
verified:
|
||||
type: integer
|
||||
minimum: 0
|
||||
honor:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example:
|
||||
total: 150
|
||||
audit: 100
|
||||
verified: 40
|
||||
honor: 10
|
||||
num_sections:
|
||||
type: integer
|
||||
minimum: 0
|
||||
description: Number of sections/chapters in the course
|
||||
example: 12
|
||||
grade_cutoffs:
|
||||
type: string
|
||||
description: Formatted string of grade cutoffs
|
||||
example: "A is 0.9, B is 0.8, C is 0.7, D is 0.6"
|
||||
course_errors:
|
||||
type: array
|
||||
description: List of course validation errors from modulestore
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: []
|
||||
studio_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to view/edit course in Studio
|
||||
example: "https://studio.example.com/course/course-v1:edX+DemoX+Demo_Course"
|
||||
permissions:
|
||||
type: object
|
||||
description: User permissions for instructor dashboard features
|
||||
required:
|
||||
- admin
|
||||
- instructor
|
||||
- finance_admin
|
||||
- sales_admin
|
||||
- staff
|
||||
- forum_admin
|
||||
- data_researcher
|
||||
properties:
|
||||
admin:
|
||||
type: boolean
|
||||
description: User is a platform administrator
|
||||
instructor:
|
||||
type: boolean
|
||||
description: User has instructor role
|
||||
finance_admin:
|
||||
type: boolean
|
||||
description: User has finance admin role
|
||||
sales_admin:
|
||||
type: boolean
|
||||
description: User has sales admin role
|
||||
staff:
|
||||
type: boolean
|
||||
description: User has staff role
|
||||
forum_admin:
|
||||
type: boolean
|
||||
description: User has forum administrator role
|
||||
data_researcher:
|
||||
type: boolean
|
||||
description: User has data researcher permissions
|
||||
example:
|
||||
admin: false
|
||||
instructor: true
|
||||
finance_admin: false
|
||||
sales_admin: false
|
||||
staff: true
|
||||
forum_admin: true
|
||||
data_researcher: false
|
||||
tabs:
|
||||
type: array
|
||||
description: List of course tabs with configuration and display information
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- tab_id
|
||||
- title
|
||||
- url
|
||||
properties:
|
||||
tab_id:
|
||||
type: string
|
||||
description: Unique tab identifier
|
||||
title:
|
||||
type: string
|
||||
description: Display title for the tab
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to access the tab
|
||||
example:
|
||||
- tab_id: "course_info"
|
||||
title: "Course Info"
|
||||
url: "https://mfe.example.com/instructor/course-v1:edX+DemoX+Demo_Course/course_info"
|
||||
- tab_id: "enrollments"
|
||||
title: "Enrollments"
|
||||
url: "https://mfe.example.com/instructor/course-v1:edX+DemoX+Demo_Course/enrollments"
|
||||
- tab_id: "grading"
|
||||
title: "Grading"
|
||||
url: "https://mfe.example.com/instructor/course-v1:edX+DemoX+Demo_Course/grading"
|
||||
disable_buttons:
|
||||
type: boolean
|
||||
description: Whether to disable certain bulk action buttons due to large course size
|
||||
example: false
|
||||
analytics_dashboard_message:
|
||||
type: string
|
||||
description: Message about analytics dashboard availability
|
||||
example: "To gain insights into student enrollment and participation, visit the analytics dashboard."
|
||||
|
||||
InstructorTaskList:
|
||||
type: object
|
||||
description: List of instructor tasks
|
||||
required:
|
||||
- tasks
|
||||
properties:
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/InstructorTask'
|
||||
|
||||
InstructorTask:
|
||||
type: object
|
||||
description: Instructor task details
|
||||
required:
|
||||
- task_id
|
||||
- task_type
|
||||
- task_state
|
||||
- status
|
||||
- created
|
||||
- duration_sec
|
||||
- task_message
|
||||
- requester
|
||||
- task_input
|
||||
properties:
|
||||
task_id:
|
||||
type: string
|
||||
description: Unique task identifier
|
||||
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
task_type:
|
||||
type: string
|
||||
description: Type of instructor task
|
||||
enum:
|
||||
- rescore_problem
|
||||
- reset_problem_attempts
|
||||
- delete_problem_state
|
||||
- override_problem_score
|
||||
- grade_course
|
||||
- calculate_grades_csv
|
||||
- problem_grade_report
|
||||
- export_ora2_data
|
||||
- cohort_students
|
||||
- send_bulk_email
|
||||
example: "rescore_problem"
|
||||
task_state:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
- PROGRESS
|
||||
- SUCCESS
|
||||
- FAILURE
|
||||
- REVOKED
|
||||
description: Current state of the task
|
||||
example: "PROGRESS"
|
||||
status:
|
||||
type: string
|
||||
description: Human-readable status message
|
||||
example: "In progress"
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Task creation timestamp (ISO 8601 with timezone)
|
||||
example: "2024-01-15T10:30:00Z"
|
||||
duration_sec:
|
||||
type: string
|
||||
description: Task duration in seconds, or "unknown" if not available
|
||||
example: "45"
|
||||
task_message:
|
||||
type: string
|
||||
description: Detailed task message or progress update
|
||||
example: "Processing 75 of 150 students"
|
||||
requester:
|
||||
type: string
|
||||
description: Username of the user who initiated the task
|
||||
example: "instructor"
|
||||
task_input:
|
||||
type: string
|
||||
description: JSON string of task input parameters
|
||||
example: '{"problem_url": "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1"}'
|
||||
task_output:
|
||||
type: string
|
||||
description: JSON string of task output/results (null if not complete)
|
||||
example: '{"total": 150, "succeeded": 150, "failed": 0}'
|
||||
x-nullable: true
|
||||
|
||||
Error:
|
||||
type: object
|
||||
description: Error response
|
||||
required:
|
||||
- error
|
||||
- message
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Machine-readable error code
|
||||
example: "RESOURCE_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
description: Human-readable error message
|
||||
example: "The specified course does not exist"
|
||||
field_errors:
|
||||
type: object
|
||||
description: Field-specific validation errors (if applicable)
|
||||
additionalProperties:
|
||||
type: string
|
||||
@@ -0,0 +1,654 @@
|
||||
swagger: '2.0'
|
||||
info:
|
||||
title: Instructor Dashboard API
|
||||
version: 2.0.0
|
||||
description: |
|
||||
Modern REST API for instructor dashboard operations.
|
||||
|
||||
**Design Principles:**
|
||||
- RESTful resource-oriented URLs
|
||||
- Query parameters for filtering operations
|
||||
- Clear separation between read and write operations
|
||||
- Consistent error handling
|
||||
|
||||
**Execution Model:**
|
||||
- Operations that affect a single learner execute synchronously (< 5s typical)
|
||||
- Operations that affect all learners queue a background task
|
||||
- Use the task status endpoint to monitor background tasks
|
||||
|
||||
host: courses.example.com
|
||||
basePath: /
|
||||
schemes:
|
||||
- https
|
||||
|
||||
securityDefinitions:
|
||||
JWTAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: Authorization
|
||||
description: JWT token authentication. Header format depends on JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] setting (default is 'JWT <token>').
|
||||
|
||||
security:
|
||||
- JWTAuth: []
|
||||
|
||||
tags:
|
||||
- name: Learners
|
||||
description: Learner information and enrollment data
|
||||
- name: Problems
|
||||
description: Problem metadata and structure
|
||||
- name: Grading
|
||||
description: Grading operations and score management
|
||||
- name: Tasks
|
||||
description: Background task monitoring
|
||||
|
||||
paths:
|
||||
# ==================== LEARNER ENDPOINTS ====================
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/learners/{email_or_username}:
|
||||
get:
|
||||
tags:
|
||||
- Learners
|
||||
summary: Get learner information
|
||||
description: |
|
||||
Retrieve comprehensive learner information including profile, enrollment status,
|
||||
progress URLs, and current grading data.
|
||||
operationId: getLearner
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- $ref: '#/parameters/LearnerIdentifierPath'
|
||||
responses:
|
||||
200:
|
||||
description: Learner information retrieved successfully
|
||||
schema:
|
||||
$ref: '#/definitions/Learner'
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
# ==================== PROBLEM ENDPOINTS ====================
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/problems/{location}:
|
||||
get:
|
||||
tags:
|
||||
- Problems
|
||||
summary: Get problem information
|
||||
description: |
|
||||
Retrieve problem metadata including display name, location in course hierarchy,
|
||||
and usage key.
|
||||
|
||||
**Note:** Requires exact problem location - no search or partial matching.
|
||||
operationId: getProblem
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- name: location
|
||||
in: path
|
||||
description: Problem block usage key
|
||||
required: true
|
||||
type: string
|
||||
x-example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem"
|
||||
responses:
|
||||
200:
|
||||
description: Problem information retrieved successfully
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
# ==================== GRADING ENDPOINTS ====================
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/{problem}/grading/attempts/reset:
|
||||
post:
|
||||
tags:
|
||||
- Grading
|
||||
summary: Reset problem attempts
|
||||
description: |
|
||||
Reset attempt counters to zero, allowing learner(s) to reattempt the problem.
|
||||
The learner's previous answers and state are preserved, only the attempt counter is reset.
|
||||
|
||||
**Scope:**
|
||||
- With `learner`: Single learner, single problem (synchronous, ~100-500ms)
|
||||
- Without `learner`: All learners, single problem (async task)
|
||||
|
||||
**Note:** To completely delete state instead of just resetting attempts,
|
||||
use `DELETE /grading/state` instead.
|
||||
operationId: resetAttempts
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- $ref: '#/parameters/ProblemLocationPath'
|
||||
- $ref: '#/parameters/LearnerIdentifierQuery'
|
||||
responses:
|
||||
200:
|
||||
description: Operation completed successfully (synchronous)
|
||||
schema:
|
||||
$ref: '#/definitions/SyncOperationResult'
|
||||
202:
|
||||
description: Task queued for background processing (asynchronous)
|
||||
schema:
|
||||
$ref: '#/definitions/AsyncOperationResult'
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/{problem}/grading/state:
|
||||
delete:
|
||||
tags:
|
||||
- Grading
|
||||
summary: Delete learner problem state
|
||||
description: |
|
||||
Permanently delete a specific learner's StudentModule record for a specific problem,
|
||||
including all answers, submissions, attempts, and scores from the database.
|
||||
|
||||
**Warning:** This operation is destructive and cannot be undone. Unlike resetting
|
||||
attempts (which preserves state), this completely removes all records.
|
||||
|
||||
**Requirements:**
|
||||
- `learner` parameter is required
|
||||
- Always executes synchronously
|
||||
operationId: deleteState
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- $ref: '#/parameters/ProblemLocationPath'
|
||||
- name: learner
|
||||
in: query
|
||||
description: Learner username or email (required)
|
||||
required: true
|
||||
type: string
|
||||
x-example: "john_harvard"
|
||||
responses:
|
||||
200:
|
||||
description: State deleted successfully
|
||||
schema:
|
||||
$ref: '#/definitions/SyncOperationResult'
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/{problem}/grading/scores/rescore:
|
||||
post:
|
||||
tags:
|
||||
- Grading
|
||||
summary: Rescore problem submissions
|
||||
description: |
|
||||
Re-evaluate learner submissions and update scores based on current grading logic.
|
||||
|
||||
**Scope:**
|
||||
- With `learner`: Single learner, single problem (synchronous)
|
||||
- Without `learner`: All learners, single problem (async task)
|
||||
operationId: rescore
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- $ref: '#/parameters/ProblemLocationPath'
|
||||
- $ref: '#/parameters/LearnerIdentifierQuery'
|
||||
- name: only_if_higher
|
||||
in: query
|
||||
description: Only update score if the new score is higher than current score
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
200:
|
||||
description: Rescore completed successfully (synchronous)
|
||||
schema:
|
||||
$ref: '#/definitions/SyncOperationResult'
|
||||
202:
|
||||
description: Rescore task queued for background processing (asynchronous)
|
||||
schema:
|
||||
$ref: '#/definitions/AsyncOperationResult'
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/{problem}/grading/scores:
|
||||
put:
|
||||
tags:
|
||||
- Grading
|
||||
summary: Override a learner's score
|
||||
description: |
|
||||
Manually set a specific score for a learner on a problem, replacing any
|
||||
automatically calculated score.
|
||||
|
||||
**Requirements:**
|
||||
- `learner` parameter is required
|
||||
- Always executes synchronously
|
||||
|
||||
**Note:** This creates or updates a PersistentSubsectionGradeOverride record.
|
||||
operationId: overrideScore
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- $ref: '#/parameters/ProblemLocationPath'
|
||||
- name: learner
|
||||
in: query
|
||||
description: Learner username or email (required)
|
||||
required: true
|
||||
type: string
|
||||
x-example: "john_harvard"
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- score
|
||||
properties:
|
||||
score:
|
||||
type: number
|
||||
description: New score value (out of problem's total possible points)
|
||||
minimum: 0
|
||||
example: 8.5
|
||||
responses:
|
||||
200:
|
||||
description: Score overridden successfully
|
||||
schema:
|
||||
$ref: '#/definitions/SyncOperationResult'
|
||||
examples:
|
||||
application/json:
|
||||
success: true
|
||||
learner: "john_harvard"
|
||||
problem_location: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem"
|
||||
score: 8.5
|
||||
previous_score: 5.0
|
||||
message: "Score overridden successfully"
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
# ==================== TASK ENDPOINTS ====================
|
||||
|
||||
/api/instructor/v2/courses/{course_key}/tasks/{task_id}:
|
||||
get:
|
||||
tags:
|
||||
- Tasks
|
||||
summary: Get task status
|
||||
description: |
|
||||
Check the status of a background task.
|
||||
|
||||
**Task States:**
|
||||
- `pending`: Task is queued but not yet started
|
||||
- `running`: Task is currently executing
|
||||
- `completed`: Task finished successfully
|
||||
- `failed`: Task encountered an error
|
||||
operationId: getTaskStatus
|
||||
produces:
|
||||
- application/json
|
||||
parameters:
|
||||
- $ref: '#/parameters/CourseKey'
|
||||
- name: task_id
|
||||
in: path
|
||||
description: Task identifier returned from async operation
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: Task status retrieved successfully
|
||||
schema:
|
||||
$ref: '#/definitions/TaskStatus'
|
||||
examples:
|
||||
application/json:
|
||||
task_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
state: "completed"
|
||||
progress:
|
||||
current: 150
|
||||
total: 150
|
||||
result:
|
||||
success: true
|
||||
message: "Reset attempts for 150 learners"
|
||||
created_at: "2024-01-15T10:30:00Z"
|
||||
updated_at: "2024-01-15T10:35:23Z"
|
||||
400:
|
||||
$ref: '#/responses/BadRequest'
|
||||
401:
|
||||
$ref: '#/responses/Unauthorized'
|
||||
403:
|
||||
$ref: '#/responses/Forbidden'
|
||||
404:
|
||||
$ref: '#/responses/NotFound'
|
||||
|
||||
# ==================== COMPONENTS ====================
|
||||
|
||||
parameters:
|
||||
CourseKey:
|
||||
name: course_key
|
||||
in: path
|
||||
required: true
|
||||
description: Course identifier in format `course-v1:{org}+{course}+{run}`
|
||||
type: string
|
||||
pattern: '^course-v1:[^/+]+(\\+[^/+]+)+(\\+[^/]+)$'
|
||||
x-example: "course-v1:edX+DemoX+Demo_Course"
|
||||
|
||||
LearnerIdentifierPath:
|
||||
name: email_or_username
|
||||
in: path
|
||||
required: true
|
||||
description: Learner's username or email address
|
||||
type: string
|
||||
minLength: 1
|
||||
|
||||
LearnerIdentifierQuery:
|
||||
name: learner
|
||||
in: query
|
||||
required: false
|
||||
description: |
|
||||
Learner's username or email address.
|
||||
If omitted, operation applies to all learners in the course.
|
||||
type: string
|
||||
x-example: "john_harvard"
|
||||
|
||||
ProblemLocationPath:
|
||||
name: problem
|
||||
in: path
|
||||
required: true
|
||||
description: Problem block usage key
|
||||
type: string
|
||||
x-example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem"
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Bad request - Invalid parameters or malformed request
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "INVALID_PARAMETER"
|
||||
message: "Invalid course key format"
|
||||
status_code: 400
|
||||
|
||||
Unauthorized:
|
||||
description: Unauthorized - Authentication required
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "AUTHENTICATION_REQUIRED"
|
||||
message: "You must be authenticated to access this endpoint"
|
||||
status_code: 401
|
||||
|
||||
Forbidden:
|
||||
description: Forbidden - Insufficient permissions
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "PERMISSION_DENIED"
|
||||
message: "You do not have instructor permissions for this course"
|
||||
status_code: 403
|
||||
|
||||
NotFound:
|
||||
description: Not found - Resource does not exist
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
examples:
|
||||
application/json:
|
||||
error: "RESOURCE_NOT_FOUND"
|
||||
message: "The specified resource does not exist"
|
||||
status_code: 404
|
||||
|
||||
definitions:
|
||||
SyncOperationResult:
|
||||
type: object
|
||||
description: Result from a synchronous grading operation
|
||||
required:
|
||||
- success
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
description: Whether the operation succeeded
|
||||
learner:
|
||||
type: string
|
||||
description: Learner identifier (if applicable)
|
||||
problem_location:
|
||||
type: string
|
||||
description: Problem location (if applicable)
|
||||
score:
|
||||
type: number
|
||||
description: Updated score (for override operations)
|
||||
message:
|
||||
type: string
|
||||
description: Human-readable result message
|
||||
example:
|
||||
success: true
|
||||
learner: "john_harvard"
|
||||
problem_location: "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1_p1"
|
||||
message: "Operation completed successfully"
|
||||
|
||||
AsyncOperationResult:
|
||||
type: object
|
||||
description: Task information for an asynchronous operation
|
||||
required:
|
||||
- task_id
|
||||
- status_url
|
||||
properties:
|
||||
task_id:
|
||||
type: string
|
||||
description: Unique task identifier
|
||||
status_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to poll for task status
|
||||
scope:
|
||||
type: object
|
||||
description: Scope of the operation
|
||||
properties:
|
||||
learners:
|
||||
type: string
|
||||
description: Either "all" or specific learner identifier
|
||||
problems:
|
||||
type: string
|
||||
description: Either "all" or specific problem location
|
||||
example:
|
||||
task_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
status_url: "/api/instructor/v2/courses/course-v1:edX+DemoX+Demo_Course/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
||||
scope:
|
||||
learners: "all"
|
||||
problem_location: "block-v1:edX+DemoX+Demo_Course+type@problem+block@hw1_p1"
|
||||
|
||||
TaskStatus:
|
||||
type: object
|
||||
description: Status of a background task
|
||||
required:
|
||||
- task_id
|
||||
- state
|
||||
- created_at
|
||||
- updated_at
|
||||
properties:
|
||||
task_id:
|
||||
type: string
|
||||
description: Task identifier
|
||||
state:
|
||||
type: string
|
||||
enum:
|
||||
- pending
|
||||
- running
|
||||
- completed
|
||||
- failed
|
||||
description: Current state of the task
|
||||
progress:
|
||||
type: object
|
||||
description: Progress information (if available)
|
||||
properties:
|
||||
current:
|
||||
type: integer
|
||||
minimum: 0
|
||||
total:
|
||||
type: integer
|
||||
minimum: 0
|
||||
required:
|
||||
- current
|
||||
- total
|
||||
result:
|
||||
type: object
|
||||
description: Task result (present when state is "completed")
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
error:
|
||||
type: object
|
||||
description: Error information (present when state is "failed")
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
Learner:
|
||||
type: object
|
||||
description: Comprehensive learner information
|
||||
required:
|
||||
- username
|
||||
- email
|
||||
- first_name
|
||||
- last_name
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
example: "john_harvard"
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
example: "john@example.com"
|
||||
first_name:
|
||||
type: string
|
||||
example: "John"
|
||||
last_name:
|
||||
type: string
|
||||
example: "Harvard"
|
||||
progress_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to learner's progress page
|
||||
x-nullable: true
|
||||
gradebook_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: URL to learner's gradebook view
|
||||
x-nullable: true
|
||||
current_score:
|
||||
type: object
|
||||
x-nullable: true
|
||||
properties:
|
||||
score:
|
||||
type: number
|
||||
format: float
|
||||
minimum: 0
|
||||
total:
|
||||
type: number
|
||||
format: float
|
||||
minimum: 0
|
||||
attempts:
|
||||
type: object
|
||||
x-nullable: true
|
||||
properties:
|
||||
current:
|
||||
type: integer
|
||||
minimum: 0
|
||||
total:
|
||||
type: integer
|
||||
minimum: 0
|
||||
|
||||
Problem:
|
||||
type: object
|
||||
description: Problem metadata and location
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- breadcrumbs
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Problem usage key
|
||||
example: "block-v1:edX+DemoX+Demo_Course+type@problem+block@sample_problem"
|
||||
name:
|
||||
type: string
|
||||
description: Problem display name
|
||||
example: "Sample Problem"
|
||||
breadcrumbs:
|
||||
type: array
|
||||
description: Course hierarchy breadcrumbs
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- display_name
|
||||
properties:
|
||||
display_name:
|
||||
type: string
|
||||
usage_key:
|
||||
type: string
|
||||
description: Block usage key (omitted for course level)
|
||||
|
||||
Error:
|
||||
type: object
|
||||
description: Error response
|
||||
required:
|
||||
- error
|
||||
- message
|
||||
- status_code
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Machine-readable error code
|
||||
example: "RESOURCE_NOT_FOUND"
|
||||
message:
|
||||
type: string
|
||||
description: Human-readable error message
|
||||
example: "The specified course does not exist"
|
||||
status_code:
|
||||
type: integer
|
||||
description: HTTP status code
|
||||
example: 404
|
||||
field_errors:
|
||||
type: object
|
||||
description: Field-specific validation errors (if applicable)
|
||||
additionalProperties:
|
||||
type: string
|
||||
Reference in New Issue
Block a user