diff --git a/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py b/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py
index d3ccb01876493db9cb74e9ba0ef60d7bd379d5e5..16bbf5b817b229ab9cdf9b82147c3e2ce1e5a952 100644
--- a/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py
+++ b/lms/djangoapps/program_enrollments/api/v1/tests/test_views.py
@@ -54,6 +54,7 @@ class ListViewTestMixin(object):
     def setUpClass(cls):
         super(ListViewTestMixin, cls).setUpClass()
         cls.program_uuid = '00000000-1111-2222-3333-444444444444'
+        cls.program_uuid_tmpl = '00000000-1111-2222-3333-4444444444{0:02d}'
         cls.curriculum_uuid = 'aaaaaaaa-1111-2222-3333-444444444444'
         cls.other_curriculum_uuid = 'bbbbbbbb-1111-2222-3333-444444444444'
 
@@ -77,6 +78,37 @@ class ListViewTestMixin(object):
         return reverse(self.view_name, kwargs=kwargs)
 
 
+class LearnerProgramEnrollmentTest(ListViewTestMixin, APITestCase):
+    """
+    Tests for the LearnerProgramEnrollment view class
+    """
+    view_name = 'programs_api:v1:learner_program_enrollments'
+
+    def test_401_if_anonymous(self):
+        response = self.client.get(reverse(self.view_name))
+        assert status.HTTP_401_UNAUTHORIZED == response.status_code
+
+    @mock.patch('lms.djangoapps.program_enrollments.api.v1.views.get_programs', autospec=True, return_value=None)
+    def test_200_if_no_programs_enrolled(self, mock_get_programs):
+        self.client.login(username=self.student.username, password=self.password)
+        response = self.client.get(reverse(self.view_name))
+        assert status.HTTP_200_OK == response.status_code
+        assert response.data == []
+        assert mock_get_programs.call_count == 1
+
+    @mock.patch('lms.djangoapps.program_enrollments.api.v1.views.get_programs', autospec=True, return_value=[
+        {'uuid': 'boop', 'marketing_slug': 'garbage-program'},
+        {'uuid': 'boop-boop', 'marketing_slug': 'garbage-study'},
+        {'uuid': 'boop-boop-boop', 'marketing_slug': 'garbage-life'},
+    ])
+    def test_200_many_programs(self, mock_get_programs):
+        self.client.login(username=self.student.username, password=self.password)
+        response = self.client.get(reverse(self.view_name))
+        assert status.HTTP_200_OK == response.status_code
+        assert len(response.data) == 3
+        assert mock_get_programs.call_count == 1
+
+
 class ProgramEnrollmentListTest(ListViewTestMixin, APITestCase):
     """
     Tests for GET calls to the Program Enrollments API.
diff --git a/lms/djangoapps/program_enrollments/api/v1/urls.py b/lms/djangoapps/program_enrollments/api/v1/urls.py
index 4089716154d368300078d9cd921bfdf5dc08c0fb..6ec312d49a384e5df23eb5470799269a351808ee 100644
--- a/lms/djangoapps/program_enrollments/api/v1/urls.py
+++ b/lms/djangoapps/program_enrollments/api/v1/urls.py
@@ -8,12 +8,18 @@ from lms.djangoapps.program_enrollments.api.v1.views import (
     ProgramEnrollmentsView,
     ProgramCourseEnrollmentsView,
     ProgramCourseEnrollmentOverviewView,
+    LearnerProgramEnrollmentsView,
 )
 from openedx.core.constants import COURSE_ID_PATTERN
 
 app_name = 'lms.djangoapps.program_enrollments'
 
 urlpatterns = [
+    url(
+        r'^programs/enrollments/$',
+        LearnerProgramEnrollmentsView.as_view(),
+        name='learner_program_enrollments'
+    ),
     url(
         r'^programs/{program_uuid}/enrollments/$'.format(program_uuid=PROGRAM_UUID_PATTERN),
         ProgramEnrollmentsView.as_view(),
diff --git a/lms/djangoapps/program_enrollments/api/v1/views.py b/lms/djangoapps/program_enrollments/api/v1/views.py
index c11493db2eb13537dfaa1332c294994531f81aeb..8c74b1813a3baaf2831b165e30235eedfc4ea907 100644
--- a/lms/djangoapps/program_enrollments/api/v1/views.py
+++ b/lms/djangoapps/program_enrollments/api/v1/views.py
@@ -440,6 +440,58 @@ class ProgramEnrollmentsView(DeveloperErrorViewMixin, PaginatedAPIView):
         )
 
 
+class LearnerProgramEnrollmentsView(DeveloperErrorViewMixin, APIView):
+    """
+    A view for checking the currently logged-in learner's program enrollments
+
+    Path: `/api/program_enrollments/v1/programs/enrollments/`
+
+    Returns:
+      * 200: OK - Contains a list of all programs in which the learner is enrolled.
+      * 401: The requesting user is not authenticated.
+
+    The list will be a list of objects with the following keys:
+      * `uuid` - the identifier of the program in which the learner is enrolled.
+      * `slug` - the string from which a link to the corresponding program page can be constructed.
+
+    Example:
+    [
+      {
+        'uuid': '00000000-1111-2222-3333-444444444444',
+        'slug': 'deadbeef'
+      },
+      {
+        'uuid': '00000000-1111-2222-3333-444444444445',
+        'slug': 'undead-cattle'
+      }
+    ]
+    """
+    authentication_classes = (
+        JwtAuthentication,
+        OAuth2AuthenticationAllowInactiveUser,
+        SessionAuthenticationAllowInactiveUser,
+    )
+    permission_classes = (IsAuthenticated,)
+
+    def get(self, request):
+        """
+        How to respond to a GET request to this endpoint
+        """
+        program_enrollments = ProgramEnrollment.objects.filter(
+            user=request.user,
+            status__in=('enrolled', 'pending')
+        )
+
+        uuids = [enrollment.program_uuid for enrollment in program_enrollments]
+
+        catalog_data_of_programs = get_programs(uuids=uuids) or []
+        programs_in_which_learner_is_enrolled = [{'uuid': program['uuid'], 'slug': program['marketing_slug']}
+                                                 for program
+                                                 in catalog_data_of_programs]
+
+        return Response(programs_in_which_learner_is_enrolled, status.HTTP_200_OK)
+
+
 class ProgramSpecificViewMixin(object):
     """
     A mixin for views that operate on or within a specific program.
diff --git a/openedx/core/djangoapps/catalog/tests/test_utils.py b/openedx/core/djangoapps/catalog/tests/test_utils.py
index 082ad7ecda481aade74bc692b9643ab552a17b7e..9708d14c22e9c3ef0c2d101230812a4088a02ce0 100644
--- a/openedx/core/djangoapps/catalog/tests/test_utils.py
+++ b/openedx/core/djangoapps/catalog/tests/test_utils.py
@@ -208,6 +208,27 @@ class TestGetPrograms(CacheIsolationTestCase):
         self.assertEqual(actual_program, [expected_program])
         self.assertFalse(mock_warning.called)
 
+    def test_get_via_uuids(self, mock_warning, _mock_info):
+        first_program = ProgramFactory()
+        second_program = ProgramFactory()
+
+        cache.set(
+            PROGRAM_CACHE_KEY_TPL.format(uuid=first_program['uuid']),
+            first_program,
+            None
+        )
+        cache.set(
+            PROGRAM_CACHE_KEY_TPL.format(uuid=second_program['uuid']),
+            second_program,
+            None
+        )
+
+        results = get_programs(uuids=[first_program['uuid'], second_program['uuid']])
+
+        assert first_program in results
+        assert second_program in results
+        assert not mock_warning.called
+
 
 @skip_unless_lms
 @mock.patch(UTILS_MODULE + '.logger.info')
diff --git a/openedx/core/djangoapps/catalog/utils.py b/openedx/core/djangoapps/catalog/utils.py
index 89c41ced23b0d75929f473de73cba5fbc5a09baa..191d337ef2e7d48686765e8b40496399a7577e5c 100644
--- a/openedx/core/djangoapps/catalog/utils.py
+++ b/openedx/core/djangoapps/catalog/utils.py
@@ -30,6 +30,8 @@ from student.models import CourseEnrollment
 
 logger = logging.getLogger(__name__)
 
+missing_details_msg_tpl = u'Failed to get details for program {uuid} from the cache.'
+
 
 def create_catalog_api_client(user, site=None):
     """Returns an API client which can be used to make Catalog API requests."""
@@ -82,7 +84,7 @@ def check_catalog_integration_and_get_user(error_message_field):
         return None, catalog_integration
 
 
-def get_programs(site=None, uuid=None, course=None):  # pylint: disable=redefined-outer-name
+def get_programs(site=None, uuid=None, uuids=None, course=None):  # pylint: disable=redefined-outer-name
     """Read programs from the cache.
 
     The cache is populated by a management command, cache_programs.
@@ -90,17 +92,16 @@ def get_programs(site=None, uuid=None, course=None):  # pylint: disable=redefine
     Keyword Arguments:
         site (Site): django.contrib.sites.models object
         uuid (string): UUID identifying a specific program to read from the cache.
+        uuids (list of string): UUIDs identifying a specific programs to read from the cache.
         course (string): course id identifying a specific course run to read from the cache.
 
     Returns:
         list of dict, representing programs.
         dict, if a specific program is requested.
     """
-    if len([arg for arg in (site, uuid, course) if arg is not None]) != 1:
+    if len([arg for arg in (site, uuid, uuids, course) if arg is not None]) != 1:
         raise TypeError('get_programs takes exactly one argument')
 
-    missing_details_msg_tpl = u'Failed to get details for program {uuid} from the cache.'
-
     if uuid:
         program = cache.get(PROGRAM_CACHE_KEY_TPL.format(uuid=uuid))
         if not program:
@@ -113,12 +114,22 @@ def get_programs(site=None, uuid=None, course=None):  # pylint: disable=redefine
             # Currently, the cache does not differentiate between a cache miss and a course
             # without programs. After this is changed, log any cache misses here.
             return []
-    else:
+    elif site:
         uuids = cache.get(SITE_PROGRAM_UUIDS_CACHE_KEY_TPL.format(domain=site.domain), [])
         if not uuids:
             logger.warning(u'Failed to get program UUIDs from the cache for site {}.'.format(site.domain))
 
-    programs = cache.get_many([PROGRAM_CACHE_KEY_TPL.format(uuid=uuid) for uuid in uuids])
+    return get_programs_by_uuids(uuids)
+
+
+def get_programs_by_uuids(uuids):
+    """
+    Gets a list of programs for the provided uuids
+    """
+    # a list of UUID objects would be a perfectly reasonable parameter to provide
+    uuid_strings = [six.text_type(handle) for handle in uuids]
+
+    programs = cache.get_many([PROGRAM_CACHE_KEY_TPL.format(uuid=handle) for handle in uuid_strings])
     programs = list(programs.values())
 
     # The get_many above sometimes fails to bring back details cached on one or
@@ -129,7 +140,7 @@ def get_programs(site=None, uuid=None, course=None):  # pylint: disable=redefine
     # immediately afterwards will succeed in bringing back all the keys. This
     # behavior can be mitigated by trying again for the missing keys, which is
     # what we do here. Splitting the get_many into smaller chunks may also help.
-    missing_uuids = set(uuids) - set(program['uuid'] for program in programs)
+    missing_uuids = set(uuid_strings) - set(program['uuid'] for program in programs)
     if missing_uuids:
         logger.info(
             u'Failed to get details for {count} programs. Retrying.'.format(count=len(missing_uuids))
@@ -138,7 +149,7 @@ def get_programs(site=None, uuid=None, course=None):  # pylint: disable=redefine
         retried_programs = cache.get_many([PROGRAM_CACHE_KEY_TPL.format(uuid=uuid) for uuid in missing_uuids])
         programs += list(retried_programs.values())
 
-        still_missing_uuids = set(uuids) - set(program['uuid'] for program in programs)
+        still_missing_uuids = set(uuid_strings) - set(program['uuid'] for program in programs)
         for uuid in still_missing_uuids:
             logger.warning(missing_details_msg_tpl.format(uuid=uuid))