diff --git a/greg/api/views/consent_type.py b/greg/api/views/consent_type.py
index 88daf30123463b249f434e94976a2892e22cc12f..1484bf5ad4a212c3d3654425736d2b86a20a60ee 100644
--- a/greg/api/views/consent_type.py
+++ b/greg/api/views/consent_type.py
@@ -1,4 +1,4 @@
-from rest_framework import viewsets
+from rest_framework import viewsets, permissions
 
 from greg.api.pagination import PrimaryKeyCursorPagination
 from greg.api.serializers.consent_type import ConsentTypeSerializer
@@ -11,4 +11,5 @@ class ConsentTypeViewSet(viewsets.ModelViewSet):
     queryset = ConsentType.objects.all().order_by("id")
     serializer_class = ConsentTypeSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     lookup_field = "id"
diff --git a/greg/api/views/organizational_unit.py b/greg/api/views/organizational_unit.py
index c6d2236cd68f3318629a4595f4ee6d3f1755918e..2c655c177753e9d6b1e711b160d73c7728e889ca 100644
--- a/greg/api/views/organizational_unit.py
+++ b/greg/api/views/organizational_unit.py
@@ -1,4 +1,4 @@
-from rest_framework import viewsets
+from rest_framework import viewsets, permissions
 
 from greg.api.pagination import PrimaryKeyCursorPagination
 from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer
@@ -11,4 +11,5 @@ class OrganizationalUnitViewSet(viewsets.ModelViewSet):
     queryset = OrganizationalUnit.objects.all().order_by("id")
     serializer_class = OrganizationalUnitSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     lookup_field = "id"
diff --git a/greg/api/views/person.py b/greg/api/views/person.py
index 3214b41504d1023b9160fc29283233424f70081a..8a9fb3f2b77cc1de30c2d49643838e3cf2cae373 100644
--- a/greg/api/views/person.py
+++ b/greg/api/views/person.py
@@ -1,7 +1,7 @@
 from django.core.exceptions import ValidationError
 from django_filters import rest_framework as filters
 from drf_spectacular.utils import extend_schema, OpenApiParameter
-from rest_framework import viewsets, status
+from rest_framework import viewsets, status, permissions
 from rest_framework.response import Response
 
 from greg.api.filters import PersonFilter, RoleFilter, IdentityFilter
@@ -20,6 +20,7 @@ class PersonViewSet(viewsets.ModelViewSet):
     queryset = Person.objects.all().order_by("id")
     serializer_class = PersonSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     lookup_field = "id"
     filter_backends = (filters.DjangoFilterBackend,)
     filterset_class = PersonFilter
@@ -50,6 +51,7 @@ class RoleViewSet(viewsets.ModelViewSet):
 
     queryset = Role.objects.all().order_by("id")
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     filter_backends = (filters.DjangoFilterBackend,)
     filterset_class = RoleFilter
     lookup_field = "id"
@@ -93,6 +95,7 @@ class IdentityViewSet(viewsets.ModelViewSet):
     queryset = Identity.objects.all().order_by("id")
     serializer_class = IdentitySerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     filter_backends = (filters.DjangoFilterBackend,)
     filterset_class = IdentityFilter
     # This is set so that the id parameter in the path of the URL is used for looking up objects
diff --git a/greg/api/views/role_type.py b/greg/api/views/role_type.py
index cb39712515c5e87325dac0ae3551e3b5399af8ac..85f54f3004a8514b53ce7c9dcccd0e74601e39f7 100644
--- a/greg/api/views/role_type.py
+++ b/greg/api/views/role_type.py
@@ -1,4 +1,4 @@
-from rest_framework import viewsets
+from rest_framework import viewsets, permissions
 
 from greg.api.pagination import PrimaryKeyCursorPagination
 from greg.api.serializers.role_type import RoleTypeSerializer
@@ -11,4 +11,5 @@ class RoleTypeViewSet(viewsets.ModelViewSet):
     queryset = RoleType.objects.all().order_by("id")
     serializer_class = RoleTypeSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     lookup_field = "id"
diff --git a/greg/api/views/sponsor.py b/greg/api/views/sponsor.py
index 818fc302b610693f994111a4c1b6f70a3ff93440..8c7a337f6c1ecdd17fb49ee5631bf5efc94170b1 100644
--- a/greg/api/views/sponsor.py
+++ b/greg/api/views/sponsor.py
@@ -3,7 +3,7 @@ import logging
 from django.db.models import ProtectedError
 from django.core.exceptions import ValidationError
 from drf_spectacular.utils import extend_schema, OpenApiParameter
-from rest_framework import mixins, status
+from rest_framework import mixins, status, permissions
 from rest_framework.response import Response
 from rest_framework.viewsets import GenericViewSet, ModelViewSet
 
@@ -22,6 +22,7 @@ class SponsorViewSet(ModelViewSet):
     queryset = Sponsor.objects.all().order_by("id")
     serializer_class = SponsorSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     lookup_field = "id"
 
     def destroy(self, request, *args, **kwargs):
