diff --git a/src/consents/migrations/0005_remove_userconsents_provider_schema_and_more.py b/src/consents/migrations/0005_remove_userconsents_provider_schema_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..2dffac17584025b4d77354926d5a2c83493260c3
--- /dev/null
+++ b/src/consents/migrations/0005_remove_userconsents_provider_schema_and_more.py
@@ -0,0 +1,46 @@
+from django.db import migrations, models
+import django.db.models.deletion
+
+def migrate_user_consent_to_verb(apps, schema_editor):
+    UserConsents = apps.get_model('consents', 'UserConsents')
+    Verb = apps.get_model('providers', 'Verb')
+    for consent in UserConsents.objects.all():
+        schema = consent.provider_schema
+        verb_id = consent.verb
+        verb = Verb.objects.get(verb_id=verb_id, provider_schema=schema)
+        consent.verb = verb.id
+        group = verb.providerverbgroup_set.first()
+        consent.verb_group_id = group.id
+        consent.save()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('providers', '0008_verb_remove_providerschema_essential_verbs_and_more'),
+        ('consents', '0004_userconsents_active'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='userconsents',
+            name='verb_group',
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE,
+                                    to='providers.providerverbgroup'),
+        ),
+        migrations.RunPython(migrate_user_consent_to_verb, reverse_code=migrations.RunPython.noop),
+        migrations.AlterField(
+            model_name='userconsents',
+            name='verb',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.verb'),
+        ),
+        migrations.RemoveField(
+            model_name='userconsents',
+            name='provider_schema',
+        ),
+        migrations.AlterField( # now we can remove the null default
+            model_name='userconsents',
+            name='verb_group',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+                                    to='providers.providerverbgroup'),
+        )
+    ]
diff --git a/src/consents/models.py b/src/consents/models.py
index 37761ed51c88683392be06ab84641a3b34b5d64e..442e3f6c958aff6d0dc01529ebab92bd045d19ff 100644
--- a/src/consents/models.py
+++ b/src/consents/models.py
@@ -3,19 +3,19 @@ from email.policy import default
 from django.conf import settings
 from django.db import models
 
-from providers.models import Provider, ProviderSchema
+from providers.models import Provider, ProviderVerbGroup, Verb
 
 
 class UserConsents(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
     consented = models.BooleanField()
-    provider_schema = models.ForeignKey(ProviderSchema, on_delete=models.CASCADE)
     provider = models.ForeignKey(Provider, on_delete=models.CASCADE)
-    verb = models.CharField(db_index=True, max_length=500)
+    verb_group = models.ForeignKey(ProviderVerbGroup, default=None, on_delete=models.CASCADE)
+    verb = models.ForeignKey(Verb, on_delete=models.CASCADE)
     object = models.JSONField()
     updated = models.DateTimeField(auto_now=True)
     created = models.DateTimeField(auto_now_add=True)
     active = models.BooleanField(default=True)
 
     def __str__(self):
-        return "User consent: " + self.verb
+        return "User consent for verb " + str(self.verb)
diff --git a/src/consents/serializers.py b/src/consents/serializers.py
index 59886d527222e3286aada33527f9f0479430448b..7dd3c72b6400b3becd0af2e879218641660e9193 100644
--- a/src/consents/serializers.py
+++ b/src/consents/serializers.py
@@ -1,9 +1,10 @@
 import json
 from typing import Dict
 
+from django.core.exceptions import ObjectDoesNotExist
 from rest_framework import serializers
 
-from providers.models import AnalyticsToken, Provider, ProviderSchema
+from providers.models import AnalyticsToken, Provider, ProviderSchema, Verb, VerbObject, ProviderVerbGroup
 
 
 class ProviderSchemaSerializer(serializers.ModelSerializer):
@@ -14,8 +15,8 @@ class ProviderSchemaSerializer(serializers.ModelSerializer):
             "id": obj.provider.definition_id,
             "name": obj.provider.name,
             "description": obj.provider.description,
-            "groups": obj.groups,
-            "essential_verbs": obj.essential_verbs
+            "verbs": VerbSerializer(obj.verbs(), many=True).data,
+            "essential_verbs": VerbSerializer(obj.essential_verbs(), many=True).data
         }
 
     class Meta:
@@ -23,21 +24,94 @@ class ProviderSchemaSerializer(serializers.ModelSerializer):
         fields = "__all__"
 
 
+class VerbSerializer(serializers.ModelSerializer):
+    id = serializers.SerializerMethodField("_id")
+    label = serializers.SerializerMethodField("_label")
+    description = serializers.SerializerMethodField("_description")
+    defaultConsent = serializers.SerializerMethodField("_defaultConsent")
+    objects = serializers.SerializerMethodField("_objects")
+
+    def _id(self, obj: Verb):
+        return obj.verb_id
+
+    def _label(self, obj: Verb):
+        return obj.label
+
+    def _description(self, obj: Verb):
+        return obj.description
+
+    def _defaultConsent(self, obj: Verb):
+        return obj.default_consent
+
+    def _objects(self, obj: Verb):
+        return VerbObjectSerializer(VerbObject.objects.filter(verb=obj), many=True).data
+
+    class Meta:
+        model = Verb
+        exclude = ["verb_id", "default_consent", "provider", "provider_schema", "active",
+                   "essential", "allow_anonymized_collection"]
+
+
+class VerbObjectSerializer(serializers.ModelSerializer):
+    id = serializers.SerializerMethodField("_id")
+    objectType = serializers.SerializerMethodField("_objectType")
+    defaultConsent = serializers.SerializerMethodField("_defaultConsent")
+    definition = serializers.SerializerMethodField("_definition")
+
+    def _id(self, obj: VerbObject):
+        return obj.object_id
+
+    def _objectType(self, obj: VerbObject):
+        return obj.object_type
+
+    def _definition(self, obj: VerbObject):
+        return obj.definition
+
+    def _defaultConsent(self, obj: VerbObject):
+        return obj.verb.default_consent
+
+    class Meta:
+        model = VerbObject
+        exclude = ["object_id", "verb"]
+
+class ProviderVerbGroupSerializer(serializers.ModelSerializer):
+    verbs = serializers.SerializerMethodField("_verbs")
+    purposeOfCollection = serializers.SerializerMethodField("_purposeOfCollection")
+
+    def _verbs(self, obj):
+        return VerbSerializer(obj.verbs.all(), many=True).data
+
+    def _purposeOfCollection(self, obj):
+        return obj.purpose_of_collection
+
+    class Meta:
+        model = ProviderVerbGroup
+        exclude = ["purpose_of_collection"]
+
 class ProvidersSerializer(serializers.ModelSerializer):
     versions = serializers.SerializerMethodField("_versions")
+    groups = serializers.SerializerMethodField("_groups")
 
     def _versions(self, obj):
         schemas = ProviderSchema.objects.filter(provider=obj).order_by("-created")
         serializer = ProviderSchemaSerializer(schemas, many=True)
         return serializer.data
 
+    def _groups(self, obj):
+        groups = ProviderVerbGroup.objects.filter(provider=obj).order_by("-created")
+        serializer = ProviderVerbGroupSerializer(groups, many=True)
+        return serializer.data
+
     class Meta:
         model = Provider
-        fields = ["id", "name", "versions"]
+        fields = ["id", "name", "groups", "versions"]
 
 
 class UserVerbSerializer(serializers.Serializer):
     id = serializers.CharField()
+    group_id = serializers.PrimaryKeyRelatedField(
+        queryset=ProviderVerbGroup.objects.all()
+    )
     consented = serializers.BooleanField()
     objects = serializers.CharField()
 
@@ -89,6 +163,40 @@ class SaveUserConsentSerializer(serializers.Serializer):
     verbs = serializers.ListSerializer(child=UserVerbSerializer())
 
 
+class GroupVerbSerializer(serializers.Serializer):
+    id = serializers.CharField()
+
+
+class CreateProviderVerbGroupSerializer(serializers.Serializer):
+    def __init__(self, provider, **kwargs):
+        super().__init__(**kwargs)
+        self.provider = provider
+
+    def validate(self, data):
+        """
+        Validate the existence of verbs associated with the new / updated group
+        and check if the chosen group ID is available.
+        """
+        # new or updated groups have to use the newest schema
+        provider_schema = ProviderSchema.objects.get(provider=self.provider, superseded_by__isnull=True)
+        for verb_data in data["verbs"]:
+            try:
+                # we just validate verb IDs here since verbs are retrieved via the verb ID later
+                verb = Verb.objects.filter(verb_id=verb_data["id"], active=True,
+                                           provider=self.provider,
+                                           provider_schema=provider_schema)
+            except ObjectDoesNotExist:
+                raise serializers.ValidationError(
+                    f"Verb {verb['id']} not found in newest schema."
+                )
+        return data
+    id = serializers.CharField(allow_blank=True)
+    label = serializers.CharField()
+    description = serializers.CharField()
+    purposeOfCollection = serializers.CharField()
+    requiresConsent = serializers.BooleanField()
+    verbs = serializers.ListSerializer(child=GroupVerbSerializer())
+
 class CreateUserSerializer(serializers.Serializer):
     email = serializers.CharField()
     first_name = serializers.CharField()
diff --git a/src/consents/tests/tests_consent_operations.py b/src/consents/tests/tests_consent_operations.py
index e9be69bcaaed0c71ea5967ce96d8d26532541976..c433730d381cfe824cd3f0f00e0d8e8f97b94be8 100644
--- a/src/consents/tests/tests_consent_operations.py
+++ b/src/consents/tests/tests_consent_operations.py
@@ -6,7 +6,7 @@ from django.test import TransactionTestCase
 from rest_framework.test import APIClient
 from rolepermissions.roles import assign_role
 
-from providers.models import Provider, ProviderAuthorization, ProviderSchema
+from providers.models import Provider, ProviderAuthorization, ProviderSchema, ProviderVerbGroup
 from users.models import CustomUser
 
 from ..models import UserConsents
@@ -27,100 +27,80 @@ class BaseTestCase(TransactionTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "http://h5p.example.com/expapi/verbs/experienced",
+                "label": "Experienced",
+                "description": "Experienced",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/experienced",
-                        "label": "Experienced",
-                        "description": "Experienced",
+                        "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                        "label": "1.1.1 Funktionen",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                "label": "1.1.1 Funktionen",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                    "name": {"enUS": "1.1.1 Funktionen"},
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                            "name": {"enUS": "1.1.1 Funktionen"},
+                        },
+                    }
+                ],
+            },
+            {
+                "id": "http://h5p.example.com/expapi/verbs/attempted",
+                "label": "Attempted",
+                "description": "Attempted",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/attempted",
-                        "label": "Attempted",
-                        "description": "Attempted",
+                        "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+                        "label": "2.3.1 Funktion Zirkulationsleitung",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-                                "label": "2.3.1 Funktion Zirkulationsleitung",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-                                    "name": {
-                                        "enUS": "2.3.1 Funktion Zirkulationsleitung"
-                                    },
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+                            "name": {
+                                "enUS": "2.3.1 Funktion Zirkulationsleitung"
+                            },
+                        },
+                    }
                 ],
-                "isDefault": True,
             },
             {
-                "id": "group_2",
-                "label": "Group 2",
+                "id": "http://h5p.example.com/expapi/verbs/interacted",
+                "label": "Interacted",
                 "description": "Lorem ipsum",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/interacted",
-                        "label": "Interacted",
-                        "description": "Lorem ipsum",
+                        "id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
+                        "label": "1.2.3 Kappenventil",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
-                                "label": "1.2.3 Kappenventil",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
-                                    "name": {"enUS": "1.2.3 Kappenventil"},
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
+                            "name": {"enUS": "1.2.3 Kappenventil"},
+                        },
+                    }
+                ],
+            },
+            {
+                "id": "http://h5p.example.com/expapi/verbs/answered",
+                "label": "Answered",
+                "description": "lorem ipsum",
+                "defaultConsent": False,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/answered",
-                        "label": "Answered",
-                        "description": "lorem ipsum",
+                        "id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+                        "label": "7.2.1 Ventil Basics",
                         "defaultConsent": False,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-                                "label": "7.2.1 Ventil Basics",
-                                "defaultConsent": False,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-                                    "name": {"enUS": "7.2.1 Ventil Basics"},
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+                            "name": {"enUS": "7.2.1 Ventil Basics"},
+                        },
+                    }
                 ],
-                "isDefault": False,
             },
         ],
         "essentialVerbs": [
@@ -138,54 +118,44 @@ class BaseTestCase(TransactionTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "http://h5p.example.com/expapi/verbs/experienced",
+                "label": "Experienced",
+                "description": "Experienced",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/experienced",
-                        "label": "Experienced",
-                        "description": "Experienced",
+                        "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                        "label": "1.1.1 Funktionen",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                "label": "1.1.1 Funktionen",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                    "name": {"enUS": "1.1.1 Funktionen"},
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                            "name": {"enUS": "1.1.1 Funktionen"},
+                        },
+                    }
+                ],
+            },
+            {
+                "id": "http://h5p.example.com/expapi/verbs/attempted",
+                "label": "Attempted",
+                "description": "Attempted",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/attempted",
-                        "label": "Attempted",
-                        "description": "Attempted",
+                        "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+                        "label": "2.3.1 Funktion Zirkulationsleitung",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-                                "label": "2.3.1 Funktion Zirkulationsleitung",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-                                    "name": {
-                                        "enUS": "2.3.1 Funktion Zirkulationsleitung"
-                                    },
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+                            "name": {
+                                "enUS": "2.3.1 Funktion Zirkulationsleitung"
+                            },
+                        },
+                    }
                 ],
-                "isDefault": True,
             },
         ],
         "essentialVerbs": [],
@@ -195,14 +165,7 @@ class BaseTestCase(TransactionTestCase):
         "id": "moodle-0",
         "name": "Moodle",
         "description": "Open-source learning management system",
-        "groups": [
-            {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+        "verbs": [
                     {
                         "id": "http://moodle.example.com/expapi/verbs/completed",
                         "label": "Completed",
@@ -274,9 +237,6 @@ class BaseTestCase(TransactionTestCase):
                         ],
                     },
                 ],
-                "isDefault": True,
-            }
-        ],
         "essentialVerbs": [],
     }
 
@@ -358,47 +318,40 @@ class TestProviderSchemaCreation(BaseTestCase):
             "id": "h5p-0",
             "name": "H5P",
             "description": "Open-source content collaboration framework",
-            "group": [
+            "verb": [
                 {
-                    "label": "Default group",
-                    "description": "default",
-                    "verbs": [
+                    "id": "http://h5p.example.com/expapi/verbs/experienced",
+                    "label": "Experienced",
+                    "description": "Experienced",
+                    "defaultConsent": True,
+                    "objects": [
                         {
-                            "id": "http://h5p.example.com/expapi/verbs/experienced",
-                            "label": "Experienced",
-                            "description": "Experienced",
+                            "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                            "label": "1.1.1 Funktionen",
                             "defaultConsent": True,
-                            "objects": [
-                                {
-                                    "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                    "label": "1.1.1 Funktionen",
-                                    "defaultConsent": True,
-                                    "definition": {
-                                        "name": {"enUS": "1.1.1 Funktionen"}
-                                    },
-                                }
-                            ],
-                        },
+                            "definition": {
+                                "name": {"enUS": "1.1.1 Funktionen"}
+                            },
+                        }
+                    ],
+                },
+                {
+                    "id": "http://h5p.example.com/expapi/verbs/attempted",
+                    "label": "Attempted",
+                    "description": "Attempted",
+                    "defaultConsent": True,
+                    "objects": [
                         {
-                            "id": "http://h5p.example.com/expapi/verbs/attempted",
-                            "label": "Attempted",
-                            "description": "Attempted",
+                            "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+                            "label": "2.3.1 Funktion Zirkulationsleitung",
                             "defaultConsent": True,
-                            "objects": [
-                                {
-                                    "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-                                    "label": "2.3.1 Funktion Zirkulationsleitung",
-                                    "defaultConsent": True,
-                                    "definition": {
-                                        "name": {
-                                            "enUS": "2.3.1 Funktion Zirkulationsleitung"
-                                        }
-                                    },
+                            "definition": {
+                                "name": {
+                                    "enUS": "2.3.1 Funktion Zirkulationsleitung"
                                 }
-                            ],
-                        },
+                            },
+                        }
                     ],
-                    "isDefault": True,
                 },
             ],
             "essentialVerbs": [],
@@ -461,25 +414,25 @@ class TestUserConsentInvalidProviderIdFails(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": 1,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":True,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": 1,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":True,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": 1,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":True,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": 1,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":False,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":True}]',
@@ -498,32 +451,55 @@ class TestUserConsentCreate(BaseTestCase):
         """
         Ensure initial user consent gets created.
         """
+        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
+            response = self.provider_client.put(
+                "/api/v1/consents/provider/create",
+                {"provider-schema": fp},
+                format="multipart",
+            )
+            self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
         payload = [
             {
-                "providerId": 1,
-                "providerSchemaId": 1,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -532,13 +508,6 @@ class TestUserConsentCreate(BaseTestCase):
             }
         ]
 
-        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
-            response = self.provider_client.put(
-                "/api/v1/consents/provider/create",
-                {"provider-schema": fp},
-                format="multipart",
-            )
-            self.assertEqual(response.status_code, 201)
 
         response = self.user_client.post(
             "/api/v1/consents/user/save", payload, format="json"
@@ -549,22 +518,26 @@ class TestUserConsentCreate(BaseTestCase):
         self.assertEqual(len(user_consents), 4)
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/experienced"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/experienced",
+                active=True
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/attempted"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/attempted",
+                active=True
             ).consented
         )
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/interacted"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/interacted",
+                active=True
             ).consented
         )
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/answered"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/answered",
+                active=True
             ).consented
         )
 
@@ -574,31 +547,57 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
         """
         Ensure user consent gets updated.
         """
+
+        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
+            response = self.provider_client.put(
+                "/api/v1/consents/provider/create",
+                {"provider-schema": fp},
+                format="multipart",
+            )
+            self.assertEqual(response.status_code, 201)
+
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         payload_second_consent = [
             {
-                "providerId": 1,
-                "providerSchemaId": 2,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":True,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":True,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":True,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":False,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":True}]',
@@ -606,13 +605,6 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
                 ],
             }
         ]
-        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
-            response = self.provider_client.put(
-                "/api/v1/consents/provider/create",
-                {"provider-schema": fp},
-                format="multipart",
-            )
-            self.assertEqual(response.status_code, 201)
 
         response = self.user_client.post(
             "/api/v1/consents/user/save", payload_second_consent, format="json"
@@ -623,50 +615,54 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
         self.assertEqual(len(user_consents), 4)
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/experienced"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/experienced",
+                active=True,
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/attempted"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/attempted",
+                active=True,
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/interacted"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/interacted",
+                active=True,
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/answered"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/answered",
+                active=True,
             ).consented
         )
 
         payload_second_consent = [
             {
-                "providerId": 1,
-                "providerSchemaId": 2,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":True,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":True,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":True,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":False,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":True}]',
@@ -681,25 +677,29 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
         self.assertEqual(response.status_code, 200)
 
         user_consents = UserConsents.objects.filter(user__email=self.test_user_email)
-        self.assertEqual(len(user_consents), 4)
+        self.assertEqual(len(user_consents), 8)
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/experienced"
+                verb="http://h5p.example.com/expapi/verbs/experienced",
+                active=True,
             ).consented
         )
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/attempted"
+                verb="http://h5p.example.com/expapi/verbs/attempted",
+                active=True,
             ).consented
         )
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/interacted"
+                verb="http://h5p.example.com/expapi/verbs/interacted",
+                active=True,
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/answered"
+                verb="http://h5p.example.com/expapi/verbs/answered",
+                active=True,
             ).consented
         )
 
@@ -709,31 +709,56 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
         """
         Ensure user consent save is rejected, if referenced provider schema id is outdated.
         """
+        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
+            response = self.provider_client.put(
+                "/api/v1/consents/provider/create",
+                {"provider-schema": fp},
+                format="multipart",
+            )
+            self.assertEqual(response.status_code, 201)
+
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         payload = [
             {
-                "providerId": 1,
-                "providerSchemaId": 1,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -742,14 +767,6 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
             }
         ]
 
-        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
-            response = self.provider_client.put(
-                "/api/v1/consents/provider/create",
-                {"provider-schema": fp},
-                format="multipart",
-            )
-            self.assertEqual(response.status_code, 201)
-
         # Add newer provider schema for H5P
         with StringIO(json.dumps(self.h5p_provider_schema_v2)) as fp:
             response = self.provider_client.put(
@@ -762,6 +779,7 @@ class TestUserConsentOutdatedProviderSchemaId(BaseTestCase):
         response = self.user_client.post(
             "/api/v1/consents/user/save", payload, format="json"
         )
+
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
             response.json()["message"],
@@ -782,33 +800,48 @@ class TestUserConsentSaveUpdatedProviderSchema(BaseTestCase):
                 format="multipart",
             )
             self.assertEqual(response.status_code, 201)
-
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
         # Create user consent for first provider schema version
         first_provider_schema_consent = [
             {
-                "providerId": 1,
-                "providerSchemaId": 1,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -825,22 +858,26 @@ class TestUserConsentSaveUpdatedProviderSchema(BaseTestCase):
         self.assertEqual(len(user_consents), 4)
         self.assertFalse(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/experienced"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/experienced",
+                active=True
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/attempted"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/attempted",
+                active=True
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/interacted"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/interacted",
+                active=True
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/answered"
+                verb__verb_id="http://h5p.example.com/expapi/verbs/answered",
+                active=True
             ).consented
         )
 
@@ -853,19 +890,34 @@ class TestUserConsentSaveUpdatedProviderSchema(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         second_provider_schema_consent = [
             {
-                "providerId": 1,
-                "providerSchemaId": 2,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
@@ -882,161 +934,41 @@ class TestUserConsentSaveUpdatedProviderSchema(BaseTestCase):
         self.assertEqual(len(user_consents), 2)
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/experienced",
+                verb__verb_id="http://h5p.example.com/expapi/verbs/experienced",
                 active=True
             ).consented
         )
         self.assertTrue(
             UserConsents.objects.get(
-                verb="http://h5p.example.com/expapi/verbs/attempted",
+                verb__verb_id="http://h5p.example.com/expapi/verbs/attempted",
                 active=True
             ).consented
         )
 
 
-class TestUserConsentSave(BaseTestCase):
-    def test_update_user_consent_fails_incomplete_verb_consents(self):
-        """
-        Ensure user consent gets rejected with incomplete verbs.
-        """
-        # Create first provider schema version
-        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
-            response = self.provider_client.put(
-                "/api/v1/consents/provider/create",
-                {"provider-schema": fp},
-                format="multipart",
-            )
-            self.assertEqual(response.status_code, 201)
-        provider = ProviderAuthorization.objects.get(provider__name="H5P")
-
-        # Create consent for one verb instead of 4 verbs
-        payload = [
-            {
-                "providerId": 1,
-                "providerSchemaId": 1,
-                "verbs": [
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/experienced",
-                        "consented": False,
-                        "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
-                    },
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/attempted",
-                        "consented": True,
-                        "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true, "matching": "definitionType","definition":{"type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm", "name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
-                    },
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/answered",
-                        "consented": True,
-                        "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false, "matching": "definitionType","definition":{"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b", "name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
-                    },
-                ],
-            }
-        ]
-
-        response = self.user_client.post(
-            "/api/v1/consents/user/save", data=payload, format="json"
-        )
-        self.assertEqual(response.status_code, 400)
-        self.assertJSONEqual(
-            str(response.content, encoding="utf8"),
-            {"message": "user consent contains missing provider schema verb consents"},
-        )
-
-    def test_update_user_consent_fails_incomplete_object_consents(self):
-        """
-        Ensure user consent gets rejected with incomplete verb objects.
-        """
-        # Create first provider schema version
-        with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
-            response = self.provider_client.put(
-                "/api/v1/consents/provider/create",
-                {"provider-schema": fp},
-                format="multipart",
-            )
-            self.assertEqual(response.status_code, 201)
-        provider = ProviderAuthorization.objects.get(provider__name="H5P")
-
-        # Create consent for one verb instead of 4 verbs
-        payload = [
-            {
-                "providerId": 1,
-                "providerSchemaId": 1,
-                "verbs": [
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/experienced",
-                        "consented": False,
-                        "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
-                    },
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/attempted",
-                        "consented": True,
-                        "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
-                    },
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/interacted",
-                        "consented": True,
-                        "objects": "[]",
-                    },
-                    {
-                        "provider": 1,
-                        "id": "http://h5p.example.com/expapi/verbs/answered",
-                        "consented": True,
-                        "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
-                    },
-                ],
-            }
-        ]
-
-        response = self.user_client.post(
-            "/api/v1/consents/user/save", data=payload, format="json"
-        )
-        self.assertEqual(response.status_code, 400)
-        self.assertJSONEqual(
-            str(response.content, encoding="utf8"),
-            {"message": "user consent contains missing provider schema verb consents"},
-        )
-
-
 class TestMultiUserConsent(BaseTestCase):
     provider_schema = {
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "http://h5p.example.com/expapi/verbs/experienced",
+                "label": "Experienced",
+                "description": "Experienced",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "http://h5p.example.com/expapi/verbs/experienced",
-                        "label": "Experienced",
-                        "description": "Experienced",
+                        "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                        "label": "1.1.1 Funktionen",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                "label": "1.1.1 Funktionen",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                                    "name": {"enUS": "1.1.1 Funktionen"},
-                                },
-                            }
-                        ],
-                    },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+                            "name": {"enUS": "1.1.1 Funktionen"},
+                        },
+                    }
                 ],
-                "isDefault": True,
             },
         ],
         "essentialVerbs": [],
