Repository navigation
Open
Description
Functional Area: User
Endpoints under this functional area:
| Module | API Version | Resource / Endpoint | HTTP Method | Description |
|---|---|---|---|---|
| Account Management | v1 | /user/v1/account/password_reset/ |
GET | Retrieves the password reset form description |
| Account Management | v1 | /user/v1/account/password_reset/token/validate/ |
POST | Validates a password reset token |
| Account Management | v1 | /user/v1/account/registration/ |
GET | Retrieves user registration form description |
| Account Management | v1 | /user/v1/account/registration/ |
POST | Creates a new user account |
| Account Management | v1 | /user/v1/accounts |
GET | Returns a list of user detail objects; supports filtering by usernames, emails, or LMS IDs |
| Account Management | v1 | /user/v1/accounts/cancel_retirement/ |
POST | Cancels the retirement of a user's account |
| Account Management | v1 | /user/v1/accounts/deactivate_logout/ |
POST | Marks user as having no password and logs them out |
| Account Management | v1 | /user/v1/accounts/name_change/ |
POST | Creates a PendingNameChange request for profile name update |
| Account Management | v1 | /user/v1/accounts/name_change/{username}/confirm/ |
POST | Confirms a pending name change request and updates user's profile name |
| Account Management | v1 | /user/v1/accounts/replace_usernames/ |
POST | Replaces current usernames with desired usernames; returns mapping of successful and failed replacements |
| Account Management | v1 | /user/v1/accounts/retire/ |
POST | Retires a user account including username, email, and other PII |
| Account Management | v1 | /user/v1/accounts/retire_misc/ |
POST | Retires a user in the LMS |
| Account Management | v1 | /user/v1/accounts/retirement_cleanup/ |
POST | Deletes a batch of retirement requests by username |
| Account Management | v1 | /user/v1/accounts/retirement_partner_report/ |
POST | Returns list of UserRetirementPartnerReportingStatus users and updates their processing status |
| Account Management | v1 | /user/v1/accounts/retirement_partner_report/ |
PUT | Creates a UserRetirementPartnerReportingStatus object for a user in the retirement pipeline |
| Account Management | v1 | /user/v1/accounts/retirement_partner_report_cleanup/ |
POST | Deletes UserRetirementPartnerReportingStatus objects for reported users |
| Account Management | v1 | /user/v1/accounts/retirement_queue/ |
GET | Returns RetirementStatus users in specified states and cool-off period |
| Account Management | v1 | /user/v1/accounts/retirements_by_status_and_date/ |
GET | Lists UserRetirementStatusSerializer rows filtered by status and date range |
| Account Management | v1 | /user/v1/accounts/search_emails |
POST | Returns user information for a list of emails; staff-only access |
| Account Management | v1 | /user/v1/accounts/update_retirement_status/ |
PATCH | Updates the RetirementStatus for a user and appends messages to the log |
| Account Management | v1 | /user/v1/accounts/{username} |
GET | Retrieves a single detailed user object |
| Account Management | v1 | /user/v1/accounts/{username} |
PATCH | Updates user account/profile info (merge-patch supported) |
| Account Management | v1 | /user/v1/accounts/{username}/deactivate/ |
POST | Marks user as having no password for deactivation purposes |
| Account Management | v1 | /user/v1/accounts/{username}/image |
POST | Upload/update user profile image |
| Account Management | v1 | /user/v1/accounts/{username}/image |
DELETE | Deletes a user's profile image |
| Account Management | v1 | /user/v1/accounts/{username}/retirement_status/ |
GET | Returns the RetirementStatus of a user |
| Account Management | v1 | /user/v1/accounts/{username}/verification_status/ |
GET | Returns ID verification status for a user |
| Account Management | v1 | /user/v1/accounts/{username}/verifications/ |
GET | Retrieves detailed ID verification information for a user |
| Account Management | v1 | /user/v1/me |
GET | Returns the authenticated user's username |
| Forum Management | v1 | /user/v1/forum_roles/{name}/users/ |
GET | Lists users for a given forum role |
| Preferences Management | v1 | /user/v1/preferences/email_opt_in/ |
POST | Update or create user's email opt-in preference |
| Preferences Management | v1 | /user/v1/preferences/time_zones/ |
GET | List all time zones or by country |
| Preferences Management | v1 | /user/v1/preferences/{pref_key}/users/ |
GET | List a user's preferences |
| Preferences Management | v1 | /user/v1/preferences/{username} |
GET | Retrieve all preferences for a user |
| Preferences Management | v1 | /user/v1/preferences/{username} |
PATCH | Update user preferences |
| Preferences Management | v1 | /user/v1/preferences/{username}/{preference_key} |
GET | Retrieve a specific preference for a user |
| Preferences Management | v1 | /user/v1/preferences/{username}/{preference_key} |
PUT | Update a specific preference for a user |
| Preferences Management | v1 | /user/v1/preferences/{username}/{preference_key} |
DELETE | Delete a specific user preference |
| Preferences Management | v1 | /user/v1/user_prefs/ |
GET | List all user preferences via ORM |
| Preferences Management | v1 | /user/v1/user_prefs/{id}/ |
GET | Retrieve a single user preference via ORM |
| User Management | v1 | /user/v1/users/ |
GET | List all users via ORM |
| User Management | v1 | /user/v1/users/{id}/ |
GET | Retrieve a single user object via ORM |
| Validation & Registration | v1 | /user/v1/validation/registration |
POST | Validate registration form inputs before creating a new user |
| Account Registration | v2 | /user/v2/account/registration/ |
GET | Retrieve registration endpoint info |
| Account Registration | v2 | /user/v2/account/registration/ |
POST | Create a new user account |
| Login Session | v1 | /user/{api_version}/account/login_session/ |
GET | Get login session info |
| Login Session | v1 | /user/{api_version}/account/login_session/ |
POST | Authenticate user and create login session |
Metadata
Metadata
Assignees
Labels
No labels
Activity
Abdul-Muqadim-Arbisoft commentedon Oct 15, 2026
Standardization PR open: #39237
PR: #39237 — feat: standardize the user directory, preference and time-zone APIs as /api/user/v2/
State: open
What this does
This is phase A of five for the
[API] Userfunctional area(#39058), under the API standardization
umbrella #38137. The area is split A → E
on that issue's plan; this PR carries A only — seven read-only operations — and B, C, D and E
are separate PRs. B (accounts and profile images), C (preferences and email opt-in) and D (the
retirement pipeline and username replacement) follow this one; E (registration, login session,
validation and password reset) is not approved to build and is listed here only so the phase count
is not mistaken for four.
ADRs applied
14 of 17 decisions applied on this pass; every one left open carries its reason.
v2/package, no superseded file is in the diff, and the superseded addresses are flagged in the schema — but they carry no docstring marker, which decision 4 also asks for. The reason (FC-0118 review, OEP-21 timing) and the follow-up form are in the matrix row and in Deprecationstags=["openedx-platform-sdk"], and SDK tagging starts at phase B. The learner-facing account and preference endpoints are the SDK candidates; an admin-only directory is not, and tagging it would put it in a generated client for no callerThe evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.
Gates
run_gates.sh — feat/api-adr-user-v2-phase-a vs origin/master @ ce074d1 (2026-10-15)
Six gates ran; none skipped; two are red.
==================== summary ====================
versions PASS
hygiene PASS
schema PASS
urls FAIL
old-tests FAIL
tests PASS
artifacts: /tmp/api-adr-gates.pByf8M
GATES FAILED: 2
RESULT: PASS
RESULT: PASS (0 FAIL, 1 WARN)
RESULT: PASS
RESULT: PASS (0 BREAKING, 18 WARN, 12 INFO)
URL gate — head routes: 3042, base routes: 3036, new api routes checked: 6
RESULT: FAIL (2 FAIL, 1 WARN)
==================== orchestrator reconciliation: final, post checkpoint-2 ====================
Run 2026-10-15 after the two checkpoint-2 decisions (active-user requirement; alias removed
and unknown query parameters refused). Committed as 1b30148 + a8eecf2.
versions PASS 14 --protect paths unchanged. The --allow arguments are mechanically
inert ("allowed: 0"); the evidence for the 7 in-place files is the
per-file diff review, not this gate.
hygiene PASS 0 FAIL, 1 WARN -- H3 on v2/paginators.py only. The two H11 warnings on
the former ?user= alias text are gone with the alias.
schema PASS 0 BREAKING, 18 WARN, 12 INFO. No path, operationId or component on
master removed or renamed, in the LMS document or in /api-docs.
tests PASS 302 passed (was 284 before the two decisions).
urls FAIL DELIBERATE. All three findings are the JSON-404 catch-all
api/user/v2/{_}.(R6, R8, R11) -- not an addressable resource. Sixcandidate shapes were measured; the only conforming one cannot answer
the "unknown collection" case the fallback exists for. run_gates.sh
passes no allowlist, so the composite gate cannot go green regardless.
BLIND ON: R6/R8/R11 for a fallback route. The five real addresses pass
every machine-checkable 0038 rule.
old-tests FAIL RED AND BLIND, environment, identical on both refs:
branch 7 failed / 100 passed / 16 errors (378.02s)
base 7 failed / 100 passed / 16 errors (381.02s)
23 FAILED/ERROR lines over 19 distinct tests, both sets diff EMPTY.
Every one a pymongo ServerSelectionTimeoutError; the image has no
modulestore, so these cannot run here on master either.
PROVES: nothing that passes on master fails here.
DOES NOT PROVE: that those 19 pass at all. Only CI has Mongo.
Also: only 2 of the 11 legacy suites in this app were exercised.
NOT a gate, but the same duty: the legacy comparison above was run by the orchestrator in a
clean origin/master worktree, same image and interpreter, not inferred from the branch run.
Deprecations
Exactly seven legacy paths are marked by this PR — the seven Phase-A addresses below, and no
others:
GET /api/user/v1/users/GET /api/user/v2/users/GET /api/user/v1/users/{id}/GET /api/user/v2/users/{username}/GET /api/user/v1/user_prefs/GET /api/user/v2/user_preferences/GET /api/user/v1/user_prefs/{id}/GET /api/user/v2/user_preferences/{username},{key}/GET /api/user/v1/preferences/{pref_key}/users/GET /api/user/v2/users/?preference_key={pref_key}GET /api/user/v1/forum_roles/{name}/users/GET /api/user/v2/users/?forum_role={name}&course_key={course_key}GET /api/user/v1/preferences/time_zones/GET /api/user/v2/time_zones/They are marked only by
deprecated: truein the published schema, set by the LMSpost-processing hook (
lms/lib/spectacular.py) from a tuple of those seven exact paths. There isno response header and no deprecation note in any superseded code, docstring or comment — the
superseded modules are not in the diff at all. That is a deliberate departure from ADR 0037's
decision 4, which marks the superseded version in the schema and in the endpoint's docstring: the
FC-0118 reviewer asked for the depr notes to be dropped from the replacement PRs (#39100, #39102),
and OEP-21's marker for a REST API is a single
Deprecated <issue link>line added after the DEPRticket reaches Plan Accepted, not a notice. If a reviewer wants the marker, that one line in a
follow-up is the form it should take.
The
/user_api/v1/…twins of these addresses are not deprecated here: they are called byfrontend-app-account,frontend-app-profileand the Android app, and this PR mounts no successorat those addresses.
No query parameter is deprecated alongside them.
GET /api/user/v1/user_prefs/accepts?user=<id>; its successor names owners by username and publishes no identifier filter, soGET /api/user/v2/user_preferences/?user=<id>is a 400validationenvelope naming the parametersthat list does read. Nothing in this version is published with
deprecated: trueand no responsecarries a
Deprecationheader. The removal note for the seven addresses carries the replacementfilter name, so a caller reading the ticket has the whole move in one place.
Tracked on the FC-0118 umbrella DEPR ticket,
#39128, which removes them one named
release after the release this ships in. This area's entry is posted on #39128 once the PR is
open.
Five behaviour notes the removal must carry:
GET /forum_roles/{name}/users/runsRole.objects.get_or_createon every call today. The v2filter does not create the role; an unknown role returns an empty page. No receiver is attached
to
Role, so nothing else fans out.Where that write still lives, for an operator who relied on reading the list to bring a role
into existence:
seed_permissions_roles(course_key)(
openedx/core/djangoapps/django_comment_common/utils.py:49) creates the five standard roleswith their permissions, and already runs when a course is created or imported in Studio
(
cms/djangoapps/contentstore/utils.py:126,contentstore/management/commands/import.py:82) andwhen a CCX course is set up (
lms/djangoapps/ccx/utils.py:517); the LMS management commandseed_permissions_roles <course_id>runs it for a course that predates that; andassign_role(course_id, user, rolename)(django_comment_common/models.py:62) creates a rolewhen granting it to a user. A role created by the legacy GET carried no permissions and no
members, so none of these is a like-for-like replacement for it — they are what should have
created the role in the first place.
What has to exist before the v1 address can be retired: every course whose roles were
brought into existence by calling it must have them seeded, because the instructor endpoint that
grants a role (
POST …/instructor/api/update_forum_role_membership) reads the role withRole.objects.getand raisesRole.DoesNotExistwhen it is missing.are_permissions_roles_seeded(course_id)reports whether a course is in that state and themanagement command puts it right; the check belongs in the removal ticket, not in this PR, since
the legacy address keeps its behaviour until then.
GET /api/user/v2/time_zones/validatescountry_codeas two letters. A well-formed but unknowncode still falls back to every time zone, exactly as before; a malformed one (
FRA) is now a 400envelope where v1 returned the full list. An empty value (
?country_code=) narrows nothing andreturns every zone, as it does at v1; that branch has a test on both counts.
/api/user/v2/does not acceptX-Edx-Api-Key. Server-to-server callers of the v1 directoryendpoints must issue a staff JWT before moving.
GET /api/user/v1/users/{id}/raise; the v2 entry returnsname: null. A caller moving to v2 gets a row where it used to get a server fault.username, so an integration holding identifiers has a step to take before it can address an
entry:
GET /api/user/v1/accounts?lms_user_id={id}— staff-gated, untouched by this PR anditself standardized in phase B — answers with the account whose
usernamekeys the v2 entry.The v2 directory publishes no identifier filter, and this version refuses a parameter it does
not read, so
GET /api/user/v2/users/?id={id}is a 400validationenvelope namingidratherthan a page that looks narrowed, and the resolution step is not optional.
v2/tests/test_parity.py::test_a_database_identifier_is_refused_instead_of_narrowing_nothingrecords the refusal and
::test_an_identifier_held_by_a_caller_resolves_to_the_username_the_entry_is_keyed_bywalks thetwo calls end to end. This step belongs in this area's entry on the DEPR ticket. Whether a v2
idfilter should exist instead is Open decisions, item 3.Review on the PR, please — this issue stays open until it merges.
Abdul-Muqadim-Arbisoft commentedon Oct 23, 2026
Standardization PR open: #39297
PR: #39297 — feat: standardize the user accounts, profile image, verification and name-change API
State: open
ADRs applied
15 of 16 applied on this pass; every box left open carries its reason.
permission_classeson every view; every legacy check survives and twoAPIViewscaptureOnCommitCallbacks(execute=True)GET /api/user/v1/meis merged into the account member address,orderingand every selector validated by a serializer and publishedv2package and a newprofile_images/v2package; noGates
run_gates.sh — feat/api-adr-user-v2-phase-b vs origin/master @ 7f01c42 (2026-10-23)
Six gates ran; none skipped; two are red.
==================== summary ====================
versions PASS
hygiene PASS
schema PASS
urls FAIL
old-tests FAIL
tests PASS
artifacts: /tmp/api-adr-gates.LrMgX6
GATES FAILED: 2
RESULT: PASS
RESULT: PASS (0 FAIL, 1 WARN)
RESULT: PASS
RESULT: PASS (0 BREAKING, 54 WARN, 35 INFO)
URL gate — head routes: 3051, base routes: 3036, new api routes checked: 15
RESULT: FAIL (2 FAIL, 1 WARN)
==================== orchestrator reconciliation — Phase B FINAL ====================
2026-10-21, after all 14 repair passes of the second review round. 900 passed (638 at round start).
versions PASS 14 --protect paths unchanged, verified per path by pass 14's tidy sweep as well
as by the gate. NOTE: the gate's --allow arguments are mechanically inert
("allowed: 0") and the gate does not echo its resolved --protect list -- a PACK
defect for step 9 harvest, not a repo defect. Evidence is per-file diff review.
hygiene PASS 0 FAIL, 1 WARN -- the known H3 on v2/paginators.py (justified subclass).
schema PASS 0 BREAKING, 54 WARN, 35 INFO. Nothing on master removed or renamed in either
the LMS document or service-wide /api-docs.
tests PASS 900 passed.
urls FAIL (2 FAIL, 1 WARN) -- IMPROVED from 2 FAIL / 2 WARN.
The
accounts/email_searches/R2 warning is GONE: the address moved to thetop-level
POST /api/user/v2/email_searches/in pass 5. That was themust-land-before-merge item, since ADR 0037 freezes an address once published.
What remains is ONLY Phase A's deliberate JSON-404 catch-all
api/user/v2/{_}.(R6, R8, R11) -- not an addressable resource; six candidate shapes were measured
and the only conforming one cannot answer the "unknown collection" case.
All 15 real addresses pass every machine-checkable 0038 rule.
BLIND ON: R6/R8/R11 for a fallback route. Reported, not suppressed.
old-tests FAIL RED AND BLIND, environment: 7 failed / 100 passed / 16 errors, all pymongo
timeouts. SCOPE: this gate collects only
user_api/tests/, which is NONE of thefour surfaces Phase B supersedes. Those were measured separately, base vs branch,
same image:
accounts 18F/285P/18E both refs, 36 unique nodes each, diff EMPTY
profile_images 73 passed
verification_api 16 passed
preferences only the known ModuleStoreTestCase mongo teardown pattern
PROVES: no legacy test that passes on master fails on this branch.
DOES NOT PROVE: that the mongo-dependent tests pass at all. Only CI can say.
Review on the PR, please — this issue stays open until it merges.
Abdul-Muqadim-Arbisoft commentedon Nov 1, 2026
Standardization PR open: #39314
PR: #39314 — feat: standardize the user preferences and email opt-in API
State: open (draft, stacked on #39297)
ADRs applied
12 of 16 ticked. The four open boxes are not gaps: 0032 pagination, 0033 filtering and
0036 nested JSON are applied by exclusion — no response in this slice is a flat list of
records, no operation reads a query parameter, and the document is a flat map — and 0035 MFE
config is n/a. Each states its reason in the PR body.
permission_classeson both views;IsAddressedUseron all sixViewSetholds all five preferenceGates
run_gates.sh — feat/api-adr-user-v2-phase-c vs origin/master, with a MongoDB service attached and
--old-testswidened to both superseded packages.renamed. That is the check that matters: a generated SDK derives a module per operationId and a
model class per component schema name.
user_api/tests/119 +user_api/preferences/tests/74).user_api/v2/alone 920,profile_images/124.v2/paginators.py:6).version-level JSON-404 fallback (
v2/urls.py:126): no trailing slash, an identifier directlyunder the version, and no URL name. All three are correct for a regex fallback that is not an
address and that nothing may
reverse()to. Not to be read as passing.A note on the earlier phases: #39237 and #39297 originally described this
old-testsgate as "redand blind". That was a statement about the harness, not the code — the gate container had no
MongoDB, so every failure was a
pymongoconnection timeout. Both descriptions have beencorrected; two places carrying the retracted wording have not. #39237's first comment still
reads "the gate that runs them is red and blind", and the
testsrow of #39237's own gate tablestill gives the no-database figures (7 failed, 100 passed, 16 errors) for the same directory its
old-testsrow two lines above now reports as 119 passed. Both are outstanding edits on that PR.Published-contract changes outside the preference surface
Three changes this branch carries are not in the preference resource and were previously recorded
in commit messages only. All three are now set out in the PR description: one
operationIdisrenamed in the service-wide document —
v2_user_preferences_retrievebecomesapi_user_v2_user_preferences_retrieve, a name the descriptions published for phase A and for thisphase both still show, so an SDK generated from
/api-docsduring this series must be regenerated;the
usernamepath parameter of/api/user/v2/accounts/{username}/profile_image/gains the^[\w .@_+-]+$pattern the other ten addresses already publish; and?username=on phase A's twodirectory collections is either kept as a comma-separated list and declared, or reverted — that one
is still open and the PR description carries both wordings.
Backward compatibility
The three superseded
/api/user/v1/addresses and the unversioned twin/user_api/v1/preferences/email_opt_in/keep answering exactly as they did; the twin keepsresolving and carries no schema mark, as the other unversioned twins do. The superseded packages are
measured green with the database service their modulestore cases need (119 + 74 above).
Deprecations
Entry for the umbrella DEPR ticket #39128 is drafted at
depr-entry-phase-c.mdand still needsposting — four rows, including the unversioned twin, which is marked in no document and is listed
on the ticket for exactly that reason. §9 of the PR description said the entry "is posted there
once the PR is open"; the PR has been open since 2026-10-26 and the entry is not posted, so that
sentence has been corrected to say so. This tracking comment is itself the outstanding item here.
Abdul-Muqadim-Arbisoft commentedon Nov 1, 2026
Standardization PR open: #PR-NUMBER
PR: https://github.com/openedx/openedx-platform/pull/PR-NUMBER — feat: standardize the account retirement pipeline as
/api/user/v2/State: open (draft, stacked on #39314)
This is Phase D1 of #39058: the
eleven driver-facing operations of the account retirement pipeline, onto seven conforming
addresses. Phase D2 —
replace_usernamesand the learner's owndeactivate_logout— followsin a second PR; they have different callers and different credentials, and one of them has an open
authentication question.
ADRs applied
13 of 16 ticked. The three open boxes are not gaps, and each states its reason in the PR body:
0032 pagination carries one declared deviation (the partner-report claim answers an array, and
carries no maximum, because the entries it answers with are the entries it marks and nothing can
release one); 0035 MFE config is n/a; and 0037 versioning is deliberately left unticked
because two of its clauses are unmet on purpose — no docstring marker on the superseded addresses,
and two lines inside this unpublished version that earlier phases added are edited here.
serializer_classon all four views,help_texton every fieldFilterSet+ strict ordering, deterministic default orderauthentication_classes?view=minimaland?fields=, both refusing an unknown valueGates
run_gates.shagainst the merge-base87cd3811a4,--settings test, with a MongoDB serviceattached and
--old-testswidened to all three superseded packages.schema compareran, and it is not this PR's compatibility evidence. The gate's0 BREAKINGcompares the narrow document against the merge-base, whose base document holds19 paths, all
/api/enrollment/…, and no/api/user/path at all — so every operation this PRalters is an addition there and cannot be reported. Re-run against this PR's branch point it is
FAIL on both documents: 1 BREAKING narrow and 20 BREAKING, 35 WARN, 22 INFO
service-wide. The twenty are the eight
requestBody.requiredgains, the one loss and the ninechanged version 1 success responses, all of them declared in the PR body.
generated at the merge-base and at this head and compared by hand, because the gate reads only
one of the two: 0 operationIds, 0 component names and 0 paths removed in either, and
0 operationIds moved onto an existing address.
A's
v2/paginators.py:6). Four are new H7s and are the point of the last commit: the fourretirement views inherit their permission classes from the layer they share instead of restating
them, which ADR 0026 allows in terms; H7 can only see a class body, and a test asserts the exact
tuple each view resolves. It is 5 WARN, not 1.
the version-level JSON-404 fallback, which has no trailing slash, puts an identifier directly
under the version and carries no URL name, all three correct for a regex fallback that is not an
address.
head routes: 3061, base routes: 3036, nothing removed. Against this PR's own branchpoint the same gate is PASS (0 FAIL, 0 WARN) over the 7 routes this PR adds. Not to be read
as passing against master.
user_api/v2/; 1443 passed over all four packages thisstack touches under
lms.envs.test.configuration in this image raises
ModuleNotFoundError: openedx_learning.urls, on themerge-base and on this head alike, so it says nothing about this change. No-Studio-impact is
argued by inspection instead: the conforming version is mounted at one place,
lms/urls.py:145,and this branch's
cms/*diff is empty. Studio collection did run, with 0 collectionerrors, which is the whole of the claim: 2237 tests on this PR's own test tier's path set —
the one figure comparable with that tier's baseline — and, for completeness, 1443 over the four
packages and 2010 widened to the superseded ones, neither of which matches a baseline measured
anywhere in this work.
Backward compatibility
All eleven superseded addresses keep answering exactly as they did, to the same views and the same
bound actions; the superseded view modules are absent from the diff and their tests are untouched
and green. Three answers change at the conforming addresses — an ambiguous account name refused
400, the platform's language-sync write on the two reads, and405withAllowinstead of403for an unwired method sent by a caller without the capability — each declared in the PR body and
repeated on the DEPR entry.
Across the whole stack, eight operations gain
requestBody.required: truein the service-widedocument and one — the partner-report claim — loses its published body entirely, because its
handler reads none. That is the only
requestBody.requiredlost in either document. Five of theeight gains belong to this PR, including
POST /api/user/v1/preferences/email_opt_in/, which isnot this area's address; the other three belong to earlier phases.
Nine already-published version 1 operations change the success response the document publishes
— six replace
200with204and three replace a singleUserRetirementStatuswith an array ofthem. No handler changes and none of the nine carries the SDK tag, so no generated client is
rebuilt;
docs/lms-openapi.yamland/api-docsdo change. Set out operation by operation in thePR body.
Deprecations
The entry for the umbrella DEPR ticket #39128
is drafted at
depr-entry-phase-d1.mdand still needs posting — eleven rows, plus themigration steps an operator has to act on: the superseded addresses are mounted by Studio as well
as the LMS while every successor is LMS-only; the three changed answers above; the
partner-report drain, which takes the whole queue in one transaction unless a
?limit=is sent —measured at 91.2 s, an 8.97 MB response and 369.9 MB of worker heap for 50,000 queued entries;
and the two schema settings keys (
APPEND_COMPONENTS,ENUM_NAME_OVERRIDES) that a deploymentassigning them replaces rather than extends, silently dropping the component and enum pins.
Abdul-Muqadim-Arbisoft commentedon Nov 1, 2026
Standardization PR open: #39331
PR: #39331 — feat: standardize the registration, sign-in and password reset APIs
State: open
What this does
Part of [API] User, #39058, under the API standardization umbrella #38137 (FC-0118). This PR applies the platform's REST API decisions and OEP-69 to the last group of the user API: registration, sign-in, password reset and registration validation. The decisions are
0025 ·
0026 ·
0027 ·
0028 ·
0029 ·
0030 ·
0031 ·
0032 ·
0033 ·
0034 ·
0035 ·
0036 ·
0037 ·
0038,
plus OEP-66 for scoping.
ADRs applied
8 of 16 decisions applied on this pass; every one left open carries its reason.
error_code,context.failure_count, field-keyeduser_messagelists,value);/api/user/v2/therefore answers in two error shapes until then (Error-format decision).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
Trees: base =
feat/api-adr-user-v2-phase-d1@ed83beab3d(#39330), head =47925143f3, master =d178206575. Each number names the pair it compares. Full outputs are in the first comment. The URL dumps, thedocuments compared against base and master, and the wide test runs were made at
7325fef195, the head before thelast two commits, which touch only
authn_schemas.pyandtest_authn_addresses.py; a number from that tree says so.Gates
Gates for feat/api-adr-user-v2-phase-e @ 4792514 (base ed83bea), all run at head, MongoDB attached:
LMS against base: versions PASS | hygiene PASS | schema PASS (0 BREAKING) | urls PASS | old-tests PASS (820 passed, 4 skipped) | tests PASS (1528 passed)
LMS against master: versions PASS | hygiene FAIL (H3, test_views.py:1500 - earlier phase of the stack) | schema PASS (0 BREAKING) | urls FAIL (R6/R8 on the stack's /api/user/v2/ fallback route) | old-tests PASS | tests PASS (1528 passed)
Studio against base: versions PASS | hygiene PASS | schema FAIL (check-present: Studio serves no /api/user/v2/ path, by design) | urls PASS | old-tests PASS (177 passed) | tests PASS (40 passed)
Full gate output: first comment of the PR.
Deprecations
The nine operations below are superseded by the addresses above. They are marked
deprecated: truein thepublished schemas: the LMS's own document, and the service-wide documents of the LMS and of Studio, which both
serve them. The superseded views are unchanged: there is no response header and no deprecation note in code.
The replacements are served by the LMS only. Studio mounts no version 2 URLconf. Its document marks the nine
operations because Studio serves them, but a client following a deprecation to a replacement address on the
Studio host gets a 404 (
SupersededUserOperationTest::test_no_address_replacing_a_superseded_one_is_published_here).GET /api/user/v1/account/registration/GET /api/user/v2/registrations/confirm_emailfield is now listed (version 2)POST /api/user/v1/account/registration/POST /api/user/v2/registrations/confirm_emailis enforced (version 2)GET /api/user/v2/account/registration/GET /api/user/v2/registrations/POST /api/user/v2/account/registration/POST /api/user/v2/registrations/GET /api/user/v1/account/password_reset/GET /api/user/v2/password_resets/POST /api/user/v1/account/password_reset/token/validate/POST /api/user/v2/password_reset_token_validations/POST /api/user/v1/validation/registrationPOST /api/user/v2/registration_validations/GET /api/user/{api_version}/account/login_session/(v1, v2)GET /api/user/v2/login_sessions/POST /api/user/{api_version}/account/login_session/(v1, v2)POST /api/user/v2/login_sessions/email_or_usernamein place ofemail, which is the existing difference between the two versionsThe unversioned twins are not marked:
/user_api/v1/account/registration/,/user_api/v2/account/registration/,/user_api/{api_version}/account/login_session/,/user_api/v1/account/password_reset/and/user_api/v1/account/password_reset/token/validate/. Neither is/create_account. The/user_api/v1/twins of every address superseded earlier in this series are not markedeither, and deprecating the twins is one decision for all of them at once.
Removal prerequisites. The DEPR entry carries these too.
ENABLE_AUTHN_MICROFRONTENDis off, which is itsdefault. That page posts to the form description's
submit_url, which names the v1 sign-in address withthe v1 field
email. Before the v1 arm can be removed, the form description has to namePOST /api/user/v2/login_sessions/andemail_or_username.submit_urlnames/api/user/v2/account/registration/for every non-v1 request,including those at the new address. Before that address can be removed, it has to name
POST /api/user/v2/registrations/.POST /api/user/v1/account/registration/. The authenticationfront end posts the v2 registration and sign-in addresses and the v1 validation address.
This is tracked on the FC-0118 umbrella DEPR ticket, #39128, which removes them one named release after the
release this ships in. This area's entry is posted there once the PR is open. Of the umbrella's markers, only
the schema flag is applied: there is no
Warningheader and no separate docs page.Follow-ups
/api/user/v3/): serializers and the standardized error envelope (0025,0029), JSON in place of Django's HTML CSRF page, the 200-with-error-body case above, and a sign-in form
description naming the version 2 fields.
account family), and adding the tag later is an addition, never a removal.
docs/lms-openapi.yamlis not regenerated here. It carries none of the version 2 work of this stack.Review on the PR, please — this issue stays open until it merges.