diff --git a/gracedb/api/v1/superevents/permissions.py b/gracedb/api/v1/superevents/permissions.py
index 022926300ec0228cc642371ca3f51c6bf626063b..c10418f839c7613d98e43be983f33c3196be8093 100644
--- a/gracedb/api/v1/superevents/permissions.py
+++ b/gracedb/api/v1/superevents/permissions.py
@@ -342,6 +342,32 @@ class SupereventVOEventModelPermissions(permissions.DjangoModelPermissions):
     message = 'You do not have permission to create VOEvents.'
 
 
+class SupereventGroupObjectPermissionPermissions(
+    FunctionalModelPermissions):
+    allowed_methods = ['OPTIONS', 'HEAD', 'GET', 'POST']
+
+    def get_get_permissions(self, request):
+        required_permissions = [
+            'superevents.view_supereventgroupobjectpermission',
+        ]
+        self.message = 'You are not allowed to view superevent permissions.'
+        return required_permissions
+
+    def get_post_permissions(self, request):
+        # Get action from request data
+        action = request.data.get('action', None)
+
+        required_permissions = []
+        if (action == 'expose'):
+            required_permissions.append('superevents.expose_superevent')
+            self.message = 'You are not allowed to expose superevents.'
+        elif (action == 'hide'):
+            required_permissions.append('superevents.hide_superevent')
+            self.message = 'You are not allowed to hide superevents.'
+
+        return required_permissions
+
+
 class SupereventSignoffModelPermissions(FunctionalModelPermissions):
     allowed_methods = ['OPTIONS', 'HEAD', 'GET', 'POST', 'PATCH', 'DELETE']
 
diff --git a/gracedb/api/v1/superevents/serializers.py b/gracedb/api/v1/superevents/serializers.py
index 1c66747aafb094aaadb7fb056564a38a4f1f6717..d228c6d3a0c1714cf8f0e948516ab96d103df0fe 100644
--- a/gracedb/api/v1/superevents/serializers.py
+++ b/gracedb/api/v1/superevents/serializers.py
@@ -5,6 +5,7 @@ import os
 
 from django.conf import settings
 from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group as AuthGroup
 from django.utils.translation import ugettext_lazy as _
 
 from rest_framework import serializers, validators
@@ -12,7 +13,7 @@ from rest_framework.exceptions import ValidationError
 
 from events.models import Event, Label, Tag, EMGroup
 from superevents.models import Superevent, Labelling, Log, VOEvent, \
-    EMObservation, EMFootprint, Signoff
+    EMObservation, EMFootprint, Signoff, SupereventGroupObjectPermission
 from .settings import SUPEREVENT_LOOKUP_URL_KWARG
 from ..fields import ParentObjectDefault, CommaSeparatedOrListField, \
     ChoiceDisplayField
@@ -777,3 +778,23 @@ class SupereventSignoffSerializer(serializers.ModelSerializer):
         instance = update_signoff(instance, updater, status, comment,
             add_log_message = True, issue_alert=True)
         return instance
+
+
+class SupereventGroupObjectPermissionSerializer(serializers.ModelSerializer):
+    """
+    NOTE: this is actually a Group serializer, but the purpose is to show
+    a list of GroupObjectPermissions for this group-superevent pair.
+    """
+    permissions = serializers.SerializerMethodField(read_only=True)
+    action = serializers.ChoiceField(write_only=True,
+        choices=['expose', 'hide'])
+    superevent = serializers.HiddenField(write_only=True,
+        default=ParentObjectDefault(context_key='superevent'))
+
+    class Meta:
+        model = AuthGroup
+        fields = ['name', 'permissions', 'action', 'superevent']
+
+    def get_permissions(self, obj):
+        return [sgop.permission.codename for sgop in
+            obj.supereventgroupobjectpermission_set.all()]
diff --git a/gracedb/api/v1/superevents/tests/test_access.py b/gracedb/api/v1/superevents/tests/test_access.py
index 45937bc353841781cc10c4b6e9999927472ac4cc..7677b27da1bebe8cf6531f5c7bbf133628590018 100644
--- a/gracedb/api/v1/superevents/tests/test_access.py
+++ b/gracedb/api/v1/superevents/tests/test_access.py
@@ -18,7 +18,7 @@ from core.tests.utils import GraceDbTestBase, \
 from events.models import Label, Tag, EMGroup
 from superevents.models import Superevent, Labelling, Log, VOEvent, \
     EMObservation, Signoff
