diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index 2e49b2c9967d806354b5483aa957a3b138b34820..db2b0d1d707c841cddded6c60bf9ba0bf8b1dbd6 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -25,6 +25,7 @@ from django.test import RequestFactory, TestCase
 from django.urls import reverse as django_reverse
 from django.utils.translation import ugettext as _
 from edx_when.api import get_dates_for_course, get_overrides_for_user, set_date_for_block
+from freezegun import freeze_time
 from mock import Mock, NonCallableMock, patch
 from opaque_keys.edx.keys import CourseKey
 from opaque_keys.edx.locator import UsageKey
@@ -2781,8 +2782,11 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
         """
         Test the CSV output for the anonymized user ids.
         """
+        base_time = datetime.datetime.now(UTC)
         url = reverse('get_anon_ids', kwargs={'course_id': text_type(self.course.id)})
-        response = self.client.post(url, {})
+        with freeze_time(base_time):
+            response = self.client.post(url, {})
+
         self.assertEqual(response['Content-Type'], 'text/csv')
         body = response.content.decode("utf-8").replace('\r', '')
         self.assertTrue(body.startswith(
@@ -2794,6 +2798,19 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
         )
         self.assertIn("attachment; filename=org", response['Content-Disposition'])
 
+        # Test rate-limiting
+        # The get_anon_ids view is computationally intensive and its execution time can vary
+        # depending on the number of enrollments in a course.  We are rate limiting it to
+        # prevent too many concurrent calls which could result in a denial of service for
+        # other users of the lms.
+        with freeze_time(base_time + datetime.timedelta(minutes=1)):
+            response = self.client.post(url, {})
+            assert response.status_code == 403
+
+        with freeze_time(base_time + datetime.timedelta(minutes=5)):
+            response = self.client.post(url, {})
+            assert response.status_code == 200
+
     @patch('lms.djangoapps.instructor_task.models.logger.error')
     @patch.dict(settings.GRADES_DOWNLOAD, {'STORAGE_TYPE': 's3', 'ROOT_PATH': 'tmp/edx-s3/grades'})
     def test_list_report_downloads_error(self, mock_error):
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 3f09cacf2a61d5539128d2777bc168905993b8fc..26574c5e80128f2f03fc294380d6250dd4da8d14 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -34,6 +34,7 @@ from edx_rest_framework_extensions.auth.session.authentication import SessionAut
 from edx_when.api import get_date_for_block
 from opaque_keys import InvalidKeyError
 from opaque_keys.edx.keys import CourseKey, UsageKey
+from ratelimit.decorators import ratelimit
 from rest_framework import status
 from rest_framework.permissions import IsAuthenticated, IsAdminUser
 from rest_framework.response import Response
@@ -1381,6 +1382,7 @@ def get_proctored_exam_results(request, course_id):
 
 @ensure_csrf_cookie
 @cache_control(no_cache=True, no_store=True, must_revalidate=True)
+@ratelimit(key="user", rate="1/5m", block=True)
 @require_course_permission(permissions.CAN_RESEARCH)
 def get_anon_ids(request, course_id):
     """