diff --git a/cms/envs/common.py b/cms/envs/common.py
index af2236b219559d74deb83e877fe0c407f361a457..3b82d90008f579ba0dcf99e8191df3bd1f5b7a26 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -966,6 +966,7 @@ INSTALLED_APPS = [
     # Standard apps
     'django.contrib.auth',
     'django.contrib.contenttypes',
+    'django.contrib.humanize',
     'django.contrib.redirects',
     'django.contrib.sessions',
     'django.contrib.sites',
@@ -980,6 +981,9 @@ INSTALLED_APPS = [
     # Common views
     'openedx.core.djangoapps.common_views',
 
+    # API access administration
+    'openedx.core.djangoapps.api_admin',
+
     # History tables
     'simple_history',
 
@@ -1045,7 +1049,13 @@ INSTALLED_APPS = [
     # Dark-launching languages
     'openedx.core.djangoapps.dark_lang',
 
+    #
     # User preferences
+    'wiki',
+    'django_notify',
+    'course_wiki',  # Our customizations
+    'mptt',
+    'sekizai',
     'openedx.core.djangoapps.user_api',
     'django_openid_auth',
 
diff --git a/cms/envs/test.py b/cms/envs/test.py
index 0542a2cc2e6a01a04be36522bf2e042fee401a7e..6a1e05f7ceab1897c5c1f5c02ad3b89309b53a53 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -317,9 +317,6 @@ SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
 INSTALLED_APPS.append('openedx.core.djangoapps.ccxcon.apps.CCXConnectorConfig')
 FEATURES['CUSTOM_COURSES_EDX'] = True
 
-# API access management -- needed for simple-history to run.
-INSTALLED_APPS.append('openedx.core.djangoapps.api_admin')
-
 ########################## VIDEO IMAGE STORAGE ############################
 VIDEO_IMAGE_SETTINGS = dict(
     VIDEO_IMAGE_MAX_BYTES=2 * 1024 * 1024,    # 2 MB
diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
index fca7eaf31f446d346a7683b2ce69283173dcd487..16c2d98c366f8a319897b58c924874832e7e9fa6 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
@@ -12,6 +12,7 @@ from consent.models import DataSharingConsent
 import ddt
 from django.conf import settings
 from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
 from django.core.cache import cache
 from django.core.urlresolvers import reverse
 from django.test import TestCase
@@ -35,10 +36,18 @@ from rest_framework import status
 from rest_framework.test import APIClient, APITestCase
 from six import text_type
 from social_django.models import UserSocialAuth
+from wiki.models import ArticleRevision, Article
+from wiki.models.pluginbase import RevisionPluginRevision, RevisionPlugin
+from xmodule.modulestore.tests.factories import CourseFactory
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 
 from entitlements.models import CourseEntitlementSupportDetail
 from entitlements.tests.factories import CourseEntitlementFactory
 from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
+from openedx.core.djangoapps.api_admin.models import ApiAccessRequest
+from openedx.core.djangoapps.credit.models import (
+    CreditRequirementStatus, CreditRequest, CreditCourse, CreditProvider, CreditRequirement
+)
 from openedx.core.djangoapps.course_groups.models import CourseUserGroup, UnregisteredLearnerCohortAssignments
 from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
 from openedx.core.djangoapps.user_api.accounts import ACCOUNT_VISIBILITY_PREF_KEY
@@ -47,9 +56,14 @@ from openedx.core.djangoapps.user_api.models import RetirementState, UserRetirem
 from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
 from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
 from openedx.core.lib.token_utils import JwtBuilder
+from survey.models import SurveyAnswer
 from student.models import (
+    CourseEnrollment,
     CourseEnrollmentAllowed,
+    ManualEnrollmentAudit,
+    PasswordHistory,
     PendingEmailChange,
+    PendingNameChange,
     Registration,
     SocialLink,
     UserProfile,
@@ -1918,3 +1932,124 @@ class TestAccountRetirementPost(RetirementTestCase):
         self.assertEqual(self.test_user, self.photo_verification.user)
         for field in ('name', 'face_image_url', 'photo_id_image_url', 'photo_id_key'):
             self.assertEqual('', getattr(self.photo_verification, field))
+
+
+@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS')
+class TestLMSAccountRetirementPost(RetirementTestCase, ModuleStoreTestCase):
+    """
+    Tests the LMS account retirement (GDPR P2) endpoint.
+    """
+    def setUp(self):
+        super(TestLMSAccountRetirementPost, self).setUp()
+        self.pii_standin = 'PII here'
+        self.course = CourseFactory()
+        self.test_user = UserFactory()
+        self.test_superuser = SuperuserFactory()
+        self.original_username = self.test_user.username
+        self.original_email = self.test_user.email
+        self.retired_username = get_retired_username_by_username(self.original_username)
+        self.retired_email = get_retired_email_by_email(self.original_email)
+
+        retirement_state = RetirementState.objects.get(state_name='RETIRING_LMS')
+        self.retirement_status = UserRetirementStatus.create_retirement(self.test_user)
+        self.retirement_status.current_state = retirement_state
+        self.retirement_status.last_state = retirement_state
+        self.retirement_status.save()
+
+        # wiki data setup
+        rp = RevisionPlugin.objects.create(article_id=0)
+        RevisionPluginRevision.objects.create(
+            revision_number=1,
+            ip_address="ipaddresss",
+            plugin=rp,
+            user=self.test_user,
+        )
+        article = Article.objects.create()
+        ArticleRevision.objects.create(ip_address="ipaddresss", user=self.test_user, article=article)
+
+        # ManualEnrollmentAudit setup
+        course_enrollment = CourseEnrollment.enroll(user=self.test_user, course_key=self.course.id)
+        ManualEnrollmentAudit.objects.create(
+            enrollment=course_enrollment, reason=self.pii_standin, enrolled_email=self.pii_standin
+        )
+
+        # CreditRequest and CreditRequirementStatus setup
+        provider = CreditProvider.objects.create(provider_id="Hogwarts")
+        credit_course = CreditCourse.objects.create(course_key=self.course.id)
+        CreditRequest.objects.create(
+            username=self.test_user.username,
+            course=credit_course,
+            provider_id=provider.id,
+            parameters={self.pii_standin},
+        )
+        req = CreditRequirement.objects.create(course_id=credit_course.id)
+        CreditRequirementStatus.objects.create(username=self.test_user.username, requirement=req)
+
+        # ApiAccessRequest setup
+        site = Site.objects.create()
+        ApiAccessRequest.objects.create(
+            user=self.test_user,
+            site=site,
+            website=self.pii_standin,
+            company_address=self.pii_standin,
+            company_name=self.pii_standin,
+            reason=self.pii_standin,
+        )
+
+        # SurveyAnswer setup
+        SurveyAnswer.objects.create(user=self.test_user, field_value=self.pii_standin, form_id=0)
+
+        # other setup
+        PendingNameChange.objects.create(user=self.test_user, new_name=self.pii_standin, rationale=self.pii_standin)
+        PasswordHistory.objects.create(user=self.test_user, password=self.pii_standin)
+
+        # setup for doing POST from test client
+        self.headers = self.build_jwt_headers(self.test_superuser)
+        self.headers['content_type'] = "application/json"
+        self.url = reverse('accounts_retire_misc')
+
+    def post_and_assert_status(self, data, expected_status=status.HTTP_204_NO_CONTENT):
+        """
+        Helper function for making a request to the retire subscriptions endpoint, and asserting the status.
+        """
+        response = self.client.post(self.url, json.dumps(data), **self.headers)
+        self.assertEqual(response.status_code, expected_status)
+        return response
+
+    def test_retire_user(self):
+        # check that rows that will not exist after retirement exist now
+        self.assertTrue(CreditRequest.objects.filter(username=self.test_user.username).exists())
+        self.assertTrue(CreditRequirementStatus.objects.filter(username=self.test_user.username).exists())
+        self.assertTrue(PendingNameChange.objects.filter(user=self.test_user).exists())
+
+        retirement = UserRetirementStatus.get_retirement_for_retirement_action(self.test_user.username)
+        data = {'username': self.original_username}
+        self.post_and_assert_status(data)
+
+        self.test_user.refresh_from_db()
+        self.test_user.profile.refresh_from_db()  # pylint: disable=no-member
+        self.assertEqual(RevisionPluginRevision.objects.get(user=self.test_user).ip_address, None)
+        self.assertEqual(ArticleRevision.objects.get(user=self.test_user).ip_address, None)
+        self.assertFalse(PendingNameChange.objects.filter(user=self.test_user).exists())
+        self.assertEqual(PasswordHistory.objects.get(user=self.test_user).password, '')
+
+        self.assertEqual(
+            ManualEnrollmentAudit.objects.get(
+                enrollment=CourseEnrollment.objects.get(user=self.test_user)
+            ).enrolled_email,
+            retirement.retired_email
+        )
+        self.assertFalse(CreditRequest.objects.filter(username=self.test_user.username).exists())
+        self.assertTrue(CreditRequest.objects.filter(username=retirement.retired_username).exists())
+        self.assertEqual(CreditRequest.objects.get(username=retirement.retired_username).parameters, {})
+
+        self.assertFalse(CreditRequirementStatus.objects.filter(username=self.test_user.username).exists())
+        self.assertTrue(CreditRequirementStatus.objects.filter(username=retirement.retired_username).exists())
+        self.assertEqual(CreditRequirementStatus.objects.get(username=retirement.retired_username).reason, {})
+
+        retired_api_access_request = ApiAccessRequest.objects.get(user=self.test_user)
+        self.assertEqual(retired_api_access_request.website, '')
+        self.assertEqual(retired_api_access_request.company_address, '')
+        self.assertEqual(retired_api_access_request.company_name, '')
+        self.assertEqual(retired_api_access_request.reason, '')
+        self.assertEqual(SurveyAnswer.objects.get(user=self.test_user).field_value, '')
diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py
index b87bf27527966aaa9376de11e3b81762c3573804..e2404481dc38f12a4be082b5f7fb562303dc2bf4 100644
--- a/openedx/core/djangoapps/user_api/accounts/views.py
+++ b/openedx/core/djangoapps/user_api/accounts/views.py
@@ -16,6 +16,7 @@ from django.db import transaction
 from django.utils.translation import ugettext as _
 from edx_rest_framework_extensions.authentication import JwtAuthentication
 from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomerUser, PendingEnterpriseCustomerUser
+from integrated_channels.degreed.models import DegreedLearnerDataTransmissionAudit
 from integrated_channels.sap_success_factors.models import SapSuccessFactorsLearnerDataTransmissionAudit
 from rest_framework import permissions, status
 from rest_framework.authentication import SessionAuthentication
@@ -25,20 +26,29 @@ from rest_framework.views import APIView
 from rest_framework.viewsets import ViewSet
 from six import text_type
 from social_django.models import UserSocialAuth
+from wiki.models import ArticleRevision
+from wiki.models.pluginbase import RevisionPluginRevision
 
 from entitlements.models import CourseEntitlement
 from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
+from openedx.core.djangoapps.api_admin.models import ApiAccessRequest
+from openedx.core.djangoapps.credit.models import CreditRequirementStatus, CreditRequest
 from openedx.core.djangoapps.course_groups.models import UnregisteredLearnerCohortAssignments
 from openedx.core.djangoapps.profile_images.images import remove_profile_images
 from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image
 from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
-from openedx.core.djangolib.oauth2_retirement_utils import retire_dop_oauth2_models, retire_dot_oauth2_models
+from openedx.core.djangolib.oauth2_retirement_utils import retire_dot_oauth2_models, retire_dop_oauth2_models
 from openedx.core.lib.api.authentication import (
     OAuth2AuthenticationAllowInactiveUser,
     SessionAuthenticationAllowInactiveUser
 )
 from openedx.core.lib.api.parsers import MergePatchParser
+from survey.models import SurveyAnswer
 from student.models import (
+    CourseEnrollment,
+    ManualEnrollmentAudit,
+    PasswordHistory,
+    PendingNameChange,
     CourseEnrollmentAllowed,
     PendingEmailChange,
     Registration,
@@ -581,6 +591,54 @@ class AccountRetirementStatusView(ViewSet):
             return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
 
 
+class LMSAccountRetirementView(ViewSet):
+    """
+    Provides an API endpoint for retiring a user in the LMS.
+    """
+    authentication_classes = (JwtAuthentication,)
+    permission_classes = (permissions.IsAuthenticated, CanRetireUser,)
+    parser_classes = (JSONParser,)
+
+    @request_requires_username
+    def post(self, request):
+        """
+        POST /api/user/v1/accounts/retire_misc/
+
+        {
+            'username': 'user_to_retire'
+        }
+
+        Retires the user with the given username in the LMS.
+        """
+
+        username = request.data['username']
+        if is_username_retired(username):
+            return Response(status=status.HTTP_404_NOT_FOUND)
+
+        try:
+            retirement = UserRetirementStatus.get_retirement_for_retirement_action(username)
+            RevisionPluginRevision.retire_user(retirement.user)
+            ArticleRevision.retire_user(retirement.user)
+            PendingNameChange.delete_by_user_value(retirement.user, field='user')
+            PasswordHistory.retire_user(retirement.user.id)
+            course_enrollments = CourseEnrollment.objects.filter(user=retirement.user)
+            ManualEnrollmentAudit.retire_manual_enrollments(course_enrollments, retirement.retired_email)
+
+            CreditRequest.retire_user(retirement.original_username, retirement.retired_username)
+            ApiAccessRequest.retire_user(retirement.user)
+            CreditRequirementStatus.retire_user(retirement.user.username)
+            SurveyAnswer.retire_user(retirement.user.id)
+
+        except UserRetirementStatus.DoesNotExist:
+            return Response(status=status.HTTP_404_NOT_FOUND)
+        except RetirementStateError as exc:
+            return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST)
+        except Exception as exc:  # pylint: disable=broad-except
+            return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+
 class AccountRetirementView(ViewSet):
     """
     Provides API endpoint for retiring a user.
@@ -621,6 +679,7 @@ class AccountRetirementView(ViewSet):
             # Retire data from Enterprise models
             self.retire_users_data_sharing_consent(username, retired_username)
             self.retire_sapsf_data_transmission(user)
+            self.retire_degreed_data_transmission(user)
             self.retire_user_from_pending_enterprise_customer_user(user, retired_email)
             self.retire_entitlement_support_detail(user)
 
@@ -688,6 +747,17 @@ class AccountRetirementView(ViewSet):
                 )
                 audits.update(sapsf_user_id='')
 
+    @staticmethod
+    def retire_degreed_data_transmission(user):
+        for ent_user in EnterpriseCustomerUser.objects.filter(user_id=user.id):
+            for enrollment in EnterpriseCourseEnrollment.objects.filter(
+                enterprise_customer_user=ent_user
+            ):
+                audits = DegreedLearnerDataTransmissionAudit.objects.filter(
+                    enterprise_course_enrollment_id=enrollment.id
+                )
+                audits.update(degreed_user_email='')
+
     @staticmethod
     def retire_user_from_pending_enterprise_customer_user(user, retired_email):
         PendingEnterpriseCustomerUser.objects.filter(user_email=user.email).update(user_email=retired_email)
diff --git a/openedx/core/djangoapps/user_api/urls.py b/openedx/core/djangoapps/user_api/urls.py
index d2bcb29fca1c256ead1eeee94c9771fb5d3816a4..61086021ab62460bfed5a95dcec384bf61739800 100644
--- a/openedx/core/djangoapps/user_api/urls.py
+++ b/openedx/core/djangoapps/user_api/urls.py
@@ -12,7 +12,8 @@ from .accounts.views import (
     AccountRetirementStatusView,
     AccountRetirementView,
     AccountViewSet,
-    DeactivateLogoutView
+    DeactivateLogoutView,
+    LMSAccountRetirementView
 )
 from .preferences.views import PreferencesDetailView, PreferencesView
 from .verification_api.views import IDVerificationStatusView
@@ -47,6 +48,9 @@ RETIREMENT_POST = AccountRetirementView.as_view({
     'post': 'post',
 })
 
+RETIREMENT_LMS_POST = LMSAccountRetirementView.as_view({
+    'post': 'post',
+})
 
 urlpatterns = [
     url(
@@ -104,6 +108,11 @@ urlpatterns = [
         RETIREMENT_POST,
         name='accounts_retire'
     ),
+    url(
+        r'^v1/accounts/retire_misc/$',
+        RETIREMENT_LMS_POST,
+        name='accounts_retire_misc'
+    ),
     url(
         r'^v1/accounts/update_retirement_status/$',
         RETIREMENT_UPDATE,