diff --git a/greg/api/urls.py b/greg/api/urls.py
index cf3e904813d8cbe80aec9558118fe63989297cc1..77b91ebc608851cdab1c1479e5bf8a37670a844d 100644
--- a/greg/api/urls.py
+++ b/greg/api/urls.py
@@ -1,12 +1,7 @@
 from django.urls import (
-    path,
     re_path,
 )
 from rest_framework.routers import DefaultRouter
-from drf_spectacular.views import (
-    SpectacularAPIView,
-    SpectacularSwaggerView,
-)
 
 from greg.api.views.consent import ConsentViewSet
 from greg.api.views.organizational_unit import OrganizationalUnitViewSet
@@ -16,7 +11,6 @@ from greg.api.views.person import (
     PersonIdentityViewSet,
 )
 from greg.api.views.role import RoleViewSet
-from greg.api.views.health import Health
 from greg.api.views.sponsor import SponsorViewSet, SponsorGuestsViewSet
 
 router = DefaultRouter()
@@ -30,13 +24,6 @@ router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit")
 urlpatterns = router.urls
 
 urlpatterns += [
-    path("schema/", SpectacularAPIView.as_view(), name="schema"),
-    path(
-        "schema/swagger-ui/",
-        SpectacularSwaggerView.as_view(url_name="schema"),
-        name="swagger-ui",
-    ),
-    path("health/", Health.as_view()),
     re_path(
         r"^persons/(?P<person_id>[0-9]+)/roles/$",
         PersonRoleViewSet.as_view({"get": "list", "post": "create"}),
diff --git a/greg/tests/api/test_consent.py b/greg/tests/api/test_consent.py
index 24ae8c837c73054d8edcad98abcefa1f39624e59..dd59ad52e5a87aa7eb7b16d13aeae765eb72e532 100644
--- a/greg/tests/api/test_consent.py
+++ b/greg/tests/api/test_consent.py
@@ -22,7 +22,7 @@ def consent_foo() -> Consent:
 
 @pytest.mark.django_db
 def test_get_consent(client, consent_foo):
-    resp = client.get(reverse("consent-detail", kwargs={"id": consent_foo.id}))
+    resp = client.get(reverse("v1:consent-detail", kwargs={"id": consent_foo.id}))
     assert resp.status_code == status.HTTP_200_OK
     data = resp.json()
     assert data.get("id") == consent_foo.id
diff --git a/greg/tests/api/test_person.py b/greg/tests/api/test_person.py
index 211f141720cf461a9b43071dcce9ca32b4ee96f8..772c6ee546a8823dc339d19b12495ffe1008a0c6 100644
--- a/greg/tests/api/test_person.py
+++ b/greg/tests/api/test_person.py
@@ -72,7 +72,7 @@ def consent_foo() -> Consent:
 
 @pytest.mark.django_db
 def test_get_person(client, person_foo):
-    resp = client.get(reverse("person-detail", kwargs={"id": person_foo.id}))
+    resp = client.get(reverse("v1:person-detail", kwargs={"id": person_foo.id}))
     assert resp.status_code == HTTP_200_OK
     data = resp.json()
     assert data.get("id") == person_foo.id
@@ -82,7 +82,7 @@ def test_get_person(client, person_foo):
 
 @pytest.mark.django_db
 def test_persons(client, person_foo, person_bar):
-    resp = client.get(reverse("person-list"))
+    resp = client.get(reverse("v1:person-list"))
     assert resp.status_code == HTTP_200_OK
     data = resp.json()
     assert len(data["results"]) == 2
@@ -92,7 +92,7 @@ def test_persons(client, person_foo, person_bar):
 def test_persons_verified_filter_include(
     client, person_bar, person_foo, person_foo_verified
 ):
-    url = reverse("person-list")
+    url = reverse("v1:person-list")
     response = client.get(url, {"verified": "true"})
     results = response.json()["results"]
     assert len(results) == 1
@@ -104,7 +104,7 @@ def test_persons_verified_filter_include(
 def test_persons_verified_filter_exclude(
     client, person_bar, person_foo, person_foo_verified
 ):
-    url = reverse("person-list")
+    url = reverse("v1:person-list")
     response = client.get(url, {"verified": "false"})
     results = response.json()["results"]
     assert len(results) == 1
@@ -116,7 +116,7 @@ def test_persons_verified_filter_exclude(
 def test_add_role(
     client, person_foo, role_visiting_professor, sponsor_guy, unit_human_resources
 ):
-    url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
+    url = reverse("v1:person_role-list", kwargs={"person_id": person_foo.id})
     roles_for_person = client.get(url).json()["results"]
 
     # Check that there are no roles for the person, and then add one
@@ -143,7 +143,7 @@ def test_add_role(
 
 @pytest.mark.django_db
 def test_update_role(client, person_foo, role_data_guest):
-    url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
+    url = reverse("v1:person_role-list", kwargs={"person_id": person_foo.id})
     response = client.post(url, role_data_guest)
     response_data = response.json()
 
@@ -155,7 +155,7 @@ def test_update_role(client, person_foo, role_data_guest):
     updated_role["start_date"] = "2021-06-15"
 
     url_detail = reverse(
-        "person_role-detail", kwargs={"person_id": person_foo.id, "id": role_id}
+        "v1:person_role-detail", kwargs={"person_id": person_foo.id, "id": role_id}
     )
     client.patch(url_detail, updated_role)
 
@@ -168,14 +168,14 @@ def test_update_role(client, person_foo, role_data_guest):
 
 @pytest.mark.django_db
 def test_delete_role(client, person_foo, role_data_guest):
-    url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
+    url = reverse("v1:person_role-list", kwargs={"person_id": person_foo.id})
     role_id = client.post(url, role_data_guest).json()["id"]
     roles_for_person = client.get(url).json()["results"]
 
     assert len(roles_for_person) == 1
 
     url_detail = reverse(
-        "person_role-detail", kwargs={"person_id": person_foo.id, "id": role_id}
+        "v1:person_role-detail", kwargs={"person_id": person_foo.id, "id": role_id}
     )
     client.delete(url_detail)
 
@@ -187,12 +187,12 @@ def test_identity_list(
     client, person_foo, person_foo_verified, person_foo_not_verified
 ):
     response = client.get(
-        reverse("person-list"),
+        reverse("v1:person-list"),
         data={"first_name": person_foo.first_name, "last_name": person_foo.last_name},
     )
     person_id = response.json()["results"][0]["id"]
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_id})
     )
     data = response.json()["results"]
     assert len(data) == 2
@@ -201,7 +201,7 @@ def test_identity_list(
 @pytest.mark.django_db
 def test_identity_add(client, person_foo):
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 0
@@ -212,11 +212,12 @@ def test_identity_add(client, person_foo):
         "value": "12345",
     }
     client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id}),
