Skip to content

[API] Course Management #39059

Open
Open

Description

@FuaadZam

Functional Area: Course Management

Endpoints under this functional area. Note: this issue merges two blocks that both use the "Course Management" functional-area label in the source spec — the contentstore/v0 settings/assets/tabs/transcripts group, and the contentstore/v1 rerun/settings/team/waffle-flags/group-configurations group.

Module API Version Resource / Endpoint HTTP Method Description
Advanced Settings v0 /contentstore/v0/advanced_settings/{course_id} GET Retrieves all advanced settings for a specific course, including display names, help text, current values, and deprecation status. Supports optional query parameters filter_fields (comma-separated list of fields to filter) and fetch_all (boolean to fetch all settings or only enabled ones). Returns a dictionary where each key is a setting name and the value contains display_name, help, value, deprecated, and other metadata. Handles authentication and authorization; returns 401 if unauthenticated, 403 if access denied, or 404 if course does not exist.
Advanced Settings v0 /contentstore/v0/advanced_settings/{course_id} PATCH Updates one or more advanced settings for a specific course. Accepts a JSON object where each key is a setting name and the value field is updated; all other fields in the payload are ignored. Multiple settings can be updated in a single request. Returns the updated course settings in the same format as the GET endpoint. Handles authentication and authorization; returns 401 if unauthenticated, 403 if access denied, or 404 if course does not exist.
File Assets v0 /contentstore/v0/file_assets/{course_id} GET Retrieves a list of all file assets associated with a specific course. Each asset object includes file (URI), title, and locked status. Handles authentication and authorization; returns 401 if unauthenticated, 403 if access denied, or 404 if course does not exist. Supports integration for AI or external tools that need to list course assets for analysis, auditing, or automated processing.
File Assets v0 /contentstore/v0/file_assets/{course_id} POST Creates a new file asset for a specific course. Accepts a JSON body with locked status; the response returns the created asset with file URI, title, and locked status. Useful for programmatically adding assets to courses.
File Assets v0 /contentstore/v0/file_assets/{course_id}/{asset_key_string} PUT Updates an existing file asset completely for a given course and asset key. Accepts a JSON body (e.g., locked) and returns the full updated asset object.
File Assets v0 /contentstore/v0/file_assets/{course_id}/{asset_key_string} PATCH Partially updates an existing file asset for a given course and asset key. Only the fields provided in the body (e.g., locked) are updated; other fields remain unchanged. Returns the updated asset.
File Assets v0 /contentstore/v0/file_assets/{course_id}/{asset_key_string} DELETE Deletes a specific file asset from a course based on asset_key_string. Returns HTTP 204 on success.
Heartbeat v0 /contentstore/v0/heartbeat GET Returns the current status of the Authoring API. Response contains authoring_api_enabled boolean. Useful for monitoring service availability.
Link Checking v0 /contentstore/v0/link_check/{course_id} POST Queues a Celery task to scan a course for broken links. Returns LinkCheckStatus: "Pending". Handles authentication; 401 if unauthenticated, 403 if access denied, 404 if course not found.
Link Checking v0 /contentstore/v0/link_check_status/{course_id} GET Retrieves the current status of a link check task for a course. Returns LinkCheckStatus (Uninitiated, Pending, In-Progress, Succeeded, Failed, Canceled, Retrying), LinkCheckCreatedAt, and optionally detailed output including sections, units, blocks, and previous run links. Useful for AI/external systems to monitor progress and gather detailed link integrity information.
Rerun Link Update v0 /contentstore/v0/rerun_link_update/{course_id} POST Queues a Celery task to update course links to the latest course rerun. Accepts action (all to update all links or single for specific links) and optional data array with URLs, types, and block IDs. Returns status: "pending". Handles 400 for invalid payloads, 401/403/404 for auth/course errors.
Rerun Link Update v0 /contentstore/v0/rerun_link_update_status/{course_id} GET Retrieves the status of a course rerun link update task. Returns status (pending, in_progress, completed, failed, uninitiated) and optionally results for completed tasks, including updated URLs and success flags.
Course Tabs v0 /contentstore/v0/tabs/{course_id} GET Retrieves all tabs for a course, including hidden tabs. Each tab object includes tab_id, title, type, name, visibility flags (is_hidden, is_hideable, course_staff_only), movability, and custom settings. Useful for AI/external tools analyzing course structure.
Course Tabs v0 /contentstore/v0/tabs/{course_id}/reorder POST Reorders tabs in a course. Accepts an array of objects with tab_locator identifying each tab's Usage Key. Returns HTTP 204 on success. Handles auth and course access errors (401/403/404).
Course Tabs v0 /contentstore/v0/tabs/{course_id}/settings POST Changes the visibility of a course tab. Accepts is_hidden boolean in the body and identifies the tab either by tab_id or tab_location (Usage Key). Returns HTTP 204 on success. Useful for programmatically hiding or showing tabs for students or staff.
Video Transcripts v0 /contentstore/v0/video_transcripts/{course_id} GET Retrieves all video transcripts for a course. Each transcript includes file URI, edx_video_id, language_code, and optional new_language_code. Useful for AI tools analyzing content accessibility or generating localized versions.
Video Transcripts v0 /contentstore/v0/video_transcripts/{course_id} POST Uploads a new transcript for a course video. Accepts file (formData), edx_video_id, optional language_code, and optional new_language_code. Returns the uploaded transcript object with URI and metadata.
Video Transcripts v0 /contentstore/v0/video_transcripts/{course_id} DELETE Deletes all video transcripts for a course. Returns HTTP 204 on success.
Video Images v0 /contentstore/v0/videos/images/{course_id}/{edx_video_id} POST Uploads an image for a specific course video. Accepts file (formData) and returns the uploaded file URI.
Course Rerun v1 /contentstore/v1/course_rerun/{course_id} GET Retrieves detailed information about a specific course rerun. Returns keys including allow_unicode_course_id, course_creator_status, number, display_name, org, and run. Requires course_id as path parameter. Handles 401, 403, 404 errors for authentication, permissions, or non-existent courses. Useful for AI tools to understand course versioning and plan content updates.
Course Settings v1 /contentstore/v1/course_settings/{course_id} GET Retrieves all configurable settings for a course, including display name, credit eligibility, language options, marketing settings, LMS links, possible prerequisites, sidebar HTML, upgrade deadlines, and flags for entrance exams and extended details. Supports complex nested structures such as possible_pre_requisite_courses. Requires course_id path param. Returns 401/403/404 as per auth and access. Essential for AI systems that analyze course attributes, adapt content, or recommend courses based on eligibility and metadata.
Course Team v1 /contentstore/v1/course_team/{course_id} GET Returns the list of CMS users assigned as editors or instructors for a specific course. Includes flags show_transfer_ownership_hint and allow_actions. Each user object contains id, username, email, and role. Requires course_id. Returns 401/403/404 for auth or access issues. Important for AI tools coordinating workflow automation for course management, notifications, or instructor assignments.
Waffle Flags v1 /contentstore/v1/course_waffle_flags GET Retrieves the list of waffle feature flags for courses. No parameters required. Returns JSON with flag configurations to control experimental or feature-specific behavior for a course. Can be used by AI systems to adapt workflows based on active flags, e.g., enabling/disabling certain features dynamically.
Group Configurations v1 /contentstore/v1/group_configurations/{course_id} GET Retrieves course group configuration data including all_group_configurations and experiment_group_configurations. Each group includes id, name, usage, version, and description. Flags include should_show_enrollment_track and should_show_experiment_groups. Supports path param course_id. Handles 401/403/404 errors. Useful for AI or external systems managing cohort assignments, experiment tracking, and monitoring user segmentation.