@@ -52,6 +53,7 @@ class SponsorGuestsViewSet(mixins.ListModelMixin, GenericViewSet):
     queryset = Person.objects.all().order_by("id")
     serializer_class = PersonSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     lookup_field = "id"
 
     def get_queryset(self):
@@ -74,6 +76,7 @@ class SponsorOrgunitLinkView(
     queryset = OrganizationalUnit.objects.all().order_by("id")
     serializer_class = OrganizationalUnitSerializer
     pagination_class = PrimaryKeyCursorPagination
+    permission_classes = (permissions.IsAdminUser,)
     # This is set so that the orgunit_id parameter in the path of the URL is used for looking up objects
     lookup_url_kwarg = "orgunit_id"
 
diff --git a/greg/tests/api/test_authz.py b/greg/tests/api/test_authz.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2e00a2a638a47680db4f03614c858156c1ee51b
--- /dev/null
+++ b/greg/tests/api/test_authz.py
@@ -0,0 +1,64 @@
+import pytest
+
+from rest_framework.reverse import reverse
+from rest_framework.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
+
+
+ROUTES: tuple[tuple[str, dict], ...] = (
+    ("v1:consenttype-list", {}),
+    ("v1:consenttype-detail", {"id": 1}),
+    ("v1:orgunit-list", {}),
+    ("v1:orgunit-detail", {"id": 1}),
+    ("v1:person-list", {}),
+    ("v1:person-detail", {"id": 1}),
+    ("v1:person_identity-list", {"person_id": 1}),
+    ("v1:person_identity-detail", {"person_id": 1, "id": 1}),
+    ("v1:person_role-list", {"person_id": 1}),
+    ("v1:person_role-detail", {"person_id": 1, "id": 1}),
+    ("v1:roletype-list", {}),
+    ("v1:roletype-detail", {"id": 1}),
+    ("v1:sponsor-list", {}),
+    ("v1:sponsor-detail", {"id": 1}),
+)
+
+
+def assert_returns_status(endpoint, query_kwargs, client, status):
+    url = reverse(endpoint, kwargs=query_kwargs)
+    get_response = client.get(url)
+    assert get_response.status_code == status
+    post_response = client.post(url, data={"foo": "bar"})
+    assert post_response.status_code == status
+    patch_response = client.post(url, data={"foo": "bar"})
+    assert patch_response.status_code == status
+    delete_response = client.get(url)
+    assert delete_response.status_code == status
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+    ("endpoint", "query_kwargs"),
+    ROUTES,
+)
+def test_forbids_non_admin_users(endpoint, query_kwargs, non_admin_client):
+    # client authenticates with a token, but is not an administrator
+    assert_returns_status(
+        endpoint=endpoint,
+        query_kwargs=query_kwargs,
+        client=non_admin_client,
+        status=HTTP_403_FORBIDDEN,
+    )
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+    ("endpoint", "query_kwargs"),
+    ROUTES,
+)
+def test_requires_authentication(endpoint, query_kwargs, unauthenticated_client):
+    # client does not authenticate
+    assert_returns_status(
+        endpoint=endpoint,
+        query_kwargs=query_kwargs,
+        client=unauthenticated_client,
+        status=HTTP_401_UNAUTHORIZED,
+    )
diff --git a/greg/tests/conftest.py b/greg/tests/conftest.py
index 7b6acedc3680b78217467923cae30bed61fa769a..abeafcdf3c7e88bc7d38fc45f5be11fe1b22d3be 100644
--- a/greg/tests/conftest.py
+++ b/greg/tests/conftest.py
@@ -29,6 +29,15 @@ logging.getLogger("faker").setLevel(logging.ERROR)
 
 @pytest.fixture
 def client() -> APIClient:
+    user, _ = get_user_model().objects.get_or_create(username="test", is_staff=True)
+    token, _ = Token.objects.get_or_create(user=user)
+    client = APIClient()
+    client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}")
+    return client
+
+
+@pytest.fixture
+def non_admin_client() -> APIClient:
     user, _ = get_user_model().objects.get_or_create(username="test")
     token, _ = Token.objects.get_or_create(user=user)
     client = APIClient()
@@ -36,6 +45,12 @@ def client() -> APIClient:
     return client
 
 
+@pytest.fixture
+def unauthenticated_client() -> APIClient:
+    client = APIClient()
+    return client
+
+
 @pytest.fixture
 def pcm_mock():
     class PCMMock:
diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py
index 86267c22ad5cbeea9ae96a13b5b15d83ee410a7d..d916a29e4acb20fae15b24d459e2e44877831ec9 100644
--- a/gregsite/settings/base.py
+++ b/gregsite/settings/base.py
@@ -89,7 +89,7 @@ REST_FRAMEWORK = {
         "rest_framework.authentication.TokenAuthentication",
         "rest_framework.authentication.SessionAuthentication",
     ),
-    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
+    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAdminUser",),
     "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
     # Rate limit settings of invite endpoint
     "DEFAULT_THROTTLE_CLASSES": [