+        data=data,
     )
 
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 1
@@ -230,12 +231,13 @@ def test_identity_add_duplicate(client, person_foo, person_bar):
         "value": "12345",
     }
     client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_bar.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_bar.id}),
+        data=data,
     )
 
     with pytest.raises(ValidationError):
         client.post(
-            reverse("person_identity-list", kwargs={"person_id": person_foo.id}),
+            reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id}),
             data=data,
         )
 
@@ -248,18 +250,20 @@ def test_identity_add_valid_duplicate(client, person_foo, person_bar):
         "value": "12345",
     }
     client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_bar.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_bar.id}),
+        data=data,
     )
 
     client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id}),
+        data=data,
     )
 
 
 @pytest.mark.django_db
 def test_identity_delete(client, person_foo):
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 0
@@ -270,7 +274,8 @@ def test_identity_delete(client, person_foo):
         "value": "12345",
     }
     post_response = client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id}),
+        data=data,
     )
     identity_id = post_response.json()["id"]
 
@@ -281,12 +286,13 @@ def test_identity_delete(client, person_foo):
         "value": "1234413241235",
     }
     post_response2 = client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id}),
+        data=data,
     )
     identity_id2 = post_response2.json()["id"]
 
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 2
@@ -294,14 +300,14 @@ def test_identity_delete(client, person_foo):
     # Delete the first identity created
     client.delete(
         reverse(
-            "person_identity-detail",
+            "v1:person_identity-detail",
             kwargs={"person_id": person_foo.id, "id": identity_id},
         )
     )
 
     # Check that the other identity is still there
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
 
@@ -312,7 +318,7 @@ def test_identity_delete(client, person_foo):
 @pytest.mark.django_db
 def test_identity_update(client, person_foo):
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 0
@@ -323,11 +329,12 @@ def test_identity_update(client, person_foo):
         "value": "12345",
     }
     client.post(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id}),