Activity

  1. added theissue type on Sep 2, 2026
  2. taimoor-ahmed-1 commented on Oct 25, 2026

    @taimoor-ahmed-1
    Contributor

    Standardization PR open: #39303

    PR: #39303 — feat: serve course advanced settings from authoring v1
    State: open

    What this does

    Standardizes the advanced settings resource of the Course Management area (#39059, part of #38137). This is the first of eight resource-group PRs for the area. The others (course optimizer, custom pages and tabs, file assets, video transcripts and images, course settings, group configurations, team members) follow separately.

    ADRs applied

    7 of 16 decisions applied on this pass; every one left open carries its reason.

    • 0025 serializers
    • 0026 permissions
    • 0027 schema / docs
    • 0034 authentication
    • 0036 nested JSON
    • 0038 URL structure
    • OEP-69 conventions
    • 0028 viewsets — applied except router registration: a ViewSet backed by a service module, mounted with path(...) and the shared course_key converter, as every route in authoring_urls.py is
    • 0029 errors — partial: the envelope on every response this view produces, with fixed messages; malformed JSON, 405, 406 and 415 are typed internal until edx-drf-extensions catalogues them, and a malformed key gets the CMS HTML 404 at routing (both pinned by tests)
    • 0030 GET idempotence — met for this code (no write, signal or event on GET or HEAD), with one inherited write outside it: openedx-authz inserts its PolicyCacheControl row on the first permission check against a database that lacks it, as on v0
    • 0031 merged endpoints — n/a: GET and PATCH on one resource are standard verbs, not an action-URL family
    • 0032 pagination — n/a: a single object whose size is fixed by the course's settings fields, not a list
    • 0033 filtering — n/a: no list; view and fields select a representation and are validated by a serializer
    • 0035 MFE config — n/a: per-course authoring data, not site or front-end configuration
    • 0037 versioning — partial: new v1 module, v0 frozen (docstring-only markers) and deprecated: true in the schema; the DEPR entry is a comment on [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 until it is folded into the issue body
    • OEP-66 queryset scoping — n/a as a mechanism: one object read from the modulestore with no get_queryset(); fields and view only narrow what the caller may already read

    The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.

    Backward compatibility

    • v0 is unchanged. v0/views/advanced_settings.py gains docstring text only; its syntax tree with docstrings stripped equals the base. Its route, view and URL name are untouched, and v0's own tests are not edited.
    • Parity. v1 is compared with v0 through the Django test client, with policy permissions off and on. Equivalent reads return identical bodies: no view matches v0 fetch_all=0, view=full matches v0's default, and fields matches filter_fields. A valid PATCH stores the same values and returns the same body. The only declared body differences (the error envelope and the fixed per-setting message) are listed verbatim in the PR body, with a table of every status difference from v0 and the test that pins it.

    Gates

    Gate report (PR A, step 7) — 2026-10-25 15:06 — base upstream-pr/master 87cd381

    versions (23 --protect paths from plan §8.1; --allow v1/authoring_urls.py, v0/views/advanced_settings.py)

    files in new version dirs: 0 other files: 3 added in old dirs: 4 allowed: 2
    RESULT: PASS

    hygiene

    Hygiene gate — 9 file(s) scanned (added lines vs upstream-pr/master)
    RESULT: PASS (0 FAIL, 0 WARN)

    ruff

    All checks passed!

    schema: SKIPPED (no platform environment)

    urls: SKIPPED (no platform environment)

    old-tests: SKIPPED (no platform environment)

    tests: SKIPPED (no platform environment)

    supplementary (not gates): v0 docstring-only AST check True; offline harness harness-a 58/58 with mutation checks; url_snapshot on hand-built snapshot PASS

    Deprecations

    GET/PATCH /api/contentstore/v0/advanced_settings/{course_id} are superseded by GET/PATCH /api/authoring/v1/courses/{course_key}/advanced_settings/. They are deprecated: true in the CMS schema, and the docstrings name the successor. The view is otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; this resource's entry is posted there with the PR. Window per #39128: removed one named release after the release this ships in.

    Migration notes:

    • Call /api/authoring/v1/courses/{course_key}/advanced_settings/, with the trailing slash.
    • v0 fetch_all=0: no view. v0 default (every field): view=full. filter_fields=a,b: fields=a,b (it selects within the chosen view). fetch_all and filter_fields are ignored on v1.
    • A refused value is reported under its setting name with a fixed message.
    • Errors use the standard envelope.
    • Bearer tokens are refused; use JWT or a session.

    Follow-ups

    • SDK: both operations carry openedx-platform-sdk; the next openedx-platform-sdk regeneration gains them.
    • frontend-app-authoring: src/advanced-settings/data/api.ts and src/pages-and-resources/data/api.js can move to the v1 address. Pages & Resources reads v0's default full set, so it needs view=full (or fields=).
    • Specific validation messages. v1 reports refused values with a fixed message. Returning the curated messages from the teams and proctoring validators, while keeping field-conversion errors generic, needs CourseMetadata to tell the two apart. That is a future-version candidate.
    • edx-drf-extensions: catalogue ParseError, MethodNotAllowed, NotAcceptable and UnsupportedMediaType, and render a field-keyed detail without the Python repr of the error dict. Issue to be filed.
    • openedx-authz PolicyCacheControl: the first permission check on a fresh database inserts the cache row on a read; seeding it by migration would make reads write-free. Issue to be filed.
    • Shared existence check. feat: serve the course outline from authoring v2 #39229 (open) adds the same COURSE_NOT_FOUND_MESSAGE and an inline course-existence check in CanViewCourse. Whichever of the two lands second should build on IsExistingCourse.
    • Authentication across /api/authoring/: v1 and the feat: apply FC-0118 ADRs to course content APIs #39157 v2 views keep SessionAuthenticationAllowInactiveUser; feat: serve the course outline from authoring v2 #39229 and feat: serve course containers from authoring v2 #39274 use the default. One policy for new versions of such endpoints should be agreed.

    Review on the PR, please — this issue stays open until it merges.

  3. taimoor-ahmed-1 commented on Oct 25, 2026

    @taimoor-ahmed-1
    Contributor

    Standardization PR open: #39304

    PR: #39304 — feat: serve the course optimizer from authoring v1
    State: open

    What this does

    Standardizes the course optimizer resources of the Course Management area (#39059, part of #38137). This is PR B of the eight resource-group PRs for the area. #39303 (advanced settings) is the first; custom pages and tabs, file assets, video transcripts and images, course settings, group configurations and team members follow separately.

    Stacked on #39303 (advanced settings, which adds IsExistingCourse and the shared test helpers). Until #39303 merges this branch also carries its commits; only the last 3 commits belong to this PR.

    ADRs applied

    8 of 16 decisions applied on this pass; every one left open carries its reason.

    • 0025 serializers
    • 0026 permissions
    • 0027 schema / docs
    • 0031 merged endpoints
    • 0034 authentication
    • 0036 nested JSON
    • 0038 URL structure
    • OEP-69 conventions
    • 0028 viewsets — applied except router registration: two ViewSets backed by a service module, mounted with path(...) and the shared course_key converter, as every route in authoring_urls.py is
    • 0029 errors — partial: the envelope on every response these views produce, with fixed messages in place of recorded failure text. Library gaps are pinned by tests: malformed JSON, 405, 406 and 415 are typed internal, and per-link errors under data are rendered as Python repr strings. A malformed key gets the CMS HTML 404 at routing
    • 0030 GET idempotence — met for this code (no write, signal, event or modulestore change on GET or HEAD, either view), with two inherited exceptions outside it. openedx-authz inserts its PolicyCacheControl row on the first permission check against a database that lacks it, as on v0. And the report builder that view=full reuses would create a course_info block if a reported one had been deleted since the check ran; that path is currently unreachable in practice and also exists on v0
    • 0032 pagination — n/a: each read returns one status object; the report is opt-in with view=full and bounded by the links the run flagged
    • 0033 filtering — n/a: no list and no filter; view selects a representation and is validated by a serializer
    • 0035 MFE config — n/a: per-course, per-user authoring data, not site or front-end configuration
    • 0037 versioning — partial: new v1 modules, v0 frozen (docstring-only markers) and deprecated: true in the schema; the DEPR entry is a comment on [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 until it is folded into the issue body
    • OEP-66 queryset scoping — n/a as a mechanism: no get_queryset(); record visibility is django-user-tasks' StatusFilterBackend, reused unchanged through the provider

    The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.

    Backward compatibility

    • v0 is unchanged. v0/views/course_optimizer.py gains docstring text only; its syntax tree with docstrings stripped equals the base. Its routes, views and URL names are untouched, and v0's own tests are not edited.
    • Same tasks, same arguments. Each accepted start queues the task v0 queues, with the arguments v0 passes. The tests assert the two calls are equal for the same caller and body. The one exception: when action is all, v0 hands any data it was sent to the task, which ignores it, while v1 hands [].
    • Parity. v1 is compared with v0 through the Django test client, with policy permissions off and on, after translating v0's member names to v1's. A successful link check's view=full report is v0's report member for member. The only declared body differences, verbatim from the test module; they are quoted verbatim in the PR body.

    Gates

    Gate report (PR B, step 7) — 2026-10-25 21:54 — base 9b4d60e (PR A head incl. shared test helpers; stacked)

    versions (23 --protect from plan §8.1; --allow v1/authoring_urls.py, v0/views/course_optimizer.py)

    files in new version dirs: 0 other files: 3 added in old dirs: 4 allowed: 2
    RESULT: PASS

    hygiene

    RESULT: PASS (0 FAIL, 0 WARN)

    ruff

    All checks passed!

    lines >120: 0

    schema: SKIPPED (no platform environment)

    urls: SKIPPED (no platform environment)

    old-tests: SKIPPED (no platform environment)

    tests: SKIPPED (no platform environment)

    Deprecations

    The four v0 operations are superseded as in the Summary table. They are deprecated: true in the CMS schema, and the docstrings name the successors. The views are otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; this PR's entry is posted there with the PR. Window per #39128: removed one named release after the release this ships in.

    Migration notes:

    • Call /api/authoring/v1/courses/{course_key}/link_check/ and .../previous_run_link_update/, with the trailing slash: POST to start a run, GET for the latest one.
    • Starting a run answers 202 (was 200) with {"status": "Pending"}.
    • Member names are snake_case: LinkCheckStatus → status, LinkCheckCreatedAt → created_at, LinkCheckError → error, LinkCheckOutput → output, and in the report displayName → display_name, brokenLinks → broken_links, lockedLinks → locked_links, externalForbiddenLinks → external_forbidden_links, previousRunLinks → previous_run_links (original_link, is_updated, updated_link).
    • The link check report and the link update results need view=full. Without it, the reads return the run's state only.
    • "No run" is Uninitiated on both resources (the link update read spelled it uninitiated). error and error_message carry fixed messages, not the recorded failure text.
    • Where updating links to a previous run is not enabled, the link-update operations answer 404 (was 400).
    • With action: all, data is not read and may be omitted. With action: single, every link needs a non-blank url, type and id.
    • Errors use the standard envelope. Bearer tokens are refused; use JWT or a session.

    Follow-ups

    • SDK: the four operations carry openedx-platform-sdk; the next openedx-platform-sdk regeneration gains them.
    • frontend-app-authoring: src/optimizer-page/data/api.ts can move to the v1 addresses. It needs: view=full for the report and the results; the new member names (the page camel-cases responses with camelCaseObject, so it reads status, createdAt and output where it read linkCheckStatus, linkCheckCreatedAt and linkCheckOutput); 202 on start; 404 for the feature off.
    • edx-drf-extensions: catalogue ParseError, MethodNotAllowed, NotAcceptable and UnsupportedMediaType, and normalize nested validation errors (per-item objects instead of Python repr strings; detail without the repr of the error dict). Issue to be filed; the pinned tests flip when it lands.
    • openedx-authz PolicyCacheControl: the first permission check on a fresh database inserts the cache row on a read; seeding it by migration would make reads write-free. Issue to be filed.
    • Course optimizer report shape: links inside components nested below a unit's child (library_content, split_test) break the four-level report on v0 and v1. A future version could report such components at the unit level. Recorded as a future-version candidate; the provider is not changed here.
    • Shared existence check. feat: serve the course outline from authoring v2 #39229 (open) adds the same COURSE_NOT_FOUND_MESSAGE and an inline course-existence check in CanViewCourse. Whichever of feat: serve the course outline from authoring v2 #39229 and feat: serve course advanced settings from authoring v1 #39303 lands second should build on IsExistingCourse, which this PR reuses.
    • Authentication across /api/authoring/: v1 here, feat: serve course advanced settings from authoring v1 #39303 and the feat: apply FC-0118 ADRs to course content APIs #39157 v2 views keep SessionAuthenticationAllowInactiveUser; feat: serve the course outline from authoring v2 #39229 and feat: serve course containers from authoring v2 #39274 use the default. One policy for new versions of such endpoints should be agreed.
    • Naming. link_check/ and previous_run_link_update/ are singular, one-per-course children of courses/{course_key}/. They follow ADR 0038's own courses/{course_key}/details/ singleton precedent: each course has one current check and one current update. rerun_link_update was renamed because "rerun" reads as a verb and the resource is the update of links to the previous run.

    Review on the PR, please — this issue stays open until it merges.

  4. taimoor-ahmed-1 commented on Oct 25, 2026

    @taimoor-ahmed-1
    Contributor

    Standardization PR open: #39305

    PR: #39305 — feat: serve course custom pages and tabs from authoring v1
    State: open

    What this does

    Standardizes the custom pages and tabs resources of the Course Management area (#39059, part of #38137). This is PR C of the eight resource-group PRs for the area; #39303 (advanced settings) is PR A.

    ADRs applied

    7 of 16 decisions applied on this pass; every one left open carries its reason.

    • 0025 serializers
    • 0026 permissions
    • 0027 schema / docs
    • 0032 pagination
    • 0034 authentication
    • 0038 URL structure
    • OEP-69 conventions
    • 0028 viewsets — applied except router registration: three ViewSets over a service module, mounted with path(...) like every route in authoring_urls.py, because a router's regex routes cannot use the shared path converters
    • 0029 errors — partial: the envelope on every response these views produce, with fixed messages; malformed JSON, 405, 406 and 415 are typed internal and a malformed course key on the nested routes gets the CMS HTML 404 (library and routing gaps, pinned by tests); detail still carries the library's ErrorDetail(...) text for validation errors while errors is clean
    • 0030 GET idempotence — met for this code (no write, signal or event on GET or HEAD), with one inherited write outside it: openedx-authz inserts its PolicyCacheControl row on the first permission check against a database that lacks it, as on v0
    • 0031 merged endpoints — n/a: not merged, because the list and order act on custom pages and the visibility change on built-in tabs, which are different resources; the reorder verb became a noun under ADR 0038 instead
    • 0033 filtering — n/a: no filter or sort parameter; the order is the course's tab order, which is what learners see, and adding filters would be a new capability
    • 0035 MFE config — n/a: per-course authoring data, not site or front-end configuration
    • 0036 nested JSON — n/a: a flat list of thin objects (nine scalar members and a small settings map each) and a single tab
    • 0037 versioning — partial: new v1 modules, v0 frozen (docstring-only markers) and deprecated: true in the schema; the DEPR entry is a comment on [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 until it is folded into the issue body
    • OEP-66 queryset scoping — n/a as a mechanism: the list reads one course's tab list from the modulestore, with no get_queryset(); the layering is shown in the matrix

    The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.

    Backward compatibility

    • v0 is unchanged. v0/views/tabs.py gains docstring text only; its syntax tree with docstrings stripped equals the base. Its routes, views and URL names are untouched, and v0's own tests are not edited.
    • Parity. v1 is compared with v0 through the Django test client, with policy permissions off and on:
      • the list returns the same custom pages with identical members, on both sides of the pages-and-resources page flag;
      • a full reorder, and a reorder naming all pages but one, store the same tabs;
      • a visibility change stores the same tab.

    Gates

    Gate report (PR C, step 7) — 2026-10-25 21:54 — base 9b4d60e (PR A head incl. shared test helpers; stacked)

    versions (23 --protect from plan §8.1; --allow v1/authoring_urls.py, v0/views/tabs.py)

    files in new version dirs: 0 other files: 4 added in old dirs: 4 allowed: 2
    RESULT: PASS

    hygiene

    WARN H3 cms/djangoapps/contentstore/rest_api/pagination.py:8 local subclass of a shared primitive: keep only if it adds behaviour and says why
    RESULT: PASS (0 FAIL, 1 WARN)

    ruff

    All checks passed!

    lines >120: 0

    schema: SKIPPED (no platform environment)

    urls: SKIPPED (no platform environment)

    old-tests: SKIPPED (no platform environment)

    tests: SKIPPED (no platform environment)

    Deprecations

    The three v0 operations are superseded by the three v1 operations in the Summary. They are deprecated: true in the CMS schema, and the docstrings name each successor. The views are otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; this resource's entry is posted there with the PR. Window per #39128: removed one named release after the release this ships in.

    Migration notes:

    • List: call GET /api/authoring/v1/custom_pages/?course_key={course_key}. It is paginated: read results, and follow next or pass page/page_size (at most 100) to get every page. Items are unchanged, including id.
    • Reorder: PUT /api/authoring/v1/courses/{course_key}/custom_page_order/ with {"custom_pages": [<usage key>, …]}, the ids from the list (v0's tab_locator values). Pages left out follow the listed ones in their current order. Answers 204.
    • Show or hide a tab: PATCH /api/authoring/v1/courses/{course_key}/tabs/{tab_id}/ with {"is_hidden": true|false}. Answers 200 with the tab. A body without is_hidden changes nothing.
    • Refusals use fixed messages under the member they concern (course_key, custom_pages, is_hidden).
    • Errors use the standard envelope.
    • Bearer tokens are refused; use JWT or a session.
    • Every address ends with a slash.

    Follow-ups

    • SDK: all three operations carry openedx-platform-sdk; the next openedx-platform-sdk regeneration gains them.
    • frontend-app-authoring: the custom pages screen (src/custom-pages/data/api.ts) can move to the v1 list (request page_size=100 or follow next) and the PUT order. No MFE caller uses the v0 settings endpoint today.
    • edx-drf-extensions:
      • L3: DefaultPagination.get_paginated_response_schema with all seven keys, after which DocumentedDefaultPagination can be deleted. Issue to be filed.
      • Catalogue ParseError, MethodNotAllowed, NotAcceptable and UnsupportedMediaType.
      • Render a validation detail without the ErrorDetail(...) text.
    • v2 textbooks: CourseTextbookPagination (frozen in v2/views/textbooks.py) duplicates the shared paginator's schema; point the next version of that resource at rest_api/pagination.py.
    • openedx-authz PolicyCacheControl: the first permission check on a fresh database inserts the cache row on a read; seeding it by migration would make reads write-free. Issue to be filed.
    • Authentication across /api/authoring/: v1 here, feat: serve course advanced settings from authoring v1 #39303 and the feat: apply FC-0118 ADRs to course content APIs #39157 v2 views keep SessionAuthenticationAllowInactiveUser; feat: serve the course outline from authoring v2 #39229 and feat: serve course containers from authoring v2 #39274 use the default. One policy for new versions of such endpoints should be agreed.
    • Sibling PR B (course optimizer) edits the same shared files: views/permissions.py, v1/authoring_urls.py, cms/lib/spectacular.py and its test. The conflicts are additive, resolved by rebasing in merge order. When both land, the merged openedx_authz.constants.permissions import must be wrapped to stay within 120 characters.

    Review on the PR, please — this issue stays open until it merges.

  5. taimoor-ahmed-1 commented on Oct 25, 2026

    @taimoor-ahmed-1
    Contributor

    Standardization PR open: #39306

    PR: #39306 — feat: serve course team members from authoring v2
    State: open

    What this does

    Standardizes the course team resource of the Course Management area (#39059, part of #38137). This is PR H of the eight resource-group PRs for the area; #39303 (advanced settings) is PR A and #39305 (custom pages and tabs) is PR C.

    ADRs applied

    7 of 16 decisions applied on this pass; every one left open carries its reason.

    • 0025 serializers
    • 0026 permissions
    • 0027 schema / docs
    • 0032 pagination
    • 0034 authentication
    • 0038 URL structure
    • OEP-69 conventions
    • 0028 viewsets — applied except router registration: one ViewSet over a service module, mounted with path(...) like every route in v2/authoring_urls.py, because a router's regex routes cannot use the shared path converters
    • 0029 errors — partial: the envelope on every response these views produce; 405 and 406 are typed internal, and a malformed course key or an address no route matches gets the CMS HTML 404 (library and routing gaps, pinned by tests); the published ErrorResponse component's text comes from edx-drf-extensions
    • 0030 GET idempotence — met for this code (no write, signal or event on GET or HEAD), with one inherited write outside it: openedx-authz inserts its PolicyCacheControl row on the first permission check against a database that lacks it, as on v1
    • 0031 merged endpoints — n/a: nothing merged; v1's one endpoint becomes the list and the caller's me member of the same resource, because the caller's flags cannot ride the page envelope
    • 0033 filtering — n/a: no filter or sort parameter; the order is fixed and documented (instructors, then staff, each by username), and adding filters would be a new capability
    • 0035 MFE config — n/a: per-course authoring data, not site or front-end configuration
    • 0036 nested JSON — n/a: a flat list of thin objects (four scalar members each) and a flat me of five scalars
    • 0037 versioning — partial: new v2 modules, v1 frozen (docstring-only markers) and deprecated: true in the schema; the DEPR entry for [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 is to be posted with this PR, and no removal release is named yet
    • OEP-66 queryset scoping — n/a as a mechanism: the view owns no get_queryset(); the team is read through Studio's course role helpers; the layering is shown in the matrix

    The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.

    Backward compatibility

    • v1 is unchanged. v1/views/course_team.py gains docstring text only; its syntax tree with docstrings stripped equals the base. Its route, view and URL name are untouched, and v1's own tests are not edited.
    • Parity. v2 is compared with v1 through the Django test client, with policy permissions off and on:
      • the list holds v1's members, in listed order, with identical members (two instructors and three staff, seen by an instructor and by a staff member);
      • me says what v1's body says about the caller, for every member;
      • 401, 403 and 404 differ only in the error body.

    Gates

    Gate report (PR H, step 7) — 2026-10-25 — base 8721326 (PR C head; stacked on C and A)

    versions (23 --protect; --allow v1/views/course_team.py (docstring marker), v2/authoring_urls.py)

    RESULT: PASS (2 allowed, 4 added in existing v2 dirs)

    hygiene: 9 files, RESULT: PASS (0 FAIL, 0 WARN)

    ruff: All checks passed! py_compile clean lines >120: 0

    pylint (4.1.3, repo pylintrc): only C1803 == [] (tolerated; PR A has the same and passed CI)

    supplementary: offline harness-h 63/63; mutants 22/22 killed

    schema / urls / old-tests / tests: SKIPPED (no platform environment)

    Deprecations

    The v1 operation is superseded by the two v2 operations in the Summary. It is deprecated: true in the CMS schema, and the docstrings name both successors. The view is otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; this resource's entry is posted there with the PR. Window per #39128: removed one named release after the release this ships in.

    Migration notes:

    • Members: call GET /api/authoring/v2/courses/{course_key}/team_members/. It is paginated: read results, and follow next or pass page/page_size (at most 100) to get every member. Items are unchanged (id, username, email, role). Instructors come first, then staff, each by username.
    • The caller's flags: call GET /api/authoring/v2/courses/{course_key}/team_members/me/. allow_actions is can_manage_team; show_transfer_ownership_hint is is_only_instructor. role is the caller's team role, or null when they hold none.
    • Errors use the standard envelope.
    • Bearer tokens are refused; use JWT or a session.
    • Every address ends with a slash. Slash-separated (deprecated) course keys are not accepted.

    Follow-ups

    • SDK: both operations carry openedx-platform-sdk; the next openedx-platform-sdk regeneration gains them.
    • frontend-app-authoring: the Course Team page (src/course-team/data/api.ts) can move to the v2 list (request page_size=100 or follow next) and read its flags from me. Its writes still go to Studio's course_team/{course}/{email} handler, which has no REST successor in this area.
    • A team_members/{username}/ member (the ADR's example address), if added, must be reconciled with me: one component for a person, or the difference documented.
    • edx-drf-extensions:
      • Catalogue MethodNotAllowed and NotAcceptable.
      • The shared ErrorResponse component's description is ErrorResponseSerializer's docstring, which names ADR 0029 and carries a usage example; it should describe the error body instead. Issue to be filed.
      • L3: DefaultPagination.get_paginated_response_schema with all seven keys (from feat: serve course custom pages and tabs from authoring v1 #39305), after which the shared paginator can be deleted.
    • Platform: a JSON 404 for addresses under /api/ that match no route, instead of the CMS HTML page. Issue to be filed.
    • openedx-authz PolicyCacheControl: the first permission check on a fresh database inserts the cache row on a read; seeding it by migration would make reads write-free. Issue to be filed.
    • Authentication across /api/authoring/v2/: v2 here and the feat: apply FC-0118 ADRs to course content APIs #39157 v2 views keep SessionAuthenticationAllowInactiveUser; feat: serve the course outline from authoring v2 #39229 and feat: serve course containers from authoring v2 #39274 use the default on the same mount. One policy for new versions of such endpoints should be agreed.
    • feat: serve course advanced settings from authoring v1 #39303 and feat: serve course custom pages and tabs from authoring v1 #39305 explain superuser admission with the wrong cause, in their descriptions and test docstrings; to be corrected in those PRs.
    • Sibling PRs F (course settings) and G (group configurations) edit the same shared files: views/permissions.py, v2/authoring_urls.py, cms/lib/spectacular.py and its test. The conflicts are additive, resolved by rebasing in merge order.

    Review on the PR, please — this issue stays open until it merges.

  6. taimoor-ahmed-1 commented on Oct 25, 2026

    @taimoor-ahmed-1
    Contributor

    Standardization PR open: #39307

    PR: #39307 — feat: serve course rerun defaults and settings from authoring v2
    State: open (stacked on #39303 and #39229)

    What this does

    Standardizes the course rerun defaults and course settings resources of the Course Management area (#39059, part of #38137). This is PR F of the eight resource-group PRs for the area; #39303 (advanced settings) is PR A and #39305 (custom pages and tabs) is PR C.

    • GET /api/authoring/v2/courses/{course_key}/rerun_defaults/ replaces GET /api/contentstore/v1/course_rerun/{course_id} (global staff only, as v1)
    • GET /api/authoring/v2/courses/{course_key}/settings/ (view=minimal by default, or view=full) replaces GET /api/contentstore/v1/course_settings/{course_id}; it gains content_experiments_enabled, which replaces should_show_experiment_groups of the group configurations listing (PR G)

    ADRs applied

    7 of 16 decisions applied on this pass; every one left open carries its reason.

    • 0025 serializers
    • 0026 permissions
    • 0027 schema / docs
    • 0034 authentication
    • 0036 nested JSON
    • 0038 URL structure
    • OEP-69 conventions
    • 0028 viewsets — applied except router registration: two ViewSets over service modules, mounted with path(...) like every route in v2/authoring_urls.py, because a router's regex routes cannot use the shared path converters
    • 0029 errors — partial: the envelope on every response these views produce; 405 and 406 are typed internal, and a malformed course key or an address no route matches gets the CMS HTML 404 (library and routing gaps, pinned by tests); the published ErrorResponse component's text comes from edx-drf-extensions
    • 0030 GET idempotence — met for this code (no write, signal or event on GET or HEAD, on either view), with one inherited write outside it: openedx-authz may insert its PolicyCacheControl row on the first lookup against a database that lacks it, exactly as the v1 read does there
    • 0031 merged endpoints — n/a: nothing merged; rerun defaults and settings stay separate resources (different payloads, different gates), and content_experiments_enabled is one member moved from the group configurations listing, not a merged endpoint
    • 0032 pagination — n/a: both resources are one object per course; the full view's prerequisite list stays unbounded, as on v1 (see Known gaps and risks)
    • 0033 filtering — n/a: no filter or sort parameter; the course listing's parameters that leaked into v1's prerequisite list are no longer read
    • 0035 MFE config — n/a: per-course authoring data behind authentication; the site switches settings carries are part of what the settings pages need about the course, not front-end configuration
    • 0037 versioning — partial: new v2 modules, v1 frozen (docstring-only markers) and deprecated: true in the schema; the DEPR entry for [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 is to be posted with this PR, and no removal release is named yet
    • OEP-66 queryset scoping — n/a as a mechanism: neither view owns a get_queryset(); the prerequisite list comes from Studio's course listing for the caller, as on v1; the layering is shown in the matrix

    The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.

    Backward compatibility

    • v1 is unchanged. v1/views/course_rerun.py and v1/views/settings.py gain docstring text only; for each, the syntax tree with docstrings stripped equals the base. Their routes, views and URL names are untouched, and v1's own tests are not edited.
    • Parity. v2 is compared with v1 through the Django test client, with policy permissions off and on:
      • rerun defaults: global staff get an identical body, with the same members in the same order;
      • settings: view=full is v1's body plus content_experiments_enabled, and view=minimal is that without the three heavy members, across the prerequisite, credit, publisher and verified-mode fixtures;
      • 401, 403 and 404 differ only in the error body.
    • Declared differences (each observed against v1's own answer, listed verbatim in the PR body):
      • settings returns the prerequisite list unchanged when the course list's parameters (search, ordering, active_only and the others) are sent;
      • a malformed start_date_on_or_after is ignored (200; v1 400);
      • view=default is a 400 (v1 200);
      • a course the modulestore returns nothing for is a 404 on both endpoints (v1 500);
      • Bearer tokens get 401 (v1 200);
      • a slash-form key gets the HTML 404.

    Gates

    Gate report (PR F, step 7) — 2026-10-25 — base a1b1c07 (merge of PR A 9b4d60e into #39229 53ea5f4)

    versions (23 --protect; --allow v1/views/course_rerun.py, v1/views/settings.py (docstring markers), v2/authoring_urls.py)

    RESULT: PASS (8 new files in v2, 2 other files, 3 allowed)

    hygiene: 13 files, RESULT: PASS (0 FAIL, 0 WARN)

    ruff: All checks passed! lines >120: 0

    pylint (4.1.3, repo pylintrc): only C1803 == [] (tolerated; PR A has the same and passed CI)

    supplementary: offline harness-f 44/44; mutants 15/15 killed (v1 modelled by stand-ins)

    schema / urls / old-tests / tests: SKIPPED (no platform environment)

    Deprecations

    Both v1 operations are superseded by the v2 operations above. They are deprecated: true in the CMS schema, and the docstrings name the successors. The views are otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; one entry covering both is posted there with the PR. Window per #39128: removed one named release after the release this ships in.

    Migration notes:

    • Rerun defaults: call GET /api/authoring/v2/courses/{course_key}/rerun_defaults/. Same six fields and values; still global staff only.
    • Course settings: call GET /api/authoring/v2/courses/{course_key}/settings/?view=full for v1's body plus content_experiments_enabled. Without view=full (or with view=minimal), language_options, possible_pre_requisite_courses and credit_requirements are left out. view takes only minimal or full; any other value is a 400.
    • The prerequisite list no longer changes with search, ordering, active_only or the course listing's other parameters, and a malformed start_date_on_or_after is ignored.
    • Group configurations callers: read content_experiments_enabled here instead of should_show_experiment_groups, and mfe_proctored_exam_settings_url here; the group configurations successor (PR G) does not return them.
    • Errors use the standard envelope.
    • Bearer tokens are refused; use JWT or a session.
    • Every address ends with a slash. Slash-separated (deprecated) course keys are not accepted.

    Known gaps and risks

    • view=minimal saves payload, not work: the frozen shared helper builds the lists whatever the view (future-version item).
    • settings/ is named after a screen; reviewers may ask for a narrower noun.
    • The components are .v2-suffixed while feat: serve the course outline from authoring v2 #39229's on the same mount are not; the suffix is needed because v1's settings component is published as CourseSettings.
    • Nullability is corrected in the new components (names, prerequisite links, about page link, proctored exam settings path).
    • Query counts are relative to v1 (no more than v1), not literal.
    • The urls gate's verb heuristic is expected to flag rerun_defaults ("rerun" is a noun here; false positive).

    Follow-ups

    • SDK: both operations carry openedx-platform-sdk; the next openedx-platform-sdk regeneration gains them.
    • frontend-app-authoring: the rerun page (src/generic/data/api.ts) can move to rerun_defaults/ unchanged. The settings callers (src/data/api.ts, src/content-tags-drawer/data/api.ts) can move to settings/, requesting view=full where they use the language list, prerequisites or credit requirements. The group configurations page needs this PR before it moves to PR G's listing, because it reads content_experiments_enabled and mfe_proctored_exam_settings_url from here.
    • Future version of course settings: a settings helper that does not build the lists for view=minimal, and a bounded or referenced prerequisite list.
    • Naming on /api/authoring/v2/: agree one component-name convention (suffixed or not) for the mount, and settle the settings noun in review.
    • edx-drf-extensions:
      • Catalogue MethodNotAllowed and NotAcceptable.
      • The shared ErrorResponse component's description is ErrorResponseSerializer's docstring, which names ADR 0029 and carries a usage example; it should describe the error body instead. Issue to be filed.
    • Platform: a JSON 404 for addresses under /api/ that match no route, instead of the CMS HTML page. Issue to be filed.
    • openedx-authz PolicyCacheControl: the first permission check on a fresh database inserts the cache row on a read; seeding it by migration would make reads write-free. Issue to be filed.
    • Authentication across /api/authoring/v2/: v2 here and the feat: apply FC-0118 ADRs to course content APIs #39157 v2 views keep SessionAuthenticationAllowInactiveUser; feat: serve the course outline from authoring v2 #39229 and feat: serve course containers from authoring v2 #39274 use the default on the same mount. One policy for new versions of such endpoints should be agreed.
    • feat: serve course advanced settings from authoring v1 #39303 and feat: serve course custom pages and tabs from authoring v1 #39305 explain superuser admission with the wrong cause, in their descriptions and test docstrings; to be corrected in those PRs.
    • Rebase: once feat: serve course advanced settings from authoring v1 #39303 and feat: serve the course outline from authoring v2 #39229 merge, this branch is rebased onto master and the merge commit drops out.
    • Sibling PRs G (group configurations) and H (course team) edit the same shared files: v2/authoring_urls.py, cms/lib/spectacular.py and its test. The conflicts are additive, resolved by rebasing in merge order.

    Review on the PR, please — this issue stays open until it merges.

  7. taimoor-ahmed-1 commented on Oct 25, 2026

    @taimoor-ahmed-1
    Contributor

    Standardization PR open: #39308

    PR: #39308 — feat: serve group configurations from authoring v2
    State: open

    What this does

    Standardizes the group configurations resource of the Course Management area (#39059, part of #38137). This is PR G of the eight resource-group PRs for the area; #39303 (advanced settings) is PR A, #39305 (custom pages and tabs) is PR C, and #39307 (course settings and rerun defaults) is PR F.

    Stacked on #39305, which stacks on #39303. Merge after #39307 before any client moves to this endpoint: v1's should_show_experiment_groups and mfe_proctored_exam_settings_url are served by #39307's GET /api/authoring/v2/courses/{course_key}/settings/ (as content_experiments_enabled and mfe_proctored_exam_settings_url), not by this resource.

    ADRs applied

    8 of 16 decisions applied on this pass; every one left open carries its reason.

    • 0025 serializers
    • 0026 permissions
    • 0027 schema / docs
    • 0032 pagination
    • 0034 authentication
    • 0036 nested JSON
    • 0038 URL structure
    • OEP-69 conventions
    • 0028 viewsets — applied except router registration: one ViewSet over a service module, mounted with path(...) like every route in v2/authoring_urls.py, because a router's regex routes cannot use the shared path converters
    • 0029 errors — partial: the envelope on every response this view produces; 405 and 406 are typed internal, and a malformed course key or an address no route matches gets the CMS HTML 404 (library and routing gaps, pinned by tests); the published ErrorResponse component's text comes from edx-drf-extensions
    • 0030 GET idempotence — met for this code (no write, signal or event on GET or HEAD, either view), with one inherited write outside it: openedx-authz inserts its PolicyCacheControl row on the first permission check against a database that lacks it, as on v1
    • 0031 merged endpoints — n/a: nothing merged across endpoints; v1's one endpoint becomes one list, its two arrays concatenated in v1's order
    • 0033 filtering — n/a: no filter or sort parameter; the order is v1's, fixed and documented, and adding filters would be a new capability
    • 0035 MFE config — n/a: per-course authoring data, not site or front-end configuration
    • 0037 versioning — partial: new v2 modules, v1 frozen (docstring-only markers) and deprecated: true in the schema; the DEPR entry for [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 is to be posted with this PR, and no removal release is named yet
    • OEP-66 queryset scoping — n/a as a mechanism: the view owns no get_queryset(); configurations are user partitions stored with the course in the modulestore; the layering is shown in the matrix

    The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.

    Backward compatibility

    • v1 is unchanged. v1/views/group_configurations.py gains docstring text only; its syntax tree with docstrings stripped equals the base. Its route, view and URL name are untouched, and v1's own tests are not edited.
    • Parity. v2 is compared with v1 through the Django test client, with policy permissions off (course staff) and on (course auditor), on six courses that between them cover every way the listing changes (only the unsaved configuration; experiments on with none defined; experiment configurations with experiments off; one enrollment track; two tracks, Studio-settable gating, content groups and experiments; a team-set with content groups for teams on):
      • with view=full, the items are v1's two arrays in order, identical apart from can_manage;
      • with the default view, they are the same without usage;
      • every item's can_manage is v1's can_manage;
      • 401, 403 and 404 differ only in the error body.

    Gates

    Gate report (PR G, step 7) — 2026-10-25 — base 8721326 (PR C head; stacked on C and A)

    versions (23 --protect; --allow v1/views/group_configurations.py (docstring marker), v2/authoring_urls.py)

    RESULT: PASS (2 allowed, 4 added in existing v2 dirs)

    hygiene: 9 files, RESULT: PASS (0 FAIL, 0 WARN)

    ruff: All checks passed! lines >120: 0

    pylint (4.1.3, repo pylintrc): only C1803 == [] (tolerated; PR A has the same and passed CI)

    supplementary: serializer-level parity mutation check (6/6 mutants caught); static check of the differences-table guard

    schema / urls / old-tests / tests: SKIPPED (no platform environment)

    Deprecations

    The v1 operation is superseded by the v2 operation in the Summary. It is deprecated: true in the CMS schema, and the docstrings name the successor. The view is otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; this resource's entry is posted there with the PR. Window per #39128: removed one named release after the release this ships in.

    Migration notes:

    • Configurations: call GET /api/authoring/v2/courses/{course_key}/group_configurations/. It is paginated: read results, and follow next or pass page/page_size (at most 100) to get every configuration. The items are v1's all_group_configurations followed by v1's experiment_group_configurations, in the same order and shape.
    • Usage: pass view=full to get usage on configurations and groups, as v1 returns it.
    • can_manage: read it from any item; it is the same on all of them and does not override an item's read_only.
    • should_show_enrollment_track: true exactly when an item has scheme enrollment_track.
    • should_show_experiment_groups and mfe_proctored_exam_settings_url: read content_experiments_enabled and mfe_proctored_exam_settings_url from GET /api/authoring/v2/courses/{course_key}/settings/ (feat: serve course rerun defaults and settings from authoring v2 #39307). Do not migrate before feat: serve course rerun defaults and settings from authoring v2 #39307 is merged.
    • The unsaved content group configuration is still listed when the course has none, with an id that changes per request until a group is added.
    • Errors use the standard envelope. view accepts only minimal and full; an out-of-range or non-numeric page is a 404.
    • Bearer tokens are refused; use JWT or a session.
    • Every address ends with a slash. Slash-separated (deprecated) course keys are not accepted.

    Follow-ups

    Review on the PR, please — this issue stays open until it merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions