docs: add ADR for standardizing REST API URL structure - #39003
Open
Abdul-Muqadim-Arbisoft wants to merge 2 commits into
Open
docs: add ADR for standardizing REST API URL structure#39003Abdul-Muqadim-Arbisoft wants to merge 2 commits into
Abdul-Muqadim-Arbisoft wants to merge 2 commits into
Conversation
Open edX REST URLs follow no consistent pattern. The /api/ prefix, the position of the version, pluralisation, word separators, and trailing slashes all vary, in places between adjacent lines of a single URLconf. The FC-0118 ADRs (0025-0037) standardize what happens inside an endpoint but never its address. Add docs/decisions/0038-standardize-rest-api-url-structure.rst as an accepted ADR defining twelve rules: a leading /api/ prefix, singular API names with plural collections, domain-based rather than app-based naming, lowercase snake_case segments, mounts that declare their own prefix, exact-match routes with a required trailing slash, version position and form, hierarchy capped at one level of nesting, opaque-key identifiers resolved by shared path converters, verb-free resource paths, snake_case Django URL names, and a single URL namespace shared by the LMS and Studio. Existing endpoints migrate under OEP-21 with the conforming path mounted alongside the legacy one. This restates the still-applicable rules from the Open edX REST API Conventions wiki that OEP-49 defers to, settles what that page left as TBD, and adds a CI conformance check so the convention is enforced rather than remembered. Findings recorded in the ADR: - /api/courses/ is mounted in both services on unrelated implementations, both at v1, which blocks the endpoint-by-endpoint combined headless LMS+CMS migration. - /api/enrollment/v1/enrollment and /api/enrollment/v1/enrollments/ are different views, so pluralisation is load-bearing today. - Deprecated Org/Course/Run course keys contain slashes, so nesting one mid-path requires a shared path converter; the platform has three, in two apps, none reusable. - Django resolves re_path with re.search, so unanchored patterns in course_experience and learner_home match under arbitrary prefixes.
Abdul-Muqadim-Arbisoft
added a commit
to edly-io/openedx-proposals
that referenced
this pull request
Aug 21, 2026
…nvention Address review feedback on PR openedx#805: - Link every convention to its source ADR and that ADR's code-example section; link the docs/decisions folder from the Abstract and Reference Implementation. - Add Convention 14 (REST API URL structure) from ADR 0038. - Rework Convention 3: reframe as consolidating RPC-style action endpoints, clarify it is not about HTTP verbs, add when-to-apply guidance and an illustrative example. - Fix Convention 1 to require @extend_schema(request=..., responses=...) when request and response serializers differ. - Remove the redundant inline error-response example in favour of the linked ADR. Refs: openedx/openedx-platform#39003
bradenmacdonald
approved these changes
Aug 21, 2026
bradenmacdonald
left a comment
Contributor
There was a problem hiding this comment.
This is excellent. Thanks!
Comment on lines
+215
to
+216
| their own ``urls.py`` — ``content_staging``, ``olx_rest_api``, | ||
| ``content_libraries``, and ``instructor`` all do — so a conformance check must |
Contributor
There was a problem hiding this comment.
These are all pretty much core apps at this point; we can also just make them always enabled instead of having them use the django app plugins feature. (If that would be helpful)
Contributor
Author
There was a problem hiding this comment.
Pushed the changes, it now notes these four are core apps and recommends moving them into INSTALLED_APPS with explicit prefixed mounts.
| **Shared opaque-key converter**, registered once per service with | ||
| ``register_converter(CourseKeyConverter, "course_key")``. The regex is | ||
| ``COURSE_KEY_PATTERN`` with the named group stripped and the alternations made | ||
| non-capturing, because a converter regex is embedded into a larger pattern: |
Contributor
There was a problem hiding this comment.
Will this be added to edx-drf-extensions ?
Four review comments on PR openedx#39003: - Fix the RST formatting in rule 11. RST does not nest inline markup, so **``snake_case``**, ... rendered with visible asterisks and backticks. Rule 11 now keeps the literal outside the bold lead-in. The same construct in the kebab-case rejected-alternative bullet is fixed the same way. - Stop requiring slash-tolerant course keys. openedx-platform#31134 removed Old Mongo create and update operations, leaving only read-only access to static assets and the root CourseBlock, so no new deprecated-key course can be authored. New and migrated APIs now accept non-deprecated keys only, which covers course-v1: and ccx-v1: and refuses Org/Course/Run. The converter regex becomes a plain "no slash" match with the check made on course_key.deprecated, and mid-path nesting is unambiguous as a result. The now-invalid "breaks on keys containing /" clause is dropped from the deep-nesting rejected alternative. Only endpoints still serving pre-existing Old Mongo courses need the slash-tolerant pattern. - Record that content_staging, olx_rest_api, content_libraries, and lms.djangoapps.instructor are core apps rather than optional extensions. All four are absent from the static INSTALLED_APPS lists and arrive through get_plugin_apps(), so the ADR recommends moving them into INSTALLED_APPS and mounting them explicitly under their own prefix, which is what rule 5 asks of any API. The conformance check still walks the composed resolver, because third-party plugins will always contribute routes the project URLconfs cannot show. - Answer where the path converters belong: they are generic and need only edx-opaque-keys, so they go in edx-drf-extensions alongside the pagination and JWT classes, with openedx/core/lib shown as the interim home.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Open edX REST URLs follow no consistent pattern. The /api/ prefix, the position of the version, pluralisation, word separators, and trailing slashes all vary, in places between adjacent lines of a single URLconf. The FC-0118 ADRs (0025-0037) standardize what happens inside an endpoint but never its address.
Add docs/decisions/0038-standardize-rest-api-url-structure.rst as an accepted ADR defining twelve rules: a leading /api/ prefix, singular API names with plural collections, domain-based rather than app-based naming, lowercase snake_case segments, mounts that declare their own prefix, exact-match routes with a required trailing slash, version position and form, hierarchy capped at one level of nesting, opaque-key identifiers resolved by shared path converters, verb-free resource paths, snake_case Django URL names, and a single URL namespace shared by the LMS and Studio. Existing endpoints migrate under OEP-21 with the conforming path mounted alongside the legacy one.
This restates the still-applicable rules from the Open edX REST API Conventions wiki that OEP-49 defers to, settles what that page left as TBD, and adds a CI conformance check so the convention is enforced rather than remembered.