+        data=data,
     )
 
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 1
@@ -345,7 +352,7 @@ def test_identity_update(client, person_foo):
     }
     patch_response = client.patch(
         reverse(
-            "person_identity-detail",
+            "v1:person_identity-detail",
             kwargs={"person_id": person_foo.id, "id": identity_id},
         ),
         data=data_updated,
@@ -353,7 +360,7 @@ def test_identity_update(client, person_foo):
     assert patch_response.status_code == status.HTTP_200_OK
 
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     results = response.json()["results"]
     assert len(results) == 1
@@ -367,25 +374,25 @@ def test_identity_update(client, person_foo):
 def test_remove_person(
     client, person_foo, role_data_guest, person_foo_verified, person_foo_not_verified
 ):
-    url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
+    url = reverse("v1:person_role-list", kwargs={"person_id": person_foo.id})
     client.post(url, role_data_guest)
 
     roles_for_person = client.get(url).json()["results"]
     assert len(roles_for_person) == 1
 
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     assert len(response.json()["results"]) == 2
 
     # Delete the person and check that the data has been removed
-    client.delete(reverse("person-detail", kwargs={"id": person_foo.id}))
+    client.delete(reverse("v1:person-detail", kwargs={"id": person_foo.id}))
 
     updated_role_data = client.get(url)
     assert len(updated_role_data.json()["results"]) == 0
 
     response = client.get(
-        reverse("person_identity-list", kwargs={"person_id": person_foo.id})
+        reverse("v1:person_identity-list", kwargs={"person_id": person_foo.id})
     )
     assert len(response.json()["results"]) == 0
 
@@ -394,7 +401,7 @@ def test_remove_person(
 def test_add_duplicate_role_fails(
     client, person_foo: Person, person_foo_role: PersonRole
 ):
-    url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
+    url = reverse("v1:person_role-list", kwargs={"person_id": person_foo.id})
     roles_for_person = client.get(url).json()["results"]
 
     assert len(roles_for_person) == 1
diff --git a/greg/tests/api/test_sponsor.py b/greg/tests/api/test_sponsor.py
index 06e5eb0c74045772401d143b3e2f7eb1171e88d4..ce444dbbf5e0dbd58527895c81381a4ffcf1b635 100644
--- a/greg/tests/api/test_sponsor.py
+++ b/greg/tests/api/test_sponsor.py
@@ -12,13 +12,13 @@ def test_add_sponsor(client):
         "last_name": "Sponsor",
     }
 
-    post_response = client.post(reverse("sponsor-list"), data=data)
+    post_response = client.post(reverse("v1:sponsor-list"), data=data)
 
     assert post_response.status_code == status.HTTP_201_CREATED
 
     response_data = post_response.json()
     list_response = client.get(
-        reverse("sponsor-detail", kwargs={"id": response_data["id"]})
+        reverse("v1:sponsor-detail", kwargs={"id": response_data["id"]})
     )
     list_response_data = list_response.json()
 
@@ -29,7 +29,7 @@ def test_add_sponsor(client):
 
 @pytest.mark.django_db
 def test_sponsor_guest_list(client, sponsor_guy, person_foo_role):
-    url = reverse("sponsor_guests-list", kwargs={"sponsor_id": sponsor_guy.id})
+    url = reverse("v1:sponsor_guests-list", kwargs={"sponsor_id": sponsor_guy.id})
     guests_for_sponsor = client.get(url).json()["results"]
 
     assert len(guests_for_sponsor) == 1
@@ -38,7 +38,7 @@ def test_sponsor_guest_list(client, sponsor_guy, person_foo_role):
 
 @pytest.mark.django_db
 def test_sponsor_empty_guest_list(client, sponsor_guy):
-    url = reverse("sponsor_guests-list", kwargs={"sponsor_id": sponsor_guy.id})
+    url = reverse("v1:sponsor_guests-list", kwargs={"sponsor_id": sponsor_guy.id})
     guests_for_sponsor = client.get(url).json()["results"]
 
     assert len(guests_for_sponsor) == 0
diff --git a/greg/urls.py b/greg/urls.py
index aae406a6ca1128569d8b7efd1140e97e1b912da4..b378dd77e8b70f451eedd24b350543662a8c8c71 100644
--- a/greg/urls.py
+++ b/greg/urls.py
@@ -16,9 +16,25 @@ Including another URLconf
 from typing import List
 from django.urls import path, include
 from django.urls.resolvers import URLResolver
+from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
+from rest_framework.versioning import NamespaceVersioning
 
 from greg.api import urls as api_urls
+from greg.api.views.health import Health
 
 urlpatterns: List[URLResolver] = [
-    path("api/", include(api_urls.urlpatterns)),
+    path(
+        "schema/",
+        SpectacularAPIView.as_view(versioning_class=NamespaceVersioning),
+        name="schema",
+    ),  # type: ignore
+    path(
+        "schema/swagger-ui/",
+        SpectacularSwaggerView.as_view(
+            url_name="schema", versioning_class=NamespaceVersioning
+        ),  # type: ignore
+        name="swagger-ui",
+    ),
+    path("health/", Health.as_view()),  # type: ignore
+    path("api/v1/", include((api_urls.urlpatterns, "greg"), namespace="v1")),
 ]
diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py
index fb28c3b132d1a5df72cc2eeb3abe08bc02b9ca4b..11236a8a01cee893538b3e94dea43b32bfa6d865 100644
--- a/gregsite/settings/base.py
+++ b/gregsite/settings/base.py
@@ -58,6 +58,9 @@ MIDDLEWARE = [
 ]
 
 REST_FRAMEWORK = {
+    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
+    "DEFAULT_VERSION": "v1",
+    "ALLOWED_VERSIONS": ("v1",),
     "DEFAULT_AUTHENTICATION_CLASSES": (
         "rest_framework.authentication.TokenAuthentication",
         "rest_framework.authentication.SessionAuthentication",