@@ -1101,13 +1033,27 @@ class TestMultiUserConsent(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         user_consent_1 = [
             {
-                "providerId": 1,
-                "providerSchemaId": 1,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
@@ -1117,11 +1063,11 @@ class TestMultiUserConsent(BaseTestCase):
         ]
         user_consent_2 = [
             {
-                "providerId": 1,
-                "providerSchemaId": 1,
+                "providerId": Provider.objects.latest('id').id,
+                "providerSchemaId": ProviderSchema.objects.latest('id').id,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
@@ -1145,12 +1091,12 @@ class TestMultiUserConsent(BaseTestCase):
         self.assertTrue(
             UserConsents.objects.get(
                 user__email=self.user_email_1,
-                verb="http://h5p.example.com/expapi/verbs/experienced",
+                verb__verb_id="http://h5p.example.com/expapi/verbs/experienced",
             ).consented
         )
         self.assertFalse(
             UserConsents.objects.get(
                 user__email=self.user_email_2,
-                verb="http://h5p.example.com/expapi/verbs/experienced",
+                verb__verb_id="http://h5p.example.com/expapi/verbs/experienced",
             ).consented
         )
diff --git a/src/consents/tests/tests_create_provider_schema.py b/src/consents/tests/tests_create_provider_schema.py
index 0a9d0d5a71123376d155882026f593bfe3c9e490..22e583a51214e8315f7230e255e8afb1cbcab912 100644
--- a/src/consents/tests/tests_create_provider_schema.py
+++ b/src/consents/tests/tests_create_provider_schema.py
@@ -6,95 +6,101 @@ from providers.models import Provider
 
 
 class TestProviderSchemaCreation(BaseTestCase):
-    def test_create_provider_schema_fails(self):
+    def test_create_provider_verb_groups(self):
         """
-        Ensure provider schema with duplicate group ids fails.
+        Test verb group creation and update.
         """
         provider_schema = {
             "id": "h5p-0",
             "name": "H5P",
             "description": "Open-source content collaboration framework",
-            "groups": [
+            "verbs": [
                 {
-                    "id": "default_group",
-                    "label": "Default group",
-                    "description": "default",
-                    "showVerbDetails": True,
-                    "purposeOfCollection": "Lorem Ipsum",
-                    "verbs": [
+                    "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
+                    "label": "Unlocked",
+                    "description": "Actor unlocked an object",
+                    "defaultConsent": True,
+                    "objects": [
                         {
-                            "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                            "label": "Unlocked",
-                            "description": "Actor unlocked an object",
+                            "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
+                            "label": "Course",
                             "defaultConsent": True,
-                            "objects": [
-                                {
-                                    "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                    "label": "Course",
-                                    "defaultConsent": True,
-                                    "matching": "id",
-                                    "definition": {
-                                        "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                        "name": {
-                                            "enUS": "A course within an LMS. Contains learning materials and activities"
-                                        },
-                                    },
+                            "matching": "id",
+                            "definition": {
+                                "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
+                                "name": {
+                                    "enUS": "A course within an LMS. Contains learning materials and activities"
                                 },
-                            ],
-                        }
+                            },
+                        },
                     ],
-                    "isDefault": True,
                 },
                 {
-                    "id": "default_group",
-                    "label": "Group 2",
+                    "id": "http://h5p.example.com/expapi/verbs/interacted",
+                    "label": "Interacted",
                     "description": "Lorem ipsum",
-                    "showVerbDetails": True,
-                    "purposeOfCollection": "Lorem Ipsum",
-                    "verbs": [
+                    "defaultConsent": True,
+                    "objects": [
                         {
-                            "id": "http://h5p.example.com/expapi/verbs/interacted",
-                            "label": "Interacted",
-                            "description": "Lorem ipsum",
+                            "id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
+                            "label": "1.2.3 Kappenventil",
                             "defaultConsent": True,
-                            "objects": [
-                                {
-                                    "id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
-                                    "label": "1.2.3 Kappenventil",
-                                    "defaultConsent": True,
-                                    "matching": "definitionType",
-                                    "definition": {
-                                        "type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
-                                        "name": {"enUS": "1.2.3 Kappenventil"},
-                                    },
-                                }
-                            ],
-                        },
+                            "matching": "definitionType",
+                            "definition": {
+                                "type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
+                                "name": {"enUS": "1.2.3 Kappenventil"},
+                            },
+                        }
+                    ],
+                },
+                {
+                    "id": "http://h5p.example.com/expapi/verbs/answered",
+                    "label": "Answered",
+                    "description": "lorem ipsum",
+                    "defaultConsent": False,
+                    "objects": [
                         {
-                            "id": "http://h5p.example.com/expapi/verbs/answered",
-                            "label": "Answered",
-                            "description": "lorem ipsum",
+                            "id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+                            "label": "7.2.1 Ventil Basics",
                             "defaultConsent": False,
-                            "objects": [
-                                {
-                                    "id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-                                    "label": "7.2.1 Ventil Basics",
-                                    "defaultConsent": False,
-                                    "matching": "definitionType",
-                                    "definition": {
-                                        "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-                                        "name": {"enUS": "7.2.1 Ventil Basics"},
-                                    },
-                                }
-                            ],
-                        },
+                            "matching": "definitionType",
+                            "definition": {
+                                "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+                                "name": {"enUS": "7.2.1 Ventil Basics"},
+                            },
+                        }
                     ],
-                    "isDefault": False,
-                },
+                }
             ],
             "essentialVerbs": [],
         }
 
+        verb_groups = [
+            {
+                "id": "default_group",
+                "label": "Default group",
+                "description": "default",
+                "showVerbDetails": True,
+                "purposeOfCollection": "Lorem Ipsum",
+                "requiresConsent": True,
+                "verbs": [
+                    {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"}
+                ]
+            },
+            {
+                "id": "default_group",
+                "label": "Group 2",
+                "description": "Lorem ipsum",
+                "showVerbDetails": True,
+                "purposeOfCollection": "Lorem Ipsum",
+                "requiresConsent": True,
+                "verbs": [
+                    {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                    {"id": "http://h5p.example.com/expapi/verbs/answered"},
+                ]
+            }
+        ]
+
         # Upload provider schema
         with StringIO(json.dumps(provider_schema)) as fp:
             response = self.provider_client.put(
@@ -102,7 +108,22 @@ class TestProviderSchemaCreation(BaseTestCase):
                 {"provider-schema": fp},
                 format="multipart",
             )
-            self.assertEqual(response.status_code, 400)
-            self.assertEqual(
-                response.json()["message"], "Provider schema contains ambiguous group ids."
+            self.assertEqual(response.status_code, 201)
+        # create some verb groups
+        for group in verb_groups:
+            group["provider_id"] = Provider.objects.latest('id').id
+            response = self.provider_client.post(
+                "/api/v1/consents/provider/create-verb-group",
+                group,
+                format="json",
+            )
+            self.assertEqual(response.status_code, 200)
+        # one more time to simulate update
+        for group in verb_groups:
+            group["provider_id"] = Provider.objects.latest('id').id
+            response = self.provider_client.post(
+                "/api/v1/consents/provider/create-verb-group",
+                group,
+                format="json",
             )
+            self.assertEqual(response.status_code, 200)
\ No newline at end of file
diff --git a/src/consents/tests/tests_paused_data_recording.py b/src/consents/tests/tests_paused_data_recording.py
index fe451a4c5a053570228ce675d801b3449579deed..c68ef555866b50f9de27279960b5b380123263eb 100644
--- a/src/consents/tests/tests_paused_data_recording.py
+++ b/src/consents/tests/tests_paused_data_recording.py
@@ -2,7 +2,7 @@ import json
 from io import StringIO
 
 from consents.tests.tests_consent_operations import BaseTestCase
-from providers.models import Provider
+from providers.models import Provider, ProviderVerbGroup
 
 
 class TestPauseDataRecording(BaseTestCase):
@@ -32,7 +32,7 @@ class TestPauseDataRecording(BaseTestCase):
 
     def test_pause_data_recording(self):
         """
-        Ensure data recording can be turn on and off.
+        Ensure data recording can be turned on and off.
         """
         # Create provider H5P
         with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
@@ -97,31 +97,48 @@ class TestPauseDataRecording(BaseTestCase):
             {"consent": None, "paused_data_recording": True}
         )
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         user_consent = [
             {
                 "providerId": 1,
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
diff --git a/src/consents/tests/tests_third_party.py b/src/consents/tests/tests_third_party.py
index 6c704f51fb5a33457395af3d022c73ab2ce2bcd0..fcbf91e1e4c8d92a9fd32e09e3afdbd6c8e2088c 100644
--- a/src/consents/tests/tests_third_party.py
+++ b/src/consents/tests/tests_third_party.py
@@ -6,13 +6,30 @@ from rest_framework.test import APIClient
 from rolepermissions.roles import assign_role
 
 from consents.tests.tests_consent_operations import BaseTestCase
-from providers.models import ProviderAuthorization
+from providers.models import ProviderAuthorization, Provider, ProviderVerbGroup, ProviderSchema
 from users.models import CustomUser
 
 PROJECT_PATH = os.path.abspath(os.path.dirname(__name__))
 
 
 class TestThirdPartyGetUserStatus(BaseTestCase):
+    verb_groups = [
+        {
+            "provider_id": 1,
+            "id": "default_group",
+            "label": "Default group",
+            "description": "default",
+            "showVerbDetails": True,
+            "purposeOfCollection": "Lorem Ipsum",
+            "requiresConsent": True,
+            "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"}
+            ]
+        },
+    ]
     def setUp(self):
         normal_user = CustomUser.objects.create_user(
             self.test_user_email, self.test_user_password
@@ -69,6 +86,16 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
             HTTP_AUTHORIZATION="Basic " + self.application_token
         )
 
+        # create some verb groups
+        for group in self.verb_groups:
+            group["provider_id"] = Provider.objects.latest('id').id  # database id is 2 on second go bc autoincrement
+            response = self.provider_client.post(
+                "/api/v1/consents/provider/create-verb-group",
+                group,
+                format="json",
+            )
+            self.assertEqual(response.status_code, 200)
+
     def test_user_status_401(self):
         """
         Ensure requests including an invalid application token are rejected.
@@ -101,6 +128,7 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
         )
 
     def test_user_status_none_empty(self):
+        group_id = ProviderVerbGroup.objects.latest('id').id
         # Create user consent for first provider schema version
         first_provider_schema_consent = [
             {
@@ -108,25 +136,25 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":false}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -157,6 +185,23 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
 
 
 class TestThirdPartyUserConsentUpdate(BaseTestCase):
+    verb_groups = [
+        {
+            "provider_id": 1,
+            "id": "default_group",
+            "label": "Default group",
+            "description": "default",
+            "showVerbDetails": True,
+            "purposeOfCollection": "Lorem Ipsum",
+            "requiresConsent": True,
+            "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"}
+            ]
+        },
+    ]
     def setUp(self):
         normal_user = CustomUser.objects.create_user(
             self.test_user_email, self.test_user_password
@@ -212,37 +257,46 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
         self.third_party_client.credentials(
             HTTP_AUTHORIZATION="Basic " + self.application_token
         )
+        # create some verb groups
+        for group in self.verb_groups:
+            group["provider_id"] = Provider.objects.latest('id').id  # database id is 2 on second go bc autoincrement
+            response = self.provider_client.post(
+                "/api/v1/consents/provider/create-verb-group",
+                group,
+                format="json",
+            )
+            self.assertEqual(response.status_code, 200)
 
     def test_update_user_consent(self):
         """
         Ensure user consent gets updated via third party endpoint.
         """
         provider = ProviderAuthorization.objects.get(provider__name="H5P")
-
+        group_id = ProviderVerbGroup.objects.latest('id').id
         payload = {
             "user_id": self.test_user_email,
             "provider_schema_id": provider.id,
             "verbs": [
                 {
-                    "provider": 1,
+                    "group_id": group_id,
                     "id": "http://h5p.example.com/expapi/verbs/experienced",
                     "consented": False,
                     "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                 },
                 {
-                    "provider": 1,
+                    "group_id": group_id,
                     "id": "http://h5p.example.com/expapi/verbs/attempted",
                     "consented": True,
                     "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                 },
                 {
-                    "provider": 1,
+                    "group_id": group_id,
                     "id": "http://h5p.example.com/expapi/verbs/interacted",
                     "consented": True,
                     "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                 },
                 {
-                    "provider": 1,
+                    "group_id": group_id,
                     "id": "http://h5p.example.com/expapi/verbs/answered",
                     "consented": True,
                     "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -271,22 +325,23 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
             responseDict["consent"]["groups"][0]["verbs"][1]["consented"], True
         )
         self.assertEqual(
-            responseDict["consent"]["groups"][1]["verbs"][0]["consented"], True
+            responseDict["consent"]["groups"][0]["verbs"][2]["consented"], True
         )
         self.assertEqual(
-            responseDict["consent"]["groups"][1]["verbs"][0]["consented"], True
+            responseDict["consent"]["groups"][0]["verbs"][3]["consented"], True
         )
 
     def test_update_user_consent_fails_without_access_token(self):
         """
         Ensure unauthenticated request gets rejected.
         """
+        group_id = ProviderVerbGroup.objects.latest('id').id
         payload = {
             "user_id": self.test_user_email,
             "provider_schema_id": 1,
             "verbs": [
                 {
-                    "provider": 1,
+                    "group_id": group_id,
                     "id": "http://h5p.example.com/expapi/verbs/experienced",
                     "consented": False,
                     "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/attempted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/interacted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/answered","consented":false,"objects":"[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":false}]',
@@ -306,14 +361,15 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
 
     def test_update_on_none_existing_provider_fails(self):
         """
-        Ensure attempt to update verb on none existing provider fails.
+        Ensure attempt to update verb on non-existing provider fails.
         """
+        group_id = ProviderVerbGroup.objects.latest('id').id
         payload = {
             "user_id": self.test_user_email,
-            "provider_schema_id": 1,
+            "provider_schema_id": 2,
             "verbs": [
                 {
-                    "provider": 2,
+                    "group_id": group_id,
                     "id": "http://h5p.example.com/expapi/verbs/experienced",
                     "consented": False,
                     "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/attempted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/interacted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/answered","consented":false,"objects":"[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":false}]',
@@ -325,121 +381,3 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
             "/api/v1/consents/user/save/third-party", data=payload, format="json"
         )
         self.assertEqual(response.status_code, 400)
-
-    def test_update_other_provider_fails(self):
-        """
-        Ensure you are not authorized to access providers other than your own.
-        """
-
-        # Create provider Moodle
-        with StringIO(json.dumps(self.moodle_schema)) as fp:
-            response = self.provider_client.put(
-                "/api/v1/consents/provider/create",
-                {"provider-schema": fp},
-                format="multipart",
-            )
-            self.assertEqual(response.status_code, 201)
-
-        moodle_provider_id = ProviderAuthorization.objects.get(
-            provider__name="Moodle"
-        ).provider.id
-
-        payload = {
-            "user_id": self.test_user_email,
-            "provider_schema_id": 1,
-            "verbs": [
-                {
-                    "provider": moodle_provider_id,
-                    "id": "http://h5p.example.com/expapi/verbs/experienced",
-                    "consented": False,
-                    "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/attempted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/interacted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/answered","consented":false,"objects":"[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":false}]',
-                }
-            ],
-        }
-
-        response = self.third_party_client.post(
-            "/api/v1/consents/user/save/third-party", data=payload, format="json"
-        )
-        self.assertEqual(response.status_code, 403)
-        self.assertJSONEqual(
-            str(response.content, encoding="utf8"),
-            {
-                "message": "you are not authorized to access providers other than your own"
-            },
-        )
-
-    def test_update_user_consent_fails_incomplete_verb_consents(self):
-        """
-        Ensure user consent gets with incomplete verbs.
-        """
-        provider = ProviderAuthorization.objects.get(provider__name="H5P")
-
-        # Create consent for one verb instead of 4 verbs
-        payload = {
-            "user_id": self.test_user_email,
-            "provider_schema_id": provider.id,
-            "verbs": [
-                {
-                    "provider": 1,
-                    "id": "http://h5p.example.com/expapi/verbs/experienced",
-                    "consented": False,
-                    "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
-                }
-            ],
-        }
-
-        response = self.third_party_client.post(
-            "/api/v1/consents/user/save/third-party", data=payload, format="json"
-        )
-        self.assertEqual(response.status_code, 400)
-        self.assertJSONEqual(
-            str(response.content, encoding="utf8"),
-            {"message": "user consent contains missing provider schema verb consents"},
-        )
-
-    def test_update_user_consent_fails_incomplete_object_consents(self):
-        """
-        Ensure user consent gets with incomplete verb objects.
-        """
-        provider = ProviderAuthorization.objects.get(provider__name="H5P")
-
-        # Create consent for one verb instead of 4 verbs
-        payload = {
-            "user_id": self.test_user_email,
-            "provider_schema_id": provider.id,
-            "verbs": [
-                {
-                    "provider": 1,
-                    "id": "http://h5p.example.com/expapi/verbs/experienced",
-                    "consented": False,
-                    "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
-                },
-                {
-                    "provider": 1,
-                    "id": "http://h5p.example.com/expapi/verbs/attempted",
-                    "consented": True,
-                    "objects": "[]",
-                },
-                {
-                    "provider": 1,
-                    "id": "http://h5p.example.com/expapi/verbs/interacted",
-                    "consented": True,
-                    "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
-                },
-                {
-                    "provider": 1,
-                    "id": "http://h5p.example.com/expapi/verbs/answered",
-                    "consented": True,
-                    "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
-                },
-            ],
-        }
-
-        response = self.third_party_client.post(
-            "/api/v1/consents/user/save/third-party", data=payload, format="json"
-        )
-        self.assertEqual(response.status_code, 400)
-        self.assertJSONEqual(
-            str(response.content, encoding="utf8"),
-            {"message": "user consent contains missing provider schema verb consents"},
-        )
diff --git a/src/consents/urls.py b/src/consents/urls.py
index 54c1ed5dbde5cb02a3ffc62f7dc75573ce600ea1..647a3785d3d6097f7e513303fb23aa725973ff74 100644
--- a/src/consents/urls.py
+++ b/src/consents/urls.py
@@ -6,6 +6,8 @@ urlpatterns = [
     path('provider', views.GetProviderSchemasView.as_view()),
     path('provider-status/third-party', views.GetProviderStatusThirdPartyView.as_view()),
     path('provider/create', views.CreateProviderConsentView.as_view()),
+    path('provider/<provider_id>/create-verb-group', views.CreateProviderVerbGroupView.as_view()), # TODO
+    path('provider/<provider_id>/verb-groups', views.GetProviderVerbGroupsView.as_view()), # TODO
     path('user/save', views.SaveUserConsentView.as_view()),
     path('user/create', views.CreateUserConsentView.as_view()),
     path('user/create-via-connect-service', views.CreateUserConsentViaConnectServiceView.as_view()),
diff --git a/src/consents/views.py b/src/consents/views.py
index 51b1807fba0fcb355c6de00eb224f0e6b75df4a7..005a548882b9ebd48a4a71f8569d97b2329feb5f 100644
--- a/src/consents/views.py
+++ b/src/consents/views.py
@@ -1,3 +1,4 @@
+import hashlib
 import json
 import string
 import random
@@ -5,6 +6,7 @@ import os
 import secrets
 import time
 from datetime import timedelta, datetime
+from pydoc import describe
 
 from django.core.cache import cache
 from django.conf import settings
@@ -20,8 +22,11 @@ from rest_framework.views import APIView
 from backend.role_permission import IsProvider
 from consents.serializers import (ProviderSchemaSerializer,
                                   ProvidersSerializer,
-                                  SaveUserConsentSerializer, CreateUserSerializer, CreateUserShibbolethSerializer)
-from providers.models import AnalyticsToken, AnalyticsTokenVerb, Provider, ProviderAuthorization, ProviderSchema
+                                  SaveUserConsentSerializer, CreateUserSerializer, CreateUserShibbolethSerializer,
+                                  ProviderVerbGroupSerializer, VerbObjectSerializer, VerbSerializer,
+                                  CreateProviderVerbGroupSerializer)
+from providers.models import (AnalyticsToken, AnalyticsTokenVerb, Provider, ProviderAuthorization, ProviderSchema, Verb,
+                              VerbObject, ProviderVerbGroup)
 from providers.serializers import (AnalyticsTokenSerializer, ConsentUserVerbThirdPartySerializer,
                                    GetUsersConsentsThirdPartySerializer)
 from users.models import CustomUser
@@ -51,7 +56,7 @@ def render_provider_consent(provider_schema, supersedes=None):
 
 def find_user_verb(verb_id, user_verbs):
     for user_verb in user_verbs:
-        if user_verb.get("verb") == verb_id:
+        if user_verb.verb.verb_id == verb_id:
             return user_verb
 
     return None
@@ -61,19 +66,24 @@ def merge_consents(provider_schema: ProviderSchema, user_verbs):
     """
     Merges provider schema with actual user consent settings
     """
+    #print(user_verbs)
     user_consent = {
         "id": provider_schema.provider.id,
         "name": provider_schema.provider.name,
         "description": provider_schema.provider.description,
-        "groups": provider_schema.groups,
-        "essential_verbs": provider_schema.essential_verbs,
+        "groups": ProviderVerbGroupSerializer(provider_schema.groups(), many=True).data, # all available groups
+        "essential_verbs": VerbSerializer(provider_schema.essential_verbs(), many=True).data,
     }
     for group in user_consent.get("groups"):
-        for verb in group.get("verbs", []):
-            user_verb = find_user_verb(verb.get("id"), user_verbs)
+        all_consented = True
+        for verb in group.get('verbs'):
+            user_verb = find_user_verb(verb["id"], user_verbs)
             if user_verb:
-                verb.update({"consented": user_verb.get("consented")})
-                verb.update({"objects": json.loads(user_verb.get("object"))})
+                verb.update({"consented": user_verb.consented})
+                verb.update({"objects": json.loads(user_verb.object)})
+                all_consented = all_consented and user_verb.consented
+            else: all_consented = False
+        group["consented"] = all_consented
     return user_consent
 
 
@@ -84,9 +94,9 @@ def get_user_consent(user, provider_id):
     user_consents = UserConsents.objects.filter(user=user, provider__pk=provider_id, active=True)
 
     if user_consents.first():
-        provider_schema = user_consents.first().provider_schema
+        provider_schema = user_consents.first().verb.provider_schema
         user_consent_definition = merge_consents(
-            provider_schema, user_consents.values()
+            provider_schema, user_consents
         )
         return user_consent_definition
     return None
@@ -94,72 +104,29 @@ def get_user_consent(user, provider_id):
 
 def save_user_consent(user, provider_schema, verbs):
     """
-    Inactivates existing user consents for user and provider. Afterwards, new user consents are created.
+    Inactivates existing user consents for user and provider.
+    Afterward, new user consents are created.
     """
     old_user_consents = UserConsents.objects.filter(
         user=user, provider=provider_schema.provider.id
     ).update(active=False)
 
-    for verb in verbs:
+    for verb_data in verbs:
+        verb = Verb.objects.get(verb_id=verb_data["id"], provider_schema=provider_schema, active=True)
         try:
-            UserConsents.objects.get(user=user, verb=verb["id"], provider=provider_schema.provider.id, active=True)
+            UserConsents.objects.get(user=user, verb=verb, provider=provider_schema.provider, active=True)
             # user consent for verb already exists, skip
         except UserConsents.DoesNotExist:
-            provider = Provider.objects.get(pk=provider_schema.provider.id)
             UserConsents.objects.create(
                 user=user,
-                consented=verb.get("consented"),
-                provider_schema=provider_schema,
-                provider=provider,
-                verb=verb.get("id"),
-                object=verb.get("objects"),
+                consented=verb_data.get("consented"),
+                provider=provider_schema.provider,
+                verb_group=verb_data["group_id"],
+                verb=verb,
+                object=verb_data.get("objects"),
             )
 
 
-def is_provider_verb_or_object_missing(
-    provider_schema: ProviderSchema, user_consent: dict
-) -> bool:
-    """
-    Validate all provider schema verbs and objects are present in user consent
-    """
-    provider_schema_verbs = {}
-
-    for group in provider_schema.groups:
-        for verb in group["verbs"]:
-            provider_schema_verbs[verb["id"]] = [
-                object["id"] for object in verb["objects"]
-            ]
-
-    for provider_verb_id, provider_object_ids in provider_schema_verbs.items():
-        user_verb_consent = next(
-            (verb for verb in user_consent["verbs"] if verb["id"] == provider_verb_id),
-            None,
-        )
-
-        if user_verb_consent is None:
-            # Missing provider schema verb id in user consent
-            return True
-
-        user_verb_consent_objects_id = [
-            object["id"]
-            for object in json.loads(user_verb_consent.get("objects", "[]"))
-        ]
-
-        if any(
-            object_id not in user_verb_consent_objects_id
-            for object_id in provider_object_ids
-        ):
-            # Missing provider schema object id in user consent
-            return True
-
-    return False
-
-
-def validate_distinct_group_ids(provider_schema):
-    group_ids = [group["id"] for group in provider_schema["groups"]]
-    if len(group_ids) != len(set(group_ids)):
-        raise ValueError("Provider schema contains ambiguous group ids.")
-
 
 def group_consents_by_date(consents):
     """
@@ -209,6 +176,24 @@ class GetProviderSchemasView(generics.ListAPIView):
     permission_classes = [IsAuthenticated]
 
 
+class GetProviderVerbGroupsView(generics.ListAPIView):
+    """
+    Lists all verb groups for the current provider.
+    """
+    def get(self, request):
+        application_token = request.headers.get("Authorization", "").split("Basic ")[-1]
+        provider = ProviderAuthorization.objects.filter(key=application_token).first()
+        if provider is None:
+            return JsonResponse(
+                {"message": "invalid access token"},
+                safe=False,
+                status=status.HTTP_401_UNAUTHORIZED,
+            )
+        verb_groups = ProviderVerbGroup.objects.filter(provider=provider.provider).all()
+        serializer = ProviderVerbGroupSerializer(verb_groups, many=True)
+        return JsonResponse(serializer.data, safe=False, status=status.HTTP_200_OK)
+
+
 class GetProviderStatusThirdPartyView(APIView):
     """
     Allows a third party to query the details of one's provider.
@@ -229,7 +214,8 @@ class GetProviderStatusThirdPartyView(APIView):
 
 class CreateProviderConsentView(APIView):
     """
-    Providers can upload provider schemas. In case a provider schema already exists, the existing provider schema references the new provider schema.
+    Providers can upload provider schemas. In case a provider schema already exists,
+    the existing provider schema will reference the new provider schema.
     The latest provider schema has no superseding provider schema associated.
     """
 
@@ -252,8 +238,6 @@ class CreateProviderConsentView(APIView):
                 provider_schema = json.load(file.file)
                 validate(instance=provider_schema, schema=json.load(f))
 
-                validate_distinct_group_ids(provider_schema)
-
                 providerModel = Provider.objects.get(
                     definition_id=provider_schema["id"]
                 )  # maybe TODO: match via name, not definition_id, iff definition_id can change each definition
@@ -291,44 +275,132 @@ class CreateProviderConsentView(APIView):
 
             ProviderAuthorization.objects.create(provider=providerModel, key=auth_key)
 
-        # Create new provider schema and link older schema version to new one (if old one exists)
+        # update provider
+        providerModel.description = provider_schema["description"]
+        providerModel.name = provider_schema["name"]
+        providerModel.save()
+
         try:
             precedingProviderSchema = ProviderSchema.objects.get(
                 provider=providerModel, superseded_by__isnull=True
             )
-
-            if precedingProviderSchema:
-                providerSchemaModel = ProviderSchema.objects.create(
-                    provider=providerModel,
-                    groups=provider_schema["groups"],
-                    essential_verbs=provider_schema["essentialVerbs"],
-                    additional_lrs=provider_schema["additionalLrs"] if "additionalLrs" in provider_schema.keys() else []
-                )
-                precedingProviderSchema.superseded_by = providerSchemaModel
-                precedingProviderSchema.save()
-
-                provider = precedingProviderSchema.provider
-                provider.description = provider_schema["description"]
-                provider.name=provider_schema["name"]
-                provider.save()
-
         except ObjectDoesNotExist:
-            providerSchemaModel = ProviderSchema.objects.create(
+            precedingProviderSchema = None
+        # Create new provider schema and link older schema version to new one (if old one exists)
+        providerSchemaModel = ProviderSchema.objects.create(
+            provider=providerModel,
+            additional_lrs=provider_schema["additionalLrs"] if "additionalLrs" in provider_schema.keys() else []
+        )
+        # create regular verbs
+        for verb_schema in provider_schema["verbs"]:
+            verb = Verb.objects.create(
+                provider=providerModel,
+                provider_schema=providerSchemaModel,
+                verb_id=verb_schema["id"],
+                label=verb_schema["label"],
+                description=verb_schema["description"],
+                default_consent=verb_schema["defaultConsent"],
+                essential=False
+            )
+            for verb_object_schema in verb_schema["objects"]:
+                obj = VerbObject.objects.create(
+                    verb=verb,
+                    object_id=verb_object_schema["id"],
+                    object_type=verb_object_schema["objectType"] if "objectType" in verb_object_schema.keys() else "Activity",
+                    matching=verb_object_schema["matching"] if "matching" in verb_object_schema.keys() else "definitionType",
+                    definition=verb_object_schema["definition"])
+        # create essential verbs
+        for verb_schema in provider_schema["essentialVerbs"]:
+            verb = Verb.objects.create(
                 provider=providerModel,
-                groups=provider_schema["groups"],
-                essential_verbs=provider_schema["essentialVerbs"],
-                additional_lrs=provider_schema["additionalLrs"] if "additionalLrs" in provider_schema.keys() else []
+                provider_schema=providerSchemaModel,
+                verb_id=verb_schema["id"],
+                label=verb_schema["label"],
+                description=verb_schema["description"],
+                default_consent=verb_schema["defaultConsent"],
+                essential=True,
             )
+            for verb_object_schema in verb_schema["objects"]:
+                obj = VerbObject.objects.create(
+                    verb=verb,
+                    object_id=verb_object_schema["id"],
+                    object_type=verb_object_schema["objectType"] if "objectType" in verb_object_schema.keys() else "Activity",
+                    matching=verb_object_schema["matching"] if "matching" in verb_object_schema.keys() else "definitionType",
+                    definition=verb_object_schema["definition"]
+                )
+        # update old schema to reference the new one
+        if precedingProviderSchema:
+            Verb.objects.filter(provider=providerModel, provider_schema=precedingProviderSchema).update(
+                active=False)
+            precedingProviderSchema.superseded_by = providerSchemaModel
+            precedingProviderSchema.save()
+            Verb.objects.filter(provider_schema=precedingProviderSchema).update(active=False)
+
 
         return JsonResponse(
             {"message": "provider schema processed"}, status=status.HTTP_201_CREATED
         )
 
 
+class CreateProviderVerbGroupView(APIView):
+    permission_classes = [IsAuthenticated, IsProvider]
+
+    def post(self, request, provider_id):
+        provider = Provider.objects.get(id=provider_id)
+        serializer = CreateProviderVerbGroupSerializer(data=request.data, provider=provider)
+        serializer.is_valid(raise_exception=True)
+
+        provider_schema = ProviderSchema.objects.get(provider=provider, superseded_by__isnull=True)
+
+        verbs = Verb.objects.filter(verb_id__in=[verb["id"] for verb in serializer.validated_data["verbs"]],
+                                    active=True, provider=provider, provider_schema=provider_schema).all()
+        if len(verbs) != len(serializer.validated_data["verbs"]):
+            return JsonResponse(
+                {"message": "at least one of the given verbs could not be found"},
+                safe=False,
+                status=status.HTTP_400_BAD_REQUEST
+            )
+
+        # update existing group
+        if ProviderVerbGroup.objects.filter(provider=provider, provider_schema=provider_schema,
+                                            group_id=serializer.validated_data["id"]).first() is not None:
+            group = ProviderVerbGroup.objects.get(group_id=serializer.validated_data["id"],
+                                                  provider=provider, provider_schema=provider_schema)
+            group.group_id = serializer.validated_data["id"]
+            group.label = serializer.validated_data["label"]
+            group.description = serializer.validated_data["description"]
+            group.purpose_of_collection = serializer.validated_data["purposeOfCollection"]
+            group.requires_consent = serializer.validated_data["requiresConsent"]
+            group.save()
+        else:
+            # create new group
+            group = ProviderVerbGroup.objects.create(
+                provider = provider,
+                provider_schema = provider_schema,
+                group_id = hashlib.sha3_256(serializer.validated_data["label"].encode("utf-8")).hexdigest(),
+                label = serializer.validated_data["label"],
+                description = serializer.validated_data["description"],
+                purpose_of_collection = serializer.validated_data["purposeOfCollection"],
+                requires_consent = serializer.validated_data["requiresConsent"],
+            )
+        # sync verbs
+        group.verbs.set(verbs)
+
+        return JsonResponse(
+            {
+                "message": "group has been saved",
+                "group": ProviderVerbGroupSerializer(group).data,
+            },
+            safe=False,
+            status=status.HTTP_200_OK,
+        )
+
+
 class GetUserConsentView(APIView):
     """
-    Users are able to make a consent decision for a specific provider schema (always the latest version at time).
-    This endpoint returns the user consent along with the associated provider schema, which might not be the latest version anymore.
+    Users are able to make a consent decision for verb groups.
+    This endpoint returns the user consents along with the associated verb groups for a given provider,
+    which may not always belong to the latest version of the corresponding provider schema.
     """
 
     permission_classes = (IsAuthenticated,)
@@ -410,21 +482,21 @@ class GetUserConsentHistoryView(APIView):
                         "consents": []
                     }
                     for provider_id, consents in consent_group["consents"].items():
-                        provider_schema = consents[0].provider_schema
+                        provider_schema = consents[0].verb.provider_schema
                         user_consent = {
                             "id": provider_schema.provider.id,
                             "name": provider_schema.provider.name,
                             "description": provider_schema.provider.description,
-                            "groups": provider_schema.groups,
-                            "essential_verbs": provider_schema.essential_verbs,
+                            "groups": ProviderVerbGroupSerializer(provider_schema.groups(), many=True).data,
+                            "essential_verbs": VerbSerializer(provider_schema.essential_verbs(), many=True).data,
                             "created": consent_group["created"],
                         }
                         for group in user_consent.get("groups"):
-                            for verb in group.get("verbs", []):
-                                user_verb = find_user_verb(verb.get("id"), [consent.__dict__ for consent in consents])
+                            for verb in group.get("verbs"):
+                                user_verb = find_user_verb(verb["id"], consents)
                                 if user_verb:
-                                    verb.update({"consented": user_verb.get("consented")})
-                                    verb.update({"objects": json.loads(user_verb.get("object"))})
+                                    verb.update({"consented": user_verb.consented})
+                                    verb.update({"objects": json.loads(user_verb.object)})
                         consent_group_mapped["consents"].append(user_consent)
                     consents_mapped.append(consent_group_mapped)
 
@@ -562,17 +634,6 @@ class SaveUserConsentView(APIView):
                     status=status.HTTP_400_BAD_REQUEST,
                 )
 
-            if is_provider_verb_or_object_missing(
-                latest_provider_schema, user_provider_setting
-            ):
-                return JsonResponse(
-                    {
-                        "message": "user consent contains missing provider schema verb consents"
-                    },
-                    safe=False,
-                    status=status.HTTP_400_BAD_REQUEST,
-                )
-
         for user_provider_setting in serializer.validated_data:
             user.accepted_provider_schemas.add(
                 user_provider_setting["providerSchemaId"]
@@ -741,7 +802,8 @@ class GetUsersConsentsThirdPartyView(APIView):
 class SaveUserConsentThirdPartyView(APIView):
     """
     Saves a user consent, with authentication taking place via application tokens.
-    The provider schema id for each provider must always refer to the latest schema version of the respective provider, otherwise the request will be rejected
+    The provider schema id for each provider must always refer to the latest schema version of the respective provider,
+    otherwise the request will be rejected.
     """
 
     def post(self, request):
@@ -761,15 +823,6 @@ class SaveUserConsentThirdPartyView(APIView):
         email = shib_connector_resolver(email=email, provider=provider)
         user = CustomUser.objects.filter(email=email).first()
 
-        for verb in serializer.validated_data["verbs"]:
-            if verb["provider"].id != provider.provider.id:
-                return JsonResponse(
-                    {
-                        "message": "you are not authorized to access providers other than your own"
-                    },
-                    safe=False,
-                    status=status.HTTP_403_FORBIDDEN,
-                )
 
         # Validate user provider setting refers to the latest provider schema id
         latest_provider_schema = ProviderSchema.objects.get(
@@ -785,17 +838,6 @@ class SaveUserConsentThirdPartyView(APIView):
                 status=status.HTTP_400_BAD_REQUEST,
             )
 
-        if is_provider_verb_or_object_missing(
-            latest_provider_schema, serializer.validated_data
-        ):
-            return JsonResponse(
-                {
-                    "message": "user consent contains missing provider schema verb consents"
-                },
-                safe=False,
-                status=status.HTTP_400_BAD_REQUEST,
-            )
-
         save_user_consent(
             user,
             serializer.validated_data["provider_schema_id"],
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 4fb8d74d8055c943dd82f020aab32ddb1950e7a8..130b29f4a094aef6d33e432593417fc78b1f4be4 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -25,6 +25,7 @@
         "@fortawesome/free-solid-svg-icons": "^5.15.4",
         "jwt-decode": "^3.1.2",
         "ng-zorro-antd": "^14.0.0",
+        "ngx-markdown": "^14.0.1",
         "rxjs": "~7.5.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.11.4"
@@ -2397,6 +2398,12 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@braintree/sanitize-url": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
+      "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==",
+      "license": "MIT"
+    },
     "node_modules/@colors/colors": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -3212,6 +3219,12 @@
       "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
       "dev": true
     },
+    "node_modules/@types/marked": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz",
+      "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==",
+      "license": "MIT"
+    },
     "node_modules/@types/mime": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@@ -4322,6 +4335,17 @@
         "node": ">= 10"
       }
     },
+    "node_modules/clipboard": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
+      "license": "MIT",
+      "dependencies": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
     "node_modules/cliui": {
       "version": "7.0.4",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -4702,6 +4726,15 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/cose-base": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+      "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+      "license": "MIT",
+      "dependencies": {
+        "layout-base": "^1.0.0"
+      }
+    },
     "node_modules/cosmiconfig": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
@@ -4949,6 +4982,486 @@
       "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==",
       "dev": true
     },
+    "node_modules/cytoscape": {
+      "version": "3.31.0",
+      "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.31.0.tgz",
+      "integrity": "sha512-zDGn1K/tfZwEnoGOcHc0H4XazqAAXAuDpcYw9mUnUjATjqljyCNGJv8uEvbvxGaGHaVshxMecyl6oc6uKzRfbw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/cytoscape-cose-bilkent": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+      "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+      "license": "MIT",
+      "dependencies": {
+        "cose-base": "^1.0.0"
+      },
+      "peerDependencies": {
+        "cytoscape": "^3.2.0"
+      }
+    },
+    "node_modules/cytoscape-fcose": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
+      "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
+      "license": "MIT",
+      "dependencies": {
+        "cose-base": "^2.2.0"
+      },
+      "peerDependencies": {
+        "cytoscape": "^3.2.0"
+      }
+    },
+    "node_modules/cytoscape-fcose/node_modules/cose-base": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
+      "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
+      "license": "MIT",
+      "dependencies": {
+        "layout-base": "^2.0.0"
+      }
+    },
+    "node_modules/cytoscape-fcose/node_modules/layout-base": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
+      "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
+      "license": "MIT"
+    },
+    "node_modules/d3": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+      "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "3",
+        "d3-axis": "3",
+        "d3-brush": "3",
+        "d3-chord": "3",
+        "d3-color": "3",
+        "d3-contour": "4",
+        "d3-delaunay": "6",
+        "d3-dispatch": "3",
+        "d3-drag": "3",
+        "d3-dsv": "3",
+        "d3-ease": "3",
+        "d3-fetch": "3",
+        "d3-force": "3",
+        "d3-format": "3",
+        "d3-geo": "3",
+        "d3-hierarchy": "3",
+        "d3-interpolate": "3",
+        "d3-path": "3",
+        "d3-polygon": "3",
+        "d3-quadtree": "3",
+        "d3-random": "3",
+        "d3-scale": "4",
+        "d3-scale-chromatic": "3",
+        "d3-selection": "3",
+        "d3-shape": "3",
+        "d3-time": "3",
+        "d3-time-format": "4",
+        "d3-timer": "3",
+        "d3-transition": "3",
+        "d3-zoom": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-array": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+      "license": "ISC",
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-axis": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+      "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-brush": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+      "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "3",
+        "d3-transition": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-chord": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+      "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-path": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-contour": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+      "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "^3.2.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+      "license": "ISC",
+      "dependencies": {
+        "delaunator": "5"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dispatch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-drag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+      "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-selection": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dsv": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+      "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+      "license": "ISC",
+      "dependencies": {
+        "commander": "7",
+        "iconv-lite": "0.6",
+        "rw": "1"
+      },
+      "bin": {
+        "csv2json": "bin/dsv2json.js",
+        "csv2tsv": "bin/dsv2dsv.js",
+        "dsv2dsv": "bin/dsv2dsv.js",
+        "dsv2json": "bin/dsv2json.js",
+        "json2csv": "bin/json2dsv.js",
+        "json2dsv": "bin/json2dsv.js",
+        "json2tsv": "bin/json2dsv.js",
+        "tsv2csv": "bin/dsv2dsv.js",
+        "tsv2json": "bin/dsv2json.js"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dsv/node_modules/commander": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/d3-dsv/node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-fetch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+      "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dsv": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-force": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+      "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-quadtree": "1 - 3",
+        "d3-timer": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-format": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-geo": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+      "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2.5.0 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-hierarchy": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+      "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-polygon": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+      "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-quadtree": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+      "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-random": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+      "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale-chromatic": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+      "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3",
+        "d3-interpolate": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-selection": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+      "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time-format": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-time": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-transition": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+      "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3",
+        "d3-dispatch": "1 - 3",
+        "d3-ease": "1 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-timer": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "d3-selection": "2 - 3"
+      }
+    },
+    "node_modules/d3-zoom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+      "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "2 - 3",
+        "d3-transition": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/dagre-d3-es": {
+      "version": "7.0.9",
+      "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz",
+      "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==",
+      "license": "MIT",
+      "dependencies": {
+        "d3": "^7.8.2",
+        "lodash-es": "^4.17.21"
+      }
+    },
     "node_modules/date-fns": {
       "version": "2.30.0",
       "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@@ -4992,6 +5505,12 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/dayjs": {
+      "version": "1.11.13",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+      "license": "MIT"
+    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -5058,6 +5577,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delaunator": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
+      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+      "license": "ISC",
+      "dependencies": {
+        "robust-predicates": "^3.0.2"
+      }
+    },
+    "node_modules/delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
+      "license": "MIT"
+    },
     "node_modules/delegates": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -5187,6 +5721,12 @@
         "url": "https://github.com/fb55/domhandler?sponsor=1"
       }
     },
+    "node_modules/dompurify": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
+      "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==",
+      "license": "(MPL-2.0 OR Apache-2.0)"
+    },
     "node_modules/domutils": {
       "version": "2.8.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
@@ -5213,12 +5753,24 @@
       "integrity": "sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA==",
       "dev": true
     },
+    "node_modules/elkjs": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz",
+      "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==",
+      "license": "EPL-2.0"
+    },
     "node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
       "dev": true
     },
+    "node_modules/emoji-toolkit": {
+      "version": "6.6.0",
+      "resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-6.6.0.tgz",
+      "integrity": "sha512-pEu0kow2p1N8zCKnn/L6H0F3rWUBB3P3hVjr/O5yl1fK7N9jU4vO4G7EFapC5Y3XwZLUCY0FZbOPyTkH+4V2eQ==",
+      "license": "MIT"
+    },
     "node_modules/emojis-list": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
@@ -6351,6 +6903,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+      "license": "MIT",
+      "dependencies": {
+        "delegate": "^3.1.2"
+      }
+    },
     "node_modules/graceful-fs": {
       "version": "4.2.10",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
@@ -6868,6 +7429,15 @@
         "node": ">=8"
       }
     },
+    "node_modules/internmap": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/ip": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@@ -7541,6 +8111,36 @@
         "node": ">=10"
       }
     },
+    "node_modules/katex": {
+      "version": "0.16.21",
+      "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz",
+      "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==",
+      "funding": [
+        "https://opencollective.com/katex",
+        "https://github.com/sponsors/katex"
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "commander": "^8.3.0"
+      },
+      "bin": {
+        "katex": "cli.js"
+      }
+    },
+    "node_modules/katex/node_modules/commander": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+      "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/khroma": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+      "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+    },
     "node_modules/kind-of": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -7559,6 +8159,12 @@
         "node": ">= 8"
       }
     },
+    "node_modules/layout-base": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+      "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+      "license": "MIT"
+    },
     "node_modules/less": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
@@ -7698,6 +8304,12 @@
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
       "dev": true
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+      "license": "MIT"
+    },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -7878,6 +8490,18 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/marked": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+      "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+      "license": "MIT",
+      "bin": {
+        "marked": "bin/marked.js"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -7920,6 +8544,43 @@
         "node": ">= 8"
       }
     },
+    "node_modules/mermaid": {
+      "version": "9.4.3",
+      "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz",
+      "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==",
+      "license": "MIT",
+      "dependencies": {
+        "@braintree/sanitize-url": "^6.0.0",
+        "cytoscape": "^3.23.0",
+        "cytoscape-cose-bilkent": "^4.1.0",
+        "cytoscape-fcose": "^2.1.0",
+        "d3": "^7.4.0",
+        "dagre-d3-es": "7.0.9",
+        "dayjs": "^1.11.7",
+        "dompurify": "2.4.3",
+        "elkjs": "^0.8.2",
+        "khroma": "^2.0.0",
+        "lodash-es": "^4.17.21",
+        "non-layered-tidy-tree-layout": "^2.0.2",
+        "stylis": "^4.1.2",
+        "ts-dedent": "^2.2.0",
+        "uuid": "^9.0.0",
+        "web-worker": "^1.2.0"
+      }
+    },
+    "node_modules/mermaid/node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -8208,6 +8869,7 @@
       "version": "2.29.4",
       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
       "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+      "dev": true,
       "engines": {
         "node": "*"
       }
@@ -8325,6 +8987,29 @@
         "@angular/router": "^14.1.0"
       }
     },
+    "node_modules/ngx-markdown": {
+      "version": "14.0.1",
+      "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-14.0.1.tgz",
+      "integrity": "sha512-y5CY4e0QM0uR6+MvU1rnh1Ks+rku14309kVVojyXLcWl4zlrt8VAYCcf/+A+8z/IDOaz38yTrxNBnvYDJzNzYA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/marked": "^4.0.3",
+        "clipboard": "^2.0.11",
+        "emoji-toolkit": "^6.6.0",
+        "katex": "^0.16.0",
+        "marked": "^4.0.17",
+        "mermaid": "^9.1.2",
+        "prismjs": "^1.28.0",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/common": "^14.0.0",
+        "@angular/core": "^14.0.0",
+        "@angular/platform-browser": "^14.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0",
+        "zone.js": "^0.11.4"
+      }
+    },
     "node_modules/nice-napi": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@@ -8430,6 +9115,12 @@
       "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
       "dev": true
     },
+    "node_modules/non-layered-tidy-tree-layout": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
+      "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==",
+      "license": "MIT"
+    },
     "node_modules/nopt": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
@@ -9817,6 +10508,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/prismjs": {
+      "version": "1.29.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+      "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/proc-log": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz",
@@ -10328,6 +11028,12 @@
         "node": "*"
       }
     },
+    "node_modules/robust-predicates": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+      "license": "Unlicense"
+    },
     "node_modules/run-async": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -10360,6 +11066,12 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/rw": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/rxjs": {
       "version": "7.5.7",
       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz",
@@ -10391,8 +11103,7 @@
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "dev": true
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "node_modules/sass": {
       "version": "1.54.4",
@@ -10473,6 +11184,12 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
+      "license": "MIT"
+    },
     "node_modules/select-hose": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -11051,6 +11768,12 @@
         "node": ">=6"
       }
     },
+    "node_modules/stylis": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+      "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+      "license": "MIT"
+    },
     "node_modules/stylus": {
       "version": "0.59.0",
       "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz",
@@ -11320,6 +12043,12 @@
       "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
       "dev": true
     },
+    "node_modules/tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
+      "license": "MIT"
+    },
     "node_modules/tmp": {
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -11371,6 +12100,15 @@
         "tree-kill": "cli.js"
       }
     },
+    "node_modules/ts-dedent": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+      "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.10"
+      }
+    },
     "node_modules/tslib": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@@ -11651,6 +12389,12 @@
         "defaults": "^1.0.3"
       }
     },
+    "node_modules/web-worker": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz",
+      "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==",
+      "license": "Apache-2.0"
+    },
     "node_modules/webpack": {
       "version": "5.74.0",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 57533321b8345aab91ed0e8205d09fb212453725..0bf9f9e2be2793bcbc5906e34fe9413a391050d4 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -27,6 +27,7 @@
     "@fortawesome/free-solid-svg-icons": "^5.15.4",
     "jwt-decode": "^3.1.2",
     "ng-zorro-antd": "^14.0.0",
+    "ngx-markdown": "^14.0.1",
     "rxjs": "~7.5.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.11.4"
diff --git a/src/frontend/src/app/analytics-engine/analytics-engine.component.html b/src/frontend/src/app/analytics-engine/analytics-engine.component.html
index 7a99fbc9b716a32bdd04d5266dd5552df69e19fd..d803b1cfd795a78406b075bc2e7188c82de30384 100644
--- a/src/frontend/src/app/analytics-engine/analytics-engine.component.html
+++ b/src/frontend/src/app/analytics-engine/analytics-engine.component.html
@@ -1,24 +1,22 @@
-<div class="analytics-engines">
-  <h1 i18n="Analyses | @@analysis">
-    Analyses
-  </h1>
-  <nz-tabset>
-    <nz-tab i18n-nzTitle="Aktive Analysen | @@activeAnalyses" nzTitle="Active analyses">
-      <div *ngIf="!activeAnalyses.length">
-        <h1 i18n="no analyses | @@noAnalyses">No analyses</h1>
+<h1 i18n="Analyses | @@analysis">
+  Analyses
+</h1>
+<nz-tabset>
+  <nz-tab i18n-nzTitle="Aktive Analysen | @@activeAnalyses" nzTitle="Active analyses">
+    <div *ngIf="!activeAnalyses.length">
+      <h1 i18n="no analyses | @@noAnalyses">No analyses</h1>
+    </div>
+    <div nz-row [nzGutter]="[16, 24]">
+      <div nz-col [nzSpan]="6" *ngFor="let analysis of activeAnalyses">
+        <app-analysis-card [providers]="providers" class="card" [analysis]="analysis"  [reloadList]="reloadList"></app-analysis-card>
       </div>
-      <div nz-row [nzGutter]="[16, 24]">
-        <div nz-col [nzSpan]="6" *ngFor="let analysis of activeAnalyses">
-          <app-analysis-card [providers]="providers" class="card" [analysis]="analysis"  [reloadList]="reloadList"></app-analysis-card>
-        </div>
+    </div>
+  </nz-tab>
+  <nz-tab i18n-nzTitle="Inaktive Analysen | @@inactiveAnalyses" nzTitle="Inactive analyses">
+    <div nz-row [nzGutter]="[16, 24]">
+      <div nz-col [nzSpan]="6" *ngFor="let analysis of inactiveAnalyses">
+        <app-analysis-card [providers]="providers" class="card" [analysis]="analysis"  [reloadList]="reloadList"></app-analysis-card>
       </div>
-    </nz-tab>
-    <nz-tab i18n-nzTitle="Inaktive Analysen | @@inactiveAnalyses" nzTitle="Inactive analyses">
-      <div nz-row [nzGutter]="[16, 24]">
-        <div nz-col [nzSpan]="6" *ngFor="let analysis of inactiveAnalyses">
-          <app-analysis-card [providers]="providers" class="card" [analysis]="analysis"  [reloadList]="reloadList"></app-analysis-card>
-        </div>
-      </div>
-    </nz-tab>
-  </nz-tabset>
-</div>
+    </div>
+  </nz-tab>
+</nz-tabset>
diff --git a/src/frontend/src/app/analytics-engine/analytics-engine.component.scss b/src/frontend/src/app/analytics-engine/analytics-engine.component.scss
index 1c5898f399b8201f91ff1ecc1e3da6b8c968a156..f04d96d209af072dbb84b5ec0052c4ecdbd3e817 100644
--- a/src/frontend/src/app/analytics-engine/analytics-engine.component.scss
+++ b/src/frontend/src/app/analytics-engine/analytics-engine.component.scss
@@ -1,7 +1,3 @@
-.analytics-engines {
-    margin: 20px;
-}
-
 .card{
     width : 100%
-}
\ No newline at end of file
+}
diff --git a/src/frontend/src/app/analytics-engine/analytics-engine.component.ts b/src/frontend/src/app/analytics-engine/analytics-engine.component.ts
index bb7e4588cf436b37de420929b67d2a620790a38c..6b9c5f9ee3577f1c5933bbc177cf077c1c5901c4 100644
--- a/src/frontend/src/app/analytics-engine/analytics-engine.component.ts
+++ b/src/frontend/src/app/analytics-engine/analytics-engine.component.ts
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core';
-import { AnalyticsToken, AnalyticsTokenVerb, ApiService, Provider, UserConsentResponse, Verb } from '../services/api.service';
+import { AnalyticsToken, AnalyticsTokenVerb, ApiService, Provider, UserConsentResponse } from '../services/api.service';
 import { ProviderSchema, UserConsent, XApiVerbConsented } from '../consent-management/consentDeclaration';
 
 
@@ -43,12 +43,12 @@ export class AnalyticsEngineComponent implements OnInit {
   userConsentProviderPairs : UserConsentProviderPair[] = [];
   providerVerbConsentPairs : ProviderVerbConsentPair[] = []
 
-  constructor(private _apiService: ApiService) { 
+  constructor(private _apiService: ApiService) {
       this._apiService = _apiService
 
       this.reloadList = this.reloadList.bind(this)
   }
-  
+
   ngOnInit(): void {
     this.loadAnalyticsTokens()
   }
@@ -61,7 +61,7 @@ export class AnalyticsEngineComponent implements OnInit {
     this.providers = [];
     this.userConsentProviderPairs = [];
     this.providerVerbConsentPairs = []
-  
+
     this.loadAnalyticsTokens()
   }
 
@@ -72,7 +72,7 @@ export class AnalyticsEngineComponent implements OnInit {
       })
       .add(() => this.loadProviders())
   }
-  
+
   loadProviders()
   {
     this._apiService.getProvidersUsers().subscribe((providers) => {
@@ -80,7 +80,7 @@ export class AnalyticsEngineComponent implements OnInit {
       for(let provider of providers)
         this.loadConsent(provider)
     })
-    
+
   }
 
   loadConsent(provider : Provider)
@@ -111,7 +111,7 @@ export class AnalyticsEngineComponent implements OnInit {
     for(let group of consentGroups)
         for(let verb of group.verbs)
           allVerbs.push({...verb, providerId : provider.id});
-      
+
     this.providerVerbConsentPairs.push({provider : provider, providerSchema : providerSchema!, allVerbs : allVerbs})
 
   }
@@ -125,7 +125,7 @@ export class AnalyticsEngineComponent implements OnInit {
     for(let analysis of this.analysisTokens){
       let consentedVerbs : XApiVerbConsentedWithProvider[] = []
       let notConsentedVerbs : XApiVerbConsentedWithProvider[]= []
-  
+
       const verbs = analysis.analyticTokenVerbs
       for(let verb of verbs)
       {
@@ -145,5 +145,5 @@ export class AnalyticsEngineComponent implements OnInit {
       this.activeAnalyses = active
       this.inactiveAnalyses = inactive
   }
-  
+
 }
diff --git a/src/frontend/src/app/app-routing.module.ts b/src/frontend/src/app/app-routing.module.ts
index 3d57b7bd4c89ef08c93ae9beb89d123d3d1b43e8..2a6ceb3ae59e838083d49db00905e4b1644ecf4d 100644
--- a/src/frontend/src/app/app-routing.module.ts
+++ b/src/frontend/src/app/app-routing.module.ts
@@ -15,11 +15,13 @@ import { ApplicationTokensComponent } from './consent-management/application-tok
 import { AnalyticsTokensComponent } from './consent-management/analytics-tokens/analytics-tokens.component'
 import { AnalyticsEngineComponent } from './analytics-engine/analytics-engine.component'
 import { ConsentHistoryComponent } from './consent-management/consent-history/consent-history.component'
+import { ControlCenterComponent } from './control-center/control-center.component'
 
 const routes: Routes = [
   { path: 'home', component: HomeComponent },
   { path: 'consent-management', component: ConsentManagementComponent, canActivate: [AuthGuard] },
   { path: 'consent-history', component: ConsentHistoryComponent, canActivate: [AuthGuard] },
+  { path: 'control-center', component: ControlCenterComponent, canActivate: [AuthGuard]},
   { path: 'merge-actors', component: MergeDataComponent, canActivate: [AuthGuard] },
   { path: 'analytics-engines', component: AnalyticsEngineComponent, canActivate: [AuthGuard] },
   { path: 'login', component: LoginPageComponent },
diff --git a/src/frontend/src/app/app.component.html b/src/frontend/src/app/app.component.html
index ea94ceef30bdc8f5bfe161af90f908f757be622d..4cb27041c1fe241d7ccf2f4724222a9d858989a4 100644
--- a/src/frontend/src/app/app.component.html
+++ b/src/frontend/src/app/app.component.html
@@ -4,7 +4,9 @@
     <div class="loading-spinner" *ngIf="loading">
       <img ngSrc="assets/spinner_small.gif" alt="loading" height="100" width="100" />
     </div>
-    <router-outlet></router-outlet>
+    <div class="content">
+      <router-outlet></router-outlet>
+    </div>
   </nz-content>
   <app-footer></app-footer>
 </nz-layout>
diff --git a/src/frontend/src/app/app.module.ts b/src/frontend/src/app/app.module.ts
index fab3a639c51b373f4a3d71f26e44094202025007..f20d7f044eb761ce903442d0055ba0a343bad474 100644
--- a/src/frontend/src/app/app.module.ts
+++ b/src/frontend/src/app/app.module.ts
@@ -72,6 +72,11 @@ import { NzMessageService } from 'ng-zorro-antd/message'
 import { NzSpinModule } from 'ng-zorro-antd/spin'
 import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'
 import { ConsentHistoryComponent } from './consent-management/consent-history/consent-history.component'
+import { ControlCenterComponent } from './control-center/control-center.component'
+import { VerbGroupsComponent } from './consent-management/verb-groups/verb-groups.component'
+import { MarkdownModule, MarkdownService } from 'ngx-markdown'
+import { CreateVerbGroupDialog } from './dialogs/create-verb-group-dialog/create-verb-group-dialog'
+import { NzRadioModule } from 'ng-zorro-antd/radio'
 
 registerLocaleData(de)
 
@@ -81,6 +86,7 @@ registerLocaleData(de)
     HeaderComponent,
     ConsentManagementComponent,
     ConsentHistoryComponent,
+    ControlCenterComponent,
     PageNotFoundComponent,
     LoginPageComponent,
     WizardDialog,
@@ -98,47 +104,51 @@ registerLocaleData(de)
     SchemaChangeComponent,
     ObjectChangesComponent,
     CreateTokenDialog,
+    CreateVerbGroupDialog,
     OrderByPipe,
     MergeDataComponent,
     AnalysisCard,
-    AnalyticsEngineComponent
-  ],
-  imports: [
-    BrowserModule,
-    AppRoutingModule,
-    LayoutModule,
-    FormsModule,
-    ReactiveFormsModule,
-    HttpClientModule,
-    BrowserAnimationsModule,
-    NzTabsModule,
-    NzGridModule,
-    NzCardModule,
-    NzButtonModule,
-    FontAwesomeModule,
-    NzLayoutModule,
-    NgOptimizedImage,
-    NzMenuModule,
-    NzAffixModule,
-    NzSwitchModule,
-    NzDropDownModule,
-    NzCollapseModule,
-    NzListModule,
-    NzToolTipModule,
-    NzSelectModule,
-    NzInputModule,
-    NzSpaceModule,
-    NzFormModule,
-    NzBadgeModule,
-    NzTagModule,
-    NzTableModule,
-    NzCheckboxModule,
-    NzProgressModule,
-    NzAlertModule,
-    NzModalModule,
-    NzSpinModule,
-    NzDatePickerModule
+    AnalyticsEngineComponent,
+    VerbGroupsComponent
   ],
+    imports: [
+        BrowserModule,
+        AppRoutingModule,
+        LayoutModule,
+        FormsModule,
+        ReactiveFormsModule,
+        HttpClientModule,
+        BrowserAnimationsModule,
+        NzTabsModule,
+        NzGridModule,
+        NzCardModule,
+        NzButtonModule,
+        FontAwesomeModule,
+        NzLayoutModule,
+        NgOptimizedImage,
+        NzMenuModule,
+        NzAffixModule,
+        NzSwitchModule,
+        NzDropDownModule,
+        NzCollapseModule,
+        NzListModule,
+        NzToolTipModule,
+        NzSelectModule,
+        NzInputModule,
+        NzSpaceModule,
+        NzFormModule,
+        NzBadgeModule,
+        NzTagModule,
+        NzTableModule,
+        NzCheckboxModule,
+        NzProgressModule,
+        NzAlertModule,
+        NzModalModule,
+        NzSpinModule,
+        NzDatePickerModule,
+        MarkdownModule.forRoot(),
+        NzRadioModule
+    ],
   providers: [
     {
       provide: APP_INITIALIZER,
diff --git a/src/frontend/src/app/consent-management/analytics-tokens/analytics-tokens.component.html b/src/frontend/src/app/consent-management/analytics-tokens/analytics-tokens.component.html
index 3584fc00504a53d390b054c85e5eb2a337842431..891e70f6bf79340c1c4fa589442396fc2da089ea 100644
--- a/src/frontend/src/app/consent-management/analytics-tokens/analytics-tokens.component.html
+++ b/src/frontend/src/app/consent-management/analytics-tokens/analytics-tokens.component.html
@@ -18,8 +18,8 @@
         <th></th>
         <th>Name</th>
         <th>Token</th>
-        <th>Created At</th>
-        <th>Expires</th>
+        <th i18n="Created At | Table column header @@createdAt">Created At</th>
+        <th i18n="Expires | Table column header @@expires">Expires</th>
       </tr>
       </thead>
       <tbody>
diff --git a/src/frontend/src/app/consent-management/consent-history/consent-history.component.html b/src/frontend/src/app/consent-management/consent-history/consent-history.component.html
index 52fc28ad9f91fb246f26e204c4a192cd78ef09f7..4869b3d3b32c86380d4a53287d3865cd4c283936 100644
--- a/src/frontend/src/app/consent-management/consent-history/consent-history.component.html
+++ b/src/frontend/src/app/consent-management/consent-history/consent-history.component.html
@@ -1,23 +1,59 @@
-<nz-card style='margin: 8px;' [nzTitle]="cardTitle">
-  <ng-template #cardTitle>
-    <h2 i18n="Consent History | Header entry @@consentHistoryHeader">Consent History</h2>
-  </ng-template>
-  <nz-collapse>
-    <nz-collapse-panel *ngFor="let consent of consentHistory" [nzHeader]="dateHeader">
-      <ng-template #dateHeader>
-        {{ consent.created | date }}
-      </ng-template>
-      <nz-card
-        *ngFor="let userConsent of consent.consents"
-        [nzTitle]="userConsent.description"
-      >
-        <app-provider-setting
-          [preview]="true"
-          [consentDeclaration]="userConsent"
-          [previousUserConsent]="null"
-        ></app-provider-setting>
-      </nz-card>
+<h1 i18n="Consent History | Header entry @@consentHistoryHeader">Consent Management</h1>
+
+<p>
+  POLARIS verarbeitet deine persönlichen Lerndaten nur mit deiner vorherigen Zustimmung. In dieser Übersicht kannst du deine Einwilligungen zu verschiedenen Datenbündeln ansehen und bearbeiten. Du kannst deine Einwilligung widerrufen oder die Datenübertragung vorübergehend pausieren.
+</p>
+<p>&nbsp;</p>
+
+<nz-collapse>
+  <nz-collapse-panel [nzHeader]="faqPauseHeader">
+    <ng-template #faqPauseHeader>
+      Was passiert, wenn ich die Datenübertragung pausiere?
+    </ng-template>
+    <p>
+      Du kannst die Übertragung deiner Lerndaten an POLARIS jederzeit pausieren. Ab diesem Zeitpunkt werden auf unbestimmte Zeit keine weiteren Lerndaten mehr an POLARIS geschickt. Anders als beim Widerruf bleiben die in POLARIS gespeicherten Daten erhalten.
+    </p>
+    <p>
+      Die persönlichen Statistiken, die dir auf Basis der Analyse dieser Lerndaten angezeigt wurden, werden ausgeblendet.
+    </p>
+    <p>
+      Die Datenübertragung wird fortgesetzt, wenn du die Pausierung wieder aufhebst. Dies kannst du unten unter „Meine Einwilligungen“ tun oder indem du auf den Button „Statistiken aktivieren“ im jeweiligen Quellsystem, z.B. Moodle oder Dynexite, klickst.
+    </p>
+  </nz-collapse-panel>
+  <nz-collapse-panel [nzHeader]="faqRevokeHeader">
+    <ng-template #faqRevokeHeader>
+      Was passiert, wenn ich meine Einwilligung widerrufe?
+    </ng-template>
+    <p>
+      Eine Einwilligung in die Verarbeitung personenbezogener Daten kannst du jederzeit widerrufen – dieses Recht ist durch die Datenschutz-Grundverordnung gesichert. Du musst dazu keine Gründe angeben und deine Entscheidung darf keine Nachteile für dich zur Folge haben. In diesem Fall können jedoch die entsprechenden Analysen nicht für Dich durchgeführt werden.
+    </p>
+    <p>
+      Wenn du die Zustimmung zur Verarbeitung bestimmter Lerndaten in POLARIS widerrufst, werden ab diesem Zeitpunkt keine weiteren Daten mehr an POLARIS geschickt. Außerdem werden die bis zu diesem Zeitpunkt über dich gespeicherten Lerndaten innerhalb der gesetzlichen Frist von vier Wochen unwiderruflich anonymisiert. Anonyme Daten können dir nie wieder zugeordnet werden.
+    </p>
+    <p>
+      Die persönlichen Statistiken, die dir auf Basis der Analyse dieser Lerndaten angezeigt wurden, werden ausgeblendet.
+    </p>
+  </nz-collapse-panel>
+</nz-collapse>
+<p style="padding-bottom: 88px">&nbsp;</p>
+
+<h2>Deine Einwilligungen</h2>
+
+<nz-collapse>
+  <nz-collapse-panel *ngFor="let consent of consentHistory" [nzHeader]="dateHeader">
+    <ng-template #dateHeader>
+      {{ consent.created | date }}
+    </ng-template>
+    <nz-card
+      *ngFor="let userConsent of consent.consents"
+      [nzTitle]="userConsent.description"
+    >
+      <app-provider-setting
+        [preview]="true"
+        [consentDeclaration]="userConsent"
+        [previousUserConsent]="null"
+      ></app-provider-setting>
+    </nz-card>
+  </nz-collapse-panel>
+</nz-collapse>
 
-    </nz-collapse-panel>
-  </nz-collapse>
-</nz-card>
diff --git a/src/frontend/src/app/consent-management/consentDeclaration.ts b/src/frontend/src/app/consent-management/consentDeclaration.ts
index 51e06e7c1fb1e33ab37591d503448249c681ba35..013133fd39a4f178f8e3c4afc1e6d354a0e67cec 100644
--- a/src/frontend/src/app/consent-management/consentDeclaration.ts
+++ b/src/frontend/src/app/consent-management/consentDeclaration.ts
@@ -2,6 +2,7 @@ export interface XApiObjectSchema {
   definition: Object
   consented?: boolean
   defaultConsent: boolean
+  objectType: string
   label: string
   id: string
 }
@@ -10,18 +11,17 @@ export interface XApiObjectConsented {
   definition: Object
   consented: boolean
   defaultConsent: boolean
+  objectType: string
   label: string
   id: string
 }
 
-interface XApiVerb {
-}
-
 export interface XApiVerbSchema {
   id: string
   label: string
   description: string
   defaultConsent: boolean
+  essential: boolean
   objects: XApiObjectSchema[]
 }
 
@@ -30,18 +30,24 @@ export interface XApiVerbConsented extends XApiVerbSchema {
   objects: XApiObjectConsented[]
 }
 
-export interface ConsentGroupSchema {
+export interface XApiVerbGroupSchema {
   id: string
   label: string
   description: string
   purposeOfCollection: string
-  showVerbDetails: boolean
+  requiresConsent: boolean
+  provider_schema: number
   verbs: XApiVerbSchema[]
-  isDefault: boolean
 }
 
-export const instanceOfXApiVerbConsented = (object: any): object is XApiObjectConsented => {
-  return 'consented' in object
+export interface XApiVerbGroupConsented extends XApiVerbGroupSchema {
+  id: string
+  label: string
+  description: string
+  purposeOfCollection: string
+  showVerbDetails: boolean
+  verbs: XApiVerbSchema[]
+  isDefault: boolean
 }
 
 export interface ConsentGroupConsented {
@@ -55,37 +61,30 @@ export interface ConsentGroupConsented {
 }
 
 interface ConsentDeclaration {
-  id: string
+  id: number
   description: string
 }
 
-export interface ProviderConsent {
-  id: string
-  name: string
-  superseded_by: number | null
-  createdAt: string
-  definition: ConsentDeclaration[]
-  supersedes?: ProviderConsent
-}
-
 export interface ProviderSchemaDefinition {
   id: string
   name: string
   description: string
-  groups: ConsentGroupSchema[]
+  verbs: XApiVerbSchema[]
   essential_verbs: XApiVerbSchema[] // verbs which can be collected without the user consent
 }
 
-export interface ProviderSchema extends ConsentDeclaration {
+export interface ProviderSchema {
+  id: number
+  description: string
   superseded_by: number | null
   createdAt: string
-  groups: ConsentGroupSchema[]
+  groups: XApiVerbGroupConsented[]
   essential_verbs: XApiVerbSchema[] // verbs which can be collected without the user consent
   definition: ProviderSchemaDefinition
 }
 
 export interface UserConsent extends ConsentDeclaration {
-  id: string
+  id: number
   groups: ConsentGroupConsented[]
   essential_verbs: XApiVerbSchema[] // verbs which can be collected without the user consent
   created?: Date
@@ -93,11 +92,12 @@ export interface UserConsent extends ConsentDeclaration {
 
 export interface UserConsentVerbs {
   providerId: ProviderId
-  providerSchemaId: string
+  providerSchemaId: ProviderSchemaId
   verbs: { id: string; consented?: boolean; objects: string }[]
 }
 
 export type ProviderId = number
+export type ProviderSchemaId = number
 
 /**
  * Iterates over each verb and object of a provider schema and sets the consented field to the default consent
@@ -108,7 +108,7 @@ export const providerSchemaToUserConsent = (providerSchema: ProviderSchema): Use
   return {
     id: providerSchema.id,
     description: providerSchema.description,
-    groups: providerSchema.groups.map((group) => ({
+    groups:  providerSchema.groups? providerSchema.groups.map((group) => ({
       ...group,
       verbs: group.verbs.map((verb) => ({
         ...verb,
@@ -118,7 +118,7 @@ export const providerSchemaToUserConsent = (providerSchema: ProviderSchema): Use
           consented: object.defaultConsent
         })) : []
       }))
-    })),
+    })) : [],
     essential_verbs: providerSchema.essential_verbs
   }
 }
diff --git a/src/frontend/src/app/consent-management/provider/provider.component.html b/src/frontend/src/app/consent-management/provider/provider.component.html
index bb8eb6789e03adb9e39cc0198af590f7e3a739b5..843d5b189d49e4384c978aeb7ae2fb5e8d43e4ee 100644
--- a/src/frontend/src/app/consent-management/provider/provider.component.html
+++ b/src/frontend/src/app/consent-management/provider/provider.component.html
@@ -1,80 +1,89 @@
-<div class="provider">
-  <h1 i18n="Provider Schemas | Provider Schemas header @@providerSchemas">Provider Schemas</h1>
+<h1 i18n="Provider Schemas | Provider Schemas header @@providerSchemas">Provider Schemas</h1>
 
-  <nz-tabset>
-    <nz-tab *ngFor="let provider of providers" [nzTitle]="provider.name">
-      <div class="provider-details">
-        <div
-          *ngFor="
-                        let schemaVersion of provider.versions;
-                        let cnt = count;
-                        let idx = index
-                    ">
-          <nz-card class="existing-consents">
-            <nz-card-meta [nzTitle]="cardTitle" [nzDescription]="schemaVersion.createdAt | date : 'medium'"></nz-card-meta>
-            <ng-template #cardTitle>
-              <div class="schema-version-title">
-                <div>Version: {{ cnt - idx }}</div>
-                <div>
-                  <nz-tag *ngIf="idx === 0" nzColor="default">latest</nz-tag>
-                </div>
+<nz-tabset>
+  <nz-tab *ngFor="let provider of providers" [nzTitle]="provider.name">
+    <div class="provider-details">
+      <div
+        *ngFor="
+                      let schemaVersion of provider.versions;
+                      let cnt = count;
+                      let idx = index
+                  ">
+        <nz-card class="existing-consents">
+          <nz-card-meta [nzTitle]="cardTitle" [nzDescription]="schemaVersion.createdAt | date : 'medium'"></nz-card-meta>
+          <ng-template #cardTitle>
+            <div class="schema-version-title">
+              <div>Version: {{ cnt - idx }}</div>
+              <div>
+                <nz-tag *ngIf="idx === 0" nzColor="default" i18n="Latest Tag @@latestTag">latest</nz-tag>
               </div>
-            </ng-template>
-
-            <div
-              *ngIf="schemaVersion.superseded_by"
-              i18n="Superseded By | Provider schema information @@supersededBy">
-              Superseded by: {{ cnt - idx + 1 }}
-            </div>
-            <nz-switch [(ngModel)]="definitionVisible[provider.id][schemaVersion.id]"></nz-switch>
-            <span i18n="Toggle Definition | Slide Toggle Description @@toggleDefinition">Toggle Definition</span>
-            <div
-              class="definiton-view"
-              *ngIf="definitionVisible[provider.id][schemaVersion.id]">
-              <pre>{{ schemaVersion.definition | json }}</pre>
             </div>
-          </nz-card>
+          </ng-template>
+
+          <div
+            *ngIf="schemaVersion.superseded_by"
+            i18n="Superseded By | Provider schema information @@supersededBy">
+            Superseded by: {{ cnt - idx + 1 }}
+          </div>
+          <nz-switch [(ngModel)]="definitionVisible[provider.id][schemaVersion.id]"></nz-switch>
+          <span i18n="Toggle Definition | Slide Toggle Description @@toggleDefinition">Toggle Definition</span>
+          <div
+            class="definiton-view"
+            *ngIf="definitionVisible[provider.id][schemaVersion.id]">
+            <pre>{{ schemaVersion.definition | json }}</pre>
+          </div>
+
+          <p></p>
+
+          <app-verb-groups
+            [provider_id]="provider.id"
+            [allVerbs]="schemaVersion.definition.verbs"
+            [verbGroups]="getGroupsForProviderSchema(schemaVersion)"
+          >
+          </app-verb-groups>
 
           <app-schema-change
             *ngIf="idx < provider.versions.length - 1"
             [newSchema]="provider.versions[idx].definition"
             [oldSchema]="provider.versions[idx + 1].definition">
           </app-schema-change>
-        </div>
+
+        </nz-card>
       </div>
-    </nz-tab>
-  </nz-tabset>
+    </div>
+  </nz-tab>
+</nz-tabset>
 
-  <nz-card [nzTitle]="createCardTitle">
-    <ng-template #createCardTitle i18n="Create Provider Schema @@createProviderSchema">
-      Create/Update Provider Schema
-    </ng-template>
-    <input
-      type="file"
-      class="file-input"
-      accept="application/json"
-      (change)="onFileSelected($event)"
-      #fileUpload />
+<nz-card [nzTitle]="createCardTitle">
+  <ng-template #createCardTitle i18n="Create Provider Schema @@createProviderSchema">
+    Create/Update Provider Schema
+  </ng-template>
+  <input
+    type="file"
+    class="file-input"
+    accept="application/json"
+    (change)="onFileSelected($event)"
+    #fileUpload />
 
-    <div class="file-upload">
-      {{ fileName }}
+  <div class="file-upload">
+    {{ fileName }}
 
-      <button nz-button nzShape="circle" nzSize="small" nzType="primary" class="upload-btn" (click)="fileUpload.click()">
-        <fa-icon [icon]="faPaperclip"></fa-icon>
-      </button>
-    </div>
+    <button nz-button nzShape="circle" nzSize="small" nzType="primary" class="upload-btn" (click)="fileUpload.click()">
+      <fa-icon [icon]="faPaperclip"></fa-icon>
+    </button>
+  </div>
 
-    <div class="progress">
-      <nz-progress
-        class="progress-bar"
-        [nzPercent]="uploadProgress"
-        *ngIf="uploadProgress">
-      </nz-progress>
+  <div class="progress">
+    <nz-progress
+      class="progress-bar"
+      [nzPercent]="uploadProgress"
+      *ngIf="uploadProgress">
+    </nz-progress>
+
+    <fa-icon [icon]="faTrash" *ngIf="uploadProgress" (click)="cancelUpload()"></fa-icon>
+  </div>
+  <button nz-button nzType="primary" (click)="onSubmit()" i18n="Submit @@submit">
+    Submit
+  </button>
+</nz-card>
 
-      <fa-icon [icon]="faTrash" *ngIf="uploadProgress" (click)="cancelUpload()"></fa-icon>
-    </div>
-    <button nz-button nzType="primary" (click)="onSubmit()" i18n="Submit @@submit">
-      Submit
-    </button>
-  </nz-card>
-</div>
diff --git a/src/frontend/src/app/consent-management/provider/provider.component.scss b/src/frontend/src/app/consent-management/provider/provider.component.scss
index 994467b3c0668c3b40dca2709b5c0ac0ddd83683..e7c0f1011de5b5ecff6064b33ad768714dad2c89 100644
--- a/src/frontend/src/app/consent-management/provider/provider.component.scss
+++ b/src/frontend/src/app/consent-management/provider/provider.component.scss
@@ -1,6 +1,3 @@
-.provider {
-    padding: 20px;
-}
 .file-input {
     display: none;
 }
diff --git a/src/frontend/src/app/consent-management/provider/provider.component.ts b/src/frontend/src/app/consent-management/provider/provider.component.ts
index 8c4fcde46e5521311c3ba39be994446219f99c6a..fcb03daba8e9590bd2632c204bab95bbfe1f1504 100644
--- a/src/frontend/src/app/consent-management/provider/provider.component.ts
+++ b/src/frontend/src/app/consent-management/provider/provider.component.ts
@@ -6,7 +6,7 @@ import { ApiService, Provider } from 'src/app/services/api.service'
 import {
   ProviderSchema,
   providerSchemaToUserConsent,
-  UserConsent
+  UserConsent, XApiVerbGroupSchema
 } from '../consentDeclaration'
 import { faPaperclip, faTrash } from '@fortawesome/free-solid-svg-icons'
 import { NzMessageService } from 'ng-zorro-antd/message'
@@ -54,6 +54,21 @@ export class ProviderComponent implements OnInit {
     })
   }
 
+  getGroupsForProviderSchema(provider_schema: ProviderSchema): XApiVerbGroupSchema[] {
+    let groups: XApiVerbGroupSchema[] = [];
+    this.providers.forEach((provider) => {
+      provider.versions.forEach((version) => {
+        if(version.id === provider_schema.id) {
+          let temp_groups = provider.groups?.filter((group) => group.provider_schema == provider_schema.id);
+          if(temp_groups !== undefined) {
+            groups = temp_groups;
+          }
+        }
+      })
+    })
+    return groups;
+  }
+
   onFileSelected(event: any) {
     const file: File = event.target.files[0]
 
diff --git a/src/frontend/src/app/consent-management/schema-change/schema-change.component.ts b/src/frontend/src/app/consent-management/schema-change/schema-change.component.ts
index b797e8a83d922582cd02085ea3b7ad383ac64aaf..07ed5552671f9a62de74dda934d60430ae2b8723 100644
--- a/src/frontend/src/app/consent-management/schema-change/schema-change.component.ts
+++ b/src/frontend/src/app/consent-management/schema-change/schema-change.component.ts
@@ -1,9 +1,9 @@
 import { Component, OnInit, Input } from '@angular/core'
 import {
-    ConsentGroupSchema,
-    ProviderSchemaDefinition,
-    XApiObjectSchema,
-    XApiVerbSchema
+  XApiVerbGroupConsented,
+  ProviderSchemaDefinition,
+  XApiObjectSchema,
+  XApiVerbSchema
 } from '../consentDeclaration'
 import { faPlus, faTrash, faWrench } from '@fortawesome/free-solid-svg-icons'
 
@@ -12,448 +12,370 @@ type VerbChangeType = 'new' | 'removed' | 'changed' | 'onlyObjectChanged' | 'mov
 type ObjectChangeType = 'new' | 'removed' | 'changed'
 
 export interface ObjectChange {
-    type: ObjectChangeType
-    old?: XApiObjectSchema
-    new?: XApiObjectSchema
-    description: string
+  type: ObjectChangeType
+  old?: XApiObjectSchema
+  new?: XApiObjectSchema
+  description: string
 }
 
 interface VerbChange {
-    type: VerbChangeType
-    old?: XApiVerbSchema
-    new?: XApiVerbSchema
-    description: string
-    isEssentialVerb: boolean
-    objectChanges: ObjectChange[]
+  type: VerbChangeType
+  old?: XApiVerbSchema
+  new?: XApiVerbSchema
+  description: string
+  isEssentialVerb: boolean
+  objectChanges: ObjectChange[]
 }
 
 interface SchemaChange {
-    id: string
-    name: string
-    type: 'new' | 'removed' | 'changed' | 'moved'
-    changedVerbs?: VerbChange[]
-    newVerbs?: VerbChange[]
-    removedVerbs?: VerbChange[]
-    verbsWithChangedObjects?: VerbChange[]
+  id: string
+  name: string
+  type: 'new' | 'removed' | 'changed' | 'moved'
+  changedVerbs?: VerbChange[]
+  newVerbs?: VerbChange[]
+  removedVerbs?: VerbChange[]
+  verbsWithChangedObjects?: VerbChange[]
 }
 
 @Component({
-    selector: 'app-schema-change',
-    templateUrl: './schema-change.component.html',
-    styleUrls: ['./schema-change.component.scss']
+  selector: 'app-schema-change',
+  templateUrl: './schema-change.component.html',
+  styleUrls: ['./schema-change.component.scss']
 })
 export class SchemaChangeComponent implements OnInit {
-    @Input() oldSchema?: ProviderSchemaDefinition
-    @Input() newSchema?: ProviderSchemaDefinition
+  @Input() oldSchema?: ProviderSchemaDefinition
+  @Input() newSchema?: ProviderSchemaDefinition
 
-    schemaChanges: SchemaChange[] = []
+  schemaChanges: SchemaChange[] = []
 
-    constructor() {}
+  constructor() {
+  }
 
-    ngOnInit(): void {
-        this.detectChanges()
-    }
+  ngOnInit(): void {
+    this.detectChanges()
+  }
 
-    detectObjectChanges(oldVerb: XApiVerbSchema, newVerb: XApiVerbSchema): ObjectChange[] {
-        const objectChanges: ObjectChange[] = []
+  detectObjectChanges(oldVerb: XApiVerbSchema, newVerb: XApiVerbSchema): ObjectChange[] {
+    const objectChanges: ObjectChange[] = []
 
-        for (const oldObject of oldVerb.objects) {
-            const matchingNewObject = newVerb.objects.find((e) => e.id === oldObject.id)
-            if (matchingNewObject) {
-                if (oldObject.defaultConsent && !matchingNewObject.defaultConsent)
-                    objectChanges.push({
-                        description: `Object ${matchingNewObject.label} changed from Opt Out to Opt In.`,
-                        type: 'changed',
-                        old: oldObject,
-                        new: matchingNewObject
-                    })
-                if (!oldObject.defaultConsent && matchingNewObject.defaultConsent)
-                    objectChanges.push({
-                        description: `Object ${matchingNewObject.label} changed from Opt In to Opt Out.`,
-                        type: 'changed',
-                        old: oldObject,
-                        new: matchingNewObject
-                    })
-            } else {
-                objectChanges.push({
-                    description: `Object ${oldObject.label} removed.`,
-                    type: 'removed',
-                    old: oldObject
-                })
-            }
-        }
-        for (const newObject of newVerb.objects) {
-            const matchingOldObject = oldVerb.objects.find((e) => e.id === newObject.id)
-            if (!matchingOldObject) {
-                objectChanges.push({
-                    description: `Object ${newObject.label} added.`,
-                    type: 'new',
-                    new: newObject
-                })
-            }
-        }
-        return objectChanges
+    for (const oldObject of oldVerb.objects) {
+      const matchingNewObject = newVerb.objects.find((e) => e.id === oldObject.id)
+      if (matchingNewObject) {
+        if (oldObject.defaultConsent && !matchingNewObject.defaultConsent)
+          objectChanges.push({
+            description: `Object ${matchingNewObject.label} changed from Opt Out to Opt In.`,
+            type: 'changed',
+            old: oldObject,
+            new: matchingNewObject
+          })
+        if (!oldObject.defaultConsent && matchingNewObject.defaultConsent)
+          objectChanges.push({
+            description: `Object ${matchingNewObject.label} changed from Opt In to Opt Out.`,
+            type: 'changed',
+            old: oldObject,
+            new: matchingNewObject
+          })
+      } else {
+        objectChanges.push({
+          description: `Object ${oldObject.label} removed.`,
+          type: 'removed',
+          old: oldObject
+        })
+      }
+    }
+    for (const newObject of newVerb.objects) {
+      const matchingOldObject = oldVerb.objects.find((e) => e.id === newObject.id)
+      if (!matchingOldObject) {
+        objectChanges.push({
+          description: `Object ${newObject.label} added.`,
+          type: 'new',
+          new: newObject
+        })
+      }
     }
+    return objectChanges
+  }
 
-    /**
-     * Compares default consent between two xAPI Verbs.
-     * @param oldVerb
-     * @param newVerb
-     * @returns
-     */
-    detectVerbChange(
-        oldVerb: XApiVerbSchema,
-        newVerb: XApiVerbSchema,
-        isEssentialVerb: boolean
-    ): VerbChange | null {
-        if (
-            (oldVerb.defaultConsent && newVerb.defaultConsent) ||
-            (!oldVerb.defaultConsent && !newVerb.defaultConsent)
-        ) {
-            const objectChanges = this.detectObjectChanges(oldVerb, newVerb)
-            if (objectChanges.length > 0) {
-                return {
-                    description: $localize`:@@schemaChangeVerbObjChanges:Verb ${newVerb.label} includes object changes`,
-                    type: 'onlyObjectChanged',
-                    isEssentialVerb: isEssentialVerb,
-                    objectChanges: objectChanges
-                }
-            }
-            return null
+  /**
+   * Compares default consent between two xAPI Verbs.
+   * @param oldVerb
+   * @param newVerb
+   * @returns
+   */
+  detectVerbChange(
+    oldVerb: XApiVerbSchema,
+    newVerb: XApiVerbSchema
+  ): VerbChange | null {
+    const isEssentialVerb = oldVerb.essential
+    if (
+      (oldVerb.defaultConsent && newVerb.defaultConsent) ||
+      (!oldVerb.defaultConsent && !newVerb.defaultConsent)
+    ) {
+      const objectChanges = this.detectObjectChanges(oldVerb, newVerb)
+      if (objectChanges.length > 0) {
+        return {
+          description: $localize`:@@schemaChangeVerbObjChanges:Verb ${newVerb.label} includes object changes`,
+          type: 'onlyObjectChanged',
+          isEssentialVerb: isEssentialVerb,
+          objectChanges: objectChanges
         }
-        if (!oldVerb.defaultConsent && newVerb.defaultConsent)
-            return {
-                description: $localize`:@@schemaChangeVerbOptInToOptOut:Verb ${newVerb.label} changed from Opt In to Opt Out`,
-                old: oldVerb,
-                new: newVerb,
-                type: 'changed',
-                isEssentialVerb: isEssentialVerb,
-                objectChanges: this.detectObjectChanges(oldVerb, newVerb)
-            }
-        if (oldVerb.defaultConsent && !newVerb.defaultConsent)
-            return {
-                description: $localize`:@@schemaChangeVerbOptOutToOptIn:Verb ${newVerb.label} changed from Opt Out to Opt In`,
-                old: oldVerb,
-                new: newVerb,
-                type: 'changed',
-                isEssentialVerb: isEssentialVerb,
-                objectChanges: this.detectObjectChanges(oldVerb, newVerb)
-            }
-        return null
+      }
+      return null
     }
+    if (!oldVerb.defaultConsent && newVerb.defaultConsent)
+      return {
+        description: $localize`:@@schemaChangeVerbOptInToOptOut:Verb ${newVerb.label} changed from Opt In to Opt Out`,
+        old: oldVerb,
+        new: newVerb,
+        type: 'changed',
+        isEssentialVerb: isEssentialVerb,
+        objectChanges: this.detectObjectChanges(oldVerb, newVerb)
+      }
+    if (oldVerb.defaultConsent && !newVerb.defaultConsent)
+      return {
+        description: $localize`:@@schemaChangeVerbOptOutToOptIn:Verb ${newVerb.label} changed from Opt Out to Opt In`,
+        old: oldVerb,
+        new: newVerb,
+        type: 'changed',
+        isEssentialVerb: isEssentialVerb,
+        objectChanges: this.detectObjectChanges(oldVerb, newVerb)
+      }
+    return null
+  }
 
-    detectChanges(): void {
-        if (!this.newSchema || !this.oldSchema) return
-
-        const matchingOldSchema = this.oldSchema.id === this.newSchema.id ? this.oldSchema : null
-        if (matchingOldSchema) {
-            const newSchemaVerbs = this.newSchema.groups.flatMap((group) => group.verbs)
-            const oldSchemaVerbs = matchingOldSchema.groups.flatMap((group) => group.verbs)
-
-            for (const newSchemaVerb of newSchemaVerbs) {
-                const oldVerb = oldSchemaVerbs.find((oldVerb) => oldVerb.id === newSchemaVerb.id)
-                if (oldVerb) {
-                    const verbChange = this.detectVerbChange(oldVerb, newSchemaVerb, false)
-                    if (verbChange) this.updateSchemaChanges(matchingOldSchema, verbChange)
-                } else {
-                    const verbChange: VerbChange = {
-                        description: $localize`:@@schemaChangeVerbAdded:Verb ${newSchemaVerb.label} added`,
-                        type: 'new',
-                        new: newSchemaVerb,
-                        isEssentialVerb: false,
-                        objectChanges: []
-                    }
-                    this.updateSchemaChanges(matchingOldSchema, verbChange)
-                }
-            }
-
-            for (const oldVerb of oldSchemaVerbs) {
-                const matchingNewVerb = newSchemaVerbs.find((newVerb) => newVerb.id === oldVerb.id)
-                if (!matchingNewVerb) {
-                    const verbChange: VerbChange = {
-                        description: $localize`:@@schemaChangeVerbRemoved:Verb ${oldVerb.label} removed`,
-                        old: oldVerb,
-                        type: 'removed',
-                        isEssentialVerb: false,
-                        objectChanges: []
-                    }
-                    this.updateSchemaChanges(matchingOldSchema, verbChange)
-                }
-            }
-
-            for (const newEssentialVerb of this.newSchema.essential_verbs) {
-                const matchingOldVerb = matchingOldSchema.essential_verbs.find(
-                    (e) => e.id === newEssentialVerb.id
-                )
+  detectChanges(): void {
+    if (!this.newSchema || !this.oldSchema) return
 
-                if (matchingOldVerb) {
-                    const verbChange = this.detectVerbChange(
-                        matchingOldVerb,
-                        newEssentialVerb,
-                        true
-                    )
-                    if (verbChange) this.updateSchemaChanges(matchingOldSchema, verbChange)
-                } else {
-                    const verbChange: VerbChange = {
-                        description: $localize`:@@schemaChangeEssentialVerbAdded:Essential Verb ${newEssentialVerb.label} added`,
-                        type: 'new',
-                        new: newEssentialVerb,
-                        isEssentialVerb: true,
-                        objectChanges: []
-                    }
-                    this.updateSchemaChanges(this.newSchema, verbChange)
-                }
-            }
+    const matchingOldSchema = this.oldSchema.id === this.newSchema.id ? this.oldSchema : null
+    if (matchingOldSchema) {
+      const newSchemaVerbs = this.newSchema.verbs
+      const oldSchemaVerbs = matchingOldSchema.verbs
 
-            for (const oldEssentialVerb of matchingOldSchema.essential_verbs) {
-                const matchingNewVerb = this.newSchema.essential_verbs.find(
-                    (e) => e.id === oldEssentialVerb.id
-                )
-
-                if (!matchingNewVerb) {
-                    const verbChange: VerbChange = {
-                        description: $localize`:@@schemaChangeEssentialVerbRemoved:Essential Verb ${oldEssentialVerb.label} removed`,
-                        old: oldEssentialVerb,
-                        type: 'removed',
-                        isEssentialVerb: true,
-                        objectChanges: []
-                    }
-                    this.updateSchemaChanges(matchingOldSchema, verbChange)
-                }
-            }
+      for (const newSchemaVerb of newSchemaVerbs) {
+        const oldVerb = oldSchemaVerbs.find((oldVerb) => oldVerb.id === newSchemaVerb.id)
+        if (oldVerb) {
+          const verbChange = this.detectVerbChange(oldVerb, newSchemaVerb)
+          if (verbChange) this.updateSchemaChanges(matchingOldSchema, verbChange)
         } else {
-            // New schema added
-            this.schemaChanges.push({
-                id: this.newSchema.id,
-                name: this.newSchema.name,
-                type: 'new'
-            })
-            const verbChanges: VerbChange[] = [
-                ...this.newSchema.groups.flatMap((group) =>
-                    group.verbs.map((verb) => {
-                        const verbChange: VerbChange = {
-                            description: $localize`:@@schemaChangeVerbAdded:Verb ${verb.label} added`,
-                            type: 'new',
-                            new: verb,
-                            isEssentialVerb: false,
-                            objectChanges: []
-                        }
-                        return verbChange
-                    })
-                ),
-                ...this.newSchema.essential_verbs.map((verb) => {
-                    const verbChange: VerbChange = {
-                        description: $localize`:@@schemaChangeVerbAdded:Verb ${verb.label} added`,
-                        type: 'new',
-                        new: verb,
-                        isEssentialVerb: false,
-                        objectChanges: []
-                    }
-                    return verbChange
-                })
-            ]
-            for (const verbChange of verbChanges)
-                this.updateSchemaChanges(this.newSchema, verbChange)
+          const verbChange: VerbChange = {
+            description: $localize`:@@schemaChangeVerbAdded:Verb ${newSchemaVerb.label} added`,
+            type: 'new',
+            new: newSchemaVerb,
+            isEssentialVerb: false,
+            objectChanges: []
+          }
+          this.updateSchemaChanges(matchingOldSchema, verbChange)
         }
-        const matchingNewSchema = this.newSchema.id === this.oldSchema.id ? this.newSchema : null
-        if (!matchingNewSchema) {
-            // Schema was remove
-            this.schemaChanges.push({
-                id: this.oldSchema.id,
-                name: this.oldSchema.name,
-                type: 'removed'
-            })
-            const verbChanges: VerbChange[] = [
-                ...this.oldSchema.groups.flatMap((group) =>
-                    group.verbs.map((verb) => {
-                        const verbChange: VerbChange = {
-                            description: $localize`:@@schemaChangeVerbRemoved:Verb ${verb.label} removed`,
-                            type: 'removed',
-                            new: verb,
-                            isEssentialVerb: false,
-                            objectChanges: []
-                        }
-                        return verbChange
-                    })
-                ),
-                ...this.oldSchema.essential_verbs.map((verb) => {
-                    const verbChange: VerbChange = {
-                        description: $localize`:@@schemaChangeVerbRemoved:Verb ${verb.label} removed`,
-                        type: 'removed',
-                        new: verb,
-                        isEssentialVerb: false,
-                        objectChanges: []
-                    }
-                    return verbChange
-                })
-            ]
-            for (const verbChange of verbChanges)
-                this.updateSchemaChanges(this.oldSchema, verbChange)
+      }
+
+      for (const oldVerb of oldSchemaVerbs) {
+        const matchingNewVerb = newSchemaVerbs.find((newVerb) => newVerb.id === oldVerb.id)
+        if (!matchingNewVerb) {
+          const verbChange: VerbChange = {
+            description: $localize`:@@schemaChangeVerbRemoved:Verb ${oldVerb.label} removed`,
+            old: oldVerb,
+            type: 'removed',
+            isEssentialVerb: false,
+            objectChanges: []
+          }
+          this.updateSchemaChanges(matchingOldSchema, verbChange)
         }
+      }
 
-        // Detect if verb from old schema was moved to a different group in new schema
-        this.detectVerbGroupChange().forEach((verbChange) =>
-            this.updateSchemaChanges(this.oldSchema!, verbChange)
+      for (const newEssentialVerb of this.newSchema.essential_verbs) {
+        const matchingOldVerb = matchingOldSchema.essential_verbs.find(
+          (e) => e.id === newEssentialVerb.id
         )
-    }
 
-    /**
-     * Detects if verbs have been moved from one group to another between old and new schema.
-     */
-    detectVerbGroupChange(): VerbChange[] {
-        const groupsOldSchema = this.oldSchema?.groups ?? []
-        const groupsNewSchema = this.newSchema?.groups ?? []
+        if (matchingOldVerb) {
+          const verbChange = this.detectVerbChange(
+            matchingOldVerb,
+            newEssentialVerb
+          )
+          if (verbChange) this.updateSchemaChanges(matchingOldSchema, verbChange)
+        } else {
+          const verbChange: VerbChange = {
+            description: $localize`:@@schemaChangeEssentialVerbAdded:Essential Verb ${newEssentialVerb.label} added`,
+            type: 'new',
+            new: newEssentialVerb,
+            isEssentialVerb: true,
+            objectChanges: []
+          }
+          this.updateSchemaChanges(this.newSchema, verbChange)
+        }
+      }
 
-        const verbChanges: VerbChange[] = []
+      for (const oldEssentialVerb of matchingOldSchema.essential_verbs) {
+        const matchingNewVerb = this.newSchema.essential_verbs.find(
+          (e) => e.id === oldEssentialVerb.id
+        )
 
-        for (const groupOldSchema of groupsOldSchema) {
-            // Find matching group in new schema
-            const matchingGroupNewSchema = groupsNewSchema.find(
-                (group) => group.id === groupOldSchema.id
-            )
-            if (matchingGroupNewSchema) {
-                for (const verb of groupOldSchema.verbs) {
-                    const verbOldGroupNotInNewGroup = !matchingGroupNewSchema.verbs.find(
-                        ({ id }) => id === verb.id
-                    )
-                    if (verbOldGroupNotInNewGroup) {
-                        // Check if verb was moved to another group in new schema
-                        const otherGroup = this.findVerbInGroups(groupsNewSchema, verb)
-                        if (otherGroup) {
-                            const verbMovedGroupChange: VerbChange = {
-                                description: $localize`:@@schemaChangeVerbMovedGroup:Verb ${verb.label} moved from group "${groupOldSchema.label}" to "${otherGroup.label}"`,
-                                type: 'moved',
-                                new: verb,
-                                isEssentialVerb: false,
-                                objectChanges: []
-                            }
-                            verbChanges.push(verbMovedGroupChange)
-                        } else {
-                            // Verb was removed, already handled!
-                        }
-                    }
-                }
-            } else {
-                // Group from old schema doesn't exist in new schema, check if verbs were moved to another group
-                for (const verb of groupOldSchema.verbs) {
-                    const otherGroup = this.findVerbInGroups(groupsNewSchema, verb)
-                    if (otherGroup) {
-                        const verbMovedGroupChange: VerbChange = {
-                            description: $localize`:@@schemaChangeVerbMovedGroupAlt:Verb ${verb.label} moved to group "${otherGroup.label}"`,
-                            type: 'moved',
-                            new: verb,
-                            isEssentialVerb: false,
-                            objectChanges: []
-                        }
-                        verbChanges.push(verbMovedGroupChange)
-                    }
-                }
-            }
+        if (!matchingNewVerb) {
+          const verbChange: VerbChange = {
+            description: $localize`:@@schemaChangeEssentialVerbRemoved:Essential Verb ${oldEssentialVerb.label} removed`,
+            old: oldEssentialVerb,
+            type: 'removed',
+            isEssentialVerb: true,
+            objectChanges: []
+          }
+          this.updateSchemaChanges(matchingOldSchema, verbChange)
         }
-        return verbChanges
+      }
+    } else {
+      // New schema added
+      this.schemaChanges.push({
+        id: this.newSchema.id,
+        name: this.newSchema.name,
+        type: 'new'
+      })
+      const verbChanges: VerbChange[] = [
+        ...this.newSchema.verbs.map((verb) =>
+           {
+            const verbChange: VerbChange = {
+              description: $localize`:@@schemaChangeVerbAdded:Verb ${verb.label} added`,
+              type: 'new',
+              new: verb,
+              isEssentialVerb: false,
+              objectChanges: []
+            }
+            return verbChange
+          }
+        ),
+        ...this.newSchema.essential_verbs.map((verb) => {
+          const verbChange: VerbChange = {
+            description: $localize`:@@schemaChangeVerbAdded:Verb ${verb.label} added`,
+            type: 'new',
+            new: verb,
+            isEssentialVerb: false,
+            objectChanges: []
+          }
+          return verbChange
+        })
+      ]
+      for (const verbChange of verbChanges)
+        this.updateSchemaChanges(this.newSchema, verbChange)
     }
-
-    /**
-     * Find verb in array of groups.
-     * @param groups
-     * @param verb
-     * @returns
-     */
-    findVerbInGroups(
-        groups: ConsentGroupSchema[],
-        verb: XApiVerbSchema
-    ): ConsentGroupSchema | null {
-        for (const group of groups) {
-            for (const groupVerb of group.verbs) {
-                if (groupVerb.id === verb.id) return group
+    const matchingNewSchema = this.newSchema.id === this.oldSchema.id ? this.newSchema : null
+    if (!matchingNewSchema) {
+      // Schema was removed
+      this.schemaChanges.push({
+        id: this.oldSchema.id,
+        name: this.oldSchema.name,
+        type: 'removed'
+      })
+      const verbChanges: VerbChange[] = [
+        ...this.oldSchema.verbs.map((verb) => {
+            const verbChange: VerbChange = {
+              description: $localize`:@@schemaChangeVerbRemoved:Verb ${verb.label} removed`,
+              type: 'removed',
+              new: verb,
+              isEssentialVerb: false,
+              objectChanges: []
             }
-        }
-        return null
+            return verbChange
+          }
+        ),
+        ...this.oldSchema.essential_verbs.map((verb) => {
+          const verbChange: VerbChange = {
+            description: $localize`:@@schemaChangeVerbRemoved:Verb ${verb.label} removed`,
+            type: 'removed',
+            new: verb,
+            isEssentialVerb: false,
+            objectChanges: []
+          }
+          return verbChange
+        })
+      ]
+      for (const verbChange of verbChanges)
+        this.updateSchemaChanges(this.oldSchema, verbChange)
     }
 
-    updateSchemaChanges(schema: ProviderSchemaDefinition, verbChange: VerbChange): void {
-        if (!this.schemaChanges.find((e) => e.id === schema.id)) {
-            switch (verbChange.type) {
-                case 'new':
-                    this.schemaChanges.push({
-                        id: schema.id,
-                        name: schema.name,
-                        type: 'changed',
-                        newVerbs: [verbChange]
-                    })
-                    return
-                case 'changed':
-                    this.schemaChanges.push({
-                        id: schema.id,
-                        name: schema.name,
-                        type: 'changed',
-                        changedVerbs: [verbChange]
-                    })
-                    return
-                case 'removed':
-                    this.schemaChanges.push({
-                        id: schema.id,
-                        name: schema.name,
-                        type: 'changed',
-                        removedVerbs: [verbChange]
-                    })
-                    return
-                case 'onlyObjectChanged':
-                    this.schemaChanges.push({
-                        id: schema.id,
-                        name: schema.name,
-                        type: 'changed',
-                        verbsWithChangedObjects: [verbChange]
-                    })
-                    return
-                case 'moved':
-                    this.schemaChanges.push({
-                        id: schema.id,
-                        name: schema.name,
-                        type: 'moved',
-                        changedVerbs: [verbChange]
-                    })
-                    return
-                default:
-                    throw Error(`Verb change type '${verbChange.type}' didn't match!`)
-            }
-        }
+  }
 
-        this.schemaChanges = this.schemaChanges.map((schemaChange) => {
-            if (schemaChange.id === schema.id) {
-                if (verbChange.type === 'new')
-                    return {
-                        ...schemaChange,
-                        newVerbs: [...(schemaChange.newVerbs ?? []), verbChange]
-                    }
-                if (verbChange.type === 'changed')
-                    return {
-                        ...schemaChange,
-                        changedVerbs: [...(schemaChange.changedVerbs ?? []), verbChange]
-                    }
-                if (verbChange.type === 'removed')
-                    return {
-                        ...schemaChange,
-                        removedVerbs: [...(schemaChange.removedVerbs ?? []), verbChange]
-                    }
-                if (verbChange.type === 'onlyObjectChanged')
-                    return {
-                        ...schemaChange,
-                        verbsWithChangedObjects: [
-                            ...(schemaChange.verbsWithChangedObjects ?? []),
-                            verbChange
-                        ]
-                    }
-                if (verbChange.type === 'moved')
-                    return {
-                        ...schemaChange,
-                        changedVerbs: [...(schemaChange.changedVerbs ?? []), verbChange]
-                    }
-            }
-            return schemaChange
-        })
+
+  updateSchemaChanges(schema: ProviderSchemaDefinition, verbChange: VerbChange): void {
+    if (!this.schemaChanges.find((e) => e.id === schema.id)) {
+      switch (verbChange.type) {
+        case 'new':
+          this.schemaChanges.push({
+            id: schema.id,
+            name: schema.name,
+            type: 'changed',
+            newVerbs: [verbChange]
+          })
+          return
+        case 'changed':
+          this.schemaChanges.push({
+            id: schema.id,
+            name: schema.name,
+            type: 'changed',
+            changedVerbs: [verbChange]
+          })
+          return
+        case 'removed':
+          this.schemaChanges.push({
+            id: schema.id,
+            name: schema.name,
+            type: 'changed',
+            removedVerbs: [verbChange]
+          })
+          return
+        case 'onlyObjectChanged':
+          this.schemaChanges.push({
+            id: schema.id,
+            name: schema.name,
+            type: 'changed',
+            verbsWithChangedObjects: [verbChange]
+          })
+          return
+        case 'moved':
+          this.schemaChanges.push({
+            id: schema.id,
+            name: schema.name,
+            type: 'moved',
+            changedVerbs: [verbChange]
+          })
+          return
+        default:
+          throw Error(`Verb change type '${verbChange.type}' didn't match!`)
+      }
     }
 
+    this.schemaChanges = this.schemaChanges.map((schemaChange) => {
+      if (schemaChange.id === schema.id) {
+        if (verbChange.type === 'new')
+          return {
+            ...schemaChange,
+            newVerbs: [...(schemaChange.newVerbs ?? []), verbChange]
+          }
+        if (verbChange.type === 'changed')
+          return {
+            ...schemaChange,
+            changedVerbs: [...(schemaChange.changedVerbs ?? []), verbChange]
+          }
+        if (verbChange.type === 'removed')
+          return {
+            ...schemaChange,
+            removedVerbs: [...(schemaChange.removedVerbs ?? []), verbChange]
+          }
+        if (verbChange.type === 'onlyObjectChanged')
+          return {
+            ...schemaChange,
+            verbsWithChangedObjects: [
+              ...(schemaChange.verbsWithChangedObjects ?? []),
+              verbChange
+            ]
+          }
+        if (verbChange.type === 'moved')
+          return {
+            ...schemaChange,
+            changedVerbs: [...(schemaChange.changedVerbs ?? []), verbChange]
+          }
+      }
+      return schemaChange
+    })
+  }
+
   protected readonly faWrench = faWrench
   protected readonly faPlus = faPlus
   protected readonly faTrash = faTrash
diff --git a/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.html b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..29e706a4568a8f87bc71babe2f8af82ac7eb7a8f
--- /dev/null
+++ b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.html
@@ -0,0 +1,41 @@
+<nz-collapse>
+  <nz-collapse-panel [nzHeader]="panelHeader">
+    <ng-template #panelHeader>
+      <span i18n="Verb Groups | Header @@groups">
+        Verb Groups
+      </span>
+    </ng-template>
+
+    <nz-collapse>
+      <nz-collapse-panel *ngFor="let group of verbGroups.concat(additionalVerbGroups)" [nzHeader]="group.label">
+        <p class="subheader" i18n="Description @@description">>
+          Description
+        </p>
+
+        <markdown [data]="group.description"></markdown>
+
+        <p class="subheader" i18n="Purpose of collection @@purposeOfCollection">>
+          Purpose of collection
+        </p>
+        <markdown [data]="group.purposeOfCollection"></markdown>
+
+        <p>
+          <span class="subheader" i18n="Contained Verbs: @@containedVerbs">Enthaltene Verben:</span>
+          <nz-list>
+            <nz-list-item *ngFor="let verb of group.verbs">
+              {{verb.label}}: {{verb.description}}
+            </nz-list-item>
+          </nz-list>
+        </p>
+      </nz-collapse-panel>
+    </nz-collapse>
+    <p></p>
+    <button
+      (click)="createVerbGroup()"
+      nz-button
+      i18n="Create Data Packet @@createGroup">
+      Create Data Packet
+    </button>
+
+  </nz-collapse-panel>
+</nz-collapse>
diff --git a/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.scss b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..41045ec998efa6da3f455983894660ae387a69b4
--- /dev/null
+++ b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.scss
@@ -0,0 +1,3 @@
+.subheader {
+  font-weight: 500;
+}
diff --git a/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.spec.ts b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..89f6d30b6138723125045b4e15d77558b52770a7
--- /dev/null
+++ b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VerbGroupsComponent } from './verb-groups.component';
+
+describe('VerbGroupsComponent', () => {
+  let component: VerbGroupsComponent;
+  let fixture: ComponentFixture<VerbGroupsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ VerbGroupsComponent ]
+    })
+      .compileComponents();
+
+    fixture = TestBed.createComponent(VerbGroupsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.ts b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f246c41f9ad1fa890f0fac9ebbfd08f20e5a683e
--- /dev/null
+++ b/src/frontend/src/app/consent-management/verb-groups/verb-groups.component.ts
@@ -0,0 +1,43 @@
+import { Component, Input } from '@angular/core'
+import { XApiVerbGroupSchema, XApiVerbSchema } from '../consentDeclaration'
+import { NzModalService } from 'ng-zorro-antd/modal'
+import { CreateVerbGroupDialog } from '../../dialogs/create-verb-group-dialog/create-verb-group-dialog'
+import { ApiService } from '../../services/api.service'
+import { NzMessageService } from 'ng-zorro-antd/message'
+
+@Component({
+  selector: 'app-verb-groups',
+  templateUrl: './verb-groups.component.html',
+  styleUrls: ['./verb-groups.component.scss']
+})
+export class VerbGroupsComponent {
+  @Input() provider_id: number = -1;
+  @Input() allVerbs: XApiVerbSchema[] = [];
+  @Input() verbGroups: XApiVerbGroupSchema[] = [];
+
+  protected additionalVerbGroups: XApiVerbGroupSchema[] = [];
+
+  constructor(private _apiService: ApiService, private _dialog: NzModalService, private _messageService: NzMessageService) {
+  }
+
+  createVerbGroup(): void {
+    const dialogRef = this._dialog.create({
+      nzContent: CreateVerbGroupDialog,
+      nzComponentParams: {
+        headerText: $localize`:@@createGroup:Create Data Packet`,
+        verbs: this.allVerbs,
+      }
+    });
+    dialogRef.afterClose.subscribe(result => {
+      const {label, description, purposeOfCollection, requiresConsent, selectedVerbs} = result;
+      this._apiService.createOrUpdateVerbGroup(this.provider_id, "", label, description, purposeOfCollection, requiresConsent, selectedVerbs)
+        .subscribe(result => {
+          if(result.group)
+          {
+            this.additionalVerbGroups = [...this.additionalVerbGroups, result.group];
+            this._messageService.success("Das Datenbündel wurde erstellt.")
+          }
+        })
+    });
+  }
+}
diff --git a/src/frontend/src/app/control-center/control-center.component.html b/src/frontend/src/app/control-center/control-center.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..95e3c73d9e94216422e9bf42c1a84d9cbae32cf0
--- /dev/null
+++ b/src/frontend/src/app/control-center/control-center.component.html
@@ -0,0 +1,51 @@
+<div class="control-center">
+  <h1 i18n="Control Center @@controlCenter">Control Center</h1>
+
+  <h2 i18n="Users @@usersAndRoles">Users and Roles</h2>
+  <nz-table
+    #userTable
+    [nzData]="this.users"
+  >
+    <thead>
+      <tr>
+        <th></th>
+        <th>#</th>
+        <th>Vorname</th>
+        <th>Nachname</th>
+        <th>E-Mail</th>
+        <th>Admin</th>
+        <th>Pausiert</th>
+        <th>Datenschutzerklärung</th>
+      </tr>
+    </thead>
+    <tbody>
+      <ng-container *ngFor="let user of userTable.data">
+        <tr>
+          <td [nzExpand]="expandSet.has(user.id)"
+              (nzExpandChange)="onExpandChange(user.id, $event)"></td>
+          <td>{{user.id}}</td>
+          <td>{{user.first_name}}</td>
+          <td>{{user.last_name}}</td>
+          <td>{{user.email}}</td>
+          <td>{{user.is_superuser}}</td>
+          <td>{{user.paused_data_recording}}</td>
+          <td>{{user.general_privacy_policy}}</td>
+        </tr>
+        <tr [nzExpand]="expandSet.has(user.id)">
+          <td [attr.colspan]="8">
+            <h3>Gruppen</h3>
+            <nz-checkbox-group [(ngModel)]="user_group_checkboxes[user.id]">
+            </nz-checkbox-group>
+
+            <h3>Rechte</h3>
+            <nz-checkbox-group [(ngModel)]="user_permission_checkboxes[user.id]">
+            </nz-checkbox-group>
+          </td>
+        </tr>
+      </ng-container>
+    </tbody>
+  </nz-table>
+
+  <app-provider>
+  </app-provider>
+</div>
diff --git a/src/frontend/src/app/control-center/control-center.component.scss b/src/frontend/src/app/control-center/control-center.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1b9bbe4493a5d61e289dff014dac5059fbdca128
--- /dev/null
+++ b/src/frontend/src/app/control-center/control-center.component.scss
@@ -0,0 +1,3 @@
+.control-center {
+  padding: 20px;
+}
diff --git a/src/frontend/src/app/control-center/control-center.component.spec.ts b/src/frontend/src/app/control-center/control-center.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33167351b4eea8a20591a185aa8df44e0e5be257
--- /dev/null
+++ b/src/frontend/src/app/control-center/control-center.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ControlCenterComponent } from './control-center.component';
+
+describe('ControlCenterComponent', () => {
+  let component: ControlCenterComponent;
+  let fixture: ComponentFixture<ControlCenterComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ ControlCenterComponent ]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(ControlCenterComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/frontend/src/app/control-center/control-center.component.ts b/src/frontend/src/app/control-center/control-center.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..22766d090de51d9a7a4b1d07d26b73bdf336286d
--- /dev/null
+++ b/src/frontend/src/app/control-center/control-center.component.ts
@@ -0,0 +1,74 @@
+import { Component } from '@angular/core'
+import { NzMessageService } from 'ng-zorro-antd/message'
+import { NzModalService } from 'ng-zorro-antd/modal'
+import { ApiService, AuthGroup, AuthPermission, AuthUser } from '../services/api.service'
+
+interface AuthGroupCheckbox {
+  label: string
+  value: string
+  checked?: boolean
+}
+type UserGroupCheckboxMap = Record<number, AuthGroupCheckbox[]>; // id to checkboxes
+
+@Component({
+  selector: 'app-control-center',
+  templateUrl: './control-center.component.html',
+  styleUrls: ['./control-center.component.scss']
+})
+
+export class ControlCenterComponent {
+  protected users: AuthUser[] = [];
+  protected auth_permissions: AuthPermission[] = [];
+  protected auth_groups: AuthGroup[] = [];
+
+  expandSet = new Set<number>();
+  protected user_group_checkboxes: UserGroupCheckboxMap = {}
+  protected user_permission_checkboxes: UserGroupCheckboxMap = {}
+
+  onExpandChange(id: number, checked: boolean): void {
+    if (checked) {
+      this.expandSet.clear();
+      this.expandSet.add(id);
+    } else {
+      this.expandSet.delete(id);
+    }
+  }
+  constructor(
+    private _messageService: NzMessageService,
+    private _apiService: ApiService,
+    public dialog: NzModalService
+  ) {
+
+    this._apiService.getGroups().subscribe(result => {
+      this.auth_groups = result;
+      this._apiService.getPermissions().subscribe(result => {
+        this.auth_permissions = result;
+        this._apiService.getUsers().subscribe(result => {
+          this.users = result;
+          let new_group_checkboxes: UserGroupCheckboxMap = {};
+          let new_permission_checkboxes: UserGroupCheckboxMap = {};
+          result.forEach((user) => {
+            new_group_checkboxes[user.id] = this.auth_groups.map((group) => {
+              return {
+                label: group.name,
+                value: group.id.toString(),
+                checked: user.groups.map(gr => gr.id).includes(group.id)
+              }
+            })
+            new_permission_checkboxes[user.id] = this.auth_permissions.map((permission) => {
+              return {
+                label: permission.name,
+                value: permission.id.toString(),
+                checked: user.user_permissions.map(pr => pr.id).includes(permission.id)
+              }
+            })
+
+          })
+          this.user_group_checkboxes = new_group_checkboxes;
+          this.user_permission_checkboxes = new_permission_checkboxes;
+        })
+      })
+    })
+  }
+
+}
diff --git a/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.html b/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.html
new file mode 100644
index 0000000000000000000000000000000000000000..26b629ee6053e17d72f67663400bc38f4d6d575e
--- /dev/null
+++ b/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.html
@@ -0,0 +1,86 @@
+<div *nzModalTitle>{{ headerText }}</div>
+
+<div>
+  <form nz-form [formGroup]="form" (ngSubmit)="onSubmit()">
+
+    <nz-form-item>
+      <nz-form-label nzRequired [nzSpan]="10">Name</nz-form-label>
+      <nz-form-control [nzErrorTip]="labelError" [nzSpan]="14">
+        <nz-input-group>
+          <input
+            nz-input
+            placeholder="Name"
+            [formControl]="labelController"
+            autocomplete="off" />
+        </nz-input-group>
+        <ng-template #labelError let-control>
+          <ng-container *ngIf="control.hasError('required')" i18n="Name is required @@providerSchemaNameRequiredHint">
+            Name is <strong>required</strong>
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+
+
+
+    <nz-form-item>
+      <nz-form-label nzRequired [nzSpan]="10" i18n="Description @@description">Description</nz-form-label>
+      <nz-form-control [nzSpan]="14" [nzErrorTip]="descriptionError">
+        <nz-input-group>
+          <textarea
+            nz-input
+            [rows]="5"
+            [formControl]="descriptionController"
+            autocomplete="off"></textarea>
+        </nz-input-group>
+        <ng-template #descriptionError let-control>
+          <ng-container *ngIf="control.hasError('required')" i18n="Description is required @@descriptionRequiredHint">
+            Description is <strong>required</strong>
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+
+    <nz-form-item>
+      <nz-form-label nzRequired [nzSpan]="10" i18n="Purpose @@purposeOfCollectionTitle">Purpose of collection</nz-form-label>
+      <nz-form-control [nzSpan]="14" [nzErrorTip]="purposeError">
+        <nz-input-group>
+          <textarea
+            nz-input
+            [rows]="5"
+            [formControl]="purposeController"
+            autocomplete="off"></textarea>
+        </nz-input-group>
+        <ng-template #purposeError let-control>
+          <ng-container *ngIf="control.hasError('required')" i18n="Purpose is required @@purposeRequiredHint">
+            Purpose is <strong>required</strong>
+          </ng-container>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+
+    <nz-form-item>
+      <nz-form-label nzRequired [nzSpan]="10" i18n="Consent is @@consentIs">
+        Consent is
+      </nz-form-label>
+      <nz-form-control [nzSpan]="14">
+        <nz-radio-group [formControl]="requiresConsentController">
+          <label nz-radio nzValue="1" i18n="required @@required">
+            required
+          </label><br />
+          <label nz-radio nzValue="0" i18n="not required @@notRequired">
+            not required
+          </label>
+        </nz-radio-group>
+      </nz-form-control>
+    </nz-form-item>
+
+  </form>
+  <nz-checkbox-group [(ngModel)]="selectedVerbs">
+
+  </nz-checkbox-group>
+</div>
+<div *nzModalFooter>
+  <button nz-button (click)="handleClose()" i18n="@@cancel">Cancel</button>
+  <button nz-button i18n="Submit @@submit" (click)="onSubmit()"> Submit</button>
+</div>
diff --git a/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.scss b/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.ts b/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.ts
new file mode 100644
index 0000000000000000000000000000000000000000..12f31aba413c28eb3f4bfba9912cf8d6edf222bd
--- /dev/null
+++ b/src/frontend/src/app/dialogs/create-verb-group-dialog/create-verb-group-dialog.ts
@@ -0,0 +1,102 @@
+import { Component, OnInit, Input, SimpleChanges, OnChanges } from '@angular/core'
+import {
+  AbstractControl, FormArray,
+  FormBuilder,
+  FormControl,
+  FormGroup,
+  ValidationErrors,
+  ValidatorFn,
+  Validators
+} from '@angular/forms'
+import * as moment from 'moment'
+import { ApiService } from 'src/app/services/api.service'
+import { NzModalRef } from 'ng-zorro-antd/modal'
+import { faTimesCircle, faCheckCircle } from '@fortawesome/free-regular-svg-icons'
+import { XApiVerbSchema } from '../../consent-management/consentDeclaration'
+
+interface SelectedVerb {
+  label: string
+  value: string
+  checked: boolean
+}
+
+@Component({
+  selector: 'app-create-verb-group-dialog',
+  templateUrl: './create-verb-group-dialog.html',
+  styleUrls: ['./create-verb-group-dialog.scss']
+})
+export class CreateVerbGroupDialog implements OnChanges, OnInit {
+  @Input() headerText: string = '';
+  @Input() verbs: XApiVerbSchema[] = [];
+
+  form: FormGroup
+
+  protected selectedVerbs: SelectedVerb[] = [];
+
+  constructor(
+    public _dialogRef: NzModalRef<CreateVerbGroupDialog>,
+    private _formBuilder: FormBuilder,
+    private _apiService: ApiService
+  ) {
+    this.form = this._formBuilder.group({
+      label: [
+        '',
+        Validators.required
+      ],
+      description: ['', Validators.required],
+      purposeOfCollection: ['', Validators.required],
+      requiresConsent: ["1", Validators.required],
+    })
+  }
+
+  get labelController(): FormControl {
+    return this.form.get('label') as FormControl
+  }
+
+  get descriptionController(): FormControl {
+    return this.form.get('description') as FormControl
+  }
+
+  get purposeController(): FormControl {
+    return this.form.get('purposeOfCollection') as FormControl
+  }
+
+  get requiresConsentController(): FormControl {
+    return this.form.get('requiresConsent') as FormControl
+  }
+
+  updateSelectedVerbs(): void {
+    this.selectedVerbs = this.verbs.map(verb => {
+      return {
+        label: verb.label,
+        value: verb.id,
+        checked: false
+      }
+    })
+  }
+  ngOnChanges(changes: SimpleChanges) {
+    if (changes['verbs'] && this.verbs) {
+      this.updateSelectedVerbs();
+    }
+  }
+
+  ngOnInit(): void {
+    this.updateSelectedVerbs();
+  }
+
+  onSubmit(): void {
+    if (this.form.valid)
+      this._dialogRef.close({
+        label: this.form.value.label,
+        description: this.form.value.description,
+        purposeOfCollection: this.form.value.purposeOfCollection,
+        requiresConsent: this.form.value.requiresConsent == "1",
+        selectedVerbs: this.selectedVerbs.filter(verb => verb.checked).map(verb => {return {id: verb.value}}),
+      })
+  }
+
+  handleClose(): void {
+    this._dialogRef.destroy()
+  }
+
+}
diff --git a/src/frontend/src/app/home/home.component.html b/src/frontend/src/app/home/home.component.html
index b75d9c9caee859189faa50ba21ca194b8c9c7798..83dae37553e78fd9e9f32be6321ce129d15d28d0 100644
--- a/src/frontend/src/app/home/home.component.html
+++ b/src/frontend/src/app/home/home.component.html
@@ -16,6 +16,6 @@
     </div>
     <div>
         <h1>Dashboard</h1>
-        <p>Present results from Analytics Engine in graphically rich dashboards that easily integrate with existing learning platforms.</p>
+        <p>Present results from Analytics Engines in graphically rich dashboards that easily integrate with existing learning platforms.</p>
     </div>
 </div>
diff --git a/src/frontend/src/app/home/home.component.scss b/src/frontend/src/app/home/home.component.scss
index 0e4c73d68c4d88e7cd3accbbfb058d482da2765e..f887742fbd7a2feb3038336ea9c6c71d1954dae8 100644
--- a/src/frontend/src/app/home/home.component.scss
+++ b/src/frontend/src/app/home/home.component.scss
@@ -1,7 +1,7 @@
 .home-wrapper {
     position: relative;
     height: 420px;
-    background: #F0ECF0;
+    background: #f7f7f7;
 
     .home-message {
         width: 90%;
@@ -29,6 +29,6 @@
     gap: 20px;
     flex-wrap: wrap;
     & > div {
-        width: 400px;
+        width: 300px;
     }
 }
diff --git a/src/frontend/src/app/merge-data/merge-data.component.html b/src/frontend/src/app/merge-data/merge-data.component.html
index e2daddcbba8adb50e3c6f8e1d83d465dcd554a32..ec5c74edf44317087715ee447c2d8e0f95e212cd 100644
--- a/src/frontend/src/app/merge-data/merge-data.component.html
+++ b/src/frontend/src/app/merge-data/merge-data.component.html
@@ -1,29 +1,25 @@
-<div class="content">
-  <nz-card style='margin: 8px;'>
-    <nz-card-meta [nzTitle]="cardTitle" [nzDescription]="cardDescription"></nz-card-meta>
-    <ng-template #cardTitle i18n="Merge Data | Header entry @@mergeDataHeader">
-      Merge Data
-    </ng-template>
-    <ng-template #cardDescription i18n="@@mergeDataDescription">
-      This tool enables you to associate data which has been anonymized by use of a TAN to your account. All entries which match the given TAN and provider will be assigned.
-    </ng-template>
-    <p></p>
-    <form nz-form nzLayout="vertical" [formGroup]="mergeForm" (ngSubmit)="submit()">
-      <nz-form-item>
-        <nz-form-label>Provider</nz-form-label>
-        <nz-form-control>
-          <nz-select formControlName="provider">
-            <nz-option *ngFor="let provider of providers" [nzValue]="provider.id" [nzLabel]="provider.name"></nz-option>
-          </nz-select>
-        </nz-form-control>
-      </nz-form-item>
-      <nz-form-item>
-        <nz-form-label>TAN</nz-form-label>
-        <nz-form-control>
-          <input nz-input formControlName="tan" />
-        </nz-form-control>
-      </nz-form-item>
-      <button type="submit" nz-button i18n="Merge Data | Header entry @@mergeDataHeader">Merge Data</button>
-    </form>
-  </nz-card>
-</div>
+<h1 i18n="Merge Data | Header entry @@mergeDataHeader">
+  Merge Data
+</h1>
+<p i18n="@@mergeDataDescription">
+  This tool enables you to associate data which has been anonymized by use of a TAN to your account. All entries which match the given TAN and provider will be assigned.
+</p>
+
+<form nz-form nzLayout="vertical" [formGroup]="mergeForm" (ngSubmit)="submit()">
+  <nz-form-item>
+    <nz-form-label>Provider</nz-form-label>
+    <nz-form-control>
+      <nz-select formControlName="provider">
+        <nz-option *ngFor="let provider of providers" [nzValue]="provider.id" [nzLabel]="provider.name"></nz-option>
+      </nz-select>
+    </nz-form-control>
+  </nz-form-item>
+  <nz-form-item>
+    <nz-form-label>TAN</nz-form-label>
+    <nz-form-control>
+      <input nz-input formControlName="tan" />
+    </nz-form-control>
+  </nz-form-item>
+  <button type="submit" nz-button i18n="Merge Data | Header entry @@mergeDataHeader">Merge Data</button>
+</form>
+
diff --git a/src/frontend/src/app/navigation/header/header.component.html b/src/frontend/src/app/navigation/header/header.component.html
index 654112ca2b11776164333ec57f2eedfe800e2515..c83a336b0cd8c2e764b582674a2ede011665b74e 100644
--- a/src/frontend/src/app/navigation/header/header.component.html
+++ b/src/frontend/src/app/navigation/header/header.component.html
@@ -64,6 +64,15 @@
     >
       Analytics Tokens
     </li>
+
+    <li nz-menu-item
+        routerLink="/control-center"
+        *ngIf="loggedIn?.isProvider"
+        i18n="Control Center | Header Entry @@controlCenter"
+        [nzMatchRouter]="true"
+    >
+      Control Center
+    </li>
   </ul>
 
   <div class="account">
diff --git a/src/frontend/src/app/page-not-found/page-not-found.component.html b/src/frontend/src/app/page-not-found/page-not-found.component.html
index 734ba63bfcbd3fd1b44ad651c73ad42db826a4ea..2098f5fa703403bed34f1fd680c21cb07ec96c2a 100644
--- a/src/frontend/src/app/page-not-found/page-not-found.component.html
+++ b/src/frontend/src/app/page-not-found/page-not-found.component.html
@@ -1,6 +1,6 @@
 <div class="not-found-wrapper">
   <div class="not-found-content">
     <img ngSrc="assets/not_found.svg" alt="Not found" height="308" width="393">
-    <div class="message">Oops! We can't find that page.</div>
+    <div class="message" i18n="We can't find that page. | 404 message @@404message">Oops! We can't find that page.</div>
   </div>
 </div>
diff --git a/src/frontend/src/app/services/api.service.ts b/src/frontend/src/app/services/api.service.ts
index 79b00b888c54e6594fe73cf5d411d97e4f52d3f2..53be7ff3f643c989f08ca84ba48e40c17cf10c08 100644
--- a/src/frontend/src/app/services/api.service.ts
+++ b/src/frontend/src/app/services/api.service.ts
@@ -3,7 +3,7 @@ import {
   ProviderId,
   ProviderSchema,
   UserConsent,
-  UserConsentVerbs
+  UserConsentVerbs, XApiVerbGroupSchema
 } from '../consent-management/consentDeclaration'
 import { HttpClient } from '@angular/common/http'
 import { Observable } from 'rxjs'
@@ -32,6 +32,7 @@ export interface Provider {
   description: string
   createdAt: string
   versions: ProviderSchema[]
+  groups?: XApiVerbGroupSchema[]
 }
 
 export interface ApplicationTokens {
@@ -74,11 +75,6 @@ export interface ProviderAnalyticsToken {
   analytics_tokens: AnalyticsToken[]
 }
 
-export interface Verb {
-  id: string
-  label: string
-  selected: boolean
-}
 
 interface VisualizationToken {
   id: number
@@ -101,6 +97,33 @@ export interface UserConsentStatus {
   >
 }
 
+export interface AuthPermission {
+  id: number
+  name: string
+  codename: string
+}
+export interface AuthGroup {
+  id: number
+  name: string
+  permissions: AuthPermission[]
+}
+
+export interface AuthUser {
+  id: number
+  last_login?: Date
+  is_superuser: boolean
+  first_name: string
+  last_name: string
+  is_staff: boolean
+  is_active: boolean
+  date_joined: Date
+  email: string
+  paused_data_recording: boolean
+  general_privacy_policy: boolean
+  groups: AuthGroup[]
+  user_permissions: AuthPermission[]
+}
+
 @Injectable({
   providedIn: 'root'
 })
@@ -112,6 +135,18 @@ export class ApiService {
     return this.http.get<Provider[]>(`${environment.apiUrl}/api/v1/consents/provider`)
   }
 
+  getUsers(): Observable<AuthUser[]> {
+    return this.http.get<AuthUser[]>(`${environment.apiUrl}/api/v1/auth/users`)
+  }
+
+  getGroups(): Observable<AuthGroup[]> {
+    return this.http.get<AuthGroup[]>(`${environment.apiUrl}/api/v1/auth/groups`)
+  }
+
+  getPermissions(): Observable<AuthPermission[]> {
+    return this.http.get<AuthPermission[]>(`${environment.apiUrl}/api/v1/auth/permissions`)
+  }
+
   getUserConsent(providerId: number): Observable<UserConsentResponse> {
     return this.http.get<UserConsentResponse>(
       `${environment.apiUrl}/api/v1/consents/user/${providerId}`
@@ -208,6 +243,20 @@ export class ApiService {
     )
   }
 
+  createOrUpdateVerbGroup(provider_id: number, id: string, label: string, description: string,
+                  purposeOfCollection: string, requiresConsent: boolean,
+                  selectedVerbs: string[]) {
+    console.log(`${environment.apiUrl}/api/v1/consents/provider/${provider_id}/create-verb-group`)
+    return this.http.post<{message: string, group?: XApiVerbGroupSchema}>(`${environment.apiUrl}/api/v1/consents/provider/${provider_id}/create-verb-group`, {
+      id: id,
+      label: label,
+      description: description,
+      purposeOfCollection: purposeOfCollection,
+      requiresConsent: requiresConsent,
+      verbs: selectedVerbs
+    })
+  }
+
   saveAccessRelation(target: number, ids: number[]): Observable<AnalyticsToken> {
     return this.http.post<AnalyticsToken>(
       `${environment.apiUrl}/api/v1/provider/analytics-tokens/sync-engine-access`,
diff --git a/src/frontend/src/environments/environment.prod.ts b/src/frontend/src/environments/environment.prod.ts
index 8bb15f8f84bfd5a8504c76642ac412a83247ea17..4309a07e8b79b1cbb84e4d00b9f55002394bd783 100644
--- a/src/frontend/src/environments/environment.prod.ts
+++ b/src/frontend/src/environments/environment.prod.ts
@@ -4,7 +4,7 @@ export const environment = {
   pageVisibility: {
     analyses: true,
     consent_history: true,
-    consent_management: true,
+    consent_management: false,
     merge_data: true,
     data_disclosure: true,
   }
diff --git a/src/frontend/src/locale/messages.de.xlf b/src/frontend/src/locale/messages.de.xlf
index 7c46adad1cee6a502a4222feb1f98786ef9fd11e..5f9ce6ec0f0a6258ed482bf7129da415a6c1bf30 100644
--- a/src/frontend/src/locale/messages.de.xlf
+++ b/src/frontend/src/locale/messages.de.xlf
@@ -14,7 +14,7 @@
       </trans-unit>
       <trans-unit id="analysisIsActiveNote" datatype="html">
         <source>This analysis is already active since you consented to all necessary data collections.</source>
-        <target>Diese Analyse ist bereits aktiv, da Sie der notwendigen Datenerfassung bereits zugestimmt haben.</target>
+        <target>Diese Analyse ist bereits aktiv, da du der notwendigen Datenerfassung bereits zugestimmt hast.</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/analytics-engine/analysis-card/verbs-modal/verbs-modal.component.html</context>
           <context context-type="linenumber">14</context>
@@ -30,7 +30,7 @@
       </trans-unit>
       <trans-unit id="analysisNotActiveAlert" datatype="html">
         <source> This analysis is not active, since you did not consent to the necessary data collections. You have to consent to all necessary data collections to activate this analysis. You can click the following button to consent to all data collections. You can navigate to the consent management to revert this. </source>
-        <target> Diese Analyse ist nicht aktiv, da Sie der notwendigen Datenerfassung nicht zugestimmt haben. Sie müssen jeglicher notwendiger Datenerfassung zustimmen, um diese Analyse zu aktivieren. Sie können den folgenden Button anklicken, um der Erfassung zuzustimmen. Sie können dies in der Zustimmungsverwaltung widerrufen. </target>
+        <target> Diese Analyse ist nicht aktiv, da du der notwendigen Datenerfassung nicht zugestimmt hast. Du musst jeglicher notwendiger Datenerfassung zustimmen, um diese Analyse zu aktivieren. Du kannst den folgenden Button anklicken, um der Erfassung zuzustimmen. Du kannst dies in der Zustimmungsverwaltung widerrufen. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/analytics-engine/analysis-card/verbs-modal/verbs-modal.component.html</context>
           <context context-type="linenumber">26,28</context>
@@ -187,7 +187,7 @@
       </trans-unit>
       <trans-unit id="accessibleEnginesHeader" datatype="html">
         <source> Accessible Engines </source>
-        <target> Engines, auf die Sie Zugriff haben </target>
+        <target> Engines, auf die du Zugriff hast </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/analytics-tokens/analytics-tokens.component.html</context>
           <context context-type="linenumber">129,131</context>
@@ -235,7 +235,7 @@
       </trans-unit>
       <trans-unit id="deleteSelectedToken" datatype="html">
         <source>Delete selected token</source>
-        <target>Ausgewählter Token gelöscht</target>
+        <target>Ausgewählten Token löschen</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/analytics-tokens/analytics-tokens.component.ts</context>
           <context context-type="linenumber">114</context>
@@ -311,7 +311,7 @@
       </trans-unit>
       <trans-unit id="mergeDataDescription" datatype="html">
         <source>This tool enables you to associate data which has been anonymized by use of a TAN to your account. All entries which match the given TAN and provider will be assigned.</source>
-        <target>Dieses Werkzeug ermöglicht es, Daten, welche mittels einer TAN anonymisiert sind, zum aktuellen Nutzer zuzuordnen. Alle Einträge, welche auf die angegebene TAN und den angegebenen Provider zutreffen, werden zugeordnet.</target>
+        <target>Dieses Werkzeug ermöglicht es, Daten, welche mittels einer TAN anonymisiert sind, zum aktuellen Nutzer zuzuordnen. Alle Einträge, welche auf die angegebene TAN und das angegebene Quellsystem zutreffen, werden zugeordnet.</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/merge-data/merge-data.component.html</context>
           <context context-type="linenumber">7</context>
@@ -338,7 +338,7 @@
       </trans-unit>
       <trans-unit id="pausedDataRecordingDescription" datatype="html">
         <source> Would you like to continue sharing selected data with Polaris?</source>
-        <target> Möchten Sie die ausgewählten Daten weiterhin mit Polaris teilen?</target>
+        <target> Möchtest du die ausgewählten Daten weiterhin mit Polaris teilen?</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/pause-data-recording-dialog.html</context>
           <context context-type="linenumber">14,16</context>
@@ -348,7 +348,7 @@
       </trans-unit>
       <trans-unit id="continueDataRecordingDescription" datatype="html">
         <source>Would you like to pause sharing selected data with Polaris?</source>
-        <target>Möchten Sie die ausgewählte Datenerfassung durch Polaris pausieren?</target>
+        <target>Möchtest du die ausgewählte Datenerfassung durch Polaris pausieren?</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/pause-data-recording-dialog.html</context>
           <context context-type="linenumber">19,24</context>
@@ -409,7 +409,7 @@
       </trans-unit>
       <trans-unit id="pauseVerbOrObjectConfirmation" datatype="html">
         <source>Do you want the action to be executed?</source>
-        <target>Möchten Sie, dass die Aktion ausgeführt wird?</target>
+        <target>Möchtest du, dass die Aktion ausgeführt wird?</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/provider-settings/pause-verb-object-warning-dialog.html</context>
           <context context-type="linenumber">2</context>
@@ -521,6 +521,10 @@
         <source>Provider Schemas</source>
         <target>Anbieterschemas</target>
       </trans-unit>
+      <trans-unit id="latestTag" datatype="html">
+        <source>latest</source>
+        <target>neustes</target>
+      </trans-unit>
       <trans-unit id="toggleDefinition" datatype="html">
         <source>Toggle Definition</source>
         <target>Definition umschalten</target>
@@ -559,7 +563,7 @@
       </trans-unit>
       <trans-unit id="selectProviderSchema" datatype="html">
         <source>Please select a JSON file.</source>
-        <target>Bitte geben Sie eine JSON-Datei an.</target>
+        <target>Bitte gib eine JSON-Datei an.</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/provider/provider.component.ts</context>
           <context context-type="linenumber">24</context>
@@ -786,7 +790,7 @@
       </trans-unit>
       <trans-unit id="firstUserConsentHint" datatype="html">
         <source> You have not yet provided any information on data processing within the Polaris project. In the following steps, you can make an individual decision for each connected platform. </source>
-        <target> Sie haben noch keine Angaben zur Datenverarbeitung im Rahmen des Polaris-Projekts gemacht. In den folgenden Schritten können Sie eine individuelle Entscheidung für jede angeschlossene Plattform treffen. </target>
+        <target> Du hast noch keine Angaben zur Datenverarbeitung im Rahmen des Polaris-Projekts gemacht. In den folgenden Schritten kannst du eine individuelle Entscheidung für jede angeschlossene Plattform treffen. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">14,19</context>
@@ -794,8 +798,8 @@
         <note priority="1" from="description">First User Consent </note>
       </trans-unit>
       <trans-unit id="newProviderSchemaHint" datatype="html">
-        <source> The provider uploaded a newer consent declaration. Please reviewed the changes. </source>
-        <target> Der Anbieter hat eine neuere Einverständniserklärung hochgeladen. Bitte überprüfen Sie die Änderungen. </target>
+        <source> The provider uploaded a newer schema. Please review the changes. </source>
+        <target> Der Anbieter hat ein neueres Schema hochgeladen. Bitte überprüfe die Änderungen. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">23,25</context>
@@ -932,7 +936,7 @@
       </trans-unit>
       <trans-unit id="pausedDataRecordingDescription" datatype="html">
         <source> Would you like to continue sharing selected data with Polaris? </source>
-        <target> Möchten Sie das Teilen ausgewählter Daten mit Polaris fortsetzen? </target>
+        <target> Möchtest du das Teilen ausgewählter Daten mit Polaris fortsetzen? </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/pause-data-recording-dialog.html</context>
           <context context-type="linenumber">14,16</context>
@@ -979,7 +983,7 @@
         <source> Would you like to pause sharing selected data with Polaris? <x id="START_BOLD_TEXT" ctype="x-b" equiv-text="&lt;b
         &gt;"/>A delete job is set up for data deletion, which is only executed after a fixed time window.<x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="&lt;/b
     &gt;"/></source>
-      <target> Möchten Sie die Erfassung ihrer Daten durch Polaris pausieren? <x id="START_BOLD_TEXT" ctype="x-b" equiv-text="&lt;b
+      <target> Möchtest du die Erfassung deiner Daten durch Polaris pausieren? <x id="START_BOLD_TEXT" ctype="x-b" equiv-text="&lt;b
         &gt;"/> Für die Datenlöschung wird ein Löschautrag eingerichtet, welcher erst nach einem festen Zeitfenster ausgeführt wird. <x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="&lt;/b
     &gt;"/> </target>
         <context-group purpose="location">
@@ -1052,7 +1056,7 @@
       </trans-unit>
       <trans-unit id="selectProviderSchema" datatype="html">
         <source>Please select a JSON file.</source>
-        <target>Bitte wählen Sie eine JSON-Datei aus.</target>
+        <target>Bitte wähle eine JSON-Datei aus.</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/provider/provider.component.ts</context>
           <context context-type="linenumber">19</context>
@@ -1200,7 +1204,7 @@
         <source>Delete data disclosure</source>
         <target>Datenauskunft löschen</target>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">69</context>
         </context-group>
       </trans-unit>
@@ -1208,7 +1212,7 @@
         <source>Data disclosure deleted</source>
         <target>Datenauskunft wurde gelöscht</target>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">73</context>
         </context-group>
       </trans-unit>
@@ -1417,7 +1421,7 @@
       </trans-unit>
       <trans-unit id="firstConsentDeclaration" datatype="html">
         <source> You have not yet given consent for provider <x id="INTERPOLATION" equiv-text="{{ userData.provider.name }}"/>. </source>
-        <target> Sie haben bisher noch keine Zustimmung für den Provider <x id="INTERPOLATION" equiv-text="{{ userData.provider.name }}"/> gegeben. </target>
+        <target> Du hast bisher noch keine Zustimmung für den Provider <x id="INTERPOLATION" equiv-text="{{ userData.provider.name }}"/> gegeben. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">46,48</context>
@@ -1426,7 +1430,7 @@
       </trans-unit>
       <trans-unit id="consentDeclarationUpToDate" datatype="html">
         <source> You do not need to make any changes to your existing consent form, as the provider has not made any changes in the meantime. </source>
-        <target> Sie müssen keine Änderungen an deiner bestehenden Einwilligungserklärung treffen, da der Provider in der Zwischenzeit keine Änderungen vorgenommen hat. </target>
+        <target> Du musst keine Änderungen an deiner bestehenden Einwilligungserklärung treffen, da der Provider in der Zwischenzeit keine Änderungen vorgenommen hat. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">41,44</context>
@@ -1435,7 +1439,7 @@
       </trans-unit>
       <trans-unit id="providerSchemaChanged" datatype="html">
         <source> Since your last visit, the provider has made changes. For each option, you will now see your previous consent on the far left, and on the right, separated by an arrow, your current consent, which you can of course change. </source>
-        <target> Der Anbieter hat seit Ihrem letzten Besuch Änderungen vorgenommen. Für jede Option werden Sie Ihre vorherige Zustimmung auf der linken Seite sehen, während auf der rechten Seite, durch einen Pfeil getrennt, Ihre derzeitige Zustimmung dargestellt wird, mit der Möglichkeit, diese zu ändern. </target>
+        <target> Der Anbieter hat seit deinem letzten Besuch Änderungen vorgenommen. Für jede Option wirst du deine vorherige Zustimmung auf der linken Seite sehen, während auf der rechten Seite, durch einen Pfeil getrennt, deine derzeitige Zustimmung dargestellt wird, mit der Möglichkeit, diese zu ändern. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">20,25</context>
@@ -1444,7 +1448,7 @@
       </trans-unit>
       <trans-unit id="consentDeclarationUpToDate" datatype="html">
         <source> You do not need to make any changes to your existing consent form, as the provider has not made any changes in the meantime. </source>
-        <target> Sie müssen keine Änderungen an Ihren bestehenden Zustimmungen vornehmen, da der Anbieter in der Zwischenzeit keine Änderungen vorgenommen hat. </target>
+        <target> Du musst keine Änderungen an deinen bestehenden Zustimmungen vornehmen, da der Anbieter in der Zwischenzeit keine Änderungen vorgenommen hat. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">41,44</context>
@@ -1453,7 +1457,7 @@
       </trans-unit>
       <trans-unit id="firstConsentDeclaration" datatype="html">
         <source> You have not yet given consent for provider <x id="INTERPOLATION" equiv-text="{{ userData.provider.name }}"/>. </source>
-        <target> Sie haben bisher keine Zustimmung für den Anbieter <x id="INTERPOLATION" equiv-text="{{ userData.provider.name }}"/> erteilt. </target>
+        <target> Du hast bisher keine Zustimmung für den Anbieter <x id="INTERPOLATION" equiv-text="{{ userData.provider.name }}"/> erteilt. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">49,51</context>
@@ -1462,7 +1466,7 @@
       </trans-unit>
       <trans-unit id="changedProviderSchemaSummaryText" datatype="html">
         <source> The provider has made changes since your last consent declaration which requires renewed consent from you. </source>
-        <target> Der Anbieter hat seit Ihrer letzten Einverständniserklärung Änderungen vorgenommen, welche eine erneute Zustimmung Ihrerseits erfordern. </target>
+        <target> Der Anbieter hat seit deiner letzten Einwilligung Änderungen vorgenommen, welche eine erneute Zustimmung von dir erfordern. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">74,77</context>
@@ -1473,7 +1477,7 @@
       </trans-unit>
       <trans-unit id="firstProviderConsentSummaryText" datatype="html">
         <source> You have not previously given consent for this provider, this is your first consent declaration. </source>
-        <target> Sie haben für diesen Anbieter bisher keine Zustimmung erteilt, dies ist Ihre erste Einverständniserklärung. </target>
+        <target> Du hast für diesen Anbieter bisher keine Zustimmung erteilt, dies ist deine erste Einwilligung. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/consent-management/wizard/wizard.component.html</context>
           <context context-type="linenumber">96,99</context>
@@ -1564,7 +1568,7 @@
       </trans-unit>
       <trans-unit id="dataDisclosureProcessInfo" datatype="html">
         <source> You can query all data collected about you at this point. The collection of the data takes place on a time-delayed basis. You will be informed about the completion via e-mail. Afterwards, the collected data will be available for download for a specified period of time and then will then be deleted automatically. </source>
-        <target> Sie können alle Daten, die bisher über Sie erfasst wurden, abfragen. Die Sammlung der Daten erfolgt zeitverzögert. Sie werden per E-Mail informiert, sobald der Vorgang abgeschlossen ist. Anschließend werden die gesammelten Daten für einen festen Zeitraum zum Herunterladen zur Verfügung stehen und danach automatisch gelöscht. </target>
+        <target> Du kannst alle Daten, die bisher über dich erfasst wurden, abfragen. Die Sammlung der Daten erfolgt zeitverzögert. Du wirst per E-Mail informiert, sobald der Vorgang abgeschlossen ist. Anschließend werden die gesammelten Daten für einen festen Zeitraum zum Herunterladen zur Verfügung stehen und danach automatisch gelöscht. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.html</context>
           <context context-type="linenumber">4,9</context>
@@ -1616,6 +1620,10 @@
         <note priority="1" from="description"> Table column header </note>
         <note priority="1" from="meaning">Created At </note>
       </trans-unit>
+      <trans-unit id="expires" datatype="html">
+        <source>Expires</source>
+        <target>Läuft ab</target>
+      </trans-unit>
       <trans-unit id="availableUntil" datatype="html">
         <source> Available until </source>
         <target> Verfügbar bis </target>
@@ -1638,7 +1646,7 @@
         <source>Request sent</source>
         <target>Anfrage verschickt</target>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
@@ -1646,7 +1654,7 @@
         <source>Delete data disclosure</source>
         <target>Datenauskunft löschen</target>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">69</context>
         </context-group>
       </trans-unit>
@@ -1654,7 +1662,7 @@
         <source>Data disclosure deleted</source>
         <target>Datensauskunft gelöscht</target>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">73</context>
         </context-group>
       </trans-unit>
@@ -1766,7 +1774,7 @@
       </trans-unit>
       <trans-unit id="deleteDialogDescription" datatype="html">
         <source> Please confirm the action by entering <x id="START_BOLD_TEXT" ctype="x-b" equiv-text="&lt;b&gt;"/><x id="INTERPOLATION" equiv-text="{{confirmationText}}"/><x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="&lt;/b&gt;"/>. </source>
-        <target> Bitte bestätigen Sie die Aktion, indem Sie <x id="START_BOLD_TEXT" ctype="x-b" equiv-text="&lt;b&gt;"/><x id="INTERPOLATION" equiv-text="{{confirmationText}}"/><x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="&lt;/b&gt;"/> eingeben. </target>
+        <target> Bitte bestätige die Aktion, indem du <x id="START_BOLD_TEXT" ctype="x-b" equiv-text="&lt;b&gt;"/><x id="INTERPOLATION" equiv-text="{{confirmationText}}"/><x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="&lt;/b&gt;"/> eingibst. </target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/dialogs/delete-dialog/delete-dialog.html</context>
           <context context-type="linenumber">4,6</context>
@@ -1913,7 +1921,7 @@
       </trans-unit>
       <trans-unit id="consentManagmentHeader" datatype="html">
         <source>Consent Management</source>
-        <target>Einverständnis verwalten</target>
+        <target>Einwilligungsmanagement</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/navigation/header/header.component.html</context>
           <context context-type="linenumber">21</context>
@@ -1922,8 +1930,8 @@
         <note priority="1" from="meaning">Consent Management </note>
       </trans-unit>
       <trans-unit id="consentHistoryHeader" datatype="html">
-        <source>Consent History</source>
-        <target>Einverständnishistorie</target>
+        <source>Consent Management</source>
+        <target>Einwilligungsmanagement</target>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/navigation/header/header.component.html</context>
           <context context-type="linenumber">21</context>
@@ -1951,6 +1959,12 @@
         <note priority="1" from="description"> Header Entry </note>
         <note priority="1" from="meaning">Analytics Tokens </note>
       </trans-unit>
+      <trans-unit id="controlCenter" datatype="html">
+        <source>Control Center</source>
+        <target>Systemsteuerung</target>
+        <note priority="1" from="description"> Header Entry </note>
+        <note priority="1" from="meaning">Control Center</note>
+      </trans-unit>
       <trans-unit id="profileProfileDropDown" datatype="html">
         <source>Profile</source>
         <target>Profil</target>
@@ -2018,6 +2032,67 @@
           <context context-type="linenumber">41</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="uploadButton" datatype="html">
+        <source>Select new image</source>
+        <target>Neues Bild auswählen</target>
+      </trans-unit>
+      <trans-unit id="uploadButtonDoUpload" datatype="html">
+        <source>Upload selected image</source>
+        <target>Gewähltes Bild hochladen</target>
+      </trans-unit>
+      <trans-unit id="toastMessageMergeError" datatype="html">
+        <source>Error</source>
+        <target>Fehler</target>
+      </trans-unit>
+      <trans-unit id="404message" datatype="html">
+        <source>Oops! We can't find that page.</source>
+        <target>Diese Seite wurde nicht gefunden.</target>
+      </trans-unit>
+      <trans-unit id="usersAndRoles" datatype="html">
+        <source>Users and Roles</source>
+        <target>Nutzende und Rollen</target>
+      </trans-unit>
+      <trans-unit id="dataSources" datatype="html">
+        <source>Data Sources</source>
+        <target>Quellsysteme</target>
+      </trans-unit>
+      <trans-unit id="groups" datatype="html">
+        <source>Data Packets</source>
+        <target>Datenbündel</target>
+      </trans-unit>
+      <trans-unit id="containedVerbs" datatype="html">
+        <source>Contained Verbs:</source>
+        <target>Enthaltene Verben:</target>
+      </trans-unit>
+      <trans-unit id="createGroup" datatype="html">
+        <source>Create New Data Packet</source>
+        <target>Neues Datenbündel erstellen</target>
+      </trans-unit>
+      <trans-unit id="descriptionRequiredHint" datatype="html">
+        <source> Description is <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>required<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/></source>
+        <target> Beschreibung wird <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>benötigt<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/></target>
+      </trans-unit>
+      <trans-unit id="purposeOfCollectionTitle" datatype="html">
+        <source>Purpose of collection</source>
+        <target>Verwendungszweck:</target>
+      </trans-unit>
+      <trans-unit id="purposeRequiredHint" datatype="html">
+        <source> Purpose is <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>required<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/></source>
+        <target> Verwendungszweck wird <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>benötigt<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/></target>
+      </trans-unit>
+      <trans-unit id="consentIs" datatype="html">
+        <source>Zustimmung wird</source>
+      </trans-unit>
+      <trans-unit id="required" datatype="html">
+        <source>benötigt</source>
+      </trans-unit>
+      <trans-unit id="notRequired" datatype="html">
+        <source>nicht benötigt</source>
+      </trans-unit>
+      <trans-unit id="includedVerbs" datatype="html">
+        <source>Included verbs</source>
+        <target>Enthaltene Verben</target>
+      </trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/frontend/src/locale/messages.xlf b/src/frontend/src/locale/messages.xlf
index ee2cae765be2608c4af0683fb86eed7c3faa0f1a..35ccfcc1816b24b895232371ca14241b1e7b2dc5 100644
--- a/src/frontend/src/locale/messages.xlf
+++ b/src/frontend/src/locale/messages.xlf
@@ -463,6 +463,9 @@
       <trans-unit id="providerSchemas" datatype="html">
         <source>Provider Schemas</source>
       </trans-unit>
+      <trans-unit id="latestTag" datatype="html">
+        <source>latest</source>
+      </trans-unit>
       <trans-unit id="toggleDefinition" datatype="html">
         <source>Toggle Definition</source>
         <context-group purpose="location">
@@ -853,6 +856,9 @@
         <note priority="1" from="description"> Table column header </note>
         <note priority="1" from="meaning">Created At </note>
       </trans-unit>
+      <trans-unit id="expires" datatype="html">
+        <source>Expires</source>
+      </trans-unit>
       <trans-unit id="availableUntil" datatype="html">
         <source> Available until </source>
         <context-group purpose="location">
@@ -872,21 +878,21 @@
       <trans-unit id="successToastMessage" datatype="html">
         <source>Request sent</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
       <trans-unit id="deleteDataDisclosure" datatype="html">
         <source>Delete data disclosure</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">69</context>
         </context-group>
       </trans-unit>
       <trans-unit id="deleteDataDisclosureSucces" datatype="html">
         <source>Data disclosure deleted</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/data-disclosure/data-disclosure.component.ts</context>
+          <context context-type="sourcefile">src/app/data-disclosure/control-center.component.ts</context>
           <context context-type="linenumber">73</context>
         </context-group>
       </trans-unit>
@@ -1126,7 +1132,7 @@
         <note priority="1" from="meaning">Consent Management </note>
       </trans-unit>
       <trans-unit id="consentHistoryHeader" datatype="html">
-        <source>Consent History</source>
+        <source>Consent Management</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/navigation/header/header.component.html</context>
           <context context-type="linenumber">21</context>
@@ -1168,6 +1174,11 @@
         <note priority="1" from="description"> Header Entry </note>
         <note priority="1" from="meaning">Analytics Tokens </note>
       </trans-unit>
+      <trans-unit id="controlCenter" datatype="html">
+        <source>Control Center</source>
+        <note priority="1" from="description"> Header Entry </note>
+        <note priority="1" from="meaning">Control Center</note>
+      </trans-unit>
       <trans-unit id="profileProfileDropDown" datatype="html">
         <source>Profile</source>
         <context-group purpose="location">
@@ -1228,6 +1239,54 @@
           <context context-type="linenumber">41</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="uploadButton" datatype="html">
+        <source>Select new image</source>
+      </trans-unit>
+      <trans-unit id="uploadButtonDoUpload" datatype="html">
+        <source>Upload selected image</source>
+      </trans-unit>
+      <trans-unit id="toastMessageMergeError" datatype="html">
+        <source>Error</source>
+      </trans-unit>
+      <trans-unit id="404message" datatype="html">
+        <source>Oops! We can't find that page.</source>
+      </trans-unit>
+      <trans-unit id="usersAndRoles" datatype="html">
+        <source>Users and Roles</source>
+      </trans-unit>
+      <trans-unit id="dataSources" datatype="html">
+        <source>Data Sources</source>
+      </trans-unit>
+      <trans-unit id="groups" datatype="html">
+        <source>Data Packets</source>
+      </trans-unit>
+      <trans-unit id="containedVerbs" datatype="html">
+        <source>Contained Verbs:</source>
+      </trans-unit>
+      <trans-unit id="createGroup" datatype="html">
+        <source>Create Data Packet</source>
+      </trans-unit>
+      <trans-unit id="descriptionRequiredHint" datatype="html">
+        <source> Description is <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>required<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/></source>
+      </trans-unit>
+      <trans-unit id="purposeOfCollectionTitle" datatype="html">
+        <source>Purpose of collection</source>
+      </trans-unit>
+      <trans-unit id="purposeRequiredHint" datatype="html">
+        <source> Purpose is <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>required<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/></source>
+      </trans-unit>
+      <trans-unit id="consentIs" datatype="html">
+        <source>Consent is</source>
+      </trans-unit>
+      <trans-unit id="required" datatype="html">
+        <source>required</source>
+      </trans-unit>
+      <trans-unit id="notRequired" datatype="html">
+        <source>not required</source>
+      </trans-unit>
+      <trans-unit id="includedVerbs" datatype="html">
+        <source>Included verbs</source>
+      </trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/frontend/src/theme.less b/src/frontend/src/theme.less
index c8229ff113592b31cba9224652e8b1dd764da7c1..41541e652fabb539fa552f1b7f9e006573377161 100644
--- a/src/frontend/src/theme.less
+++ b/src/frontend/src/theme.less
@@ -22,12 +22,12 @@
 @primary-color-hover: #804d79;
 
 // Layout
-@layout-body-background: #F0ECF0;
-@layout-header-background: #F0ECF0;
+@layout-body-background: #f7f7f7;
+@layout-header-background: #f7f7f7;
 
 // colors: backgrounds and surfaces
-@body-background: #F0ECF0;
-@component-background: #F0ECF0;
+@body-background: #f7f7f7;
+@component-background: #f7f7f7;
 
 // colors: borders
 @popover-customize-border-color: #C6CDD4;
@@ -64,3 +64,49 @@
 @btn-default-bg: #fff;
 @btn-default-bg-hover: @primary-color; // TODO
 @btn-default-border: @primary-color;
+
+body {
+  font-size: 16px;
+}
+.content {
+  max-width: 1200px;
+  padding-top: 128px;
+  margin-left: 50%;
+  transform: translateX(-50%);
+}
+h1 {
+  font-size: 24px;
+  font-weight: 400;
+  margin-bottom: 40px;
+}
+h2 {
+  font-size: 20px;
+  font-weight: 400;
+  margin-bottom: 24px;
+}
+
+p {
+  margin-bottom: 24px;
+}
+.ant-collapse-item {
+  border-color: #c6cdd4;
+  box-shadow: none;
+  border-radius: 0;
+}
+
+.ant-collapse-header{
+  background: #fff;
+  border-radius: 0;
+  font-size: 18px;
+  height: 56px;
+}
+
+.ant-collapse-item-active > .ant-collapse-header {
+  color: @primary-color !important;
+  font-weight: 500;
+}
+.ant-collapse-content-box {
+  background: #fff;
+  border-color: #c6cdd4;
+  border-radius: 0;
+}
diff --git a/src/providers/migrations/0008_verb_remove_providerschema_essential_verbs_and_more.py b/src/providers/migrations/0008_verb_remove_providerschema_essential_verbs_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..64870167e2dae4e83d31b4479a6e36a29dbebd5a
--- /dev/null
+++ b/src/providers/migrations/0008_verb_remove_providerschema_essential_verbs_and_more.py
@@ -0,0 +1,156 @@
+import json
+
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import migrations, models
+import django.db.models.deletion
+
+groups_map = {}
+
+def migrate_schema_to_objects(apps, schema_editor):
+    ProviderSchema = apps.get_model('providers', 'ProviderSchema')
+    Verb = apps.get_model('providers', 'Verb')
+    VerbObject = apps.get_model('providers', 'VerbObject')
+    ProviderVerbGroup = apps.get_model('providers', 'ProviderVerbGroup')
+
+    for schema in ProviderSchema.objects.all():
+        groups_map[schema.id] = []
+        groups_data = schema.groups
+        essential_verbs = schema.essential_verbs
+        for group_data in groups_data:
+            group = ProviderVerbGroup.objects.create(
+                provider=schema.provider,
+                provider_schema=schema,
+                group_id = group_data["id"],
+                label = group_data["label"],
+                description = group_data["description"],
+                purpose_of_collection = group_data["purposeOfCollection"],
+                requires_consent = True,
+            )
+            groups_map[schema.id].append(group.id)
+            for verb_data in group_data["verbs"]:
+                try:
+                    verb = Verb.objects.get(
+                        provider=schema.provider,
+                        provider_schema=schema,
+                        verb_id=verb_data["id"],
+                    )
+                except ObjectDoesNotExist:
+                    verb = Verb.objects.create(
+                        provider=schema.provider,
+                        provider_schema = schema,
+                        verb_id = verb_data["id"],
+                        label = verb_data["label"],
+                        description = verb_data["description"],
+                        default_consent = verb_data["defaultConsent"],
+                        active = schema.superseded_by is None,
+                        essential = False,
+                        allow_anonymized_collection = verb_data["allowAnonymizedCollection"]
+                            if "allowAnonymizedCollection" in verb_data.keys() else False,
+                    )
+                    for verb_object_data in verb_data["objects"]:
+                        verb_object = VerbObject.objects.create(
+                            verb=verb,
+                            object_id=verb_object_data["id"],
+                            label=verb_object_data["label"] if "label" in verb_object_data.keys() else "",
+                            object_type=verb_object_data["objectType"] if "objectType" in verb_object_data.keys() else "Activity",
+                            matching=verb_object_data["matching"] if "matching" in verb_object_data.keys() else "definitionType",
+                            definition=verb_object_data["definition"]
+                        )
+                group.verbs.add(verb)
+        for verb_data in essential_verbs:
+            try:
+                verb = Verb.objects.get(
+                    provider=schema.provider,
+                    provider_schema=schema,
+                    verb_id=verb_data["id"],
+                )
+                verb.essential = True
+                verb.save()
+            except ObjectDoesNotExist:
+                verb = Verb.objects.create(
+                    provider=schema.provider,
+                    provider_schema=schema,
+                    verb_id=verb_data["id"],
+                    label=verb_data["label"],
+                    description=verb_data["description"],
+                    default_consent=verb_data["defaultConsent"],
+                    active=schema.superseded_by is None,
+                    essential=True,
+                    allow_anonymized_collection=verb_data["allowAnonymizedCollection"]
+                    if "allowAnonymizedCollection" in verb_data.keys() else False,
+                )
+                for verb_object_data in verb_data["objects"]:
+                    verb_object = VerbObject.objects.create(
+                        verb=verb,
+                        object_id=verb_object_data["id"],
+                        label=verb_object_data["label"] if "label" in verb_object_data.keys() else "",
+                        object_type=verb_object_data["objectType"] if "objectType" in verb_object_data.keys() else "Activity",
+                        matching=verb_object_data["matching"] if "matching" in verb_object_data.keys() else "definitionType",
+                        definition=json.dumps(verb_object_data["definition"])
+                    )
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('providers', '0007_providerschema_additional_lrs'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Verb',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('verb_id', models.CharField(max_length=250)),
+                ('label', models.TextField()),
+                ('description', models.TextField()),
+                ('default_consent', models.BooleanField()),
+                ('active', models.BooleanField(default=True)),
+                ('essential', models.BooleanField(default=False)),
+                ('allow_anonymized_collection', models.BooleanField(default=False)),
+                ('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.provider')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='VerbObject',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('object_id', models.CharField(max_length=250)),
+                ('object_type', models.CharField(max_length=250)),
+                ('matching', models.CharField(max_length=250)),
+                ('label', models.CharField(max_length=250)),
+                ('definition', models.JSONField()),
+                ('verb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.verb')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='verb',
+            name='provider_schema',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.providerschema'),
+        ),
+        migrations.CreateModel(
+            name='ProviderVerbGroup',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('group_id', models.CharField(max_length=250)),
+                ('label', models.TextField()),
+                ('description', models.TextField()),
+                ('purpose_of_collection', models.TextField()),
+                ('requires_consent', models.BooleanField(default=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.provider')),
+                ('provider_schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.providerschema')),
+                ('verbs', models.ManyToManyField(to='providers.verb')),
+            ],
+        ),
+        migrations.RunPython(migrate_schema_to_objects, reverse_code=migrations.RunPython.noop),
+        migrations.RemoveField(
+            model_name='providerschema',
+            name='essential_verbs',
+        ),
+        migrations.RemoveField(
+            model_name='providerschema',
+            name='groups',
+        ),
+    ]
diff --git a/src/providers/models.py b/src/providers/models.py
index e344a2be57396240144e7183b4d39fa1eab40e71..ab80e24e68d4313b44985a30038c9ec1695373ba 100644
--- a/src/providers/models.py
+++ b/src/providers/models.py
@@ -1,5 +1,3 @@
-from email.policy import default
-
 from django.conf import settings
 from django.db import models
 
@@ -20,14 +18,63 @@ class Provider(models.Model):
 class ProviderSchema(models.Model):
     provider = models.ForeignKey(Provider, on_delete=models.CASCADE)
     superseded_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL)
-    groups = models.JSONField()
-    essential_verbs = models.JSONField()
     additional_lrs = models.JSONField(default=list)
     updated = models.DateTimeField(auto_now=True)
     created = models.DateTimeField(auto_now_add=True)
 
+    def groups(self):
+        return ProviderVerbGroup.objects.filter(provider_schema=self).all()
+
+    def verbs(self):
+        return Verb.objects.filter(essential=False, provider_schema=self).all()
+
+    def essential_verbs(self):
+        return Verb.objects.filter(essential=True, provider_schema=self).all()
+
+    def __str__(self):
+        return f"Provider Schema {self.id}: ({len(self.verbs())} verbs, {len(self.essential_verbs())} essential verbs, {len(self.groups())} groups)"
+
+class Verb(models.Model):
+    provider = models.ForeignKey(Provider, on_delete=models.CASCADE)
+    provider_schema = models.ForeignKey(ProviderSchema, on_delete=models.CASCADE)
+    verb_id = models.CharField(max_length=250)
+    label = models.TextField()
+    description = models.TextField()
+    default_consent = models.BooleanField()
+    active = models.BooleanField(default=True)
+    essential = models.BooleanField(default=False)
+    allow_anonymized_collection = models.BooleanField(default=False)
+
     def __str__(self):
-        return "Provider Schema."
+        return self.verb_id
+
+class VerbObject(models.Model):
+    verb = models.ForeignKey(Verb, on_delete=models.CASCADE)
+    object_id = models.CharField(max_length=250)
+    object_type = models.CharField(max_length=250)
+    matching = models.CharField(max_length=250)
+    label = models.CharField(max_length=250)
+    definition = models.JSONField()
+
+
+class ProviderVerbGroup(models.Model):
+    provider = models.ForeignKey(Provider, on_delete=models.CASCADE)
+    provider_schema = models.ForeignKey(ProviderSchema, on_delete=models.CASCADE)
+    group_id = models.CharField(max_length=250)
+    label = models.TextField()
+    description = models.TextField()
+    purpose_of_collection = models.TextField()
+    requires_consent = models.BooleanField(default=True)
+    updated = models.DateTimeField(auto_now=True)
+    created = models.DateTimeField(auto_now_add=True)
+    verbs = models.ManyToManyField(Verb)
+
+    def __str__(self):
+        return (f"Provider Verb Group {self.id}:\n"
+                f"- contains {len(self.verbs.all())} verbs\n"
+                f"- group ID: {self.group_id}\n"
+                f"- provider Schema ID: {self.provider_schema_id}\n")
+
 
 
 class ProviderAuthorization(models.Model):
diff --git a/src/providers/serializers.py b/src/providers/serializers.py
index c1fb7fd84bbf273c772414891434bad3419e2f66..852d1cfce0a4044ce341dc0a7a398f6f15de2e94 100644
--- a/src/providers/serializers.py
+++ b/src/providers/serializers.py
@@ -3,7 +3,7 @@ from datetime import datetime, timedelta
 
 from rest_framework import serializers
 
-from providers.models import AnalyticsToken, AnalyticsTokenVerb, Provider, ProviderSchema
+from providers.models import AnalyticsToken, AnalyticsTokenVerb, Provider, ProviderSchema, ProviderVerbGroup
 from users.models import CustomUser
 
 
@@ -14,10 +14,10 @@ class ProviderSerializer(serializers.ModelSerializer):
 
 
 class ConsentVerbSerializer(serializers.Serializer):
-    provider = serializers.PrimaryKeyRelatedField(
-        queryset=Provider.objects.all()
-    )
     id = serializers.CharField()
+    group_id = serializers.PrimaryKeyRelatedField(
+        queryset=ProviderVerbGroup.objects.all()
+    )
     consented = serializers.BooleanField()
     objects = serializers.CharField()
 
diff --git a/src/providers/views.py b/src/providers/views.py
index 1accfb740427a635f11c4d07196b3634486f4155..f02f69479b9c08fcbb9a9d9442381bb9e880701a 100644
--- a/src/providers/views.py
+++ b/src/providers/views.py
@@ -22,7 +22,7 @@ from rest_framework.views import APIView
 from rolepermissions.checkers import has_permission
 from rolepermissions.roles import get_user_roles
 
-from backend.role_permission import IsAnalyst, IsProviderManager
+from backend.role_permission import IsAnalyst, IsProviderManager, IsProvider
 from backend.roles import Roles
 from backend.utils import lrs_db
 from consents.views import JsonUploadParser
@@ -94,19 +94,19 @@ class CreateProviderSchemaView(APIView):
                 return JsonResponse(
                     {"message": "invalid form data"}, status=status.HTTP_400_BAD_REQUEST
                 )
-
+            provider = Provider.objects.get(provider_id)
             try:
                 precedingSchema = ProviderSchema.objects.get(superseded_by__isnull=True)
 
                 newSchema = ProviderSchema.objects.create(
-                    object=object, provider=Provider.objects.get(provider_id)
+                    object=object, provider=provider
                 )
 
                 precedingSchema.superseded_by = newSchema
                 precedingSchema.save()
             except ObjectDoesNotExist:
                 newSchema = ProviderSchema.objects.create(
-                    object=object, provider=Provider.objects.get(provider_id)
+                    object=object, provider=provider
                 )
 
             return JsonResponse(
@@ -170,17 +170,17 @@ class GetAnalyticsTokensAvailableVerbsList(APIView):
         def createAnalyticTokenVerbs(groups, essential_verbs, provider):
             verbs = []
             for group in groups:
-                for verb in group["verbs"]:
-                    verbs.append({"id" : verb["id"], "label" : verb["label"], "description" : verb["description"], "provider" : provider.id})
+                for verb in group.verbs.all():
+                    verbs.append({"id" : verb.verb_id, "label" : verb.label, "description" : verb.description, "provider" : provider.id})
             for verb in essential_verbs:  # append essential verbs which might not be included in any groups
-                if len([verb_ for verb_ in verbs if verb_["id"] == verb["id"]]) == 0:
-                    verbs.append({"id" : verb["id"], "label" : verb["label"], "description" : verb["description"], "provider" : provider.id})
+                if len([verb_ for verb_ in verbs if verb_["id"] == verb.verb_id]) == 0:
+                    verbs.append({"id" : verb.verb_id, "label" : verb.label, "description" : verb.description, "provider" : provider.id})
             return verbs
         def prepareSchema(schema):
             return {
                 "providerId" : schema.provider.id,
                 "label" : schema.provider.name,
-                "analyticTokenVerbs" : createAnalyticTokenVerbs(schema.groups, schema.essential_verbs, schema.provider)
+                "analyticTokenVerbs" : createAnalyticTokenVerbs(schema.groups(), schema.essential_verbs(), schema.provider)
             }
         schemas = ProviderSchema.objects.filter(superseded_by=None)
         s = list(map(prepareSchema , schemas))
@@ -261,8 +261,9 @@ class UpdateAnalyticsTokenImage(APIView):
       token = AnalyticsToken.objects.filter(id=token_id).first()
       if not token:
          return Response({"message": "token doesn't exist"}, status=status.HTTP_404_NOT_FOUND)
-      
-      #TODO delete token image  
+      os.remove("static/upload/analytic-tokens-images/"+token.image_path)
+      token.image_path = None
+      token.save()
       return Response({"message": "token image deleted"}, status=status.HTTP_200_OK)
   
   def post(self, request, token_id):
@@ -777,7 +778,7 @@ class CreateVisualizationToken(APIView):
 
         provider = ProviderAuthorization.objects.filter(key=application_token).first()
         if provider is None:
-            print("Invald access token: " + application_token)
+            print("Invalid access token: " + application_token)
             return JsonResponse(
                 {"message": "invalid access token"},
                 safe=False,
diff --git a/src/static/provider_schema.schema.json b/src/static/provider_schema.schema.json
index 958cae367e69da999fae7af510a39992f36dfd4f..b2509dd7d0ba13cb7dc8a63ad5fd15879c14ed14 100644
--- a/src/static/provider_schema.schema.json
+++ b/src/static/provider_schema.schema.json
@@ -11,7 +11,7 @@
     "description": {
       "type": "string"
     },
-    "groups": {
+    "verbs": {
       "type": "array",
       "items": [
         {
@@ -26,84 +26,15 @@
             "description": {
               "type": "string"
             },
-            "showVerbDetails": {
+            "defaultConsent": {
               "type": "boolean"
             },
-            "purposeOfCollection": {
-              "type": "string"
-            },
-            "verbs": {
+            "objects": {
               "type": "array",
-              "items": [
-                {
-                  "type": "object",
-                  "properties": {
-                    "id": {
-                      "type": "string"
-                    },
-                    "label": {
-                      "type": "string"
-                    },
-                    "description": {
-                      "type": "string"
-                    },
-                    "defaultConsent": {
-                      "type": "boolean"
-                    },
-                    "allowAnonymizedCollection": {
-                      "type": "boolean"
-                    },
-                    "allowAnonymizedCollectionMinCount": {
-                      "type": "integer"
-                    },
-                    "objects": {
-                      "type": "array",
-                      "items": [
-                        {
-                          "type": "object",
-                          "properties": {
-                            "id": {
-                              "type": "string"
-                            },
-                            "label": {
-                              "type": "string"
-                            },
-                            "defaultConsent": {
-                              "type": "boolean"
-                            },
-                            "matching": {
-                              "enum": ["definitionType", "id"]
-                            },
-                            "definition": {
-                              "type": "object",
-                              "properties": {
-                                "name": {
-                                  "type": "object",
-                                  "properties": {
-                                    "enUS": {
-                                      "type": "string"
-                                    }
-                                  },
-                                  "required": ["enUS"]
-                                }
-                              },
-                              "required": ["name"]
-                            }
-                          },
-                          "required": ["id", "label", "defaultConsent", "definition", "matching"]
-                        }
-                      ]
-                    }
-                  },
-                  "required": ["id", "label", "description", "defaultConsent", "objects"]
-                }
-              ]
-            },
-            "isDefault": {
-              "type": "boolean"
+              "items": {}
             }
           },
-          "required": ["id", "label", "description", "showVerbDetails", "purposeOfCollection", "verbs", "isDefault"]
+          "required": ["id", "label", "description", "defaultConsent", "objects"]
         }
       ]
     },
@@ -160,5 +91,5 @@
       ]
     }
   },
-  "required": ["id", "name", "description", "groups", "essentialVerbs"]
+  "required": ["id", "name", "description", "verbs", "essentialVerbs"]
 }
diff --git a/src/static/provider_schema_h5p_v1.example.json b/src/static/provider_schema_h5p_v1.example.json
index 2715878ea5e33adeda08f51c4df411c0f78a06d0..aa94f1bab47ccae51dc250d4632c947101d5aabc 100644
--- a/src/static/provider_schema_h5p_v1.example.json
+++ b/src/static/provider_schema_h5p_v1.example.json
@@ -2,106 +2,86 @@
   "id": "h5p-0",
   "name": "H5P",
   "description": "Open-source content collaboration framework",
-  "groups": [
+  "verbs": [
     {
-      "id": "default_group",
-      "label": "Default group",
-      "description": "default",
-      "showVerbDetails": true,
-      "purposeOfCollection": "Lorem Ipsum",
-      "verbs": [
+      "id": "http://h5p.example.com/expapi/verbs/experienced",
+      "label": "Experienced",
+      "description": "Experienced",
+      "defaultConsent": true,
+      "objects": [
         {
-          "id": "http://h5p.example.com/expapi/verbs/experienced",
-          "label": "Experienced",
-          "description": "Experienced",
+          "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+          "label": "1.1.1 Funktionen",
           "defaultConsent": true,
-          "objects": [
-            {
-              "id": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-              "label": "1.1.1 Funktionen",
-              "defaultConsent": true,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
-                "name": {
-                  "enUS": "1.1.1 Funktionen"
-                }
-              }
+          "matching": "definitionType",
+          "definition": {
+            "type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF",
+            "name": {
+              "enUS": "1.1.1 Funktionen"
             }
-          ]
-        },
+          }
+        }
+      ]
+    },
+    {
+      "id": "http://h5p.example.com/expapi/verbs/attempted",
+      "label": "Attempted",
+      "description": "Attempted",
+      "defaultConsent": true,
+      "objects": [
         {
-          "id": "http://h5p.example.com/expapi/verbs/attempted",
-          "label": "Attempted",
-          "description": "Attempted",
+          "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+          "label": "2.3.1 Funktion Zirkulationsleitung",
           "defaultConsent": true,
-          "objects": [
-            {
-              "id": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-              "label": "2.3.1 Funktion Zirkulationsleitung",
-              "defaultConsent": true,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
-                "name": {
-                  "enUS": "2.3.1 Funktion Zirkulationsleitung"
-                }
-              }
+          "matching": "definitionType",
+          "definition": {
+            "type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm",
+            "name": {
+              "enUS": "2.3.1 Funktion Zirkulationsleitung"
             }
-          ]
+          }
         }
-      ],
-      "isDefault": true
+      ]
     },
     {
-      "id": "group_2",
-      "label": "Group 2",
+      "id": "http://h5p.example.com/expapi/verbs/interacted",
+      "label": "Interacted",
       "description": "Lorem ipsum",
-      "showVerbDetails": true,
-      "purposeOfCollection": "Lorem Ipsum",
-      "verbs": [
+      "defaultConsent": true,
+      "objects": [
         {
-          "id": "http://h5p.example.com/expapi/verbs/interacted",
-          "label": "Interacted",
-          "description": "Lorem ipsum",
+          "id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
+          "label": "1.2.3 Kappenventil",
           "defaultConsent": true,
-          "objects": [
-            {
-              "id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
-              "label": "1.2.3 Kappenventil",
-              "defaultConsent": true,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-                "name": {
-                  "enUS": "1.2.3 Kappenventil"
-                }
-              }
+          "matching": "definitionType",
+          "definition": {
+            "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+            "name": {
+              "enUS": "1.2.3 Kappenventil"
             }
-          ]
-        },
+          }
+        }
+      ]
+    },
+    {
+      "id": "http://h5p.example.com/expapi/verbs/answered",
+      "label": "Answered",
+      "description": "lorem ipsum",
+      "defaultConsent": false,
+      "objects": [
         {
-          "id": "http://h5p.example.com/expapi/verbs/answered",
-          "label": "Answered",
-          "description": "lorem ipsum",
+          "id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+          "label": "7.2.1 Ventil Basics",
           "defaultConsent": false,
-          "objects": [
-            {
-              "id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-              "label": "7.2.1 Ventil Basics",
-              "defaultConsent": false,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
-                "name": {
-                  "enUS": "7.2.1 Ventil Basics"
-                }
-              }
+          "matching": "definitionType",
+          "definition": {
+            "type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
+            "name": {
+              "enUS": "7.2.1 Ventil Basics"
             }
-          ]
+          }
         }
-      ],
-      "isDefault": false
+      ]
     }
   ],
   "essentialVerbs": [
diff --git a/src/static/provider_schema_moodle_v1.example.json b/src/static/provider_schema_moodle_v1.example.json
index 10eaf7687e55f6e4eeaec6b35a7c1705fb829cf9..4773e2e696598bd157e4f43ed3c3c92ad1c16cb1 100644
--- a/src/static/provider_schema_moodle_v1.example.json
+++ b/src/static/provider_schema_moodle_v1.example.json
@@ -2,93 +2,83 @@
   "id": "moodle-0",
   "name": "Moodle",
   "description": "Open-source learning management system",
-  "groups": [
+  "verbs": [
     {
-      "id": "default_group",
-      "label": "Default group",
-      "description": "default",
-      "showVerbDetails": true,
-      "purposeOfCollection": "Lorem Ipsum",
-      "verbs": [
+      "id": "http://moodle.example.com/expapi/verbs/completed",
+      "label": "Completed",
+      "description": "Completed",
+      "defaultConsent": true,
+      "objects": [
         {
-          "id": "http://moodle.example.com/expapi/verbs/completed",
-          "label": "Completed",
-          "description": "Completed",
+          "id": "http://moodle.example.com/expapi/activity/programming-course-python",
+          "label": "Python Programming Course",
           "defaultConsent": true,
-          "objects": [
-            {
-              "id": "http://moodle.example.com/expapi/activity/programming-course-python",
-              "label": "Python Programming Course",
-              "defaultConsent": true,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://moodle.example.com/expapi/activity/programming-course-python",
-                "name": {
-                  "enUS": "Python Programming Course"
-                }
-              }
-            },
-            {
-              "id": "http://moodle.example.com/expapi/activity/foreign-language-course",
-              "label": "Foreign language course",
-              "defaultConsent": true,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://moodle.example.com/expapi/activity/foreign-language-course",
-                "name": {
-                  "enUS": "Foreign language course"
-                }
-              }
-            }
-          ]
-        },
-        {
-          "id": "http://moodle.example.com/expapi/verbs/started",
-          "label": "Started",
-          "description": "Started",
-          "defaultConsent": false,
-          "matching": "definitionType",
-          "objects": []
-        },
-        {
-          "id": "http://moodle.example.com/expapi/verbs/paused",
-          "label": "Paused",
-          "description": "paused",
-          "defaultConsent": false,
           "matching": "definitionType",
-          "objects": []
+          "definition": {
+            "type": "http://moodle.example.com/expapi/activity/programming-course-python",
+            "name": {
+              "enUS": "Python Programming Course"
+            }
+          }
         },
         {
-          "id": "http://moodle.example.com/expapi/verbs/created",
-          "label": "Created",
-          "description": "Created",
-          "defaultConsent": false,
+          "id": "http://moodle.example.com/expapi/activity/foreign-language-course",
+          "label": "Foreign language course",
+          "defaultConsent": true,
           "matching": "definitionType",
-          "objects": []
-        },
+          "definition": {
+            "type": "http://moodle.example.com/expapi/activity/foreign-language-course",
+            "name": {
+              "enUS": "Foreign language course"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "id": "http://moodle.example.com/expapi/verbs/started",
+      "label": "Started",
+      "description": "Started",
+      "defaultConsent": false,
+      "matching": "definitionType",
+      "objects": []
+    },
+    {
+      "id": "http://moodle.example.com/expapi/verbs/paused",
+      "label": "Paused",
+      "description": "paused",
+      "defaultConsent": false,
+      "matching": "definitionType",
+      "objects": []
+    },
+    {
+      "id": "http://moodle.example.com/expapi/verbs/created",
+      "label": "Created",
+      "description": "Created",
+      "defaultConsent": false,
+      "matching": "definitionType",
+      "objects": []
+    },
+    {
+      "id": "http://moodle.example.com/expapi/verbs/answered",
+      "label": "Answered",
+      "description": "Answered",
+      "defaultConsent": true,
+      "matching": "definitionType",
+      "objects": [
         {
-          "id": "http://moodle.example.com/expapi/verbs/answered",
-          "label": "Answered",
-          "description": "Answered",
+          "id": "http://moodle.example.com/expapi/activity/poll-preferred-course-level",
+          "label": "Poll: Preferred course level",
           "defaultConsent": true,
           "matching": "definitionType",
-          "objects": [
-            {
-              "id": "http://moodle.example.com/expapi/activity/poll-preferred-course-level",
-              "label": "Poll: Preferred course level",
-              "defaultConsent": true,
-              "matching": "definitionType",
-              "definition": {
-                "type": "http://moodle.example.com/expapi/activity/poll-preferred-course-level",
-                "name": {
-                  "enUS": "Poll: Preferred course level"
-                }
-              }
+          "definition": {
+            "type": "http://moodle.example.com/expapi/activity/poll-preferred-course-level",
+            "name": {
+              "enUS": "Poll: Preferred course level"
             }
-          ]
+          }
         }
-      ],
-      "isDefault": true
+      ]
     }
   ],
   "essentialVerbs": []
diff --git a/src/users/models.py b/src/users/models.py
index fb7e41fee1ecae997130add11f565a8e6af81678..6f52c18746185e2db52d8ef53d1d3a6a59fe823e 100644
--- a/src/users/models.py
+++ b/src/users/models.py
@@ -23,4 +23,4 @@ class CustomUser(AbstractUser):
     objects = CustomUserManager()
 
     def __str__(self):
-        return "Custom user {}".format(self.email)
+        return f"Custom user {self.id}: {self.email}"
diff --git a/src/users/serializers.py b/src/users/serializers.py
index 199e3687eb5466dd6d49666229c12fa0dbbbc719..a5b595225cb0f0b380c66f71fe0a4811995e8590 100644
--- a/src/users/serializers.py
+++ b/src/users/serializers.py
@@ -1,8 +1,23 @@
+from django.contrib.auth.models import Group, Permission
 from rest_framework import serializers
-from models import CustomUser
+from .models import CustomUser
 
+class PermissionSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Permission
+        fields = ('id', 'name', 'codename')
+
+class GroupSerializer(serializers.ModelSerializer):
+    # Nest the PermissionSerializer to include permission details
+    permissions = PermissionSerializer(many=True)
+
+    class Meta:
+        model = Group
+        fields = ('id', 'name', 'permissions')
 
 class UserSerializer(serializers.ModelSerializer):
+    groups = GroupSerializer(many=True)
+    user_permissions = PermissionSerializer(many=True)
     class Meta:
         model = CustomUser
-        fields = "__all__"
+        exclude = ["password", "shibboleth_connector_identifier"]
diff --git a/src/users/urls.py b/src/users/urls.py
index 2fee4242e58ccb6db51fa3f6f311dde92282dc4b..740129c57fd00149a323c6e0b34480d13430a821 100644
--- a/src/users/urls.py
+++ b/src/users/urls.py
@@ -12,4 +12,7 @@ urlpatterns = [
     path("toggle-data-recording", views.ToggleDataRecording.as_view()),
     path("deleteUser", views.DeleteUserView.as_view()),
     path("mergeData", views.MergeDataView.as_view()),
-]
+    path("users", views.UserListView.as_view()),
+    path("groups", views.GroupListView.as_view()),
+    path("permissions", views.PermissionListView.as_view()),
+]
\ No newline at end of file
diff --git a/src/users/views.py b/src/users/views.py
index f868efb08f85b2b5acd03f4f190218521fdc8572..58f8e7a1052cd739c8c5193b09e3b61b0a6c3607 100644
--- a/src/users/views.py
+++ b/src/users/views.py
@@ -5,8 +5,9 @@ from django.shortcuts import render
 from django.template.loader import render_to_string
 from django.utils import timezone
 from django.utils.html import strip_tags
+from django.contrib.auth.models import Group, Permission, Permission
 from rest_framework import status
-from rest_framework.permissions import IsAuthenticated
+from rest_framework.permissions import IsAuthenticated, IsAdminUser
 from rest_framework.views import APIView
 from rolepermissions.checkers import has_permission
 from rolepermissions.roles import assign_role
@@ -17,6 +18,7 @@ from consents.views import JsonUploadParser
 from data_removal.models import DataRemovalJob
 
 from .models import CustomUser
+from .serializers import UserSerializer, GroupSerializer, PermissionSerializer
 
 
 class CreateUserView(APIView):
@@ -152,3 +154,38 @@ class MergeDataView(APIView):
             return JsonResponse({"status": "success", "message": f'{result.modified_count} records updated'}, status=status.HTTP_200_OK)
         else:
             return JsonResponse({"status": "error", "message": f'0 records updated'}, status=status.HTTP_400_BAD_REQUEST)
+
+
+class UserListView(APIView):
+    permission_classes = (IsAuthenticated, IsAdminUser)
+
+    def get(self, request):
+        users = CustomUser.objects.all()
+        users = UserSerializer(users, many=True).data
+        return JsonResponse(users,
+                            safe=False,
+                            status=status.HTTP_200_OK)
+
+
+class GroupListView(APIView):
+    """
+    API endpoint that returns all auth groups with their permissions.
+    """
+    def get(self, request, format=None):
+        groups = Group.objects.all()
+        serializer = GroupSerializer(groups, many=True)
+        return JsonResponse(serializer.data,
+                            safe=False,
+                            status=status.HTTP_200_OK)
+
+
+class PermissionListView(APIView):
+    """
+    API endpoint that returns all auth permissions.
+    """
+    def get(self, request, format=None):
+        groups = Permission.objects.all()
+        serializer = PermissionSerializer(groups, many=True)
+        return JsonResponse(serializer.data,
+                            safe=False,
+                            status=status.HTTP_200_OK)
\ No newline at end of file
diff --git a/src/xapi/models.py b/src/xapi/models.py
index 71a836239075aa6e6e4ecb700e9c42c95c022d91..beeb30826593dcc86f7904d5da16715af5a61ab6 100644
--- a/src/xapi/models.py
+++ b/src/xapi/models.py
@@ -1,3 +1,2 @@
 from django.db import models
 
-# Create your models here.
diff --git a/src/xapi/tests/tests.py b/src/xapi/tests/tests.py
index c056eb8885356b11fe638956947fea160bc6387a..17b414167d0c0c56fc9d75bb7ff2c5aaa0c1dc0a 100644
--- a/src/xapi/tests/tests.py
+++ b/src/xapi/tests/tests.py
@@ -13,7 +13,7 @@ from rolepermissions.roles import assign_role
 
 from consents.models import UserConsents
 from consents.tests.tests_consent_operations import BaseTestCase
-from providers.models import Provider, ProviderAuthorization, ProviderSchema
+from providers.models import Provider, ProviderAuthorization, ProviderSchema, ProviderVerbGroup
 from users.models import CustomUser
 
 PROJECT_PATH = os.path.abspath(os.path.dirname(__name__))
@@ -32,6 +32,34 @@ class XAPITestCase(TestCase):
     test_provider_email = "test2@mail.com"
     test_provider_password = "test123"
 
+    verb_groups = [
+        {
+            "provider_id": 1,
+            "id": "default_group",
+            "label": "Default group",
+            "description": "default",
+            "showVerbDetails": True,
+            "purposeOfCollection": "Lorem Ipsum",
+            "requiresConsent": True,
+            "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"}
+            ]
+        },
+        {
+            "provider_id": 1,
+            "id": "default_group",
+            "label": "Group 2",
+            "description": "Lorem ipsum",
+            "showVerbDetails": True,
+            "purposeOfCollection": "Lorem Ipsum",
+            "requiresConsent": True,
+            "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+            ]
+        }
+    ]
+
     def setUp(self):
         self.normal_user = normal_user = CustomUser.objects.create_user(
             self.test_user_email, self.test_user_password
@@ -57,7 +85,7 @@ class XAPITestCase(TestCase):
         provider_client = APIClient()
         provider_client.credentials(HTTP_AUTHORIZATION="Bearer " + provider_token)
 
-        # we need provider schemas for this test
+        # we need provider schemas with groups for this test
         with open(
             os.path.join(PROJECT_PATH, "static/provider_schema_h5p_v1.example.json")
         ) as fp:
@@ -69,10 +97,20 @@ class XAPITestCase(TestCase):
             self.assertEqual(response.status_code, 201)
             self.assertTrue("message" in response.json())
 
+        # create some verb groups
+        for group in self.verb_groups:
+            group["provider_id"] = Provider.objects.latest('id').id # database id is 2 on second go bc autoincrement
+            response = provider_client.post(
+                "/api/v1/consents/provider/create-verb-group",
+                group,
+                format="json",
+            )
+            self.assertEqual(response.status_code, 200)
+
     @patch("xapi.views.store_in_db", mock_store_in_lrs)
     def test_xapi(self):
         try:
-            provider = Provider.objects.order_by("id").first()
+            provider = Provider.objects.latest('id')
         except ObjectDoesNotExist:
             self.assertTrue(False)  # provider was not created when uploading schema
 
@@ -92,10 +130,12 @@ class XAPITestCase(TestCase):
         first = True
         accepted_verb = {}
         denied_verb = {}
-        for group in provider_schema.groups:
-            for verb in group["verbs"]:
+        for group in provider_schema.groups():
+            for verb in group.verbs.all():
+                objects = [{"id": obj.object_id, "objectType": obj.object_type, "matching": obj.matching,
+                            "label": obj.label, "definition": obj.definition} for obj in verb.verbobject_set.all()]
                 first_obj = True
-                for obj in verb["objects"]:  # consent to the first object
+                for obj in objects:  # consent to the first object
                     obj["consented"] = first_obj
                     first_obj = False
                 # consent to the very first verb
@@ -103,9 +143,9 @@ class XAPITestCase(TestCase):
                     accepted_verb = verb
                     UserConsents.objects.create(
                         consented=True,
-                        verb=verb["id"],
-                        object=json.dumps(verb["objects"]),
-                        provider_schema=provider_schema,
+                        verb=verb,
+                        object=json.dumps(objects),
+                        verb_group=group,
                         provider=provider,
                         user=self.normal_user,
                     )
@@ -114,9 +154,9 @@ class XAPITestCase(TestCase):
                     denied_verb = verb
                     UserConsents.objects.create(
                         consented=False,
-                        verb=verb["id"],
-                        object=json.dumps(verb["objects"]),
-                        provider_schema=provider_schema,
+                        verb=verb,
+                        object=json.dumps(objects),
+                        verb_group=group,
                         provider=provider,
                         user=self.normal_user,
                     )
@@ -128,11 +168,11 @@ class XAPITestCase(TestCase):
             "/xapi/statements",
             {
                 "actor": {"mbox": f"mailto:{self.test_user_email}"},
-                "verb": {"id": accepted_verb["id"]},
+                "verb": {"id": accepted_verb.verb_id},
                 "object": {
-                    "id": accepted_verb["objects"][0]["id"],
+                    "id": accepted_verb.verbobject_set.first().object_id,
                     "objectType": "Activity",
-                    "definition": accepted_verb["objects"][0]["definition"],
+                    "definition": accepted_verb.verbobject_set.first().definition,
                 },
                 "timestamp": (datetime.now() + timedelta(0,30)).strftime("%Y-%m-%dT%H:%M:%SZ"), # timedelta is used to ensure the statement is "after" the consent
             },
@@ -234,6 +274,23 @@ class TestxAPIWithDataRecordingPause(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"}
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -241,25 +298,25 @@ class TestxAPIWithDataRecordingPause(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm", "name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46", "name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b", "name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -353,6 +410,23 @@ class TestxAPIStatementActorAccount(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"}
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -360,25 +434,25 @@ class TestxAPIStatementActorAccount(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm", "name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46", "name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b", "name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -438,6 +512,23 @@ class TestxAPIStatementActorAccount(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"}
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -445,25 +536,25 @@ class TestxAPIStatementActorAccount(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm", "name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46", "name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b", "name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -520,6 +611,23 @@ class TestxAPIStatementActorAccount(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "http://h5p.example.com/expapi/verbs/experienced"},
+                {"id": "http://h5p.example.com/expapi/verbs/attempted"},
+                {"id": "http://h5p.example.com/expapi/verbs/interacted"},
+                {"id": "http://h5p.example.com/expapi/verbs/answered"}
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -527,25 +635,25 @@ class TestxAPIStatementActorAccount(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/experienced",
                         "consented": False,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF", "name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/attempted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm", "name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/interacted",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true, "matching": "definitionType", "definition":{"type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46", "name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]',
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "http://h5p.example.com/expapi/verbs/answered",
                         "consented": True,
                         "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false, "matching": "definitionType" ,"definition":{"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b", "name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]',
@@ -617,37 +725,27 @@ class TestxAPIObjectMatchingDefinitionType(BaseTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
+                "label": "Unlocked",
+                "description": "Actor unlocked an object",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                        "label": "Unlocked",
-                        "description": "Actor unlocked an object",
+                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
+                        "label": "Course",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                "label": "Course",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                    "name": {
-                                        "enUS": "A course within an LMS. Contains learning materials and activities"
-                                    },
-                                },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
+                            "name": {
+                                "enUS": "A course within an LMS. Contains learning materials and activities"
                             },
-                        ],
-                    }
+                        },
+                    },
                 ],
-                "isDefault": True,
-            },
+            }
         ],
         "essentialVerbs": [],
     }
@@ -666,6 +764,20 @@ class TestxAPIObjectMatchingDefinitionType(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -673,7 +785,7 @@ class TestxAPIObjectMatchingDefinitionType(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -759,37 +871,27 @@ class TestxAPIObjectMatchingDefinitiondId(BaseTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
+                "label": "Unlocked",
+                "description": "Actor unlocked an object",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                        "label": "Unlocked",
-                        "description": "Actor unlocked an object",
+                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
+                        "label": "Course",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                "label": "Course",
-                                "defaultConsent": True,
-                                "matching": "id",
-                                "definition": {
-                                    "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                    "name": {
-                                        "enUS": "A course within an LMS. Contains learning materials and activities"
-                                    },
-                                },
+                        "matching": "id",
+                        "definition": {
+                            "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
+                            "name": {
+                                "enUS": "A course within an LMS. Contains learning materials and activities"
                             },
-                        ],
-                    }
+                        },
+                    },
                 ],
-                "isDefault": True,
-            },
+            }
         ],
         "essentialVerbs": [],
     }
@@ -808,6 +910,20 @@ class TestxAPIObjectMatchingDefinitiondId(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -815,7 +931,7 @@ class TestxAPIObjectMatchingDefinitiondId(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -902,37 +1018,27 @@ class TestxAPITimestampAfterConsent(BaseTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
+                "label": "Unlocked",
+                "description": "Actor unlocked an object",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                        "label": "Unlocked",
-                        "description": "Actor unlocked an object",
+                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
+                        "label": "Course",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                "label": "Course",
-                                "defaultConsent": True,
-                                "matching": "id",
-                                "definition": {
-                                    "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                    "name": {
-                                        "enUS": "A course within an LMS. Contains learning materials and activities"
-                                    },
-                                },
+                        "matching": "id",
+                        "definition": {
+                            "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
+                            "name": {
+                                "enUS": "A course within an LMS. Contains learning materials and activities"
                             },
-                        ],
-                    }
+                        },
+                    },
                 ],
-                "isDefault": True,
-            },
+            }
         ],
         "essentialVerbs": [],
     }
@@ -951,6 +1057,20 @@ class TestxAPITimestampAfterConsent(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -958,7 +1078,7 @@ class TestxAPITimestampAfterConsent(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -1043,37 +1163,27 @@ class TextxAPIAdditionalLrs(BaseTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
+                "label": "Unlocked",
+                "description": "Actor unlocked an object",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                        "label": "Unlocked",
-                        "description": "Actor unlocked an object",
+                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
+                        "label": "Course",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                "label": "Course",
-                                "defaultConsent": True,
-                                "matching": "id",
-                                "definition": {
-                                    "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                    "name": {
-                                        "enUS": "A course within an LMS. Contains learning materials and activities"
-                                    },
-                                },
+                        "matching": "id",
+                        "definition": {
+                            "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
+                            "name": {
+                                "enUS": "A course within an LMS. Contains learning materials and activities"
                             },
-                        ],
-                    }
+                        },
+                    },
                 ],
-                "isDefault": True,
-            },
+            }
         ],
         "essentialVerbs": [],
         "additionalLrs": [
@@ -1117,6 +1227,20 @@ class TextxAPIAdditionalLrs(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -1124,7 +1248,7 @@ class TextxAPIAdditionalLrs(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
diff --git a/src/xapi/tests/tests_verb_id_validation.py b/src/xapi/tests/tests_verb_id_validation.py
index 6994f79c50ba24d101ed77220c7e7314a966a7fd..57c48fe2abf88f1b7d830cccd4304d6c07ca66a8 100644
--- a/src/xapi/tests/tests_verb_id_validation.py
+++ b/src/xapi/tests/tests_verb_id_validation.py
@@ -11,7 +11,7 @@ from rolepermissions.roles import assign_role
 
 from consents.models import UserConsents
 from consents.tests.tests_consent_operations import BaseTestCase
-from providers.models import Provider, ProviderAuthorization, ProviderSchema
+from providers.models import Provider, ProviderAuthorization, ProviderSchema, ProviderVerbGroup
 from users.models import CustomUser
 
 PROJECT_PATH = os.path.abspath(os.path.dirname(__name__))
@@ -26,67 +26,27 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
         "id": "h5p-0",
         "name": "H5P",
         "description": "Open-source content collaboration framework",
-        "groups": [
+        "verbs": [
             {
-                "id": "default_group",
-                "label": "Default group",
-                "description": "default",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
+                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
+                "label": "Unlocked",
+                "description": "Actor unlocked an object",
+                "defaultConsent": True,
+                "objects": [
                     {
-                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                        "label": "Unlocked",
-                        "description": "Actor unlocked an object",
+                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
+                        "label": "Course",
                         "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                "label": "Course",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                    "name": {
-                                        "enUS": "A course within an LMS. Contains learning materials and activities"
-                                    },
-                                },
-                            },
-                        ],
-                    }
-                ],
-                "isDefault": True,
-            },
-            {
-                "id": "group_2",
-                "label": "Group Two",
-                "description": "my second group",
-                "showVerbDetails": True,
-                "purposeOfCollection": "Lorem Ipsum",
-                "verbs": [
-                    {
-                        "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
-                        "label": "Unlocked",
-                        "description": "Actor unlocked an object",
-                        "defaultConsent": True,
-                        "objects": [
-                            {
-                                "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
-                                "label": "Course",
-                                "defaultConsent": True,
-                                "matching": "definitionType",
-                                "definition": {
-                                    "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
-                                    "name": {
-                                        "enUS": "A course within an LMS. Contains learning materials and activities"
-                                    },
-                                },
+                        "matching": "definitionType",
+                        "definition": {
+                            "type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
+                            "name": {
+                                "enUS": "A course within an LMS. Contains learning materials and activities"
                             },
-                        ],
-                    }
+                        },
+                    },
                 ],
-                "isDefault": False,
-            },
+            }
         ],
         "essentialVerbs": [],
     }
@@ -103,6 +63,19 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
         # Create user consent for test user
         user_consent = [
             {
@@ -110,7 +83,7 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -132,7 +105,7 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
                         ),
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": False,
                         "objects": json.dumps(
@@ -183,6 +156,20 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -190,7 +177,7 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -212,7 +199,7 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
                         ),
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -263,6 +250,20 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
             )
             self.assertEqual(response.status_code, 201)
 
+        # create a verb group
+        group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
+                 "description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
+                 "requiresConsent": True, "verbs": [
+                {"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"},
+            ]}
+        response = self.provider_client.post(
+            "/api/v1/consents/provider/create-verb-group",
+            group,
+            format="json",
+        )
+        self.assertEqual(response.status_code, 200)
+        group_id = ProviderVerbGroup.objects.latest('id').id
+
         # Create user consent for test user
         user_consent = [
             {
@@ -270,7 +271,7 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
                 "providerSchemaId": 1,
                 "verbs": [
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
@@ -292,7 +293,7 @@ class TestxAPIVerbAndObjectValidation(BaseTestCase):
                         ),
                     },
                     {
-                        "provider": 1,
+                        "group_id": group_id,
                         "id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
                         "consented": True,
                         "objects": json.dumps(
diff --git a/src/xapi/views.py b/src/xapi/views.py
index 515ecf5d65a6845a28909646aa2addb83a25a35b..2e4746a95ed3a18ca2abdea7dd9d9efbf8404f64 100644
--- a/src/xapi/views.py
+++ b/src/xapi/views.py
@@ -17,7 +17,7 @@ from rest_framework.views import APIView
 
 from backend.utils import lrs_db
 from consents.models import UserConsents
-from providers.models import Provider, ProviderAuthorization, ProviderSchema
+from providers.models import Provider, ProviderAuthorization, ProviderSchema, Verb, VerbObject, ProviderVerbGroup
 from users.models import CustomUser
 import redis
 import zeep
@@ -140,7 +140,7 @@ def process_statement(x_api_statement, provider, latest_schema):
 
     account_email = (
         x_api_statement.get("actor", {}).get("account", {}).get("name", None)
-    )        
+    )
 
     if mbox is None and account_email is None:
         return {
@@ -160,7 +160,7 @@ def process_statement(x_api_statement, provider, latest_schema):
         
     if (not "verb" in x_api_statement) or (not "id" in x_api_statement["verb"]):
         return {"valid": False, "accepted": False, "reason": "No verb given"}
-    verb = x_api_statement["verb"]["id"]
+    verb_id = x_api_statement["verb"]["id"]
 
     if (not "object" in x_api_statement) or (not "id" in x_api_statement["object"]):
         return {"valid": False, "accepted": False, "reason": "No object given"}
@@ -169,14 +169,19 @@ def process_statement(x_api_statement, provider, latest_schema):
     try:
         user = CustomUser.objects.get(email=email)
     except ObjectDoesNotExist:
-        return {"valid": False, "accepted": False, "message": "User not found"}
+        return {"valid": False, "accepted": False, "message": f"User not found ({email})"}
 
     x_api_statement["actor"]["mbox"] = "mailto:" + str(user.id) + "-polaris-id@polaris.com"
 
+    # find verb
+    verb_candidates = Verb.objects.filter(verb_id=verb_id, provider=provider, provider_schema=latest_schema)
+    if not verb_candidates:
+        return {"valid": False, "accepted": False, "message": "Verb not found"}
+
     # essential verbs do not require consent
-    if not verb in [verb["id"] for verb in latest_schema.essential_verbs]:
+    if not verb_id in [verb.verb_id for verb in latest_schema.essential_verbs()]:
 
-        anon_verbs = [verb["id"] for verblist in [group["verbs"] for group in latest_schema.groups] for verb in verblist if verb.get("allowAnonymizedCollection", False)]
+        anon_verbs = [verb.verb_id for verblist in [group.verbs.all() for group in latest_schema.groups()] for verb in verblist if verb.allow_anonymized_collection]
 
         # has the user paused data collection altogether?
         if user.paused_data_recording:
@@ -192,14 +197,13 @@ def process_statement(x_api_statement, provider, latest_schema):
         else:
             timestamp = datetime.datetime.now() # if the statement has no timestamp, use the current date s.t. validation does not fail as long as consent exists
 
-        # has the user given consent to this verb?
-        # maybe TODO: load correct provider schema pertaining to this user consent to validate the verb and objects fully
+        # has the user given consent to this verb at some point and is it still valid?
         user_consent = UserConsents.objects.filter(
-            user=user, provider=provider, verb=verb, consented=True, created__lte=timestamp, active=True
+            user=user, provider=provider, verb__in=verb_candidates, consented=True, created__lte=timestamp, active=True
         ).first()
 
         if not user_consent:
-            if verb in anon_verbs:
+            if verb_id in anon_verbs:
                 return {
                     "valid": True,
                     "accepted": True,
@@ -209,7 +213,7 @@ def process_statement(x_api_statement, provider, latest_schema):
             return {
                 "valid": True,
                 "accepted": False,
-                "reason": f"User has not given consent to verb id {verb}",
+                "reason": f"User has not given consent to verb id {verb_id}",
             }
 
         # has the user given consent to the object at hand?
@@ -235,7 +239,7 @@ def process_statement(x_api_statement, provider, latest_schema):
                     object_consent = True
 
         if not object_consent:
-            if verb in anon_verbs:
+            if verb_id in anon_verbs:
                 return {
                     "valid": True,
                     "accepted": True,
@@ -244,10 +248,10 @@ def process_statement(x_api_statement, provider, latest_schema):
             return {
                 "valid": True,
                 "accepted": False,
-                "reason": f"User has not given consent to this object: {object_statement} for verb {verb}",
+                "reason": f"User has not given consent to this object: {object_statement} for verb {verb_id}",
             }
 
-        return {"valid": True, "accepted": True}
+    return {"valid": True, "accepted": True}
 
 def process_tan_statement(x_api_statement):
     """