-from superevents.utils import create_log
+from superevents.utils import create_log, expose_superevent
 from .mixins import SupereventCreateMixin
 from ...settings import API_VERSION
 
@@ -42,8 +42,9 @@ class SupereventSetup(GraceDbTestBase, SupereventCreateMixin):
         cls.internal_superevent = cls.create_superevent(cls.internal_user)
         cls.lvem_superevent = cls.create_superevent(cls.internal_user)
 
-        # Expose one to LV-EM and assign relevant permissions
-        expose_event_or_superevent_to_lvem(cls.lvem_superevent)
+        # Expose one to the LV-EM and assign relevant permissions
+        expose_superevent(cls.lvem_superevent, cls.internal_user,
+            add_log_message=False, issue_alert=False)
 
 
 class TestSupereventListGet(SupereventSetup, GraceDbApiTestBase):
@@ -2565,6 +2566,183 @@ class TestSupereventFileDetail(SupereventSetup, GraceDbApiTestBase):
         # TODO
 
 
+class TestSupereventGroupObjectPermissionList(SupereventSetup,
+    GraceDbApiTestBase):
+
+    def test_internal_user_get_permissions(self):
+        """Internal user can view permissions list for all superevents"""
+        # Internal
+        url = v_reverse('superevents:superevent-permission-list',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "GET", self.internal_user)
+        # Check response and data
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.data['results'], [])
+
+        # Exposed
+        url = v_reverse('superevents:superevent-permission-list',
+            args=[self.lvem_superevent.superevent_id])
+        response = self.request_as_user(url, "GET", self.internal_user)
+        # Check response and data
+        data = response.data['results']
+        groups = [p['name'] for p in data]
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(data), 2)
+        self.assertIn(settings.PUBLIC_GROUP, groups)
+        self.assertIn(settings.LVEM_OBSERVERS_GROUP, groups)
+
+    def test_lvem_user_get_permissions(self):
+        """LV-EM user can't get permission list"""
+        # Internal
+        url = v_reverse('superevents:superevent-permission-list',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "GET", self.lvem_user)
+        # Check response and data
+        self.assertEqual(response.status_code, 404)
+
+        # Exposed
+        url = v_reverse('superevents:superevent-permission-list',
+            args=[self.lvem_superevent.superevent_id])
+        response = self.request_as_user(url, "GET", self.lvem_user)
+        # Check response and data
+        self.assertEqual(response.status_code, 403)
+
+    def test_public_user_get_permissions(self):
+        """Public user can't get permission list"""
+        # Internal
+        url = v_reverse('superevents:superevent-permission-list',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "GET")
+        # Check response and data
+        self.assertEqual(response.status_code, 403)
+        # TODO: this will be 404 in the future
+
+        # Exposed: TODO
+
+
+class TestSupereventGroupObjectPermissionDetail(SupereventSetup,
+    GraceDbApiTestBase):
+
+    def test_internal_user_get_permissions_detail(self):
+        """Internal user can view all permissions detail for all superevents"""
+        for s in Superevent.objects.all():
+            for gop in s.supereventgroupobjectpermission_set.all():
+                url = v_reverse('superevents:superevent-permission-detail',
+                    args=[s.superevent_id, gop.group.name])
+                response = self.request_as_user(url, "GET", self.internal_user)
+                # Check response and data
+                self.assertEqual(response.status_code, 200)
+
+    def test_lvem_user_get_permissions_detail(self):
+        """LV-EM user can't get permission details"""
+        # Internal
+        url = v_reverse('superevents:superevent-permission-detail',
+            args=[self.internal_superevent.superevent_id, 
+            settings.LVEM_OBSERVERS_GROUP])
+        response = self.request_as_user(url, "GET", self.lvem_user)
+        # Check response and data
+        self.assertEqual(response.status_code, 404)
+
+        # Exposed
+        url = v_reverse('superevents:superevent-permission-detail',
+            args=[self.lvem_superevent.superevent_id,
+            settings.LVEM_OBSERVERS_GROUP])
+        response = self.request_as_user(url, "GET", self.lvem_user)
+        # Check response and data
+        self.assertEqual(response.status_code, 403)
+
+    def test_public_user_get_permissions(self):
+        """Public user can't get permission details"""
+        # Internal
+        url = v_reverse('superevents:superevent-permission-detail',
+            args=[self.internal_superevent.superevent_id,
+            settings.PUBLIC_GROUP])
+        response = self.request_as_user(url, "GET")
+        # Check response and data
+        self.assertEqual(response.status_code, 403)
+        # TODO: this will be 404 in the future
+
+        # Exposed: TODO
+
+
+class TestSupereventGroupObjectPermissionModify(SupereventSetup,
+    AccessManagersGroupAndUserSetup, GraceDbApiTestBase):
+
+    def test_internal_user_expose_internal_superevent(self):
+        """Internal user can't modify permissions to expose superevent"""
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "POST", self.internal_user,
+            data={'action': 'expose'})
+        # Check response
+        self.assertEqual(response.status_code, 403)
+        self.assertIn('not allowed to expose superevents',
+            response.data['detail'])
+
+    def test_internal_user_hide_exposed_superevent(self):
+        """Internal user can't modify permissions to hide superevent"""
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.lvem_superevent.superevent_id])
+        response = self.request_as_user(url, "POST", self.internal_user,
+            data={'action': 'hide'})
+        # Check response
+        self.assertEqual(response.status_code, 403)
+        self.assertIn('not allowed to hide superevents',
+            response.data['detail'])
+
+    def test_access_manager_expose_internal_superevent(self):
+        """Access manager can modify permissions to expose superevent"""
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "POST", self.am_user,
+            data={'action': 'expose'})
+        # Check response
+        groups = [p['name'] for p in response.data]
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.data), 2)
+        self.assertIn(settings.PUBLIC_GROUP, groups)
+        self.assertIn(settings.LVEM_OBSERVERS_GROUP, groups)
+
+    def test_access_manager_hide_exposed_superevent(self):
+        """Access manager can modify permissions to hide superevent"""
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.lvem_superevent.superevent_id])
+        response = self.request_as_user(url, "POST", self.am_user,
+            data={'action': 'hide'})
+        # Check response
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.data, [])
+
+    def test_lvem_user_permissions_modify(self):
+        """LV-EM user can't modify permissions"""
+        # Internal - expose
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "POST", self.lvem_user,
+            data={'action': 'expose'})
+        # Check response and data
+        self.assertEqual(response.status_code, 404)
+
+        # Exposed
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.lvem_superevent.superevent_id])
+        response = self.request_as_user(url, "POST", self.lvem_user,
+            data={'action': 'hide'})
+        # Check response and data
+        self.assertEqual(response.status_code, 403)
+
+    def test_public_user_get_permissions(self):
+        # Internal
+        url = v_reverse('superevents:superevent-permission-modify',
+            args=[self.internal_superevent.superevent_id])
+        response = self.request_as_user(url, "GET")
+        # Check response and data
+        self.assertEqual(response.status_code, 403)
+        # TODO: this will be 404 in the future
+
+        # Exposed: TODO
+
+
 class TestSupereventSignoffList(SupereventSetup, GraceDbApiTestBase):
 
     @classmethod
diff --git a/gracedb/api/v1/superevents/url_templates.py b/gracedb/api/v1/superevents/url_templates.py
index 36e46babc2414f80fdf30e8b781813318bf25cea..49433e0bb5fe910f418ee4a3b78b96a1eacbaa6b 100644
--- a/gracedb/api/v1/superevents/url_templates.py
+++ b/gracedb/api/v1/superevents/url_templates.py
@@ -3,7 +3,8 @@ from __future__ import absolute_import
 from .views import SupereventViewSet, SupereventEventViewSet, \
     SupereventLabelViewSet, SupereventLogViewSet, SupereventLogTagViewSet, \
     SupereventFileViewSet, SupereventVOEventViewSet, \
-    SupereventEMObservationViewSet, SupereventSignoffViewSet
+    SupereventEMObservationViewSet, SupereventGroupObjectPermissionViewSet, \
+    SupereventSignoffViewSet
 from ...utils import api_reverse
 
 # Placeholder parameters for getting URLs with reverse
@@ -18,6 +19,7 @@ PH = {
     SupereventEMObservationViewSet.lookup_url_kwarg: '5555', # EMObservation
                                                              # number (N)
     SupereventSignoffViewSet.lookup_url_kwarg: 'TYPE_INST', # type + instrument
+    SupereventGroupObjectPermissionViewSet.lookup_url_kwarg: 'GROUP_NAME',
 }
 
 
@@ -50,6 +52,8 @@ def construct_url_templates(request=None):
         'superevent-signoff-list': [],
         'superevent-signoff-detail': [
             PH[SupereventSignoffViewSet.lookup_url_kwarg]],
+        'superevent-permission-list': [],
+        'superevent-permission-modify': [],
     }
 
     # Dict of URL templates:
diff --git a/gracedb/api/v1/superevents/urls.py b/gracedb/api/v1/superevents/urls.py
index fdd5393411669c25ffdabdd28ec485c21488ea7d..5e06e9169ed6438ea7a679bcc8c9ff0f3dd43cbc 100644
--- a/gracedb/api/v1/superevents/urls.py
+++ b/gracedb/api/v1/superevents/urls.py
@@ -93,6 +93,20 @@ suburlpatterns = [
         SupereventSignoffViewSet.as_view({'get': 'retrieve',
         'patch': 'partial_update', 'delete': 'destroy'}),
         name='superevent-signoff-detail'),
+
+    # Permissions list and creation
+    url(r'permissions/$', SupereventGroupObjectPermissionViewSet.as_view(
+        {'get': 'list'}), name='superevent-permission-list'),
+    # Permissions modification (expose/hide superevent). Has to come before
+    # permissions detail, otherwise .+ wildcard will match it first.
+    url(r'^permissions/modify/$',
+        SupereventGroupObjectPermissionViewSet.as_view({'post': 'modify'}),
+        name='superevent-permission-modify'),
+    # Permissions detail
+    url(r'permissions/(?P<{lookup_url_kwarg}>.+)/$'.format(lookup_url_kwarg=
+        SupereventGroupObjectPermissionViewSet.lookup_url_kwarg),
+        SupereventGroupObjectPermissionViewSet.as_view({'get': 'retrieve'}),
+        name='superevent-permission-detail'),
 ]
 
 # Full urlpatterns
diff --git a/gracedb/api/v1/superevents/views.py b/gracedb/api/v1/superevents/views.py
index 38f1d67b9506f45be3b2aec08301f04c52bd02c9..8b6a6b4ba37c96b92786b86cc1a20e9f79e5ba4d 100644
--- a/gracedb/api/v1/superevents/views.py
+++ b/gracedb/api/v1/superevents/views.py
@@ -6,6 +6,7 @@ import os
 from django.http import HttpResponse
 from django.db.models import QuerySet
 from django.shortcuts import get_object_or_404
+from django.contrib.auth.models import Group as AuthGroup
 
 from guardian.shortcuts import get_objects_for_user
 from rest_framework import mixins, parsers, permissions, serializers, status, \
@@ -23,7 +24,7 @@ from superevents.models import Superevent, Log, Signoff
 from superevents.utils import remove_tag_from_log, \
     remove_event_from_superevent, remove_label_from_superevent, \
     confirm_superevent_as_gw, get_superevent_by_date_id_or_404, \
-    delete_signoff
+    expose_superevent, hide_superevent, delete_signoff
 from .filters import SupereventSearchFilter, SupereventOrderingFilter
 from .paginators import CustomSupereventPagination
 from .permissions import SupereventModelPermissions, \
@@ -32,12 +33,13 @@ from .permissions import SupereventModelPermissions, \
     SupereventLogTagModelPermissions, SupereventLogTagObjectPermissions, \
     SupereventVOEventModelPermissions, ParentSupereventAnnotatePermissions, \
     SupereventSignoffModelPermissions, SupereventSignoffTypeModelPermissions, \
-    SupereventSignoffTypeObjectPermissions
+    SupereventSignoffTypeObjectPermissions, \
+    SupereventGroupObjectPermissionPermissions
 from .serializers import SupereventSerializer, SupereventUpdateSerializer, \
     SupereventEventSerializer, SupereventLabelSerializer, \
     SupereventLogSerializer, SupereventLogTagSerializer, \
     SupereventVOEventSerializer, SupereventEMObservationSerializer, \
-    SupereventSignoffSerializer
+    SupereventSignoffSerializer, SupereventGroupObjectPermissionSerializer
 from .settings import SUPEREVENT_LOOKUP_URL_KWARG, SUPEREVENT_LOOKUP_REGEX
 from .viewsets import SupereventNestedViewSet
 from ..filters import DjangoObjectAndGlobalPermissionsFilter
@@ -410,3 +412,61 @@ class SupereventSignoffViewSet(viewsets.ModelViewSet,
     def perform_destroy(self, instance):
         delete_signoff(instance, self.request.user, add_log_message=True,
             issue_alert=True)
+
+
+class SupereventGroupObjectPermissionViewSet(viewsets.ModelViewSet,
+                                             SafeCreateMixin,
+                                             SafeDestroyMixin,
+                                             SupereventNestedViewSet):
+    """
+    View for object permissions associated with exposing/hiding
+    a superevent to/from LV-EM users or the public.
+    """
+    serializer_class = SupereventGroupObjectPermissionSerializer
+    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
+        SupereventGroupObjectPermissionPermissions,)
+    lookup_url_kwarg = 'name'
+    lookup_field = 'name'
+
+    def get_queryset(self):
+        superevent = self.get_parent_object()
+
+        # Get GOPs attached to parent superevent
+        gops = superevent.supereventgroupobjectpermission_set.all()
+
+        # Determine groups for these GOPs and return queryset from that
+        gop_group_pks = gops.values_list('group', flat=True).distinct()
+        queryset = AuthGroup.objects.filter(pk__in=gop_group_pks)
+        return queryset
+
+    @action(methods=['post'], detail=False)
+    def modify(self, request, superevent_id):
+        """
+        Expose or hide a superevent by creating or deleting
+        GroupObjectPermissions
+        """
+
+        # Get superevent
+        superevent = self.get_parent_object()
+
+        # Get action from data
+        action = request.data.get('action', None)
+
+        # Validation
+        if action not in ['expose', 'hide']:
+            return Response('action must be \'expose\' or \'hide\'',
+                status=status.HTTP_400_BAD_REQUEST)
+
+        # We make exposing and hiding a superevent idempotent
+        # so as to prevent possible errors due to multi-user
+        # race conditions
+        if action == 'expose' and not superevent.is_exposed:
+            expose_superevent(superevent, request.user, add_log_message=True,
+                issue_alert=True)
+        elif action == 'hide' and superevent.is_exposed:
+            hide_superevent(superevent, request.user, add_log_message=True,
+                issue_alert=True)
+
+        # Return list of permissions
+        serializer = self.get_serializer(self.get_queryset(), many=True)
+        return Response(serializer.data, status=status.HTTP_200_OK)