diff --git a/openedx/core/djangoapps/course_groups/cohorts.py b/openedx/core/djangoapps/course_groups/cohorts.py
index 863dcd0096da45530421d1a1f032515f7b9b4ff2..bac42c7f6321d377858023833254a106e742214f 100644
--- a/openedx/core/djangoapps/course_groups/cohorts.py
+++ b/openedx/core/djangoapps/course_groups/cohorts.py
@@ -175,9 +175,7 @@ def get_cohorted_commentables(course_key):
 
 @transaction.commit_on_success
 def get_cohort(user, course_key, assign=True, use_cached=False):
-    """
-    Given a Django user and a CourseKey, return the user's cohort in that
-    cohort.
+    """Returns the user's cohort for the specified course.
 
     The cohort for the user is cached for the duration of a request. Pass
     use_cached=True to use the cached value instead of fetching from the
diff --git a/openedx/core/djangoapps/user_api/accounts/api.py b/openedx/core/djangoapps/user_api/accounts/api.py
index d625d7817a6b9b17601c79a49a758ccac064fe97..a11eab68e88a75d73feac6503d444d7788e439a9 100644
--- a/openedx/core/djangoapps/user_api/accounts/api.py
+++ b/openedx/core/djangoapps/user_api/accounts/api.py
@@ -7,7 +7,7 @@ from django.conf import settings
 from django.core.validators import validate_email, validate_slug, ValidationError
 
 from student.models import User, UserProfile, Registration
-from student.views import validate_new_email, do_email_change_request
+from student import views as student_views
 
 from ..errors import (
     AccountUpdateError, AccountValidationError, AccountUsernameInvalid, AccountPasswordInvalid,
@@ -92,7 +92,6 @@ def get_account_settings(requesting_user, username=None, configuration=None, vie
     return visible_settings
 
 
-@transaction.commit_on_success
 @intercept_errors(UserAPIInternalError, ignore_errors=[UserAPIRequestError])
 def update_account_settings(requesting_user, update, username=None):
     """Update user account information.
@@ -154,7 +153,7 @@ def update_account_settings(requesting_user, update, username=None):
     if read_only_fields:
         for read_only_field in read_only_fields:
             field_errors[read_only_field] = {
-                "developer_message": "This field is not editable via this API",
+                "developer_message": u"This field is not editable via this API",
                 "user_message": _(u"Field '{field_name}' cannot be edited.").format(field_name=read_only_field)
             }
             del update[read_only_field]
@@ -168,7 +167,7 @@ def update_account_settings(requesting_user, update, username=None):
     # If the user asked to change email, validate it.
     if changing_email:
         try:
-            validate_new_email(existing_user, new_email)
+            student_views.validate_new_email(existing_user, new_email)
         except ValueError as err:
             field_errors["email"] = {
                 "developer_message": u"Error thrown from validate_new_email: '{}'".format(err.message),
@@ -206,7 +205,7 @@ def update_account_settings(requesting_user, update, username=None):
     # And try to send the email change request if necessary.
     if changing_email:
         try:
-            do_email_change_request(existing_user, new_email)
+            student_views.do_email_change_request(existing_user, new_email)
         except ValueError as err:
             raise AccountUpdateError(
                 u"Error thrown from do_email_change_request: '{}'".format(err.message),
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 e516fc419968bade164823840e91c4105ec269dc..09c406779a5f94cef4273a842d661b16164569f5 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
@@ -6,6 +6,7 @@ from mock import patch
 
 from django.conf import settings
 from django.core.urlresolvers import reverse
+from django.test.testcases import TransactionTestCase
 from rest_framework.test import APITestCase, APIClient
 
 from student.tests.factories import UserFactory
@@ -509,3 +510,39 @@ class TestAccountAPI(UserAPITestCase):
             error_response.data["developer_message"]
         )
         self.assertIsNone(error_response.data["user_message"])
+
+
+@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+class TestAccountAPITransactions(TransactionTestCase):
+    """
+    Tests the transactional behavior of the account API
+    """
+    test_password = "test"
+
+    def setUp(self):
+        super(TestAccountAPITransactions, self).setUp()
+        self.client = APIClient()
+        self.user = UserFactory.create(password=self.test_password)
+        self.url = reverse("accounts_api", kwargs={'username': self.user.username})
+
+    @patch('student.views.do_email_change_request')
+    def test_update_account_settings_rollback(self, mock_email_change):
+        """
+        Verify that updating account settings is transactional when a failure happens.
+        """
+        # Send a PATCH request with updates to both profile information and email.
+        # Throw an error from the method that is used to process the email change request
+        # (this is the last thing done in the api method). Verify that the profile did not change.
+        mock_email_change.side_effect = [ValueError, "mock value error thrown"]
+        self.client.login(username=self.user.username, password=self.test_password)
+        old_email = self.user.email
+
+        json_data = {"email": "foo@bar.com", "gender": "o"}
+        response = self.client.patch(self.url, data=json.dumps(json_data), content_type="application/merge-patch+json")
+        self.assertEqual(400, response.status_code)
+
+        # Verify that GET returns the original preferences
+        response = self.client.get(self.url)
+        data = response.data
+        self.assertEqual(old_email, data["email"])
+        self.assertEqual(u"m", data["gender"])
diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py
index f749cfad49b35d63e5d855b3c6587dab7486f8a2..3865742d1a5104f910f802f56122b8cb085ac8bc 100644
--- a/openedx/core/djangoapps/user_api/accounts/views.py
+++ b/openedx/core/djangoapps/user_api/accounts/views.py
@@ -4,6 +4,7 @@ NOTE: this API is WIP and has not yet been approved. Do not use this API without
 For more information, see:
 https://openedx.atlassian.net/wiki/display/TNL/User+API
 """
+from django.db import transaction
 from rest_framework.views import APIView
 from rest_framework.response import Response
 from rest_framework import status
@@ -117,7 +118,8 @@ class AccountView(APIView):
         else an error response with status code 415 will be returned.
         """
         try:
-            update_account_settings(request.user, request.DATA, username=username)
+            with transaction.commit_on_success():
+                update_account_settings(request.user, request.DATA, username=username)
         except (UserNotFound, UserNotAuthorized):
             return Response(status=status.HTTP_404_NOT_FOUND)
         except AccountValidationError as err:
diff --git a/openedx/core/djangoapps/user_api/preferences/api.py b/openedx/core/djangoapps/user_api/preferences/api.py
index c3a2b3805d116ffdef436967cc3360081c9cb5a8..447282a4bd05276b15651ed1a7ee39cfad24a45d 100644
--- a/openedx/core/djangoapps/user_api/preferences/api.py
+++ b/openedx/core/djangoapps/user_api/preferences/api.py
@@ -11,8 +11,9 @@ from pytz import UTC
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import ObjectDoesNotExist
-from django.db import transaction, IntegrityError
+from django.db import IntegrityError
 from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_noop
 
 from student.models import UserProfile
 
@@ -77,7 +78,6 @@ def get_user_preferences(requesting_user, username=None):
 
 
 @intercept_errors(UserAPIInternalError, ignore_errors=[UserAPIRequestError])
-@transaction.commit_on_success
 def update_user_preferences(requesting_user, update, username=None):
     """Update the user preferences for the given username.
 
@@ -138,7 +138,6 @@ def update_user_preferences(requesting_user, update, username=None):
 
 
 @intercept_errors(UserAPIInternalError, ignore_errors=[UserAPIRequestError])
-@transaction.commit_on_success
 def set_user_preference(requesting_user, preference_key, preference_value, username=None):
     """Update a user preference for the given username.
 
@@ -173,7 +172,6 @@ def set_user_preference(requesting_user, preference_key, preference_value, usern
 
 
 @intercept_errors(UserAPIInternalError, ignore_errors=[UserAPIRequestError])
-@transaction.commit_on_success
 def delete_user_preference(requesting_user, preference_key, username=None):
     """Deletes a user preference on behalf of a requesting user.
 
@@ -353,11 +351,12 @@ def validate_user_preference_serializer(serializer, preference_key, preference_v
         PreferenceValidationError: the supplied key and/or value for a user preference are invalid.
     """
     if preference_value is None or unicode(preference_value).strip() == '':
-        message = _(u"Preference '{preference_key}' cannot be set to an empty value.").format(
-            preference_key=preference_key
-        )
+        format_string = ugettext_noop(u"Preference '{preference_key}' cannot be set to an empty value.")
         raise PreferenceValidationError({
-            preference_key: {"developer_message": message, "user_message": message}
+            preference_key: {
+                "developer_message": format_string.format(preference_key=preference_key),
+                "user_message": _(format_string).format(preference_key=preference_key)
+            }
         })
     if not serializer.is_valid():
         developer_message = u"Value '{preference_value}' not valid for preference '{preference_key}': {error}".format(
diff --git a/openedx/core/djangoapps/user_api/preferences/tests/test_views.py b/openedx/core/djangoapps/user_api/preferences/tests/test_views.py
index 3a9fac142379e73cb96933231bf05264a061c420..d3f4830edc7a72153875475a58097611c201bb4b 100644
--- a/openedx/core/djangoapps/user_api/preferences/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/preferences/tests/test_views.py
@@ -6,9 +6,13 @@ Unit tests for preference APIs.
 import unittest
 import ddt
 import json
+from mock import patch
 
 from django.core.urlresolvers import reverse
 from django.conf import settings
+from django.test.testcases import TransactionTestCase
+from rest_framework.test import APIClient
+from student.tests.factories import UserFactory
 
 from ...accounts.tests.test_views import UserAPITestCase
 from ..api import set_user_preference
@@ -288,6 +292,52 @@ class TestPreferencesAPI(UserAPITestCase):
         )
 
 
+@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+class TestPreferencesAPITransactions(TransactionTestCase):
+    """
+    Tests the transactional behavior of the preferences API
+    """
+    test_password = "test"
+
+    def setUp(self):
+        super(TestPreferencesAPITransactions, self).setUp()
+        self.client = APIClient()
+        self.user = UserFactory.create(password=self.test_password)
+        self.url = reverse("preferences_api", kwargs={'username': self.user.username})
+
+    @patch('openedx.core.djangoapps.user_api.models.UserPreference.delete')
+    def test_update_preferences_rollback(self, delete_user_preference):
+        """
+        Verify that updating preferences is transactional when a failure happens.
+        """
+        # Create some test preferences values.
+        set_user_preference(self.user, "a", "1")
+        set_user_preference(self.user, "b", "2")
+        set_user_preference(self.user, "c", "3")
+
+        # Send a PATCH request with two updates and a delete. The delete should fail
+        # after one of the updates has happened, in which case the whole operation
+        # should be rolled back.
+        delete_user_preference.side_effect = [Exception, None]
+        self.client.login(username=self.user.username, password=self.test_password)
+        json_data = {
+            "a": "2",
+            "b": None,
+            "c": "1",
+        }
+        response = self.client.patch(self.url, data=json.dumps(json_data), content_type="application/merge-patch+json")
+        self.assertEqual(400, response.status_code)
+
+        # Verify that GET returns the original preferences
+        response = self.client.get(self.url)
+        expected_preferences = {
+            "a": "1",
+            "b": "2",
+            "c": "3",
+        }
+        self.assertEqual(expected_preferences, response.data)
+
+
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class TestPreferencesDetailAPI(UserAPITestCase):
diff --git a/openedx/core/djangoapps/user_api/preferences/views.py b/openedx/core/djangoapps/user_api/preferences/views.py
index b9f7544874333bc13e13914c6cd41c71bb61a3b9..e76f735a743a8b619d418c47016917e3921e6ab7 100644
--- a/openedx/core/djangoapps/user_api/preferences/views.py
+++ b/openedx/core/djangoapps/user_api/preferences/views.py
@@ -10,6 +10,7 @@ from rest_framework import status
 from util.authentication import SessionAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser
 from rest_framework import permissions
 
+from django.db import transaction
 from django.utils.translation import ugettext as _
 
 from openedx.core.lib.api.parsers import MergePatchParser
@@ -88,7 +89,8 @@ class PreferencesView(APIView):
                 status=status.HTTP_400_BAD_REQUEST
             )
         try:
-            update_user_preferences(request.user, request.DATA, username=username)
+            with transaction.commit_on_success():
+                update_user_preferences(request.user, request.DATA, username=username)
         except (UserNotFound, UserNotAuthorized):
             return Response(status=status.HTTP_404_NOT_FOUND)
         except PreferenceValidationError as error:
diff --git a/openedx/core/djangoapps/user_api/tests/test_views.py b/openedx/core/djangoapps/user_api/tests/test_views.py
index 91d189e7641640c635a5a69096c22786fd1a6b6e..bb6d22a7a36ef8a61a9e2f2c70c779b474d10f90 100644
--- a/openedx/core/djangoapps/user_api/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/tests/test_views.py
@@ -1686,7 +1686,6 @@ class TestFacebookRegistrationView(
         self._verify_user_existence(user_exists=False, social_link_exists=False)
 
 
-
 @skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
 class TestGoogleRegistrationView(
     ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase