Skip to content

docs: add ADR for standardizing REST API URL structure - #39003

Open
Abdul-Muqadim-Arbisoft wants to merge 2 commits into
openedx:masterfrom
edly-io:docs/ADR-standardize_rest_api_url_structure
Open

docs: add ADR for standardizing REST API URL structure#39003
Abdul-Muqadim-Arbisoft wants to merge 2 commits into
openedx:masterfrom
edly-io:docs/ADR-standardize_rest_api_url_structure

Conversation

@Abdul-Muqadim-Arbisoft

@Abdul-Muqadim-Arbisoft Abdul-Muqadim-Arbisoft commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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.

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 bradenmacdonald left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is excellent. Thanks!

Comment thread docs/decisions/0038-standardize-rest-api-url-structure.rst Outdated
Comment thread docs/decisions/0038-standardize-rest-api-url-structure.rst Outdated
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants