diff --git a/README.md b/README.md
index 0f3fcade95270c59d0e7db53515849b57aa98ae7..3f4a8d0e658946e981210042743d8ba12c2687bb 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,14 @@ Alternatively, you can use `ssh root@localhost -p 8022 -o "UserKnownHostsFile /d
 
 To snapshot a running container, use `docker commit CONTAINER_ID`. It returns a snapshot ID, which you can access via `docker run -it SNAPSHOT_ID`.
 
+## Models
+To generate class structures for this project automatically: 
+1. Install OpenAPI Generator (using, e.g., `brew install openapi-generator`).
+2. Run: `openapi-generator generate -i mc_backend/mcserver/mcserver_api.yaml -g typescript-angular -o mc_frontend/openapi/ && openapi-generator generate -i mc_backend/mcserver/mcserver_api.yaml -g python-flask -o mc_backend/openapi/ && python mc_backend/openapi_generator.py`.
+
 ## Documentation
+### API
+To view the API documentation, visit https://korpling.org/mc-service/mc/api/v1.0/ui/ .
 ### Changelog
 To update the changelog, use: `git log --oneline --decorate > CHANGELOG`
 
diff --git a/mc_backend/mcserver/app/__init__.py b/mc_backend/mcserver/app/__init__.py
index 925acfb436f609e597b750bbbdfe942507b5d657..667c836c25f55db9cc49c7be32b507e7455bfbfa 100644
--- a/mc_backend/mcserver/app/__init__.py
+++ b/mc_backend/mcserver/app/__init__.py
@@ -110,6 +110,9 @@ def init_logging(app: Flask, log_file_path: str):
     app.logger.addHandler(file_handler)
     app.logger.setLevel(log_level)
     got_request_exception.connect(log_exception, app)
+    database_uri: str = app.config.get("SQLALCHEMY_DATABASE_URI", "")
+    database_uri = database_uri.split('@')[1] if '@' in database_uri else database_uri
+    app.logger.warning(f"Accessing database at: {database_uri}")
 
 
 def log_exception(sender_app: Flask, exception, **extra):
@@ -120,7 +123,7 @@ def log_exception(sender_app: Flask, exception, **extra):
         exception -- the exception to be logged
         **extra -- any additional arguments
     """
-    sender_app.logger.exception(f"ERROR for {flask.request.url}")
+    sender_app.logger.info(f"ERROR for {flask.request.url}")
 
 
 def start_updater(app: Flask) -> Thread:
diff --git a/mc_backend/mcserver/app/api/__init__.py b/mc_backend/mcserver/app/api/__init__.py
index d8b7a16727f623b0f87e1061714704de0549d5fc..84c2c0492d9403ea346585815c4d6efb7f405ef6 100644
--- a/mc_backend/mcserver/app/api/__init__.py
+++ b/mc_backend/mcserver/app/api/__init__.py
@@ -6,14 +6,13 @@ from mcserver import Config
 bp = Blueprint("api", __name__)
 api = Api(bp)
 
-from . import corpusAPI, corpusListAPI, exerciseAPI
+from . import corpusAPI, corpusListAPI, exerciseAPI, staticExercisesAPI
 from mcserver.app.api.exerciseListAPI import ExerciseListAPI
 from mcserver.app.api.fileAPI import FileAPI
 from mcserver.app.api.frequencyAPI import FrequencyAPI
 from mcserver.app.api.h5pAPI import H5pAPI
 from mcserver.app.api.kwicAPI import KwicAPI
 from mcserver.app.api.rawTextAPI import RawTextAPI
-from mcserver.app.api.staticExercisesAPI import StaticExercisesAPI
 from mcserver.app.api.textcomplexityAPI import TextComplexityAPI
 from mcserver.app.api.validReffAPI import ValidReffAPI
 from mcserver.app.api.vectorNetworkAPI import VectorNetworkAPI
@@ -25,7 +24,6 @@ api.add_resource(FrequencyAPI, Config.SERVER_URI_FREQUENCY, endpoint="frequency"
 api.add_resource(H5pAPI, Config.SERVER_URI_H5P, endpoint="h5p")
 api.add_resource(KwicAPI, Config.SERVER_URI_KWIC, endpoint="kwic")
 api.add_resource(RawTextAPI, Config.SERVER_URI_RAW_TEXT, endpoint="rawtext")
-api.add_resource(StaticExercisesAPI, Config.SERVER_URI_STATIC_EXERCISES, endpoint="exercises")
 api.add_resource(TextComplexityAPI, Config.SERVER_URI_TEXT_COMPLEXITY, endpoint='textcomplexity')
 api.add_resource(ValidReffAPI, Config.SERVER_URI_VALID_REFF, endpoint="validReff")
 api.add_resource(VectorNetworkAPI, Config.SERVER_URI_VECTOR_NETWORK, endpoint="vectorNetwork")
diff --git a/mc_backend/mcserver/app/api/staticExercisesAPI.py b/mc_backend/mcserver/app/api/staticExercisesAPI.py
index 524b6ec6704f236ee83a813d0575371da6947516..5b9aec8d2fd578e73a937240404ee1c3603b6907 100644
--- a/mc_backend/mcserver/app/api/staticExercisesAPI.py
+++ b/mc_backend/mcserver/app/api/staticExercisesAPI.py
@@ -7,12 +7,12 @@ from decimal import Decimal, ROUND_HALF_UP
 from io import BytesIO
 from tempfile import mkstemp
 from time import time
-from typing import Dict, List, Set, Match, Tuple
+from typing import Dict, List, Set, Match, Tuple, Union
 from zipfile import ZipFile
 
+import connexion
 import requests
-from flask_restful import Resource, abort
-from flask_restful.reqparse import RequestParser
+from connexion.lifecycle import ConnexionResponse
 from requests import Response
 
 from mcserver.app.models import StaticExercise
@@ -20,22 +20,14 @@ from mcserver.app.services import NetworkService, AnnotationService
 from mcserver.config import Config
 
 
-class StaticExercisesAPI(Resource):
-    """The StaticExercises API resource. It guides users to static language exercises in the frontend."""
-
-    def __init__(self):
-        """Initialize possible arguments for calls to the StaticExercises REST API."""
-        self.reqparse: RequestParser = NetworkService.base_request_parser.copy()
-        super(StaticExercisesAPI, self).__init__()
-
-    def get(self):
-        """ The GET method for the StaticExercises REST API. It provides a list of static exercises
-        and their respective URLs in the frontend. """
-        # TODO: WRITE AND READ LAST UPDATE TIME FROM THE DATABASE
-        if datetime.fromtimestamp(time() - Config.INTERVAL_STATIC_EXERCISES) > NetworkService.exercises_last_update or \
-                len(NetworkService.exercises) == 0:
-            update_exercises()
-        return NetworkService.make_json_response({k: v.__dict__ for (k, v) in NetworkService.exercises.items()})
+def get() -> Union[Response, ConnexionResponse]:
+    """ The GET method for the StaticExercises REST API. It provides a list of static exercises
+    and their respective URLs in the frontend. """
+    # TODO: WRITE AND READ LAST UPDATE TIME FROM THE DATABASE
+    if datetime.fromtimestamp(time() - Config.INTERVAL_STATIC_EXERCISES) > NetworkService.exercises_last_update \
+            or len(NetworkService.exercises) == 0:
+        return update_exercises()
+    return NetworkService.make_json_response({k: v.__dict__ for (k, v) in NetworkService.exercises.items()})
 
 
 def get_relevant_strings(response: Response):
@@ -121,12 +113,13 @@ def handle_voc_list(content: dict, url: str, relevant_strings_dict: Dict[str, Se
         relevant_strings_dict[url].add(match_parts[0])
 
 
-def update_exercises():
+def update_exercises() -> Union[Response, ConnexionResponse]:
     """ Gets all static exercises from the frontend code repository and looks for the lemmata in them."""
     # TODO: check last update of the directory before pulling the whole zip archive
     response: Response = requests.get(Config.STATIC_EXERCISES_REPOSITORY_URL, stream=True)
     if not response.ok:
-        abort(503)
+        return connexion.problem(
+            503, Config.ERROR_TITLE_SERVICE_UNAVAILABLE, Config.ERROR_MESSAGE_SERVICE_UNAVAILABLE)
     relevant_strings_dict: Dict[str, Set[str]] = get_relevant_strings(response)
     file_dict: Dict = {}
     lemma_set: Set[str] = set()
@@ -150,3 +143,4 @@ def update_exercises():
                 word = word[:-1]
             NetworkService.exercises[url].solutions.append(list(search_results[search_results_dict[word]]))
     NetworkService.exercises_last_update = datetime.fromtimestamp(time())
+    return NetworkService.make_json_response({k: v.__dict__ for (k, v) in NetworkService.exercises.items()})
diff --git a/mc_backend/mcserver/app/models.py b/mc_backend/mcserver/app/models.py
index 2904e7af06f3415e2eb0f6e2b263017064327707..b3cc0f9a9cdc7504c9a5d23cfd0ee33f72bc8f8c 100644
--- a/mc_backend/mcserver/app/models.py
+++ b/mc_backend/mcserver/app/models.py
@@ -4,13 +4,13 @@ from enum import Enum
 import typing
 from mcserver.config import Config
 from mcserver.models_auto import TExercise, Corpus, TCorpus, Exercise, TLearningResult, LearningResult
-from openapi.openapi_server.models import SolutionElement, Solution, Link, Node, TextComplexity, AnnisResponse, \
+from openapi.openapi_server.models import SolutionElement, Solution, Link, NodeMC, TextComplexity, AnnisResponse, \
     GraphData
 
 AnnisResponse = AnnisResponse
 GraphData = GraphData
 LinkMC = Link
-NodeMC = Node
+NodeMC = NodeMC
 SolutionElement = SolutionElement
 TextComplexity = TextComplexity
 
diff --git a/mc_backend/mcserver/app/services/corpusService.py b/mc_backend/mcserver/app/services/corpusService.py
index c54384c0e913d6f2b31ae4155ea0767442f0624d..3ff75d0f0ed5176e272f71ca2733bf7e14542e86 100644
--- a/mc_backend/mcserver/app/services/corpusService.py
+++ b/mc_backend/mcserver/app/services/corpusService.py
@@ -104,7 +104,7 @@ class CorpusService:
             # there is actually no text, only a URN, so we need to get it ourselves
             url: str = f"{Config.INTERNET_PROTOCOL}{Config.HOST_IP_CSM}:{Config.CORPUS_STORAGE_MANAGER_PORT}/"
             response: requests.Response = requests.get(url, params=dict(urn=cts_urn))
-            return AnnisResponse(graph_data=GraphData.from_dict(json.loads(response.text)))
+            return AnnisResponse.from_dict(json.loads(response.text))
 
     @staticmethod
     def get_frequency_analysis(urn: str, is_csm: bool) -> FrequencyAnalysis:
diff --git a/mc_backend/mcserver/config.py b/mc_backend/mcserver/config.py
index eabf6c96f637076641b31d85c1107cab63d28ba4..0020bb2d9242ce295aefd3aee8ad07ecfb85c988 100644
--- a/mc_backend/mcserver/config.py
+++ b/mc_backend/mcserver/config.py
@@ -74,10 +74,14 @@ class Config(object):
     DOCKER_SERVICE_NAME_MCSERVER = "mcserver"
     ERROR_MESSAGE_CORPUS_NOT_FOUND = "A corpus with the specified ID was not found!"
     ERROR_MESSAGE_EXERCISE_NOT_FOUND = "An exercise with the specified ID was not found!"
-    ERROR_MESSAGE_INTERNAL_SERVER_ERROR = "The server encountered an unexpected condition that prevented it from " \
-                                          "fulfilling the request."
+    ERROR_MESSAGE_INTERNAL_SERVER_ERROR = \
+        "The server encountered an unexpected condition that prevented it from fulfilling the request."
+    ERROR_MESSAGE_SERVICE_UNAVAILABLE = \
+        "The server is currently unable to handle the request due to a temporary overload or scheduled " \
+        "maintenance, which will likely be alleviated after some delay."
     ERROR_TITLE_INTERNAL_SERVER_ERROR = "Internal Server Error"
     ERROR_TITLE_NOT_FOUND = "Not found"
+    ERROR_TITLE_SERVICE_UNAVAILABLE = "Service Unavailable"
     FAVICON_FILE_NAME = "favicon.ico"
     FLASK_MIGRATE = "migrate"
     GRAPHANNIS_DEPENDENCY_LINK = "dep"
diff --git a/mc_backend/mcserver/mcserver_api.yaml b/mc_backend/mcserver/mcserver_api.yaml
index 1a33924cd3f0e9e02c61ccb7ce63e1ccdfb2df03..e9ab2c486ca29c41c4e3c5bbabfd21774fb25de8 100644
--- a/mc_backend/mcserver/mcserver_api.yaml
+++ b/mc_backend/mcserver/mcserver_api.yaml
@@ -118,38 +118,26 @@ paths:
               schema:
                 $ref: '#/components/schemas/AnnisResponse'
       requestBody:
-        $ref: '#/components/requestBodies/ExerciseForm'
-components:
-  requestBodies:
-    ExerciseForm:
-      required: true
-      content:
-        application/x-www-form-urlencoded:
-          schema:
-            x-body-name: exercise_data
-            type: object
-            allOf:
-              - $ref: '#/components/schemas/ExerciseBase'
-                required:
-                  - instructions
-                  - search_values
-              - description: Additional exercise data.
+        required: true
+        content:
+          application/x-www-form-urlencoded:
+            schema:
+              x-body-name: exercise_data
+              type: object
+              $ref: '#/components/schemas/ExerciseForm'
+  /exercises:
+    get:
+      summary: Returns metadata for static exercises.
+      operationId: mcserver.app.api.staticExercisesAPI.get
+      responses:
+        200:
+          description: Metadata for static exercises, including their respective URIs in the frontend.
+          content:
+            application/json:
+              schema:
+#                TODO: specify object properties
                 type: object
-                properties:
-                  type:
-                    type: string
-                    description: Type of exercise, concerning interaction and layout.
-                    example: markWords
-                  type_translation:
-                    type: string
-                    description: Localized expression of the exercise type.
-                    example: Cloze
-                  urn:
-                    type: string
-                    description: CTS URN for the text passage from which the exercise was created.
-                    example: urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.1
-                required:
-                  - type
+components:
   schemas:
     AnnisResponse:
       description: A response with graph data from ANNIS, possibly with additional data for exercises.
@@ -167,24 +155,7 @@ components:
           type: array
           description: List of items with frequency data for linguistic phenomena.
           items:
-            type: object
-            properties:
-              count:
-                type: integer
-                description: How often the given combination of values occurred.
-                example: 1
-              phenomena:
-                type: array
-                description: Labels for the phenomena described in this frequency entry.
-                example: []
-                items:
-                  type: string
-              values:
-                type: array
-                description: Values for the phenomena described in this frequency entry.
-                example: []
-                items:
-                  type: string
+            $ref: "#/components/schemas/FrequencyItem"
         graph_data:
           $ref: "#/components/schemas/GraphData"
         solutions:
@@ -348,6 +319,48 @@ components:
           description: Title of the base text for the exercise.
           example: Noctes Atticae
           default: ""
+      required:
+        - instructions
+        - search_values
+    ExerciseForm:
+      allOf:
+        - $ref: '#/components/schemas/ExerciseBase'
+        - description: Additional exercise data.
+          type: object
+          properties:
+            type:
+              type: string
+              description: Type of exercise, concerning interaction and layout.
+              example: markWords
+            type_translation:
+              type: string
+              description: Localized expression of the exercise type.
+              example: Cloze
+            urn:
+              type: string
+              description: CTS URN for the text passage from which the exercise was created.
+              example: urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.1
+          required:
+            - type
+    FrequencyItem:
+      type: object
+      properties:
+        count:
+          type: integer
+          description: How often the given combination of values occurred.
+          example: 1
+        phenomena:
+          type: array
+          description: Labels for the phenomena described in this frequency entry.
+          example: []
+          items:
+            type: string
+        values:
+          type: array
+          description: Values for the phenomena described in this frequency entry.
+          example: []
+          items:
+            type: string
     GraphData:
       type: object
       description: Nodes, edges and metadata for a graph.
@@ -373,7 +386,7 @@ components:
           type: array
           description: List of nodes for the graph.
           items:
-            $ref: '#/components/schemas/Node'
+            $ref: '#/components/schemas/NodeMC'
       required:
         - links
         - nodes
@@ -520,12 +533,7 @@ components:
           type: string
           description: Dependency relation described by the edge.
           example: "det"
-      required:
-        - annis_component_name
-        - annis_component_type
-        - source
-        - target
-    Node:
+    NodeMC:
       type: object
       properties:
         annis_node_name:
@@ -572,14 +580,6 @@ components:
           type: string
           description: Solution value for this node in an exercise.
           example: ""
-      required:
-        - annis_node_name
-        - annis_node_type
-        - annis_tok
-        - annis_type
-        - id
-        - udep_lemma
-        - udep_upostag
     Solution:
       type: object
       description: Correct solution for an exercise.
@@ -609,7 +609,6 @@ components:
           description: Unique identifier for the token in a sentence.
           example: 9
       required:
-        - content
         - sentence_id
         - token_id
     TextComplexity:
diff --git a/mc_backend/mcserver/models_auto.py b/mc_backend/mcserver/models_auto.py
index 9ef6420382b08a3ebcc4c1f6a2ac27eebd4e5599..5ba6612cc7043c7c2e0f2668da4266f0a33209e0 100644
--- a/mc_backend/mcserver/models_auto.py
+++ b/mc_backend/mcserver/models_auto.py
@@ -149,6 +149,8 @@ Corpus: TCorpus = models.Corpus  # type: ignore
 class _ExerciseDictBase(typing.TypedDict, total=True):
     """TypedDict for properties that are required."""
 
+    instructions: str
+    search_values: str
     eid: str
     last_access_time: float
 
@@ -159,9 +161,7 @@ class ExerciseDict(_ExerciseDictBase, total=False):
     correct_feedback: str
     general_feedback: str
     incorrect_feedback: str
-    instructions: str
     partially_correct_feedback: str
-    search_values: str
     work_author: str
     work_title: str
     conll: str
@@ -233,14 +233,14 @@ class TExercise(typing.Protocol):
 
     def __init__(
         self,
+        instructions: str,
+        search_values: str,
         eid: str,
         last_access_time: float,
         correct_feedback: str = "",
         general_feedback: str = "",
         incorrect_feedback: str = "",
-        instructions: str = "",
         partially_correct_feedback: str = "",
-        search_values: str = "[]",
         work_author: str = "",
         work_title: str = "",
         conll: str = "",
@@ -289,14 +289,14 @@ class TExercise(typing.Protocol):
     @classmethod
     def from_dict(
         cls,
+        instructions: str,
+        search_values: str,
         eid: str,
         last_access_time: float,
         correct_feedback: str = "",
         general_feedback: str = "",
         incorrect_feedback: str = "",
-        instructions: str = "",
         partially_correct_feedback: str = "",
-        search_values: str = "[]",
         work_author: str = "",
         work_title: str = "",
         conll: str = "",
diff --git a/mc_backend/mocks.py b/mc_backend/mocks.py
index e14d2aafa2b662c099391900aca6e516afb4cec7..7b61cb0967f5bfcece559c5972c1842c471407a7 100644
--- a/mc_backend/mocks.py
+++ b/mc_backend/mocks.py
@@ -98,7 +98,7 @@ class TestHelper:
         if not len(Mocks.app_dict):
             with patch.object(TextService, "init_stop_words_latin"):
                 Mocks.app_dict[class_name] = TestHelper(app_factory(TestingConfig))
-            Mocks.app_dict[class_name].app.logger.setLevel(logging.CRITICAL)
+            Mocks.app_dict[class_name].app.logger.setLevel(logging.WARNING)
             Mocks.app_dict[class_name].app.testing = True
         db.session.commit()
 
diff --git a/mc_backend/openapi/openapi_server/controllers/default_controller.py b/mc_backend/openapi/openapi_server/controllers/default_controller.py
index 3cdc45cd3b7cb01755b91ea4755f1b03cb2be71b..31bdf0f9835c93bb279fb6300a68ca08a8d341f6 100644
--- a/mc_backend/openapi/openapi_server/controllers/default_controller.py
+++ b/mc_backend/openapi/openapi_server/controllers/default_controller.py
@@ -3,8 +3,6 @@ import six
 
 from openapi.openapi_server.models.annis_response import AnnisResponse  # noqa: E501
 from openapi.openapi_server.models.corpus import Corpus  # noqa: E501
-from openapi.openapi_server.models.exercise_base import ExerciseBase  # noqa: E501
-from openapi.openapi_server.models.unknownbasetype import UNKNOWN_BASE_TYPE  # noqa: E501
 from openapi.openapi_server import util
 
 
@@ -79,16 +77,23 @@ def mcserver_app_api_exercise_api_get(eid):  # noqa: E501
     return 'do some magic!'
 
 
-def mcserver_app_api_exercise_api_post(unknown_base_type):  # noqa: E501
+def mcserver_app_api_exercise_api_post():  # noqa: E501
     """Creates a new exercise.
 
      # noqa: E501
 
-    :param unknown_base_type: 
-    :type unknown_base_type: dict | bytes
 
     :rtype: AnnisResponse
     """
-    if connexion.request.is_json:
-        unknown_base_type = UNKNOWN_BASE_TYPE.from_dict(connexion.request.get_json())  # noqa: E501
+    return 'do some magic!'
+
+
+def mcserver_app_api_static_exercises_api_get():  # noqa: E501
+    """Returns metadata for static exercises.
+
+     # noqa: E501
+
+
+    :rtype: object
+    """
     return 'do some magic!'
diff --git a/mc_backend/openapi/openapi_server/models/__init__.py b/mc_backend/openapi/openapi_server/models/__init__.py
index 64b3bd9a93b8d59cedd3d40dad902ee8bf5a5222..8bcc86264cb17efb6d09398176e22e9882ff1756 100644
--- a/mc_backend/openapi/openapi_server/models/__init__.py
+++ b/mc_backend/openapi/openapi_server/models/__init__.py
@@ -4,15 +4,17 @@
 from __future__ import absolute_import
 # import models into model package
 from openapi.openapi_server.models.annis_response import AnnisResponse
-from openapi.openapi_server.models.annis_response_frequency_analysis import AnnisResponseFrequencyAnalysis
 from openapi.openapi_server.models.corpus import Corpus
 from openapi.openapi_server.models.exercise import Exercise
 from openapi.openapi_server.models.exercise_all_of import ExerciseAllOf
 from openapi.openapi_server.models.exercise_base import ExerciseBase
+from openapi.openapi_server.models.exercise_form import ExerciseForm
+from openapi.openapi_server.models.exercise_form_all_of import ExerciseFormAllOf
+from openapi.openapi_server.models.frequency_item import FrequencyItem
 from openapi.openapi_server.models.graph_data import GraphData
 from openapi.openapi_server.models.learning_result import LearningResult
 from openapi.openapi_server.models.link import Link
-from openapi.openapi_server.models.node import Node
+from openapi.openapi_server.models.node_mc import NodeMC
 from openapi.openapi_server.models.solution import Solution
 from openapi.openapi_server.models.solution_element import SolutionElement
 from openapi.openapi_server.models.text_complexity import TextComplexity
diff --git a/mc_backend/openapi/openapi_server/models/annis_response.py b/mc_backend/openapi/openapi_server/models/annis_response.py
index e9061400c1b96bf43344fdfdf70652b1f49be11a..e09e97a7a6bf487f0b6c431d6708534e2d4ec075 100644
--- a/mc_backend/openapi/openapi_server/models/annis_response.py
+++ b/mc_backend/openapi/openapi_server/models/annis_response.py
@@ -6,13 +6,13 @@ from datetime import date, datetime  # noqa: F401
 from typing import List, Dict  # noqa: F401
 
 from openapi.openapi_server.models.base_model_ import Model
-from openapi.openapi_server.models.annis_response_frequency_analysis import AnnisResponseFrequencyAnalysis
+from openapi.openapi_server.models.frequency_item import FrequencyItem
 from openapi.openapi_server.models.graph_data import GraphData
 from openapi.openapi_server.models.solution import Solution
 from openapi.openapi_server.models.text_complexity import TextComplexity
 from openapi.openapi_server import util
 
-from openapi.openapi_server.models.annis_response_frequency_analysis import AnnisResponseFrequencyAnalysis  # noqa: E501
+from openapi.openapi_server.models.frequency_item import FrequencyItem  # noqa: E501
 from openapi.openapi_server.models.graph_data import GraphData  # noqa: E501
 from openapi.openapi_server.models.solution import Solution  # noqa: E501
 from openapi.openapi_server.models.text_complexity import TextComplexity  # noqa: E501
@@ -31,7 +31,7 @@ class AnnisResponse(Model):
         :param exercise_type: The exercise_type of this AnnisResponse.  # noqa: E501
         :type exercise_type: str
         :param frequency_analysis: The frequency_analysis of this AnnisResponse.  # noqa: E501
-        :type frequency_analysis: List[AnnisResponseFrequencyAnalysis]
+        :type frequency_analysis: List[FrequencyItem]
         :param graph_data: The graph_data of this AnnisResponse.  # noqa: E501
         :type graph_data: GraphData
         :param solutions: The solutions of this AnnisResponse.  # noqa: E501
@@ -44,7 +44,7 @@ class AnnisResponse(Model):
         self.openapi_types = {
             'exercise_id': str,
             'exercise_type': str,
-            'frequency_analysis': List[AnnisResponseFrequencyAnalysis],
+            'frequency_analysis': List[FrequencyItem],
             'graph_data': GraphData,
             'solutions': List[Solution],
             'text_complexity': TextComplexity,
@@ -133,7 +133,7 @@ class AnnisResponse(Model):
         List of items with frequency data for linguistic phenomena.  # noqa: E501
 
         :return: The frequency_analysis of this AnnisResponse.
-        :rtype: List[AnnisResponseFrequencyAnalysis]
+        :rtype: List[FrequencyItem]
         """
         return self._frequency_analysis
 
@@ -144,7 +144,7 @@ class AnnisResponse(Model):
         List of items with frequency data for linguistic phenomena.  # noqa: E501
 
         :param frequency_analysis: The frequency_analysis of this AnnisResponse.
-        :type frequency_analysis: List[AnnisResponseFrequencyAnalysis]
+        :type frequency_analysis: List[FrequencyItem]
         """
 
         self._frequency_analysis = frequency_analysis
diff --git a/mc_backend/openapi/openapi_server/models/exercise.py b/mc_backend/openapi/openapi_server/models/exercise.py
index fdeb700db1946150e9ea010f37cbfa02de2127ef..ba5bf76f56858d08ca5dc0e88a39704b0bc1d3a8 100644
--- a/mc_backend/openapi/openapi_server/models/exercise.py
+++ b/mc_backend/openapi/openapi_server/models/exercise.py
@@ -215,6 +215,8 @@ class Exercise(Model):
         :param instructions: The instructions of this Exercise.
         :type instructions: str
         """
+        if instructions is None:
+            raise ValueError("Invalid value for `instructions`, must not be `None`")  # noqa: E501
 
         self._instructions = instructions
 
@@ -261,6 +263,8 @@ class Exercise(Model):
         :param search_values: The search_values of this Exercise.
         :type search_values: str
         """
+        if search_values is None:
+            raise ValueError("Invalid value for `search_values`, must not be `None`")  # noqa: E501
 
         self._search_values = search_values
 
diff --git a/mc_backend/openapi/openapi_server/models/exercise_base.py b/mc_backend/openapi/openapi_server/models/exercise_base.py
index ece91eeb5f84f56bbad8c67f72625b534b0edf58..5e5e99fbe109e6634710d0c1eb2a35cbf156644e 100644
--- a/mc_backend/openapi/openapi_server/models/exercise_base.py
+++ b/mc_backend/openapi/openapi_server/models/exercise_base.py
@@ -166,6 +166,8 @@ class ExerciseBase(Model):
         :param instructions: The instructions of this ExerciseBase.
         :type instructions: str
         """
+        if instructions is None:
+            raise ValueError("Invalid value for `instructions`, must not be `None`")  # noqa: E501
 
         self._instructions = instructions
 
@@ -212,6 +214,8 @@ class ExerciseBase(Model):
         :param search_values: The search_values of this ExerciseBase.
         :type search_values: str
         """
+        if search_values is None:
+            raise ValueError("Invalid value for `search_values`, must not be `None`")  # noqa: E501
 
         self._search_values = search_values
 
diff --git a/mc_backend/openapi/openapi_server/models/exercise_form.py b/mc_backend/openapi/openapi_server/models/exercise_form.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd1f64293cfefa83ca5019d0de7ba003b051acd9
--- /dev/null
+++ b/mc_backend/openapi/openapi_server/models/exercise_form.py
@@ -0,0 +1,356 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from openapi.openapi_server.models.base_model_ import Model
+from openapi.openapi_server.models.exercise_base import ExerciseBase
+from openapi.openapi_server.models.exercise_form_all_of import ExerciseFormAllOf
+from openapi.openapi_server import util
+
+from openapi.openapi_server.models.exercise_base import ExerciseBase  # noqa: E501
+from openapi.openapi_server.models.exercise_form_all_of import ExerciseFormAllOf  # noqa: E501
+
+class ExerciseForm(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, correct_feedback='', general_feedback='', incorrect_feedback='', instructions='', partially_correct_feedback='', search_values='[]', work_author='', work_title='', type=None, type_translation=None, urn=None):  # noqa: E501
+        """ExerciseForm - a model defined in OpenAPI
+
+        :param correct_feedback: The correct_feedback of this ExerciseForm.  # noqa: E501
+        :type correct_feedback: str
+        :param general_feedback: The general_feedback of this ExerciseForm.  # noqa: E501
+        :type general_feedback: str
+        :param incorrect_feedback: The incorrect_feedback of this ExerciseForm.  # noqa: E501
+        :type incorrect_feedback: str
+        :param instructions: The instructions of this ExerciseForm.  # noqa: E501
+        :type instructions: str
+        :param partially_correct_feedback: The partially_correct_feedback of this ExerciseForm.  # noqa: E501
+        :type partially_correct_feedback: str
+        :param search_values: The search_values of this ExerciseForm.  # noqa: E501
+        :type search_values: str
+        :param work_author: The work_author of this ExerciseForm.  # noqa: E501
+        :type work_author: str
+        :param work_title: The work_title of this ExerciseForm.  # noqa: E501
+        :type work_title: str
+        :param type: The type of this ExerciseForm.  # noqa: E501
+        :type type: str
+        :param type_translation: The type_translation of this ExerciseForm.  # noqa: E501
+        :type type_translation: str
+        :param urn: The urn of this ExerciseForm.  # noqa: E501
+        :type urn: str
+        """
+        self.openapi_types = {
+            'correct_feedback': str,
+            'general_feedback': str,
+            'incorrect_feedback': str,
+            'instructions': str,
+            'partially_correct_feedback': str,
+            'search_values': str,
+            'work_author': str,
+            'work_title': str,
+            'type': str,
+            'type_translation': str,
+            'urn': str
+        }
+
+        self.attribute_map = {
+            'correct_feedback': 'correct_feedback',
+            'general_feedback': 'general_feedback',
+            'incorrect_feedback': 'incorrect_feedback',
+            'instructions': 'instructions',
+            'partially_correct_feedback': 'partially_correct_feedback',
+            'search_values': 'search_values',
+            'work_author': 'work_author',
+            'work_title': 'work_title',
+            'type': 'type',
+            'type_translation': 'type_translation',
+            'urn': 'urn'
+        }
+
+        self._correct_feedback = correct_feedback
+        self._general_feedback = general_feedback
+        self._incorrect_feedback = incorrect_feedback
+        self._instructions = instructions
+        self._partially_correct_feedback = partially_correct_feedback
+        self._search_values = search_values
+        self._work_author = work_author
+        self._work_title = work_title
+        self._type = type
+        self._type_translation = type_translation
+        self._urn = urn
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'ExerciseForm':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The ExerciseForm of this ExerciseForm.  # noqa: E501
+        :rtype: ExerciseForm
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def correct_feedback(self):
+        """Gets the correct_feedback of this ExerciseForm.
+
+        Feedback for successful completion of the exercise.  # noqa: E501
+
+        :return: The correct_feedback of this ExerciseForm.
+        :rtype: str
+        """
+        return self._correct_feedback
+
+    @correct_feedback.setter
+    def correct_feedback(self, correct_feedback):
+        """Sets the correct_feedback of this ExerciseForm.
+
+        Feedback for successful completion of the exercise.  # noqa: E501
+
+        :param correct_feedback: The correct_feedback of this ExerciseForm.
+        :type correct_feedback: str
+        """
+
+        self._correct_feedback = correct_feedback
+
+    @property
+    def general_feedback(self):
+        """Gets the general_feedback of this ExerciseForm.
+
+        Feedback for finishing the exercise.  # noqa: E501
+
+        :return: The general_feedback of this ExerciseForm.
+        :rtype: str
+        """
+        return self._general_feedback
+
+    @general_feedback.setter
+    def general_feedback(self, general_feedback):
+        """Sets the general_feedback of this ExerciseForm.
+
+        Feedback for finishing the exercise.  # noqa: E501
+
+        :param general_feedback: The general_feedback of this ExerciseForm.
+        :type general_feedback: str
+        """
+
+        self._general_feedback = general_feedback
+
+    @property
+    def incorrect_feedback(self):
+        """Gets the incorrect_feedback of this ExerciseForm.
+
+        Feedback for failing to complete the exercise successfully.  # noqa: E501
+
+        :return: The incorrect_feedback of this ExerciseForm.
+        :rtype: str
+        """
+        return self._incorrect_feedback
+
+    @incorrect_feedback.setter
+    def incorrect_feedback(self, incorrect_feedback):
+        """Sets the incorrect_feedback of this ExerciseForm.
+
+        Feedback for failing to complete the exercise successfully.  # noqa: E501
+
+        :param incorrect_feedback: The incorrect_feedback of this ExerciseForm.
+        :type incorrect_feedback: str
+        """
+
+        self._incorrect_feedback = incorrect_feedback
+
+    @property
+    def instructions(self):
+        """Gets the instructions of this ExerciseForm.
+
+        Hints for how to complete the exercise.  # noqa: E501
+
+        :return: The instructions of this ExerciseForm.
+        :rtype: str
+        """
+        return self._instructions
+
+    @instructions.setter
+    def instructions(self, instructions):
+        """Sets the instructions of this ExerciseForm.
+
+        Hints for how to complete the exercise.  # noqa: E501
+
+        :param instructions: The instructions of this ExerciseForm.
+        :type instructions: str
+        """
+        if instructions is None:
+            raise ValueError("Invalid value for `instructions`, must not be `None`")  # noqa: E501
+
+        self._instructions = instructions
+
+    @property
+    def partially_correct_feedback(self):
+        """Gets the partially_correct_feedback of this ExerciseForm.
+
+        Feedback for successfully completing certain parts of the exercise.  # noqa: E501
+
+        :return: The partially_correct_feedback of this ExerciseForm.
+        :rtype: str
+        """
+        return self._partially_correct_feedback
+
+    @partially_correct_feedback.setter
+    def partially_correct_feedback(self, partially_correct_feedback):
+        """Sets the partially_correct_feedback of this ExerciseForm.
+
+        Feedback for successfully completing certain parts of the exercise.  # noqa: E501
+
+        :param partially_correct_feedback: The partially_correct_feedback of this ExerciseForm.
+        :type partially_correct_feedback: str
+        """
+
+        self._partially_correct_feedback = partially_correct_feedback
+
+    @property
+    def search_values(self):
+        """Gets the search_values of this ExerciseForm.
+
+        Search queries that were used to build the exercise.  # noqa: E501
+
+        :return: The search_values of this ExerciseForm.
+        :rtype: str
+        """
+        return self._search_values
+
+    @search_values.setter
+    def search_values(self, search_values):
+        """Sets the search_values of this ExerciseForm.
+
+        Search queries that were used to build the exercise.  # noqa: E501
+
+        :param search_values: The search_values of this ExerciseForm.
+        :type search_values: str
+        """
+        if search_values is None:
+            raise ValueError("Invalid value for `search_values`, must not be `None`")  # noqa: E501
+
+        self._search_values = search_values
+
+    @property
+    def work_author(self):
+        """Gets the work_author of this ExerciseForm.
+
+        Name of the person who wrote the base text for the exercise.  # noqa: E501
+
+        :return: The work_author of this ExerciseForm.
+        :rtype: str
+        """
+        return self._work_author
+
+    @work_author.setter
+    def work_author(self, work_author):
+        """Sets the work_author of this ExerciseForm.
+
+        Name of the person who wrote the base text for the exercise.  # noqa: E501
+
+        :param work_author: The work_author of this ExerciseForm.
+        :type work_author: str
+        """
+
+        self._work_author = work_author
+
+    @property
+    def work_title(self):
+        """Gets the work_title of this ExerciseForm.
+
+        Title of the base text for the exercise.  # noqa: E501
+
+        :return: The work_title of this ExerciseForm.
+        :rtype: str
+        """
+        return self._work_title
+
+    @work_title.setter
+    def work_title(self, work_title):
+        """Sets the work_title of this ExerciseForm.
+
+        Title of the base text for the exercise.  # noqa: E501
+
+        :param work_title: The work_title of this ExerciseForm.
+        :type work_title: str
+        """
+
+        self._work_title = work_title
+
+    @property
+    def type(self):
+        """Gets the type of this ExerciseForm.
+
+        Type of exercise, concerning interaction and layout.  # noqa: E501
+
+        :return: The type of this ExerciseForm.
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this ExerciseForm.
+
+        Type of exercise, concerning interaction and layout.  # noqa: E501
+
+        :param type: The type of this ExerciseForm.
+        :type type: str
+        """
+        if type is None:
+            raise ValueError("Invalid value for `type`, must not be `None`")  # noqa: E501
+
+        self._type = type
+
+    @property
+    def type_translation(self):
+        """Gets the type_translation of this ExerciseForm.
+
+        Localized expression of the exercise type.  # noqa: E501
+
+        :return: The type_translation of this ExerciseForm.
+        :rtype: str
+        """
+        return self._type_translation
+
+    @type_translation.setter
+    def type_translation(self, type_translation):
+        """Sets the type_translation of this ExerciseForm.
+
+        Localized expression of the exercise type.  # noqa: E501
+
+        :param type_translation: The type_translation of this ExerciseForm.
+        :type type_translation: str
+        """
+
+        self._type_translation = type_translation
+
+    @property
+    def urn(self):
+        """Gets the urn of this ExerciseForm.
+
+        CTS URN for the text passage from which the exercise was created.  # noqa: E501
+
+        :return: The urn of this ExerciseForm.
+        :rtype: str
+        """
+        return self._urn
+
+    @urn.setter
+    def urn(self, urn):
+        """Sets the urn of this ExerciseForm.
+
+        CTS URN for the text passage from which the exercise was created.  # noqa: E501
+
+        :param urn: The urn of this ExerciseForm.
+        :type urn: str
+        """
+
+        self._urn = urn
diff --git a/mc_backend/openapi/openapi_server/models/exercise_form_all_of.py b/mc_backend/openapi/openapi_server/models/exercise_form_all_of.py
new file mode 100644
index 0000000000000000000000000000000000000000..360b217613907b78a5ec95c90f57ad66b497257a
--- /dev/null
+++ b/mc_backend/openapi/openapi_server/models/exercise_form_all_of.py
@@ -0,0 +1,124 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from openapi.openapi_server.models.base_model_ import Model
+from openapi.openapi_server import util
+
+
+class ExerciseFormAllOf(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, type=None, type_translation=None, urn=None):  # noqa: E501
+        """ExerciseFormAllOf - a model defined in OpenAPI
+
+        :param type: The type of this ExerciseFormAllOf.  # noqa: E501
+        :type type: str
+        :param type_translation: The type_translation of this ExerciseFormAllOf.  # noqa: E501
+        :type type_translation: str
+        :param urn: The urn of this ExerciseFormAllOf.  # noqa: E501
+        :type urn: str
+        """
+        self.openapi_types = {
+            'type': str,
+            'type_translation': str,
+            'urn': str
+        }
+
+        self.attribute_map = {
+            'type': 'type',
+            'type_translation': 'type_translation',
+            'urn': 'urn'
+        }
+
+        self._type = type
+        self._type_translation = type_translation
+        self._urn = urn
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'ExerciseFormAllOf':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The ExerciseForm_allOf of this ExerciseFormAllOf.  # noqa: E501
+        :rtype: ExerciseFormAllOf
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def type(self):
+        """Gets the type of this ExerciseFormAllOf.
+
+        Type of exercise, concerning interaction and layout.  # noqa: E501
+
+        :return: The type of this ExerciseFormAllOf.
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this ExerciseFormAllOf.
+
+        Type of exercise, concerning interaction and layout.  # noqa: E501
+
+        :param type: The type of this ExerciseFormAllOf.
+        :type type: str
+        """
+        if type is None:
+            raise ValueError("Invalid value for `type`, must not be `None`")  # noqa: E501
+
+        self._type = type
+
+    @property
+    def type_translation(self):
+        """Gets the type_translation of this ExerciseFormAllOf.
+
+        Localized expression of the exercise type.  # noqa: E501
+
+        :return: The type_translation of this ExerciseFormAllOf.
+        :rtype: str
+        """
+        return self._type_translation
+
+    @type_translation.setter
+    def type_translation(self, type_translation):
+        """Sets the type_translation of this ExerciseFormAllOf.
+
+        Localized expression of the exercise type.  # noqa: E501
+
+        :param type_translation: The type_translation of this ExerciseFormAllOf.
+        :type type_translation: str
+        """
+
+        self._type_translation = type_translation
+
+    @property
+    def urn(self):
+        """Gets the urn of this ExerciseFormAllOf.
+
+        CTS URN for the text passage from which the exercise was created.  # noqa: E501
+
+        :return: The urn of this ExerciseFormAllOf.
+        :rtype: str
+        """
+        return self._urn
+
+    @urn.setter
+    def urn(self, urn):
+        """Sets the urn of this ExerciseFormAllOf.
+
+        CTS URN for the text passage from which the exercise was created.  # noqa: E501
+
+        :param urn: The urn of this ExerciseFormAllOf.
+        :type urn: str
+        """
+
+        self._urn = urn
diff --git a/mc_backend/openapi/openapi_server/models/exercise_form_comp.py b/mc_backend/openapi/openapi_server/models/exercise_form_comp.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bd935907bceacdc2bfcc4d08fff99050aa6f509
--- /dev/null
+++ b/mc_backend/openapi/openapi_server/models/exercise_form_comp.py
@@ -0,0 +1,356 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from openapi.openapi_server.models.base_model_ import Model
+from openapi.openapi_server.models.exercise_base import ExerciseBase
+from openapi.openapi_server.models.exercise_form import ExerciseForm
+from openapi.openapi_server import util
+
+from openapi.openapi_server.models.exercise_base import ExerciseBase  # noqa: E501
+from openapi.openapi_server.models.exercise_form import ExerciseForm  # noqa: E501
+
+class ExerciseFormComp(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, correct_feedback='', general_feedback='', incorrect_feedback='', instructions='', partially_correct_feedback='', search_values='[]', work_author='', work_title='', type=None, type_translation=None, urn=None):  # noqa: E501
+        """ExerciseFormComp - a model defined in OpenAPI
+
+        :param correct_feedback: The correct_feedback of this ExerciseFormComp.  # noqa: E501
+        :type correct_feedback: str
+        :param general_feedback: The general_feedback of this ExerciseFormComp.  # noqa: E501
+        :type general_feedback: str
+        :param incorrect_feedback: The incorrect_feedback of this ExerciseFormComp.  # noqa: E501
+        :type incorrect_feedback: str
+        :param instructions: The instructions of this ExerciseFormComp.  # noqa: E501
+        :type instructions: str
+        :param partially_correct_feedback: The partially_correct_feedback of this ExerciseFormComp.  # noqa: E501
+        :type partially_correct_feedback: str
+        :param search_values: The search_values of this ExerciseFormComp.  # noqa: E501
+        :type search_values: str
+        :param work_author: The work_author of this ExerciseFormComp.  # noqa: E501
+        :type work_author: str
+        :param work_title: The work_title of this ExerciseFormComp.  # noqa: E501
+        :type work_title: str
+        :param type: The type of this ExerciseFormComp.  # noqa: E501
+        :type type: str
+        :param type_translation: The type_translation of this ExerciseFormComp.  # noqa: E501
+        :type type_translation: str
+        :param urn: The urn of this ExerciseFormComp.  # noqa: E501
+        :type urn: str
+        """
+        self.openapi_types = {
+            'correct_feedback': str,
+            'general_feedback': str,
+            'incorrect_feedback': str,
+            'instructions': str,
+            'partially_correct_feedback': str,
+            'search_values': str,
+            'work_author': str,
+            'work_title': str,
+            'type': str,
+            'type_translation': str,
+            'urn': str
+        }
+
+        self.attribute_map = {
+            'correct_feedback': 'correct_feedback',
+            'general_feedback': 'general_feedback',
+            'incorrect_feedback': 'incorrect_feedback',
+            'instructions': 'instructions',
+            'partially_correct_feedback': 'partially_correct_feedback',
+            'search_values': 'search_values',
+            'work_author': 'work_author',
+            'work_title': 'work_title',
+            'type': 'type',
+            'type_translation': 'type_translation',
+            'urn': 'urn'
+        }
+
+        self._correct_feedback = correct_feedback
+        self._general_feedback = general_feedback
+        self._incorrect_feedback = incorrect_feedback
+        self._instructions = instructions
+        self._partially_correct_feedback = partially_correct_feedback
+        self._search_values = search_values
+        self._work_author = work_author
+        self._work_title = work_title
+        self._type = type
+        self._type_translation = type_translation
+        self._urn = urn
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'ExerciseFormComp':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The ExerciseFormComp of this ExerciseFormComp.  # noqa: E501
+        :rtype: ExerciseFormComp
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def correct_feedback(self):
+        """Gets the correct_feedback of this ExerciseFormComp.
+
+        Feedback for successful completion of the exercise.  # noqa: E501
+
+        :return: The correct_feedback of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._correct_feedback
+
+    @correct_feedback.setter
+    def correct_feedback(self, correct_feedback):
+        """Sets the correct_feedback of this ExerciseFormComp.
+
+        Feedback for successful completion of the exercise.  # noqa: E501
+
+        :param correct_feedback: The correct_feedback of this ExerciseFormComp.
+        :type correct_feedback: str
+        """
+
+        self._correct_feedback = correct_feedback
+
+    @property
+    def general_feedback(self):
+        """Gets the general_feedback of this ExerciseFormComp.
+
+        Feedback for finishing the exercise.  # noqa: E501
+
+        :return: The general_feedback of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._general_feedback
+
+    @general_feedback.setter
+    def general_feedback(self, general_feedback):
+        """Sets the general_feedback of this ExerciseFormComp.
+
+        Feedback for finishing the exercise.  # noqa: E501
+
+        :param general_feedback: The general_feedback of this ExerciseFormComp.
+        :type general_feedback: str
+        """
+
+        self._general_feedback = general_feedback
+
+    @property
+    def incorrect_feedback(self):
+        """Gets the incorrect_feedback of this ExerciseFormComp.
+
+        Feedback for failing to complete the exercise successfully.  # noqa: E501
+
+        :return: The incorrect_feedback of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._incorrect_feedback
+
+    @incorrect_feedback.setter
+    def incorrect_feedback(self, incorrect_feedback):
+        """Sets the incorrect_feedback of this ExerciseFormComp.
+
+        Feedback for failing to complete the exercise successfully.  # noqa: E501
+
+        :param incorrect_feedback: The incorrect_feedback of this ExerciseFormComp.
+        :type incorrect_feedback: str
+        """
+
+        self._incorrect_feedback = incorrect_feedback
+
+    @property
+    def instructions(self):
+        """Gets the instructions of this ExerciseFormComp.
+
+        Hints for how to complete the exercise.  # noqa: E501
+
+        :return: The instructions of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._instructions
+
+    @instructions.setter
+    def instructions(self, instructions):
+        """Sets the instructions of this ExerciseFormComp.
+
+        Hints for how to complete the exercise.  # noqa: E501
+
+        :param instructions: The instructions of this ExerciseFormComp.
+        :type instructions: str
+        """
+        if instructions is None:
+            raise ValueError("Invalid value for `instructions`, must not be `None`")  # noqa: E501
+
+        self._instructions = instructions
+
+    @property
+    def partially_correct_feedback(self):
+        """Gets the partially_correct_feedback of this ExerciseFormComp.
+
+        Feedback for successfully completing certain parts of the exercise.  # noqa: E501
+
+        :return: The partially_correct_feedback of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._partially_correct_feedback
+
+    @partially_correct_feedback.setter
+    def partially_correct_feedback(self, partially_correct_feedback):
+        """Sets the partially_correct_feedback of this ExerciseFormComp.
+
+        Feedback for successfully completing certain parts of the exercise.  # noqa: E501
+
+        :param partially_correct_feedback: The partially_correct_feedback of this ExerciseFormComp.
+        :type partially_correct_feedback: str
+        """
+
+        self._partially_correct_feedback = partially_correct_feedback
+
+    @property
+    def search_values(self):
+        """Gets the search_values of this ExerciseFormComp.
+
+        Search queries that were used to build the exercise.  # noqa: E501
+
+        :return: The search_values of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._search_values
+
+    @search_values.setter
+    def search_values(self, search_values):
+        """Sets the search_values of this ExerciseFormComp.
+
+        Search queries that were used to build the exercise.  # noqa: E501
+
+        :param search_values: The search_values of this ExerciseFormComp.
+        :type search_values: str
+        """
+        if search_values is None:
+            raise ValueError("Invalid value for `search_values`, must not be `None`")  # noqa: E501
+
+        self._search_values = search_values
+
+    @property
+    def work_author(self):
+        """Gets the work_author of this ExerciseFormComp.
+
+        Name of the person who wrote the base text for the exercise.  # noqa: E501
+
+        :return: The work_author of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._work_author
+
+    @work_author.setter
+    def work_author(self, work_author):
+        """Sets the work_author of this ExerciseFormComp.
+
+        Name of the person who wrote the base text for the exercise.  # noqa: E501
+
+        :param work_author: The work_author of this ExerciseFormComp.
+        :type work_author: str
+        """
+
+        self._work_author = work_author
+
+    @property
+    def work_title(self):
+        """Gets the work_title of this ExerciseFormComp.
+
+        Title of the base text for the exercise.  # noqa: E501
+
+        :return: The work_title of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._work_title
+
+    @work_title.setter
+    def work_title(self, work_title):
+        """Sets the work_title of this ExerciseFormComp.
+
+        Title of the base text for the exercise.  # noqa: E501
+
+        :param work_title: The work_title of this ExerciseFormComp.
+        :type work_title: str
+        """
+
+        self._work_title = work_title
+
+    @property
+    def type(self):
+        """Gets the type of this ExerciseFormComp.
+
+        Type of exercise, concerning interaction and layout.  # noqa: E501
+
+        :return: The type of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this ExerciseFormComp.
+
+        Type of exercise, concerning interaction and layout.  # noqa: E501
+
+        :param type: The type of this ExerciseFormComp.
+        :type type: str
+        """
+        if type is None:
+            raise ValueError("Invalid value for `type`, must not be `None`")  # noqa: E501
+
+        self._type = type
+
+    @property
+    def type_translation(self):
+        """Gets the type_translation of this ExerciseFormComp.
+
+        Localized expression of the exercise type.  # noqa: E501
+
+        :return: The type_translation of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._type_translation
+
+    @type_translation.setter
+    def type_translation(self, type_translation):
+        """Sets the type_translation of this ExerciseFormComp.
+
+        Localized expression of the exercise type.  # noqa: E501
+
+        :param type_translation: The type_translation of this ExerciseFormComp.
+        :type type_translation: str
+        """
+
+        self._type_translation = type_translation
+
+    @property
+    def urn(self):
+        """Gets the urn of this ExerciseFormComp.
+
+        CTS URN for the text passage from which the exercise was created.  # noqa: E501
+
+        :return: The urn of this ExerciseFormComp.
+        :rtype: str
+        """
+        return self._urn
+
+    @urn.setter
+    def urn(self, urn):
+        """Sets the urn of this ExerciseFormComp.
+
+        CTS URN for the text passage from which the exercise was created.  # noqa: E501
+
+        :param urn: The urn of this ExerciseFormComp.
+        :type urn: str
+        """
+
+        self._urn = urn
diff --git a/mc_backend/openapi/openapi_server/models/frequency_item.py b/mc_backend/openapi/openapi_server/models/frequency_item.py
new file mode 100644
index 0000000000000000000000000000000000000000..86c6b7ece70fb09e37e79acb290a7ba6c5cdc008
--- /dev/null
+++ b/mc_backend/openapi/openapi_server/models/frequency_item.py
@@ -0,0 +1,122 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from openapi.openapi_server.models.base_model_ import Model
+from openapi.openapi_server import util
+
+
+class FrequencyItem(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, count=None, phenomena=None, values=None):  # noqa: E501
+        """FrequencyItem - a model defined in OpenAPI
+
+        :param count: The count of this FrequencyItem.  # noqa: E501
+        :type count: int
+        :param phenomena: The phenomena of this FrequencyItem.  # noqa: E501
+        :type phenomena: List[str]
+        :param values: The values of this FrequencyItem.  # noqa: E501
+        :type values: List[str]
+        """
+        self.openapi_types = {
+            'count': int,
+            'phenomena': List[str],
+            'values': List[str]
+        }
+
+        self.attribute_map = {
+            'count': 'count',
+            'phenomena': 'phenomena',
+            'values': 'values'
+        }
+
+        self._count = count
+        self._phenomena = phenomena
+        self._values = values
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'FrequencyItem':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The FrequencyItem of this FrequencyItem.  # noqa: E501
+        :rtype: FrequencyItem
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def count(self):
+        """Gets the count of this FrequencyItem.
+
+        How often the given combination of values occurred.  # noqa: E501
+
+        :return: The count of this FrequencyItem.
+        :rtype: int
+        """
+        return self._count
+
+    @count.setter
+    def count(self, count):
+        """Sets the count of this FrequencyItem.
+
+        How often the given combination of values occurred.  # noqa: E501
+
+        :param count: The count of this FrequencyItem.
+        :type count: int
+        """
+
+        self._count = count
+
+    @property
+    def phenomena(self):
+        """Gets the phenomena of this FrequencyItem.
+
+        Labels for the phenomena described in this frequency entry.  # noqa: E501
+
+        :return: The phenomena of this FrequencyItem.
+        :rtype: List[str]
+        """
+        return self._phenomena
+
+    @phenomena.setter
+    def phenomena(self, phenomena):
+        """Sets the phenomena of this FrequencyItem.
+
+        Labels for the phenomena described in this frequency entry.  # noqa: E501
+
+        :param phenomena: The phenomena of this FrequencyItem.
+        :type phenomena: List[str]
+        """
+
+        self._phenomena = phenomena
+
+    @property
+    def values(self):
+        """Gets the values of this FrequencyItem.
+
+        Values for the phenomena described in this frequency entry.  # noqa: E501
+
+        :return: The values of this FrequencyItem.
+        :rtype: List[str]
+        """
+        return self._values
+
+    @values.setter
+    def values(self, values):
+        """Sets the values of this FrequencyItem.
+
+        Values for the phenomena described in this frequency entry.  # noqa: E501
+
+        :param values: The values of this FrequencyItem.
+        :type values: List[str]
+        """
+
+        self._values = values
diff --git a/mc_backend/openapi/openapi_server/models/graph_data.py b/mc_backend/openapi/openapi_server/models/graph_data.py
index dc97ff8b90ec4be283951fe89e3dffbc8c963726..0c49b408bfa7d6aa20479b719800171547187f62 100644
--- a/mc_backend/openapi/openapi_server/models/graph_data.py
+++ b/mc_backend/openapi/openapi_server/models/graph_data.py
@@ -7,11 +7,11 @@ from typing import List, Dict  # noqa: F401
 
 from openapi.openapi_server.models.base_model_ import Model
 from openapi.openapi_server.models.link import Link
-from openapi.openapi_server.models.node import Node
+from openapi.openapi_server.models.node_mc import NodeMC
 from openapi.openapi_server import util
 
 from openapi.openapi_server.models.link import Link  # noqa: E501
-from openapi.openapi_server.models.node import Node  # noqa: E501
+from openapi.openapi_server.models.node_mc import NodeMC  # noqa: E501
 
 class GraphData(Model):
     """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -31,14 +31,14 @@ class GraphData(Model):
         :param multigraph: The multigraph of this GraphData.  # noqa: E501
         :type multigraph: bool
         :param nodes: The nodes of this GraphData.  # noqa: E501
-        :type nodes: List[Node]
+        :type nodes: List[NodeMC]
         """
         self.openapi_types = {
             'directed': bool,
             'graph': object,
             'links': List[Link],
             'multigraph': bool,
-            'nodes': List[Node]
+            'nodes': List[NodeMC]
         }
 
         self.attribute_map = {
@@ -167,7 +167,7 @@ class GraphData(Model):
         List of nodes for the graph.  # noqa: E501
 
         :return: The nodes of this GraphData.
-        :rtype: List[Node]
+        :rtype: List[NodeMC]
         """
         return self._nodes
 
@@ -178,7 +178,7 @@ class GraphData(Model):
         List of nodes for the graph.  # noqa: E501
 
         :param nodes: The nodes of this GraphData.
-        :type nodes: List[Node]
+        :type nodes: List[NodeMC]
         """
         if nodes is None:
             raise ValueError("Invalid value for `nodes`, must not be `None`")  # noqa: E501
diff --git a/mc_backend/openapi/openapi_server/models/link.py b/mc_backend/openapi/openapi_server/models/link.py
index 25108293ea2c968d2e4fb32253f132917f37c399..5658339d8cbfc8cae2aa7b0ae63be4382d036dc4 100644
--- a/mc_backend/openapi/openapi_server/models/link.py
+++ b/mc_backend/openapi/openapi_server/models/link.py
@@ -82,8 +82,6 @@ class Link(Model):
         :param annis_component_name: The annis_component_name of this Link.
         :type annis_component_name: str
         """
-        if annis_component_name is None:
-            raise ValueError("Invalid value for `annis_component_name`, must not be `None`")  # noqa: E501
 
         self._annis_component_name = annis_component_name
 
@@ -107,8 +105,6 @@ class Link(Model):
         :param annis_component_type: The annis_component_type of this Link.
         :type annis_component_type: str
         """
-        if annis_component_type is None:
-            raise ValueError("Invalid value for `annis_component_type`, must not be `None`")  # noqa: E501
 
         self._annis_component_type = annis_component_type
 
@@ -132,8 +128,6 @@ class Link(Model):
         :param source: The source of this Link.
         :type source: str
         """
-        if source is None:
-            raise ValueError("Invalid value for `source`, must not be `None`")  # noqa: E501
 
         self._source = source
 
@@ -157,8 +151,6 @@ class Link(Model):
         :param target: The target of this Link.
         :type target: str
         """
-        if target is None:
-            raise ValueError("Invalid value for `target`, must not be `None`")  # noqa: E501
 
         self._target = target
 
diff --git a/mc_backend/openapi/openapi_server/models/node_mc.py b/mc_backend/openapi/openapi_server/models/node_mc.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa289986949cf2bc4d2ae326a6b9b763c0982ecd
--- /dev/null
+++ b/mc_backend/openapi/openapi_server/models/node_mc.py
@@ -0,0 +1,346 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime  # noqa: F401
+
+from typing import List, Dict  # noqa: F401
+
+from openapi.openapi_server.models.base_model_ import Model
+from openapi.openapi_server import util
+
+
+class NodeMC(Model):
+    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+
+    Do not edit the class manually.
+    """
+
+    def __init__(self, annis_node_name=None, annis_node_type=None, annis_tok=None, annis_type=None, id=None, is_oov=None, udep_lemma=None, udep_upostag=None, udep_xpostag=None, udep_feats=None, solution=None):  # noqa: E501
+        """NodeMC - a model defined in OpenAPI
+
+        :param annis_node_name: The annis_node_name of this NodeMC.  # noqa: E501
+        :type annis_node_name: str
+        :param annis_node_type: The annis_node_type of this NodeMC.  # noqa: E501
+        :type annis_node_type: str
+        :param annis_tok: The annis_tok of this NodeMC.  # noqa: E501
+        :type annis_tok: str
+        :param annis_type: The annis_type of this NodeMC.  # noqa: E501
+        :type annis_type: str
+        :param id: The id of this NodeMC.  # noqa: E501
+        :type id: str
+        :param is_oov: The is_oov of this NodeMC.  # noqa: E501
+        :type is_oov: bool
+        :param udep_lemma: The udep_lemma of this NodeMC.  # noqa: E501
+        :type udep_lemma: str
+        :param udep_upostag: The udep_upostag of this NodeMC.  # noqa: E501
+        :type udep_upostag: str
+        :param udep_xpostag: The udep_xpostag of this NodeMC.  # noqa: E501
+        :type udep_xpostag: str
+        :param udep_feats: The udep_feats of this NodeMC.  # noqa: E501
+        :type udep_feats: str
+        :param solution: The solution of this NodeMC.  # noqa: E501
+        :type solution: str
+        """
+        self.openapi_types = {
+            'annis_node_name': str,
+            'annis_node_type': str,
+            'annis_tok': str,
+            'annis_type': str,
+            'id': str,
+            'is_oov': bool,
+            'udep_lemma': str,
+            'udep_upostag': str,
+            'udep_xpostag': str,
+            'udep_feats': str,
+            'solution': str
+        }
+
+        self.attribute_map = {
+            'annis_node_name': 'annis_node_name',
+            'annis_node_type': 'annis_node_type',
+            'annis_tok': 'annis_tok',
+            'annis_type': 'annis_type',
+            'id': 'id',
+            'is_oov': 'is_oov',
+            'udep_lemma': 'udep_lemma',
+            'udep_upostag': 'udep_upostag',
+            'udep_xpostag': 'udep_xpostag',
+            'udep_feats': 'udep_feats',
+            'solution': 'solution'
+        }
+
+        self._annis_node_name = annis_node_name
+        self._annis_node_type = annis_node_type
+        self._annis_tok = annis_tok
+        self._annis_type = annis_type
+        self._id = id
+        self._is_oov = is_oov
+        self._udep_lemma = udep_lemma
+        self._udep_upostag = udep_upostag
+        self._udep_xpostag = udep_xpostag
+        self._udep_feats = udep_feats
+        self._solution = solution
+
+    @classmethod
+    def from_dict(cls, dikt) -> 'NodeMC':
+        """Returns the dict as a model
+
+        :param dikt: A dict.
+        :type: dict
+        :return: The NodeMC of this NodeMC.  # noqa: E501
+        :rtype: NodeMC
+        """
+        return util.deserialize_model(dikt, cls)
+
+    @property
+    def annis_node_name(self):
+        """Gets the annis_node_name of this NodeMC.
+
+        Node name as given by ANNIS.  # noqa: E501
+
+        :return: The annis_node_name of this NodeMC.
+        :rtype: str
+        """
+        return self._annis_node_name
+
+    @annis_node_name.setter
+    def annis_node_name(self, annis_node_name):
+        """Sets the annis_node_name of this NodeMC.
+
+        Node name as given by ANNIS.  # noqa: E501
+
+        :param annis_node_name: The annis_node_name of this NodeMC.
+        :type annis_node_name: str
+        """
+
+        self._annis_node_name = annis_node_name
+
+    @property
+    def annis_node_type(self):
+        """Gets the annis_node_type of this NodeMC.
+
+        Node type as given by ANNIS.  # noqa: E501
+
+        :return: The annis_node_type of this NodeMC.
+        :rtype: str
+        """
+        return self._annis_node_type
+
+    @annis_node_type.setter
+    def annis_node_type(self, annis_node_type):
+        """Sets the annis_node_type of this NodeMC.
+
+        Node type as given by ANNIS.  # noqa: E501
+
+        :param annis_node_type: The annis_node_type of this NodeMC.
+        :type annis_node_type: str
+        """
+
+        self._annis_node_type = annis_node_type
+
+    @property
+    def annis_tok(self):
+        """Gets the annis_tok of this NodeMC.
+
+        Raw word form as given by ANNIS.  # noqa: E501
+
+        :return: The annis_tok of this NodeMC.
+        :rtype: str
+        """
+        return self._annis_tok
+
+    @annis_tok.setter
+    def annis_tok(self, annis_tok):
+        """Sets the annis_tok of this NodeMC.
+
+        Raw word form as given by ANNIS.  # noqa: E501
+
+        :param annis_tok: The annis_tok of this NodeMC.
+        :type annis_tok: str
+        """
+
+        self._annis_tok = annis_tok
+
+    @property
+    def annis_type(self):
+        """Gets the annis_type of this NodeMC.
+
+        Node type as given by ANNIS (?).  # noqa: E501
+
+        :return: The annis_type of this NodeMC.
+        :rtype: str
+        """
+        return self._annis_type
+
+    @annis_type.setter
+    def annis_type(self, annis_type):
+        """Sets the annis_type of this NodeMC.
+
+        Node type as given by ANNIS (?).  # noqa: E501
+
+        :param annis_type: The annis_type of this NodeMC.
+        :type annis_type: str
+        """
+
+        self._annis_type = annis_type
+
+    @property
+    def id(self):
+        """Gets the id of this NodeMC.
+
+        Unique identifier for the node in the SALT model.  # noqa: E501
+
+        :return: The id of this NodeMC.
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this NodeMC.
+
+        Unique identifier for the node in the SALT model.  # noqa: E501
+
+        :param id: The id of this NodeMC.
+        :type id: str
+        """
+
+        self._id = id
+
+    @property
+    def is_oov(self):
+        """Gets the is_oov of this NodeMC.
+
+        Whether the raw word form is missing in a given vocabulary.  # noqa: E501
+
+        :return: The is_oov of this NodeMC.
+        :rtype: bool
+        """
+        return self._is_oov
+
+    @is_oov.setter
+    def is_oov(self, is_oov):
+        """Sets the is_oov of this NodeMC.
+
+        Whether the raw word form is missing in a given vocabulary.  # noqa: E501
+
+        :param is_oov: The is_oov of this NodeMC.
+        :type is_oov: bool
+        """
+
+        self._is_oov = is_oov
+
+    @property
+    def udep_lemma(self):
+        """Gets the udep_lemma of this NodeMC.
+
+        Lemmatized word form.  # noqa: E501
+
+        :return: The udep_lemma of this NodeMC.
+        :rtype: str
+        """
+        return self._udep_lemma
+
+    @udep_lemma.setter
+    def udep_lemma(self, udep_lemma):
+        """Sets the udep_lemma of this NodeMC.
+
+        Lemmatized word form.  # noqa: E501
+
+        :param udep_lemma: The udep_lemma of this NodeMC.
+        :type udep_lemma: str
+        """
+
+        self._udep_lemma = udep_lemma
+
+    @property
+    def udep_upostag(self):
+        """Gets the udep_upostag of this NodeMC.
+
+        Universal part of speech tag for the word form.  # noqa: E501
+
+        :return: The udep_upostag of this NodeMC.
+        :rtype: str
+        """
+        return self._udep_upostag
+
+    @udep_upostag.setter
+    def udep_upostag(self, udep_upostag):
+        """Sets the udep_upostag of this NodeMC.
+
+        Universal part of speech tag for the word form.  # noqa: E501
+
+        :param udep_upostag: The udep_upostag of this NodeMC.
+        :type udep_upostag: str
+        """
+
+        self._udep_upostag = udep_upostag
+
+    @property
+    def udep_xpostag(self):
+        """Gets the udep_xpostag of this NodeMC.
+
+        Language-specific part of speech tag for the word form.  # noqa: E501
+
+        :return: The udep_xpostag of this NodeMC.
+        :rtype: str
+        """
+        return self._udep_xpostag
+
+    @udep_xpostag.setter
+    def udep_xpostag(self, udep_xpostag):
+        """Sets the udep_xpostag of this NodeMC.
+
+        Language-specific part of speech tag for the word form.  # noqa: E501
+
+        :param udep_xpostag: The udep_xpostag of this NodeMC.
+        :type udep_xpostag: str
+        """
+
+        self._udep_xpostag = udep_xpostag
+
+    @property
+    def udep_feats(self):
+        """Gets the udep_feats of this NodeMC.
+
+        Additional morphological information.  # noqa: E501
+
+        :return: The udep_feats of this NodeMC.
+        :rtype: str
+        """
+        return self._udep_feats
+
+    @udep_feats.setter
+    def udep_feats(self, udep_feats):
+        """Sets the udep_feats of this NodeMC.
+
+        Additional morphological information.  # noqa: E501
+
+        :param udep_feats: The udep_feats of this NodeMC.
+        :type udep_feats: str
+        """
+
+        self._udep_feats = udep_feats
+
+    @property
+    def solution(self):
+        """Gets the solution of this NodeMC.
+
+        Solution value for this node in an exercise.  # noqa: E501
+
+        :return: The solution of this NodeMC.
+        :rtype: str
+        """
+        return self._solution
+
+    @solution.setter
+    def solution(self, solution):
+        """Sets the solution of this NodeMC.
+
+        Solution value for this node in an exercise.  # noqa: E501
+
+        :param solution: The solution of this NodeMC.
+        :type solution: str
+        """
+
+        self._solution = solution
diff --git a/mc_backend/openapi/openapi_server/models/solution_element.py b/mc_backend/openapi/openapi_server/models/solution_element.py
index 1e68e2fac7c4bec5be09179ccd791cc581897ec9..b64194bf68cb80dc31218e9ec64acf5066d09120 100644
--- a/mc_backend/openapi/openapi_server/models/solution_element.py
+++ b/mc_backend/openapi/openapi_server/models/solution_element.py
@@ -77,8 +77,6 @@ class SolutionElement(Model):
         :param content: The content of this SolutionElement.
         :type content: str
         """
-        if content is None:
-            raise ValueError("Invalid value for `content`, must not be `None`")  # noqa: E501
 
         self._content = content
 
diff --git a/mc_backend/openapi/openapi_server/openapi/openapi.yaml b/mc_backend/openapi/openapi_server/openapi/openapi.yaml
index 656e96b2d4f8065e9b0e4cceee45d78cdedcdb7a..c0dac33a854f54aa0d865fb181bfd79796664787 100644
--- a/mc_backend/openapi/openapi_server/openapi/openapi.yaml
+++ b/mc_backend/openapi/openapi_server/openapi/openapi.yaml
@@ -145,7 +145,11 @@ paths:
     post:
       operationId: mcserver_app_api_exercise_api_post
       requestBody:
-        $ref: '#/components/requestBodies/ExerciseForm'
+        content:
+          application/x-www-form-urlencoded:
+            schema:
+              $ref: '#/components/schemas/ExerciseForm'
+        required: true
       responses:
         "200":
           content:
@@ -155,35 +159,20 @@ paths:
           description: Exercise data object
       summary: Creates a new exercise.
       x-openapi-router-controller: openapi_server.controllers.default_controller
+  /exercises:
+    get:
+      operationId: mcserver_app_api_static_exercises_api_get
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                type: object
+          description: Metadata for static exercises, including their respective URIs
+            in the frontend.
+      summary: Returns metadata for static exercises.
+      x-openapi-router-controller: openapi_server.controllers.default_controller
 components:
-  requestBodies:
-    ExerciseForm:
-      content:
-        application/x-www-form-urlencoded:
-          schema:
-            allOf:
-            - $ref: '#/components/schemas/ExerciseBase'
-            - description: Additional exercise data.
-              properties:
-                type:
-                  description: Type of exercise, concerning interaction and layout.
-                  example: markWords
-                  type: string
-                type_translation:
-                  description: Localized expression of the exercise type.
-                  example: Cloze
-                  type: string
-                urn:
-                  description: CTS URN for the text passage from which the exercise
-                    was created.
-                  example: urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.1
-                  type: string
-              required:
-              - type
-              type: object
-            type: object
-            x-body-name: exercise_data
-      required: true
   schemas:
     AnnisResponse:
       description: A response with graph data from ANNIS, possibly with additional
@@ -286,7 +275,7 @@ components:
         frequency_analysis:
           description: List of items with frequency data for linguistic phenomena.
           items:
-            $ref: '#/components/schemas/AnnisResponse_frequency_analysis'
+            $ref: '#/components/schemas/FrequencyItem'
           type: array
         graph_data:
           $ref: '#/components/schemas/GraphData'
@@ -402,6 +391,36 @@ components:
           description: Title of the base text for the exercise.
           example: Noctes Atticae
           type: string
+      required:
+      - instructions
+      - search_values
+      type: object
+    ExerciseForm:
+      allOf:
+      - $ref: '#/components/schemas/ExerciseBase'
+      - $ref: '#/components/schemas/ExerciseForm_allOf'
+    FrequencyItem:
+      example:
+        values: []
+        count: 1
+        phenomena: []
+      properties:
+        count:
+          description: How often the given combination of values occurred.
+          example: 1
+          type: integer
+        phenomena:
+          description: Labels for the phenomena described in this frequency entry.
+          example: []
+          items:
+            type: string
+          type: array
+        values:
+          description: Values for the phenomena described in this frequency entry.
+          example: []
+          items:
+            type: string
+          type: array
       type: object
     GraphData:
       description: Nodes, edges and metadata for a graph.
@@ -464,7 +483,7 @@ components:
         nodes:
           description: List of nodes for the graph.
           items:
-            $ref: '#/components/schemas/Node'
+            $ref: '#/components/schemas/NodeMC'
           type: array
       required:
       - links
@@ -626,13 +645,8 @@ components:
           description: Dependency relation described by the edge.
           example: det
           type: string
-      required:
-      - annis_component_name
-      - annis_component_type
-      - source
-      - target
       type: object
-    Node:
+    NodeMC:
       example:
         annis_node_name: urn:custom:latinLit:proiel.caes-gal.lat:1.1.1/doc1#sent52548tok1
         udep_xpostag: Ne
@@ -689,14 +703,6 @@ components:
         solution:
           description: Solution value for this node in an exercise.
           type: string
-      required:
-      - annis_node_name
-      - annis_node_type
-      - annis_tok
-      - annis_type
-      - id
-      - udep_lemma
-      - udep_upostag
       type: object
     Solution:
       description: Correct solution for an exercise.
@@ -742,7 +748,6 @@ components:
           example: 9
           type: integer
       required:
-      - content
       - sentence_id
       - token_id
       type: object
@@ -863,28 +868,6 @@ components:
       - resource_type
       type: object
       x-tablename: UpdateInfo
-    AnnisResponse_frequency_analysis:
-      example:
-        values: []
-        count: 1
-        phenomena: []
-      properties:
-        count:
-          description: How often the given combination of values occurred.
-          example: 1
-          type: integer
-        phenomena:
-          description: Labels for the phenomena described in this frequency entry.
-          example: []
-          items:
-            type: string
-          type: array
-        values:
-          description: Values for the phenomena described in this frequency entry.
-          example: []
-          items:
-            type: string
-          type: array
     Exercise_allOf:
       description: Data for creating and evaluating interactive exercises.
       properties:
@@ -947,3 +930,20 @@ components:
       required:
       - eid
       - last_access_time
+    ExerciseForm_allOf:
+      description: Additional exercise data.
+      properties:
+        type:
+          description: Type of exercise, concerning interaction and layout.
+          example: markWords
+          type: string
+        type_translation:
+          description: Localized expression of the exercise type.
+          example: Cloze
+          type: string
+        urn:
+          description: CTS URN for the text passage from which the exercise was created.
+          example: urn:cts:latinLit:phi0448.phi001.perseus-lat2:1.1.1
+          type: string
+      required:
+      - type
diff --git a/mc_backend/openapi_generator.py b/mc_backend/openapi_generator.py
index ff06801e54b13e344339c450c0663722032aeb14..4b940a88cc5a5e3d58581399c8d330070d1e4536 100644
--- a/mc_backend/openapi_generator.py
+++ b/mc_backend/openapi_generator.py
@@ -4,7 +4,8 @@ import glob
 import os
 
 openapi_dir: str = "openapi"
-for file_path in glob.iglob(os.path.join(openapi_dir, "**/*"), recursive=True):
+change_count: int = 0
+for file_path in glob.iglob(os.path.join("mc_backend", openapi_dir, "**/*"), recursive=True):
     if not os.path.isdir(file_path) and file_path[-3:] == ".py":
         content: str
         with open(file_path) as f:
@@ -13,3 +14,5 @@ for file_path in glob.iglob(os.path.join(openapi_dir, "**/*"), recursive=True):
         content = content.replace("import openapi_server", f"import {openapi_dir}.openapi_server")
         with open(file_path, "w+") as f2:
             f2.write(content)
+            change_count += 1
+print(f"Adjusted python imports in {change_count} files.")
diff --git a/mc_backend/tests.py b/mc_backend/tests.py
index c24331c54430cf57179dacb98341694676e14472..e7993e26fc0d4c897f929db19bcbb75374b82bcd 100644
--- a/mc_backend/tests.py
+++ b/mc_backend/tests.py
@@ -376,14 +376,14 @@ class McTestCase(unittest.TestCase):
             response: Response = Mocks.app_dict[self.class_name].client.get(
                 TestingConfig.SERVER_URI_RAW_TEXT, query_string=dict(urn=Mocks.urn_custom))
             self.assertEqual(response.status_code, 404)
-            mock_get_cs.return_value = MockResponse(json.dumps(Mocks.graph_data.to_dict()))
+            mock_get_cs.return_value = MockResponse(json.dumps(Mocks.annis_response.to_dict()))
             response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_RAW_TEXT,
                                                                   query_string=dict(urn=Mocks.urn_custom))
             ar: AnnisResponse = AnnisResponse.from_dict(json.loads(response.get_data(as_text=True)))
             self.assertEqual(len(ar.graph_data.nodes), 52)
-            graph_data_raw: dict = dict(Mocks.annis_response_dict["graph_data_raw"])
-            graph_data_raw["nodes"] = []
-            mock_get_cs.return_value = MockResponse(json.dumps(graph_data_raw))
+            ar_copy: AnnisResponse = AnnisResponse.from_dict(Mocks.annis_response.to_dict())
+            ar_copy.graph_data.nodes = []
+            mock_get_cs.return_value = MockResponse(json.dumps(ar_copy.to_dict()))
             response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_RAW_TEXT,
                                                                   query_string=dict(urn=Mocks.urn_custom))
             self.assertEqual(response.status_code, 404)
@@ -413,15 +413,19 @@ class McTestCase(unittest.TestCase):
         for exercise in exercises:
             shutil.rmtree(os.path.join(Config.TMP_DIRECTORY, exercise[0]), ignore_errors=True)
         zip_content: bytes = open(TestingConfig.STATIC_EXERCISES_ZIP_FILE_PATH, "rb").read()
-        with patch.object(mcserver.app.api.staticExercisesAPI.requests, "get",
-                          side_effect=[MockResponse("{}", ok=False), MockResponse("{}", content=zip_content)]):
-            with patch.object(AnnotationService, "get_udpipe", return_value=Mocks.static_exercises_udpipe_string):
-                response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_STATIC_EXERCISES)
-                self.assertEqual(response.status_code, 503)
+        with patch.object(
+                mcserver.app.api.staticExercisesAPI.requests, "get", side_effect=[
+                    MockResponse("{}", ok=False), MockResponse("{}", content=zip_content)]):
+            with patch.object(AnnotationService, "get_udpipe",
+                              return_value=Mocks.static_exercises_udpipe_string) as mock_udpipe:
                 response: Response = Mocks.app_dict[self.class_name].client.get(
                     TestingConfig.SERVER_URI_STATIC_EXERCISES)
+                self.assertEqual(response.status_code, 503)
+                response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_STATIC_EXERCISES)
                 os.remove(TestingConfig.STATIC_EXERCISES_ZIP_FILE_PATH)
-                self.assertGreater(len(response.data.decode("utf-8")), 1900)
+                self.assertGreater(len(response.get_data(as_text=True)), 1900)
+                response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_STATIC_EXERCISES)
+                self.assertEqual(mock_udpipe.call_count, 1)
 
     @patch('mcserver.app.services.corpusService.requests.get', side_effect=mocked_requests_get)
     def test_api_subgraph_get(self, mock_get: MagicMock):
@@ -490,7 +494,7 @@ class McTestCase(unittest.TestCase):
     def test_api_vocabulary_get(self, mock_post: MagicMock):
         """ Calculates lexical overlap between a text (specified by URN) and a static vocabulary. """
         with patch.object(mcserver.app.services.corpusService.requests, "get",
-                          return_value=MockResponse(json.dumps(Mocks.graph_data.to_dict()))):
+                          return_value=MockResponse(json.dumps(Mocks.annis_response.to_dict()))):
             args: dict = dict(query_urn=Mocks.urn_custom, show_oov=True, vocabulary=VocabularyCorpus.agldt.name,
                               frequency_upper_bound=500)
             response: Response = Mocks.app_dict[self.class_name].client.get(TestingConfig.SERVER_URI_VOCABULARY,
@@ -838,7 +842,7 @@ class CommonTestCase(unittest.TestCase):
         """Initializes the testing environment."""
         self.start_time = time.time()
         self.class_name: str = str(self.__class__)
-        TestHelper.update_flask_app(self.class_name, create_app)
+        TestHelper.update_flask_app(self.class_name, create_csm_app)
 
     def tearDown(self):
         """Finishes testing by removing the traces."""
@@ -1010,6 +1014,7 @@ class CommonTestCase(unittest.TestCase):
 
     def test_init_db_corpus(self):
         """Initializes the corpus table."""
+        db.session.query(Corpus).delete()
         cc: CustomCorpus = CustomCorpusService.custom_corpora[0]
         old_corpus: Corpus = Mocks.corpora[0]
         old_corpus.source_urn = cc.corpus.source_urn
diff --git a/mc_frontend/openapi/.gitignore b/mc_frontend/openapi/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..149b57654723c14590f35c6c0db8460f19173078
--- /dev/null
+++ b/mc_frontend/openapi/.gitignore
@@ -0,0 +1,4 @@
+wwwroot/*.js
+node_modules
+typings
+dist
diff --git a/mc_frontend/openapi/.openapi-generator-ignore b/mc_frontend/openapi/.openapi-generator-ignore
new file mode 100644
index 0000000000000000000000000000000000000000..7484ee590a3894506cf063799b885428f95a71be
--- /dev/null
+++ b/mc_frontend/openapi/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/mc_frontend/openapi/.openapi-generator/VERSION b/mc_frontend/openapi/.openapi-generator/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..ecedc98d1d5a2a7d871b8be6e3c56029e6cc5460
--- /dev/null
+++ b/mc_frontend/openapi/.openapi-generator/VERSION
@@ -0,0 +1 @@
+4.3.1
\ No newline at end of file
diff --git a/mc_frontend/openapi/README.md b/mc_frontend/openapi/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e0c9192e33e220a8d689ae9dd5f37c09c240bb4e
--- /dev/null
+++ b/mc_frontend/openapi/README.md
@@ -0,0 +1,205 @@
+## @
+
+### Building
+
+To install the required dependencies and to build the typescript sources run:
+```
+npm install
+npm run build
+```
+
+### publishing
+
+First build the package then run ```npm publish```
+
+### consuming
+
+Navigate to the folder of your consuming project and run one of next commands.
+
+_published:_
+
+```
+npm install @ --save
+```
+
+_without publishing (not recommended):_
+
+```
+npm install PATH_TO_GENERATED_PACKAGE/-.tgz --save
+```
+
+_It's important to take the tgz file, otherwise you'll get trouble with links on windows_
+
+_using `npm link`:_
+
+In PATH_TO_GENERATED_PACKAGE:
+```
+npm link
+```
+
+In your project:
+```
+npm link 
+```
+
+__Note for Windows users:__ The Angular CLI has troubles to use linked npm packages.
+Please refer to this issue https://github.com/angular/angular-cli/issues/8284 for a solution / workaround.
+Published packages are not effected by this issue.
+
+
+#### General usage
+
+In your Angular project:
+
+
+```
+// without configuring providers
+import { ApiModule } from '';
+import { HttpClientModule } from '@angular/common/http';
+
+
+@NgModule({
+    imports: [
+        ApiModule,
+        // make sure to import the HttpClientModule in the AppModule only,
+        // see https://github.com/angular/angular/issues/20575
+        HttpClientModule
+    ],
+    declarations: [ AppComponent ],
+    providers: [],
+    bootstrap: [ AppComponent ]
+})
+export class AppModule {}
+```
+
+```
+// configuring providers
+import { ApiModule, Configuration, ConfigurationParameters } from '';
+
+export function apiConfigFactory (): Configuration => {
+  const params: ConfigurationParameters = {
+    // set configuration parameters here.
+  }
+  return new Configuration(params);
+}
+
+@NgModule({
+    imports: [ ApiModule.forRoot(apiConfigFactory) ],
+    declarations: [ AppComponent ],
+    providers: [],
+    bootstrap: [ AppComponent ]
+})
+export class AppModule {}
+```
+
+```
+// configuring providers with an authentication service that manages your access tokens
+import { ApiModule, Configuration } from '';
+
+@NgModule({
+    imports: [ ApiModule ],
+    declarations: [ AppComponent ],
+    providers: [
+      {
+        provide: Configuration,
+        useFactory: (authService: AuthService) => new Configuration(
+          {
+            basePath: environment.apiUrl,
+            accessToken: authService.getAccessToken.bind(authService)
+          }
+        ),
+        deps: [AuthService],
+        multi: false
+      }
+    ],
+    bootstrap: [ AppComponent ]
+})
+export class AppModule {}
+```
+
+```
+import { DefaultApi } from '';
+
+export class AppComponent {
+	 constructor(private apiGateway: DefaultApi) { }
+}
+```
+
+Note: The ApiModule is restricted to being instantiated once app wide.
+This is to ensure that all services are treated as singletons.
+
+#### Using multiple OpenAPI files / APIs / ApiModules
+In order to use multiple `ApiModules` generated from different OpenAPI files,
+you can create an alias name when importing the modules
+in order to avoid naming conflicts:
+```
+import { ApiModule } from 'my-api-path';
+import { ApiModule as OtherApiModule } from 'my-other-api-path';
+import { HttpClientModule } from '@angular/common/http';
+
+
+@NgModule({
+  imports: [
+    ApiModule,
+    OtherApiModule,
+    // make sure to import the HttpClientModule in the AppModule only,
+    // see https://github.com/angular/angular/issues/20575
+    HttpClientModule
+  ]
+})
+export class AppModule {
+
+}
+```
+
+
+### Set service base path
+If different than the generated base path, during app bootstrap, you can provide the base path to your service. 
+
+```
+import { BASE_PATH } from '';
+
+bootstrap(AppComponent, [
+    { provide: BASE_PATH, useValue: 'https://your-web-service.com' },
+]);
+```
+or
+
+```
+import { BASE_PATH } from '';
+
+@NgModule({
+    imports: [],
+    declarations: [ AppComponent ],
+    providers: [ provide: BASE_PATH, useValue: 'https://your-web-service.com' ],
+    bootstrap: [ AppComponent ]
+})
+export class AppModule {}
+```
+
+
+#### Using @angular/cli
+First extend your `src/environments/*.ts` files by adding the corresponding base path:
+
+```
+export const environment = {
+  production: false,
+  API_BASE_PATH: 'http://127.0.0.1:8080'
+};
+```
+
+In the src/app/app.module.ts:
+```
+import { BASE_PATH } from '';
+import { environment } from '../environments/environment';
+
+@NgModule({
+  declarations: [
+    AppComponent
+  ],
+  imports: [ ],
+  providers: [{ provide: BASE_PATH, useValue: environment.API_BASE_PATH }],
+  bootstrap: [ AppComponent ]
+})
+export class AppModule { }
+```  
diff --git a/mc_frontend/openapi/api.module.ts b/mc_frontend/openapi/api.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6cdacac8b073e051d52dfe7fddb5b1cda70dfd3f
--- /dev/null
+++ b/mc_frontend/openapi/api.module.ts
@@ -0,0 +1,32 @@
+import { NgModule, ModuleWithProviders, SkipSelf, Optional } from '@angular/core';
+import { Configuration } from './configuration';
+import { HttpClient } from '@angular/common/http';
+
+
+import { DefaultService } from './api/default.service';
+
+@NgModule({
+  imports:      [],
+  declarations: [],
+  exports:      [],
+  providers: []
+})
+export class ApiModule {
+    public static forRoot(configurationFactory: () => Configuration): ModuleWithProviders<ApiModule> {
+        return {
+            ngModule: ApiModule,
+            providers: [ { provide: Configuration, useFactory: configurationFactory } ]
+        };
+    }
+
+    constructor( @Optional() @SkipSelf() parentModule: ApiModule,
+                 @Optional() http: HttpClient) {
+        if (parentModule) {
+            throw new Error('ApiModule is already loaded. Import in your base AppModule only.');
+        }
+        if (!http) {
+            throw new Error('You need to import the HttpClientModule in your AppModule! \n' +
+            'See also https://github.com/angular/angular/issues/20575');
+        }
+    }
+}
diff --git a/mc_frontend/openapi/api/api.ts b/mc_frontend/openapi/api/api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8e76619647f46d82e4dd2f73185693ec25d07cf7
--- /dev/null
+++ b/mc_frontend/openapi/api/api.ts
@@ -0,0 +1,3 @@
+export * from './default.service';
+import { DefaultService } from './default.service';
+export const APIS = [DefaultService];
diff --git a/mc_frontend/openapi/api/default.service.ts b/mc_frontend/openapi/api/default.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..90aa61b41c24c6c198f0030a4508e8177ac812ea
--- /dev/null
+++ b/mc_frontend/openapi/api/default.service.ts
@@ -0,0 +1,430 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+/* tslint:disable:no-unused-variable member-ordering */
+
+import { Inject, Injectable, Optional }                      from '@angular/core';
+import { HttpClient, HttpHeaders, HttpParams,
+         HttpResponse, HttpEvent, HttpParameterCodec }       from '@angular/common/http';
+import { CustomHttpParameterCodec }                          from '../encoder';
+import { Observable }                                        from 'rxjs';
+
+import { AnnisResponse } from '../model/models';
+import { Corpus } from '../model/models';
+
+import { BASE_PATH, COLLECTION_FORMATS }                     from '../variables';
+import { Configuration }                                     from '../configuration';
+
+
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DefaultService {
+
+    protected basePath = 'http://localhost:5000/mc/api/v1.0';
+    public defaultHeaders = new HttpHeaders();
+    public configuration = new Configuration();
+    public encoder: HttpParameterCodec;
+
+    constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
+        if (configuration) {
+            this.configuration = configuration;
+        }
+        if (typeof this.configuration.basePath !== 'string') {
+            if (typeof basePath !== 'string') {
+                basePath = this.basePath;
+            }
+            this.configuration.basePath = basePath;
+        }
+        this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
+    }
+
+
+
+    private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
+        if (typeof value === "object" && value instanceof Date === false) {
+            httpParams = this.addToHttpParamsRecursive(httpParams, value);
+        } else {
+            httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
+        }
+        return httpParams;
+    }
+
+    private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
+        if (value == null) {
+            return httpParams;
+        }
+
+        if (typeof value === "object") {
+            if (Array.isArray(value)) {
+                (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
+            } else if (value instanceof Date) {
+                if (key != null) {
+                    httpParams = httpParams.append(key,
+                        (value as Date).toISOString().substr(0, 10));
+                } else {
+                   throw Error("key may not be null if value is Date");
+                }
+            } else {
+                Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive(
+                    httpParams, value[k], key != null ? `${key}.${k}` : k));
+            }
+        } else if (key != null) {
+            httpParams = httpParams.append(key, value);
+        } else {
+            throw Error("key may not be null if value is not object or array");
+        }
+        return httpParams;
+    }
+
+    /**
+     * Deletes a single corpus by ID.
+     * @param cid Corpus identifier.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiCorpusAPIDelete(cid: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<boolean>;
+    public mcserverAppApiCorpusAPIDelete(cid: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<boolean>>;
+    public mcserverAppApiCorpusAPIDelete(cid: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<boolean>>;
+    public mcserverAppApiCorpusAPIDelete(cid: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+        if (cid === null || cid === undefined) {
+            throw new Error('Required parameter cid was null or undefined when calling mcserverAppApiCorpusAPIDelete.');
+        }
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.delete<boolean>(`${this.configuration.basePath}/corpora/${encodeURIComponent(String(cid))}`,
+            {
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+    /**
+     * Returns a single corpus by ID.
+     * @param cid Corpus identifier.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiCorpusAPIGet(cid: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<Corpus>;
+    public mcserverAppApiCorpusAPIGet(cid: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<Corpus>>;
+    public mcserverAppApiCorpusAPIGet(cid: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<Corpus>>;
+    public mcserverAppApiCorpusAPIGet(cid: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+        if (cid === null || cid === undefined) {
+            throw new Error('Required parameter cid was null or undefined when calling mcserverAppApiCorpusAPIGet.');
+        }
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.get<Corpus>(`${this.configuration.basePath}/corpora/${encodeURIComponent(String(cid))}`,
+            {
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+    /**
+     * Updates a single corpus by ID.
+     * @param cid Corpus identifier.
+     * @param author Author of the texts in the corpus.
+     * @param sourceUrn CTS base URN for referencing the corpus.
+     * @param title Corpus title.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiCorpusAPIPatch(cid: number, author?: string, sourceUrn?: string, title?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<Corpus>;
+    public mcserverAppApiCorpusAPIPatch(cid: number, author?: string, sourceUrn?: string, title?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<Corpus>>;
+    public mcserverAppApiCorpusAPIPatch(cid: number, author?: string, sourceUrn?: string, title?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<Corpus>>;
+    public mcserverAppApiCorpusAPIPatch(cid: number, author?: string, sourceUrn?: string, title?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+        if (cid === null || cid === undefined) {
+            throw new Error('Required parameter cid was null or undefined when calling mcserverAppApiCorpusAPIPatch.');
+        }
+
+        let queryParameters = new HttpParams({encoder: this.encoder});
+        if (author !== undefined && author !== null) {
+          queryParameters = this.addToHttpParams(queryParameters,
+            <any>author, 'author');
+        }
+        if (sourceUrn !== undefined && sourceUrn !== null) {
+          queryParameters = this.addToHttpParams(queryParameters,
+            <any>sourceUrn, 'source_urn');
+        }
+        if (title !== undefined && title !== null) {
+          queryParameters = this.addToHttpParams(queryParameters,
+            <any>title, 'title');
+        }
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.patch<Corpus>(`${this.configuration.basePath}/corpora/${encodeURIComponent(String(cid))}`,
+            null,
+            {
+                params: queryParameters,
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+    /**
+     * Returns a list of corpora.
+     * @param lastUpdateTime Time (in milliseconds) of the last update.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiCorpusListAPIGet(lastUpdateTime: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<Corpus>;
+    public mcserverAppApiCorpusListAPIGet(lastUpdateTime: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<Corpus>>;
+    public mcserverAppApiCorpusListAPIGet(lastUpdateTime: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<Corpus>>;
+    public mcserverAppApiCorpusListAPIGet(lastUpdateTime: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+        if (lastUpdateTime === null || lastUpdateTime === undefined) {
+            throw new Error('Required parameter lastUpdateTime was null or undefined when calling mcserverAppApiCorpusListAPIGet.');
+        }
+
+        let queryParameters = new HttpParams({encoder: this.encoder});
+        if (lastUpdateTime !== undefined && lastUpdateTime !== null) {
+          queryParameters = this.addToHttpParams(queryParameters,
+            <any>lastUpdateTime, 'last_update_time');
+        }
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.get<Corpus>(`${this.configuration.basePath}/corpora`,
+            {
+                params: queryParameters,
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+    /**
+     * Returns exercise data by ID.
+     * @param eid Unique identifier (UUID) for the exercise.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiExerciseAPIGet(eid: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<AnnisResponse>;
+    public mcserverAppApiExerciseAPIGet(eid: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<AnnisResponse>>;
+    public mcserverAppApiExerciseAPIGet(eid: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<AnnisResponse>>;
+    public mcserverAppApiExerciseAPIGet(eid: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+        if (eid === null || eid === undefined) {
+            throw new Error('Required parameter eid was null or undefined when calling mcserverAppApiExerciseAPIGet.');
+        }
+
+        let queryParameters = new HttpParams({encoder: this.encoder});
+        if (eid !== undefined && eid !== null) {
+          queryParameters = this.addToHttpParams(queryParameters,
+            <any>eid, 'eid');
+        }
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.get<AnnisResponse>(`${this.configuration.basePath}/exercise`,
+            {
+                params: queryParameters,
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+    /**
+     * Creates a new exercise.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiExerciseAPIPost(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<AnnisResponse>;
+    public mcserverAppApiExerciseAPIPost(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<AnnisResponse>>;
+    public mcserverAppApiExerciseAPIPost(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<AnnisResponse>>;
+    public mcserverAppApiExerciseAPIPost(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.post<AnnisResponse>(`${this.configuration.basePath}/exercise`,
+            null,
+            {
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+    /**
+     * Returns metadata for static exercises.
+     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
+     * @param reportProgress flag to report request and response progress.
+     */
+    public mcserverAppApiStaticExercisesAPIGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<object>;
+    public mcserverAppApiStaticExercisesAPIGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<object>>;
+    public mcserverAppApiStaticExercisesAPIGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<object>>;
+    public mcserverAppApiStaticExercisesAPIGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
+
+        let headers = this.defaultHeaders;
+
+        let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
+        if (httpHeaderAcceptSelected === undefined) {
+            // to determine the Accept header
+            const httpHeaderAccepts: string[] = [
+                'application/json'
+            ];
+            httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
+        }
+        if (httpHeaderAcceptSelected !== undefined) {
+            headers = headers.set('Accept', httpHeaderAcceptSelected);
+        }
+
+
+        let responseType: 'text' | 'json' = 'json';
+        if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
+            responseType = 'text';
+        }
+
+        return this.httpClient.get<object>(`${this.configuration.basePath}/exercises`,
+            {
+                responseType: <any>responseType,
+                withCredentials: this.configuration.withCredentials,
+                headers: headers,
+                observe: observe,
+                reportProgress: reportProgress
+            }
+        );
+    }
+
+}
diff --git a/mc_frontend/openapi/configuration.ts b/mc_frontend/openapi/configuration.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c038bbc94787446ecda4640d7b486a3a87c9c746
--- /dev/null
+++ b/mc_frontend/openapi/configuration.ts
@@ -0,0 +1,84 @@
+import { HttpParameterCodec } from '@angular/common/http';
+
+export interface ConfigurationParameters {
+    apiKeys?: {[ key: string ]: string};
+    username?: string;
+    password?: string;
+    accessToken?: string | (() => string);
+    basePath?: string;
+    withCredentials?: boolean;
+    encoder?: HttpParameterCodec;
+}
+
+export class Configuration {
+    apiKeys?: {[ key: string ]: string};
+    username?: string;
+    password?: string;
+    accessToken?: string | (() => string);
+    basePath?: string;
+    withCredentials?: boolean;
+    encoder?: HttpParameterCodec;
+
+    constructor(configurationParameters: ConfigurationParameters = {}) {
+        this.apiKeys = configurationParameters.apiKeys;
+        this.username = configurationParameters.username;
+        this.password = configurationParameters.password;
+        this.accessToken = configurationParameters.accessToken;
+        this.basePath = configurationParameters.basePath;
+        this.withCredentials = configurationParameters.withCredentials;
+        this.encoder = configurationParameters.encoder;
+    }
+
+    /**
+     * Select the correct content-type to use for a request.
+     * Uses {@link Configuration#isJsonMime} to determine the correct content-type.
+     * If no content type is found return the first found type if the contentTypes is not empty
+     * @param contentTypes - the array of content types that are available for selection
+     * @returns the selected content-type or <code>undefined</code> if no selection could be made.
+     */
+    public selectHeaderContentType (contentTypes: string[]): string | undefined {
+        if (contentTypes.length === 0) {
+            return undefined;
+        }
+
+        const type = contentTypes.find((x: string) => this.isJsonMime(x));
+        if (type === undefined) {
+            return contentTypes[0];
+        }
+        return type;
+    }
+
+    /**
+     * Select the correct accept content-type to use for a request.
+     * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type.
+     * If no content type is found return the first found type if the contentTypes is not empty
+     * @param accepts - the array of content types that are available for selection.
+     * @returns the selected content-type or <code>undefined</code> if no selection could be made.
+     */
+    public selectHeaderAccept(accepts: string[]): string | undefined {
+        if (accepts.length === 0) {
+            return undefined;
+        }
+
+        const type = accepts.find((x: string) => this.isJsonMime(x));
+        if (type === undefined) {
+            return accepts[0];
+        }
+        return type;
+    }
+
+    /**
+     * Check if the given MIME is a JSON MIME.
+     * JSON MIME examples:
+     *   application/json
+     *   application/json; charset=UTF8
+     *   APPLICATION/JSON
+     *   application/vnd.company+json
+     * @param mime - MIME (Multipurpose Internet Mail Extensions)
+     * @return True if the given MIME is JSON, false otherwise.
+     */
+    public isJsonMime(mime: string): boolean {
+        const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
+        return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
+    }
+}
diff --git a/mc_frontend/openapi/encoder.ts b/mc_frontend/openapi/encoder.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cbefb4a6dd9507f1893d81772f58f50c8f46e5ee
--- /dev/null
+++ b/mc_frontend/openapi/encoder.ts
@@ -0,0 +1,21 @@
+import { HttpParameterCodec } from '@angular/common/http';
+
+/**
+ * Custom HttpParameterCodec
+ * Workaround for https://github.com/angular/angular/issues/18261
+ */
+export class CustomHttpParameterCodec implements HttpParameterCodec {
+    encodeKey(k: string): string {
+        return encodeURIComponent(k);
+    }
+    encodeValue(v: string): string {
+        return encodeURIComponent(v);
+    }
+    decodeKey(k: string): string {
+        return decodeURIComponent(k);
+    }
+    decodeValue(v: string): string {
+        return decodeURIComponent(v);
+    }
+}
+
diff --git a/mc_frontend/openapi/git_push.sh b/mc_frontend/openapi/git_push.sh
new file mode 100644
index 0000000000000000000000000000000000000000..ced3be2b0c7b2349ff06d18da19d4b31435c9fa6
--- /dev/null
+++ b/mc_frontend/openapi/git_push.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+    git_host="github.com"
+    echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+    git_user_id="GIT_USER_ID"
+    echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+    git_repo_id="GIT_REPO_ID"
+    echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+    release_note="Minor update"
+    echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=`git remote`
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+    if [ "$GIT_TOKEN" = "" ]; then
+        echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+        git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+    else
+        git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git
+    fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
+
diff --git a/mc_frontend/openapi/index.ts b/mc_frontend/openapi/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c312b70fa3efccdb08c52608b493887052a18d30
--- /dev/null
+++ b/mc_frontend/openapi/index.ts
@@ -0,0 +1,5 @@
+export * from './api/api';
+export * from './model/models';
+export * from './variables';
+export * from './configuration';
+export * from './api.module';
\ No newline at end of file
diff --git a/mc_frontend/openapi/model/annisResponse.ts b/mc_frontend/openapi/model/annisResponse.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cbec68dc7dd565a1bbe107a6ae0757a2c34b38d8
--- /dev/null
+++ b/mc_frontend/openapi/model/annisResponse.ts
@@ -0,0 +1,45 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+import { FrequencyItem } from './frequencyItem';
+import { TextComplexity } from './textComplexity';
+import { GraphData } from './graphData';
+import { Solution } from './solution';
+
+
+/**
+ * A response with graph data from ANNIS, possibly with additional data for exercises.
+ */
+export interface AnnisResponse { 
+    /**
+     * Unique identifier (UUID) for the exercise.
+     */
+    exercise_id?: string;
+    /**
+     * Type of exercise, concerning interaction and layout.
+     */
+    exercise_type?: string;
+    /**
+     * List of items with frequency data for linguistic phenomena.
+     */
+    frequency_analysis?: Array<FrequencyItem>;
+    graph_data?: GraphData;
+    /**
+     * Correct solutions for this exercise.
+     */
+    solutions?: Array<Solution>;
+    text_complexity?: TextComplexity;
+    /**
+     * URI for accessing the exercise in this API.
+     */
+    uri?: string;
+}
+
diff --git a/mc_frontend/openapi/model/annisResponseFrequencyAnalysis.ts b/mc_frontend/openapi/model/annisResponseFrequencyAnalysis.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e9f2e78aba35117004ed6a5953825bbaccf0c5bf
--- /dev/null
+++ b/mc_frontend/openapi/model/annisResponseFrequencyAnalysis.ts
@@ -0,0 +1,28 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface AnnisResponseFrequencyAnalysis { 
+    /**
+     * How often the given combination of values occurred.
+     */
+    count?: number;
+    /**
+     * Labels for the phenomena described in this frequency entry.
+     */
+    phenomena?: Array<string>;
+    /**
+     * Values for the phenomena described in this frequency entry.
+     */
+    values?: Array<string>;
+}
+
diff --git a/mc_frontend/openapi/model/corpus.ts b/mc_frontend/openapi/model/corpus.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f7fbe4562229eb3a3daea6efe1b35851942ec835
--- /dev/null
+++ b/mc_frontend/openapi/model/corpus.ts
@@ -0,0 +1,47 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Collection of texts.
+ */
+export interface Corpus { 
+    /**
+     * Author of the texts in the corpus.
+     */
+    author?: string;
+    /**
+     * Unique identifier for the corpus.
+     */
+    cid?: number;
+    /**
+     * First level for citing the corpus.
+     */
+    citation_level_1?: string;
+    /**
+     * Second level for citing the corpus.
+     */
+    citation_level_2?: string;
+    /**
+     * Third level for citing the corpus.
+     */
+    citation_level_3?: string;
+    /**
+     * CTS base URN for referencing the corpus.
+     */
+    source_urn: string;
+    /**
+     * Corpus title.
+     */
+    title?: string;
+}
+
diff --git a/mc_frontend/openapi/model/exercise.ts b/mc_frontend/openapi/model/exercise.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b19cf214020f7a1f001cc89cb9c8f77eed1d9581
--- /dev/null
+++ b/mc_frontend/openapi/model/exercise.ts
@@ -0,0 +1,86 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+import { ExerciseAllOf } from './exerciseAllOf';
+import { ExerciseBase } from './exerciseBase';
+
+
+export interface Exercise { 
+    /**
+     * Feedback for successful completion of the exercise.
+     */
+    correct_feedback?: string;
+    /**
+     * Feedback for finishing the exercise.
+     */
+    general_feedback?: string;
+    /**
+     * Feedback for failing to complete the exercise successfully.
+     */
+    incorrect_feedback?: string;
+    /**
+     * Hints for how to complete the exercise.
+     */
+    instructions: string;
+    /**
+     * Feedback for successfully completing certain parts of the exercise.
+     */
+    partially_correct_feedback?: string;
+    /**
+     * Search queries that were used to build the exercise.
+     */
+    search_values: string;
+    /**
+     * Name of the person who wrote the base text for the exercise.
+     */
+    work_author?: string;
+    /**
+     * Title of the base text for the exercise.
+     */
+    work_title?: string;
+    /**
+     * CONLL-formatted linguistic annotations represented as a single string.
+     */
+    conll?: string;
+    /**
+     * Unique identifier (UUID) for the exercise.
+     */
+    eid: string;
+    /**
+     * Type of exercise, concerning interaction and layout.
+     */
+    exercise_type?: string;
+    /**
+     * Localized expression of the exercise type.
+     */
+    exercise_type_translation?: string;
+    /**
+     * ISO 639-1 Language Code for the localization of exercise content.
+     */
+    language?: string;
+    /**
+     * When the exercise was last accessed (as POSIX timestamp).
+     */
+    last_access_time: number;
+    /**
+     * Correct solutions for the exercise.
+     */
+    solutions?: string;
+    /**
+     * Overall text complexity as measured by the software\'s internal language analysis.
+     */
+    text_complexity?: number;
+    /**
+     * CTS URN for the text passage from which the exercise was created.
+     */
+    urn?: string;
+}
+
diff --git a/mc_frontend/openapi/model/exerciseAllOf.ts b/mc_frontend/openapi/model/exerciseAllOf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b66c2e8c12b3b9f0aabb20fda7876b1ebcd8300
--- /dev/null
+++ b/mc_frontend/openapi/model/exerciseAllOf.ts
@@ -0,0 +1,55 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Data for creating and evaluating interactive exercises.
+ */
+export interface ExerciseAllOf { 
+    /**
+     * CONLL-formatted linguistic annotations represented as a single string.
+     */
+    conll?: string;
+    /**
+     * Unique identifier (UUID) for the exercise.
+     */
+    eid: string;
+    /**
+     * Type of exercise, concerning interaction and layout.
+     */
+    exercise_type?: string;
+    /**
+     * Localized expression of the exercise type.
+     */
+    exercise_type_translation?: string;
+    /**
+     * ISO 639-1 Language Code for the localization of exercise content.
+     */
+    language?: string;
+    /**
+     * When the exercise was last accessed (as POSIX timestamp).
+     */
+    last_access_time: number;
+    /**
+     * Correct solutions for the exercise.
+     */
+    solutions?: string;
+    /**
+     * Overall text complexity as measured by the software\'s internal language analysis.
+     */
+    text_complexity?: number;
+    /**
+     * CTS URN for the text passage from which the exercise was created.
+     */
+    urn?: string;
+}
+
diff --git a/mc_frontend/openapi/model/exerciseBase.ts b/mc_frontend/openapi/model/exerciseBase.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c7bc224537348de4455b94f03befae125df690b
--- /dev/null
+++ b/mc_frontend/openapi/model/exerciseBase.ts
@@ -0,0 +1,51 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Base data for creating and evaluating interactive exercises.
+ */
+export interface ExerciseBase { 
+    /**
+     * Feedback for successful completion of the exercise.
+     */
+    correct_feedback?: string;
+    /**
+     * Feedback for finishing the exercise.
+     */
+    general_feedback?: string;
+    /**
+     * Feedback for failing to complete the exercise successfully.
+     */
+    incorrect_feedback?: string;
+    /**
+     * Hints for how to complete the exercise.
+     */
+    instructions: string;
+    /**
+     * Feedback for successfully completing certain parts of the exercise.
+     */
+    partially_correct_feedback?: string;
+    /**
+     * Search queries that were used to build the exercise.
+     */
+    search_values: string;
+    /**
+     * Name of the person who wrote the base text for the exercise.
+     */
+    work_author?: string;
+    /**
+     * Title of the base text for the exercise.
+     */
+    work_title?: string;
+}
+
diff --git a/mc_frontend/openapi/model/exerciseForm.ts b/mc_frontend/openapi/model/exerciseForm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9203fa7940edeaaaeb33d2d7b75e2ca452c4adba
--- /dev/null
+++ b/mc_frontend/openapi/model/exerciseForm.ts
@@ -0,0 +1,62 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+import { ExerciseFormAllOf } from './exerciseFormAllOf';
+import { ExerciseBase } from './exerciseBase';
+
+
+export interface ExerciseForm { 
+    /**
+     * Feedback for successful completion of the exercise.
+     */
+    correct_feedback?: string;
+    /**
+     * Feedback for finishing the exercise.
+     */
+    general_feedback?: string;
+    /**
+     * Feedback for failing to complete the exercise successfully.
+     */
+    incorrect_feedback?: string;
+    /**
+     * Hints for how to complete the exercise.
+     */
+    instructions: string;
+    /**
+     * Feedback for successfully completing certain parts of the exercise.
+     */
+    partially_correct_feedback?: string;
+    /**
+     * Search queries that were used to build the exercise.
+     */
+    search_values: string;
+    /**
+     * Name of the person who wrote the base text for the exercise.
+     */
+    work_author?: string;
+    /**
+     * Title of the base text for the exercise.
+     */
+    work_title?: string;
+    /**
+     * Type of exercise, concerning interaction and layout.
+     */
+    type: string;
+    /**
+     * Localized expression of the exercise type.
+     */
+    type_translation?: string;
+    /**
+     * CTS URN for the text passage from which the exercise was created.
+     */
+    urn?: string;
+}
+
diff --git a/mc_frontend/openapi/model/exerciseFormAllOf.ts b/mc_frontend/openapi/model/exerciseFormAllOf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..791849c1602fbdc55954f4b50735a6cfce36eab7
--- /dev/null
+++ b/mc_frontend/openapi/model/exerciseFormAllOf.ts
@@ -0,0 +1,31 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Additional exercise data.
+ */
+export interface ExerciseFormAllOf { 
+    /**
+     * Type of exercise, concerning interaction and layout.
+     */
+    type: string;
+    /**
+     * Localized expression of the exercise type.
+     */
+    type_translation?: string;
+    /**
+     * CTS URN for the text passage from which the exercise was created.
+     */
+    urn?: string;
+}
+
diff --git a/mc_frontend/openapi/model/exerciseFormComp.ts b/mc_frontend/openapi/model/exerciseFormComp.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9cb53b205619484229bc527cb823db6f6a829d3
--- /dev/null
+++ b/mc_frontend/openapi/model/exerciseFormComp.ts
@@ -0,0 +1,62 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+import { ExerciseForm } from './exerciseForm';
+import { ExerciseBase } from './exerciseBase';
+
+
+export interface ExerciseFormComp { 
+    /**
+     * Feedback for successful completion of the exercise.
+     */
+    correct_feedback?: string;
+    /**
+     * Feedback for finishing the exercise.
+     */
+    general_feedback?: string;
+    /**
+     * Feedback for failing to complete the exercise successfully.
+     */
+    incorrect_feedback?: string;
+    /**
+     * Hints for how to complete the exercise.
+     */
+    instructions: string;
+    /**
+     * Feedback for successfully completing certain parts of the exercise.
+     */
+    partially_correct_feedback?: string;
+    /**
+     * Search queries that were used to build the exercise.
+     */
+    search_values: string;
+    /**
+     * Name of the person who wrote the base text for the exercise.
+     */
+    work_author?: string;
+    /**
+     * Title of the base text for the exercise.
+     */
+    work_title?: string;
+    /**
+     * Type of exercise, concerning interaction and layout.
+     */
+    type: string;
+    /**
+     * Localized expression of the exercise type.
+     */
+    type_translation?: string;
+    /**
+     * CTS URN for the text passage from which the exercise was created.
+     */
+    urn?: string;
+}
+
diff --git a/mc_frontend/openapi/model/frequencyItem.ts b/mc_frontend/openapi/model/frequencyItem.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f91e07d14a1b8090248ed418a110cfe07e81ea1e
--- /dev/null
+++ b/mc_frontend/openapi/model/frequencyItem.ts
@@ -0,0 +1,28 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface FrequencyItem { 
+    /**
+     * How often the given combination of values occurred.
+     */
+    count?: number;
+    /**
+     * Labels for the phenomena described in this frequency entry.
+     */
+    phenomena?: Array<string>;
+    /**
+     * Values for the phenomena described in this frequency entry.
+     */
+    values?: Array<string>;
+}
+
diff --git a/mc_frontend/openapi/model/graphData.ts b/mc_frontend/openapi/model/graphData.ts
new file mode 100644
index 0000000000000000000000000000000000000000..32b8076a556439bac61d17d5065cc953b0dc17f8
--- /dev/null
+++ b/mc_frontend/openapi/model/graphData.ts
@@ -0,0 +1,41 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+import { NodeMC } from './nodeMC';
+import { Link } from './link';
+
+
+/**
+ * Nodes, edges and metadata for a graph.
+ */
+export interface GraphData { 
+    /**
+     * Whether edges in the returned graph are directed.
+     */
+    directed?: boolean;
+    /**
+     * Additional graph data.
+     */
+    graph?: object;
+    /**
+     * List of edges for the graph.
+     */
+    links: Array<Link>;
+    /**
+     * Whether the graph consists of multiple subgraphs.
+     */
+    multigraph?: boolean;
+    /**
+     * List of nodes for the graph.
+     */
+    nodes: Array<NodeMC>;
+}
+
diff --git a/mc_frontend/openapi/model/learningResult.ts b/mc_frontend/openapi/model/learningResult.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e6a1cde5d4a7b6372f7a48ac0e452d5a951b136a
--- /dev/null
+++ b/mc_frontend/openapi/model/learningResult.ts
@@ -0,0 +1,107 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Learner data for completed exercises.
+ */
+export interface LearningResult { 
+    /**
+     * H5P user ID, usually unique per device.
+     */
+    actor_account_name?: string;
+    /**
+     * Describes the kind of object that was recognized as actor.
+     */
+    actor_object_type?: string;
+    /**
+     * Link to the exercise type specification.
+     */
+    category_id?: string;
+    /**
+     * Describes the kind of object that was recognized as exercise.
+     */
+    category_object_type?: string;
+    /**
+     * JSON string containing a list of possible choices, each with ID and description.
+     */
+    choices?: string;
+    /**
+     * Whether the exercise was fully processed or not.
+     */
+    completion: boolean;
+    /**
+     * JSON string containing a list of possible solutions to the exercise, given as patterns of answers.
+     */
+    correct_responses_pattern: string;
+    /**
+     * When the learner data was received (POSIX timestamp).
+     */
+    created_time: number;
+    /**
+     * How many seconds it took a learner to complete the exercise.
+     */
+    duration?: string;
+    /**
+     * JSON string containing a mapping of keys and values (usually the local content ID, i.e. a versioning mechanism).
+     */
+    extensions?: string;
+    /**
+     * Exercise type.
+     */
+    interaction_type?: string;
+    /**
+     * Exercise content, possibly including instructions.
+     */
+    object_definition_description: string;
+    /**
+     * Type of object definition that is presented to the user.
+     */
+    object_definition_type?: string;
+    /**
+     * Type of object that is presented to the user.
+     */
+    object_object_type?: string;
+    /**
+     * Answer provided by the user, possibly as a pattern.
+     */
+    response: string;
+    /**
+     * Maximum possible score to be achieved in this exercise.
+     */
+    score_max: number;
+    /**
+     * Minimum score to be achieved in this exercise.
+     */
+    score_min: number;
+    /**
+     * Score that was actually achieved by the user in this exercise.
+     */
+    score_raw: number;
+    /**
+     * Relative score (between 0 and 1) that was actually achieved by the user in this exercise.
+     */
+    score_scaled?: number;
+    /**
+     * Whether the exercise was successfully completed or not.
+     */
+    success: boolean;
+    /**
+     * Type of action that was performed by the user.
+     */
+    verb_display?: string;
+    /**
+     * Link to the type of action that was performed by the user.
+     */
+    verb_id?: string;
+}
+
diff --git a/mc_frontend/openapi/model/link.ts b/mc_frontend/openapi/model/link.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fa66988f4a6836c87f7031315797f44f8f1a2ad7
--- /dev/null
+++ b/mc_frontend/openapi/model/link.ts
@@ -0,0 +1,36 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface Link { 
+    /**
+     * Component name as given by ANNIS.
+     */
+    annis_component_name?: string;
+    /**
+     * Component type as given by ANNIS.
+     */
+    annis_component_type?: string;
+    /**
+     * ID of the source node for the edge.
+     */
+    source?: string;
+    /**
+     * ID of the target node for the edge.
+     */
+    target?: string;
+    /**
+     * Dependency relation described by the edge.
+     */
+    udep_deprel?: string;
+}
+
diff --git a/mc_frontend/openapi/model/models.ts b/mc_frontend/openapi/model/models.ts
new file mode 100644
index 0000000000000000000000000000000000000000..915ffde075a22687dffb8062bae59a1e9d7579a5
--- /dev/null
+++ b/mc_frontend/openapi/model/models.ts
@@ -0,0 +1,16 @@
+export * from './annisResponse';
+export * from './corpus';
+export * from './exercise';
+export * from './exerciseAllOf';
+export * from './exerciseBase';
+export * from './exerciseForm';
+export * from './exerciseFormAllOf';
+export * from './frequencyItem';
+export * from './graphData';
+export * from './learningResult';
+export * from './link';
+export * from './nodeMC';
+export * from './solution';
+export * from './solutionElement';
+export * from './textComplexity';
+export * from './updateInfo';
diff --git a/mc_frontend/openapi/model/node.ts b/mc_frontend/openapi/model/node.ts
new file mode 100644
index 0000000000000000000000000000000000000000..894599fd1470cadbc633e1ee441a827e3f692428
--- /dev/null
+++ b/mc_frontend/openapi/model/node.ts
@@ -0,0 +1,60 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface Node { 
+    /**
+     * Node name as given by ANNIS.
+     */
+    annis_node_name: string;
+    /**
+     * Node type as given by ANNIS.
+     */
+    annis_node_type: string;
+    /**
+     * Raw word form as given by ANNIS.
+     */
+    annis_tok: string;
+    /**
+     * Node type as given by ANNIS (?).
+     */
+    annis_type: string;
+    /**
+     * Unique identifier for the node in the SALT model.
+     */
+    id: string;
+    /**
+     * Whether the raw word form is missing in a given vocabulary.
+     */
+    is_oov?: boolean;
+    /**
+     * Lemmatized word form.
+     */
+    udep_lemma: string;
+    /**
+     * Universal part of speech tag for the word form.
+     */
+    udep_upostag: string;
+    /**
+     * Language-specific part of speech tag for the word form.
+     */
+    udep_xpostag?: string;
+    /**
+     * Additional morphological information.
+     */
+    udep_feats?: string;
+    /**
+     * Solution value for this node in an exercise.
+     */
+    solution?: string;
+}
+
diff --git a/mc_frontend/openapi/model/nodeMC.ts b/mc_frontend/openapi/model/nodeMC.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a2d05ddb5c3430d49896f5c98fe0bd1036ceb2d7
--- /dev/null
+++ b/mc_frontend/openapi/model/nodeMC.ts
@@ -0,0 +1,60 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface NodeMC { 
+    /**
+     * Node name as given by ANNIS.
+     */
+    annis_node_name?: string;
+    /**
+     * Node type as given by ANNIS.
+     */
+    annis_node_type?: string;
+    /**
+     * Raw word form as given by ANNIS.
+     */
+    annis_tok?: string;
+    /**
+     * Node type as given by ANNIS (?).
+     */
+    annis_type?: string;
+    /**
+     * Unique identifier for the node in the SALT model.
+     */
+    id?: string;
+    /**
+     * Whether the raw word form is missing in a given vocabulary.
+     */
+    is_oov?: boolean;
+    /**
+     * Lemmatized word form.
+     */
+    udep_lemma?: string;
+    /**
+     * Universal part of speech tag for the word form.
+     */
+    udep_upostag?: string;
+    /**
+     * Language-specific part of speech tag for the word form.
+     */
+    udep_xpostag?: string;
+    /**
+     * Additional morphological information.
+     */
+    udep_feats?: string;
+    /**
+     * Solution value for this node in an exercise.
+     */
+    solution?: string;
+}
+
diff --git a/mc_frontend/openapi/model/solution.ts b/mc_frontend/openapi/model/solution.ts
new file mode 100644
index 0000000000000000000000000000000000000000..31a5799f6c7e50e6b09c6c5c70b858564937e1f8
--- /dev/null
+++ b/mc_frontend/openapi/model/solution.ts
@@ -0,0 +1,22 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+import { SolutionElement } from './solutionElement';
+
+
+/**
+ * Correct solution for an exercise.
+ */
+export interface Solution { 
+    target?: SolutionElement;
+    value?: SolutionElement;
+}
+
diff --git a/mc_frontend/openapi/model/solutionElement.ts b/mc_frontend/openapi/model/solutionElement.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4adb2da4fb439e2f6b76865a880e2420fb0205a1
--- /dev/null
+++ b/mc_frontend/openapi/model/solutionElement.ts
@@ -0,0 +1,35 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Target or value of a correct solution for an exercise.
+ */
+export interface SolutionElement { 
+    /**
+     * Content of the solution element.
+     */
+    content?: string;
+    /**
+     * Unique identifier for the node in the SALT model.
+     */
+    salt_id?: string;
+    /**
+     * Unique identifier for the sentence in a corpus.
+     */
+    sentence_id: number;
+    /**
+     * Unique identifier for the token in a sentence.
+     */
+    token_id: number;
+}
+
diff --git a/mc_frontend/openapi/model/textComplexity.ts b/mc_frontend/openapi/model/textComplexity.ts
new file mode 100644
index 0000000000000000000000000000000000000000..86334555d75d3959a43336249a04b39d35e400da
--- /dev/null
+++ b/mc_frontend/openapi/model/textComplexity.ts
@@ -0,0 +1,79 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Mapping of various elements of text complexity to their corresponding values.
+ */
+export interface TextComplexity { 
+    /**
+     * Overall text complexity of the given corpus.
+     */
+    all?: number;
+    /**
+     * Average length of a word in the given corpus.
+     */
+    avg_w_len?: number;
+    /**
+     * Average number of words per sentence.
+     */
+    avg_w_per_sent?: number;
+    /**
+     * Lexical density of the given corpus.
+     */
+    lex_den?: number;
+    /**
+     * Number of ablativi absoluti in the given corpus.
+     */
+    n_abl_abs?: number;
+    /**
+     * Number of clauses in the given corpus.
+     */
+    n_clause?: number;
+    /**
+     * Number of gerunds in the given corpus.
+     */
+    n_gerund?: number;
+    /**
+     * Number of infinitives in the given corpus.
+     */
+    n_inf?: number;
+    /**
+     * Number of participles in the given corpus.
+     */
+    n_part?: number;
+    /**
+     * Number of punctuation signs in the given corpus.
+     */
+    n_punct?: number;
+    /**
+     * Number of sentences in the given corpus.
+     */
+    n_sent?: number;
+    /**
+     * Number of subclauses in the given corpus.
+     */
+    n_subclause?: number;
+    /**
+     * Number of distinct word forms in the given corpus.
+     */
+    n_types?: number;
+    /**
+     * Number of words in the given corpus.
+     */
+    n_w?: number;
+    /**
+     * Number of distinct part of speech tags in the given corpus.
+     */
+    pos?: number;
+}
+
diff --git a/mc_frontend/openapi/model/updateInfo.ts b/mc_frontend/openapi/model/updateInfo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..61ac427fe6a7e7bc2a0ec689e951da327c74ec83
--- /dev/null
+++ b/mc_frontend/openapi/model/updateInfo.ts
@@ -0,0 +1,40 @@
+/**
+ * Machina Callida Backend REST API
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+/**
+ * Timestamps for updates of various resources.
+ */
+export interface UpdateInfo { 
+    /**
+     * When the resource was created (as POSIX timestamp).
+     */
+    created_time: number;
+    /**
+     * When the resource was last modified (as POSIX timestamp).
+     */
+    last_modified_time: number;
+    /**
+     * Name of the resource for which update timestamps are indexed.
+     */
+    resource_type: UpdateInfo.ResourceTypeEnum;
+}
+export namespace UpdateInfo {
+    export type ResourceTypeEnum = 'cts_data' | 'exercise_list' | 'file_api_clean';
+    export const ResourceTypeEnum = {
+        CtsData: 'cts_data' as ResourceTypeEnum,
+        ExerciseList: 'exercise_list' as ResourceTypeEnum,
+        FileApiClean: 'file_api_clean' as ResourceTypeEnum
+    };
+}
+
+
diff --git a/mc_frontend/openapi/variables.ts b/mc_frontend/openapi/variables.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6fe58549f395fc15d66a3b68f261d48c1af375ef
--- /dev/null
+++ b/mc_frontend/openapi/variables.ts
@@ -0,0 +1,9 @@
+import { InjectionToken } from '@angular/core';
+
+export const BASE_PATH = new InjectionToken<string>('basePath');
+export const COLLECTION_FORMATS = {
+    'csv': ',',
+    'tsv': '   ',
+    'ssv': ' ',
+    'pipes': '|'
+}
diff --git a/mc_frontend/src/app/corpus.service.spec.ts b/mc_frontend/src/app/corpus.service.spec.ts
index 665a02c6bf46e64bea383a8412e9ee319ecd5f82..0de90a5b7d81921682dcd3f4cec98c6f41fdb4bd 100644
--- a/mc_frontend/src/app/corpus.service.spec.ts
+++ b/mc_frontend/src/app/corpus.service.spec.ts
@@ -10,21 +10,18 @@ import {HttpClient, HttpErrorResponse} from '@angular/common/http';
 import {HelperService} from './helper.service';
 import MockMC from './models/mockMC';
 import {CaseValue, DependencyValue, ExerciseType, PartOfSpeechValue, Phenomenon} from './models/enum';
-import {AnnisResponse} from './models/annisResponse';
 import {ApplicationState} from './models/applicationState';
 import {QueryMC} from './models/queryMC';
 import {PhenomenonMapContent} from './models/phenomenonMap';
-import {FrequencyItem} from './models/frequencyItem';
 import {UpdateInfo} from './models/updateInfo';
 import configMC from '../configMC';
 import {TextData} from './models/textData';
 import {CorpusMC} from './models/corpusMC';
-import {LinkMC} from './models/linkMC';
-import {NodeMC} from './models/nodeMC';
 import {Author} from './models/author';
 import {take} from 'rxjs/operators';
 import {TextRange} from './models/textRange';
 import Spy = jasmine.Spy;
+import {AnnisResponse, NodeMC} from '../../openapi';
 
 describe('CorpusService', () => {
     let httpClient: HttpClient;
@@ -62,7 +59,7 @@ describe('CorpusService', () => {
         spyOn(corpusService, 'getSortedQueryValues').and.returnValue([PartOfSpeechValue.adjective.toString()]);
         helperService.applicationState.next(helperService.deepCopy(MockMC.applicationState) as ApplicationState);
         corpusService.exercise.type = ExerciseType.matching;
-        corpusService.annisResponse = new AnnisResponse({frequency_analysis: []});
+        corpusService.annisResponse = {frequency_analysis: []};
         corpusService.adjustTranslations().then(() => {
             expect(corpusService.exercise.queryItems.length).toBe(2);
             corpusService.exercise.type = ExerciseType.cloze;
@@ -146,7 +143,7 @@ describe('CorpusService', () => {
             spy.and.returnValue(Promise.resolve(
                 (helperService.deepCopy(MockMC.applicationState) as ApplicationState).mostRecentSetup.annisResponse));
             corpusService.getCTStextPassage('').then((ar: AnnisResponse) => {
-                expect(ar.nodes.length).toBe(1);
+                expect(ar.graph_data.nodes.length).toBe(1);
                 done();
             });
         });
@@ -167,7 +164,7 @@ describe('CorpusService', () => {
 
     it('should get a frequency analysis', (done) => {
         const spy: Spy = spyOn(helperService, 'makeGetRequest').and.callFake(() => Promise.reject());
-        corpusService.annisResponse = new AnnisResponse({frequency_analysis: []});
+        corpusService.annisResponse = {frequency_analysis: []};
         corpusService.getFrequencyAnalysis().then(() => {
         }, () => {
             expect(spy).toHaveBeenCalledTimes(1);
@@ -193,18 +190,18 @@ describe('CorpusService', () => {
         result = corpusService.getSortedQueryValues(query, 0);
         expect(result.length).toBe(3);
         corpusService.exercise.type = ExerciseType.matching;
-        corpusService.annisResponse = new AnnisResponse({
-            frequency_analysis: [new FrequencyItem({
+        corpusService.annisResponse = {
+            frequency_analysis: [{
                 values: [PartOfSpeechValue.adjective.toString(), 'a'],
                 phenomena: ['', Phenomenon.partOfSpeech.toString()]
-            }), new FrequencyItem({
+            }, {
                 values: [PartOfSpeechValue.adjective.toString(), 'b'],
                 phenomena: ['', Phenomenon.partOfSpeech.toString()]
-            }), new FrequencyItem({
+            }, {
                 values: [PartOfSpeechValue.adjective.toString(), 'c'],
                 phenomena: ['', Phenomenon.partOfSpeech.toString()]
-            })]
-        });
+            }]
+        };
         result = corpusService.getSortedQueryValues(query, 1);
         expect(result.length).toBe(3);
         corpusService.annisResponse.frequency_analysis.forEach(fi => fi.phenomena = [Phenomenon.partOfSpeech.toString()]);
@@ -300,13 +297,15 @@ describe('CorpusService', () => {
     });
 
     it('should process an ANNIS response', () => {
-        const ar: AnnisResponse = new AnnisResponse({
-            links: [new LinkMC({annis_component_type: 'Pointing', udep_deprel: 'nsubj'})],
-            nodes: [new NodeMC({annis_tok: 'tok .'})]
-        });
+        const ar: AnnisResponse = {
+            graph_data: {
+                links: [{annis_component_type: 'Pointing', udep_deprel: 'nsubj'}],
+                nodes: [{annis_tok: 'tok .'}]
+            }
+        };
         corpusService.processAnnisResponse(ar);
         expect(corpusService.phenomenonMap.dependency.specificValues[DependencyValue.subject]).toBe(1);
-        ar.links.push(ar.links[0]);
+        ar.graph_data.links.push(ar.graph_data.links[0]);
         corpusService.processAnnisResponse(ar);
         expect(corpusService.phenomenonMap.dependency.specificValues[DependencyValue.subject]).toBe(2);
     });
@@ -327,12 +326,12 @@ describe('CorpusService', () => {
     });
 
     it('should process nodes', () => {
-        const node: NodeMC = new NodeMC({
+        const node: NodeMC = {
             udep_lemma: 'lemma',
             udep_upostag: 'NOUN',
             udep_feats: `${Phenomenon.case.toString()}=Nom`
-        });
-        const ar: AnnisResponse = new AnnisResponse({nodes: [node, new NodeMC({...node})]});
+        };
+        const ar: AnnisResponse = {graph_data: {nodes: [node, {...node}], links: []}};
         corpusService.phenomenonMap.lemma = new PhenomenonMapContent({specificValues: {}, translationValues: {}});
         corpusService.phenomenonMap.case = new PhenomenonMapContent({specificValues: {}, translationValues: {}});
         corpusService.phenomenonMap.partOfSpeech = new PhenomenonMapContent({
@@ -355,7 +354,7 @@ describe('CorpusService', () => {
             getTextSpy.and.returnValue(Promise.resolve());
             helperService.applicationState.next(new ApplicationState({
                 mostRecentSetup: new TextData({
-                    annisResponse: new AnnisResponse({nodes: []})
+                    annisResponse: {graph_data: {nodes: [], links: []}}
                 })
             }));
             corpusService.restoreLastCorpus().then(() => {
@@ -365,7 +364,7 @@ describe('CorpusService', () => {
                     expect(getTextSpy).toHaveBeenCalledTimes(2);
                     helperService.applicationState.next(helperService.deepCopy(MockMC.applicationState) as ApplicationState);
                     corpusService.restoreLastCorpus().then(() => {
-                        expect(corpusService.annisResponse.nodes.length).toBe(1);
+                        expect(corpusService.annisResponse.graph_data.nodes.length).toBe(1);
                         done();
                     });
                 });
@@ -401,9 +400,9 @@ describe('CorpusService', () => {
     it('should update the base word', () => {
         const adjustSpy: Spy = spyOn(corpusService, 'adjustQueryValue');
         const queryValuesSpy: Spy = spyOn(corpusService, 'getSortedQueryValues').and.returnValue([]);
-        corpusService.annisResponse = new AnnisResponse({
+        corpusService.annisResponse = {
             frequency_analysis: helperService.deepCopy(MockMC.apiResponseFrequencyAnalysisGet)
-        });
+        };
         corpusService.annisResponse.frequency_analysis[0].phenomena.push(Phenomenon.case.toString());
         corpusService.exercise.type = ExerciseType.matching;
         corpusService.exercise.queryItems.push(new QueryMC());
diff --git a/mc_frontend/src/app/corpus.service.ts b/mc_frontend/src/app/corpus.service.ts
index 0bd251c08299c3d30b6c007e1748714ddce722ae..7d92ea2bee5c6a5fe043a2609df9ee35b60f879b 100644
--- a/mc_frontend/src/app/corpus.service.ts
+++ b/mc_frontend/src/app/corpus.service.ts
@@ -7,7 +7,6 @@ import {HttpClient, HttpErrorResponse, HttpParams} from '@angular/common/http';
 import {TranslateService} from '@ngx-translate/core';
 import {ToastController} from '@ionic/angular';
 import {HelperService} from 'src/app/helper.service';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {
     CaseTranslations,
     CaseValue,
@@ -20,13 +19,10 @@ import {
     PartOfSpeechValue,
     Phenomenon
 } from 'src/app/models/enum';
-import {NodeMC} from 'src/app/models/nodeMC';
-import {LinkMC} from 'src/app/models/linkMC';
 import {QueryMC} from 'src/app/models/queryMC';
 import {Exercise} from 'src/app/models/exercise';
 import {Feedback} from 'src/app/models/feedback';
 import {PhenomenonMap, PhenomenonMapContent} from 'src/app/models/phenomenonMap';
-import {FrequencyItem} from 'src/app/models/frequencyItem';
 import {ReplaySubject} from 'rxjs';
 import {ApplicationState} from './models/applicationState';
 import {take} from 'rxjs/operators';
@@ -34,6 +30,7 @@ import {TextData} from './models/textData';
 import {Storage} from '@ionic/storage';
 import {UpdateInfo} from './models/updateInfo';
 import configMC from '../configMC';
+import {AnnisResponse, FrequencyItem, Link, NodeMC} from '../../openapi';
 
 @Injectable({
     providedIn: 'root'
@@ -409,8 +406,8 @@ export class CorpusService {
         });
         this.phenomenonMap.lemma.translationValues = {};
         this.processNodes(ar);
-        const pointingLinks: LinkMC[] = ar.links.filter(x => x.annis_component_type === 'Pointing');
-        pointingLinks.forEach((link: LinkMC) => {
+        const pointingLinks: Link[] = ar.graph_data.links.filter(x => x.annis_component_type === 'Pointing');
+        pointingLinks.forEach((link: Link) => {
             const dep: DependencyValue = this.helperService.dependencyMap[link.udep_deprel];
             if (dep) {
                 const existingValue = this.phenomenonMap.dependency.specificValues[dep];
@@ -418,13 +415,13 @@ export class CorpusService {
             }
         });
         // need to add root dependencies manually because they are tricky to handle
-        const nodeIds: string[] = ar.nodes.map(x => x.id);
+        const nodeIds: string[] = ar.graph_data.nodes.map(x => x.id);
         const nodesWithDependencySet: Set<string> = new Set<string>(pointingLinks.map(x => x.target));
         const rootNodeIds: string[] = nodeIds.filter(x => !nodesWithDependencySet.has(x));
         this.phenomenonMap.dependency.specificValues[DependencyValue.root] = rootNodeIds.length;
         this.adjustQueryValue(this.exercise.queryItems[0], 0);
         // remove whitespace before punctuation
-        this.currentText = ar.nodes.map(x => x.annis_tok).join(' ').replace(/[ ]([.,\/#!$%\^&\*;:{}=\-_`~()])/g, (x: string) => {
+        this.currentText = ar.graph_data.nodes.map(x => x.annis_tok).join(' ').replace(/[ ]([.,\/#!$%\^&\*;:{}=\-_`~()])/g, (x: string) => {
             return x.trim();
         });
         this.annisResponse = ar;
@@ -480,7 +477,7 @@ export class CorpusService {
     }
 
     processNodes(ar: AnnisResponse): void {
-        ar.nodes.forEach((node: NodeMC) => {
+        ar.graph_data.nodes.forEach((node: NodeMC) => {
             let existingValue = this.phenomenonMap.lemma.specificValues[node.udep_lemma];
             this.phenomenonMap.lemma.specificValues[node.udep_lemma] = (existingValue ? existingValue : 0) + 1;
             this.phenomenonMap.lemma.translationValues[node.udep_lemma] = node.udep_lemma;
@@ -508,14 +505,15 @@ export class CorpusService {
                 this.currentCorpusCache = state.mostRecentSetup.currentCorpus;
                 this.currentTextRangeCache = state.mostRecentSetup.currentTextRange;
                 this.isTextRangeCorrect = true;
-                if (this.annisResponse && this.annisResponse.nodes.length) {
+                if (this.annisResponse && this.annisResponse.graph_data.nodes.length) {
                     this.processAnnisResponse(this.annisResponse, false);
                     return resolve();
                 } else if (this.currentText) {
                     // check if the data is already present
                     return resolve();
                 } else {
-                    const saveToCache: boolean = !state.mostRecentSetup.annisResponse || !state.mostRecentSetup.annisResponse.nodes.length;
+                    const saveToCache: boolean = !state.mostRecentSetup.annisResponse ||
+                        !state.mostRecentSetup.annisResponse.graph_data.nodes.length;
                     this.getText(saveToCache).then(() => {
                         return resolve();
                     }, () => {
diff --git a/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts b/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts
index 6fd6c06d68f302753dfbeb4101af4dca28d3db38..5f65ed1343045de7e917e7c409c15afc1bdef57c 100644
--- a/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts
+++ b/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts
@@ -13,7 +13,6 @@ import {ExerciseType, MoodleExerciseType, SortingCategory, VocabularyCorpus} fro
 import MockMC from '../models/mockMC';
 import {ApplicationState} from '../models/applicationState';
 import {ExerciseMC} from '../models/exerciseMC';
-import {AnnisResponse} from '../models/annisResponse';
 import Spy = jasmine.Spy;
 import configMC from '../../configMC';
 import {UpdateInfo} from '../models/updateInfo';
@@ -100,7 +99,7 @@ describe('ExerciseListPage', () => {
         const requestSpy: Spy = spyOn(exerciseListPage.helperService, 'makeGetRequest').and.callFake(() => Promise.reject());
         spyOn(exerciseListPage.helperService, 'goToPreviewPage').and.returnValue(Promise.resolve(true));
         exerciseListPage.showExercise(new ExerciseMC()).then(() => {
-            requestSpy.and.returnValue(Promise.resolve(new AnnisResponse()));
+            requestSpy.and.returnValue(Promise.resolve({}));
             exerciseListPage.showExercise(new ExerciseMC({exercise_type: MoodleExerciseType.markWords.toString()})).then(() => {
                 expect(exerciseListPage.corpusService.exercise.type).toBe(ExerciseType.markWords);
                 done();
diff --git a/mc_frontend/src/app/exercise-list/exercise-list.page.ts b/mc_frontend/src/app/exercise-list/exercise-list.page.ts
index a26e10cedbcf199e1b338d19a510678e2180c9fd..ce55450f14884909e55dac3a3c61dbf6bfea745c 100644
--- a/mc_frontend/src/app/exercise-list/exercise-list.page.ts
+++ b/mc_frontend/src/app/exercise-list/exercise-list.page.ts
@@ -13,7 +13,6 @@ import {
     VocabularyCorpusTranslation
 } from '../models/enum';
 import {TranslateService} from '@ngx-translate/core';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {CorpusService} from 'src/app/corpus.service';
 import {VocabularyService} from 'src/app/vocabulary.service';
 import {Storage} from '@ionic/storage';
@@ -21,6 +20,7 @@ import configMC from '../../configMC';
 import {UpdateInfo} from '../models/updateInfo';
 import {take} from 'rxjs/operators';
 import {ApplicationState} from '../models/applicationState';
+import {AnnisResponse} from '../../../openapi';
 
 @Component({
     selector: 'app-exercise-list',
diff --git a/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.spec.ts b/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.spec.ts
index 0a3ff0c1b94dda226432635d267fb4b1781c2ccc..2b65252cd4916896cecba9bbbcf00e945b80f293 100644
--- a/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.spec.ts
+++ b/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.spec.ts
@@ -8,7 +8,6 @@ import {RouterModule} from '@angular/router';
 import {TranslateTestingModule} from '../translate-testing/translate-testing.module';
 import {FormsModule} from '@angular/forms';
 import {APP_BASE_HREF} from '@angular/common';
-import {AnnisResponse} from '../models/annisResponse';
 import {TextRange} from '../models/textRange';
 import {ReplaySubject} from 'rxjs';
 import configMC from '../../configMC';
@@ -16,7 +15,6 @@ import {ExerciseType, PartOfSpeechValue, Phenomenon} from '../models/enum';
 import {ToastController} from '@ionic/angular';
 import {QueryMC} from '../models/queryMC';
 import {PhenomenonMapContent} from '../models/phenomenonMap';
-import {FrequencyItem} from '../models/frequencyItem';
 import Spy = jasmine.Spy;
 import MockMC from '../models/mockMC';
 
@@ -54,7 +52,7 @@ describe('ExerciseParametersPage', () => {
     });
 
     it('should generate an exercise', (done) => {
-        exerciseParametersPage.corpusService.annisResponse = new AnnisResponse({solutions: []});
+        exerciseParametersPage.corpusService.annisResponse = {solutions: []};
         exerciseParametersPage.corpusService.initCurrentCorpus().then(() => {
             exerciseParametersPage.corpusService.currentTextRange = new ReplaySubject<TextRange>(1);
             exerciseParametersPage.corpusService.currentTextRange.next(new TextRange({start: [], end: []}));
@@ -89,16 +87,16 @@ describe('ExerciseParametersPage', () => {
         let displayValue: string = exerciseParametersPage.getDisplayValue(new QueryMC({phenomenon: Phenomenon.lemma}), key);
         expect(displayValue.length).toBe(9);
         exerciseParametersPage.corpusService.exercise.type = ExerciseType.matching;
-        exerciseParametersPage.corpusService.annisResponse = new AnnisResponse({
-            frequency_analysis: [new FrequencyItem({values: [PartOfSpeechValue.adjective.toString(), key], count: 10})]
-        });
+        exerciseParametersPage.corpusService.annisResponse = {
+            frequency_analysis: [{values: [PartOfSpeechValue.adjective.toString(), key], count: 10}]
+        };
         displayValue = exerciseParametersPage.getDisplayValue(new QueryMC({phenomenon: Phenomenon.lemma}), key, 1);
         expect(displayValue.length).toBe(10);
-        exerciseParametersPage.corpusService.annisResponse.frequency_analysis[0] = new FrequencyItem({
+        exerciseParametersPage.corpusService.annisResponse.frequency_analysis[0] = {
             phenomena: [Phenomenon.lemma.toString()],
             values: [key],
             count: 100
-        });
+        };
         exerciseParametersPage.corpusService.annisResponse.frequency_analysis.push(
             exerciseParametersPage.corpusService.annisResponse.frequency_analysis[0]);
         displayValue = exerciseParametersPage.getDisplayValue(new QueryMC({phenomenon: Phenomenon.lemma}), key, 0);
@@ -125,10 +123,9 @@ describe('ExerciseParametersPage', () => {
     });
 
     it('should get a H5P exercise', (done) => {
-        const requestSpy: Spy = spyOn(exerciseParametersPage.helperService, 'makePostRequest').and.returnValue(
-            Promise.resolve(new AnnisResponse()));
+        const requestSpy: Spy = spyOn(exerciseParametersPage.helperService, 'makePostRequest').and.returnValue(Promise.resolve({}));
         const navSpy: Spy = spyOn(exerciseParametersPage.helperService, 'goToPreviewPage').and.returnValue(Promise.resolve(true));
-        exerciseParametersPage.corpusService.annisResponse = new AnnisResponse();
+        exerciseParametersPage.corpusService.annisResponse = {};
         exerciseParametersPage.helperService.applicationState.next(exerciseParametersPage.helperService.deepCopy(MockMC.applicationState));
         exerciseParametersPage.getH5Pexercise(new FormData()).then(() => {
             expect(navSpy).toHaveBeenCalledTimes(1);
diff --git a/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.ts b/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.ts
index 8ec79a8a54a9d0a926f01e50492c4da840616b53..07910a1e8412310a61cdf093ed17c7275c73fc81 100644
--- a/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.ts
+++ b/mc_frontend/src/app/exercise-parameters/exercise-parameters.page.ts
@@ -6,7 +6,6 @@ import {
     Phenomenon,
     PhenomenonTranslation
 } from '../models/enum';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {NavController, ToastController} from '@ionic/angular';
 import {HttpClient} from '@angular/common/http';
 import {Component, OnInit} from '@angular/core';
@@ -16,12 +15,12 @@ import {HelperService} from 'src/app/helper.service';
 import {CorpusService} from 'src/app/corpus.service';
 import {QueryMC} from 'src/app/models/queryMC';
 import {PhenomenonMapContent} from 'src/app/models/phenomenonMap';
-import {FrequencyItem} from 'src/app/models/frequencyItem';
 import {CorpusMC} from '../models/corpusMC';
 import {ApplicationState} from '../models/applicationState';
 import {take} from 'rxjs/operators';
 import {TextRange} from '../models/textRange';
 import configMC from '../../configMC';
+import {AnnisResponse, FrequencyItem} from '../../../openapi';
 
 @Component({
     selector: 'app-exercise-parameters',
diff --git a/mc_frontend/src/app/exercise/exercise.page.spec.ts b/mc_frontend/src/app/exercise/exercise.page.spec.ts
index 0133b0b695a294bd6a54cc27a750e7ff640433f4..2bacd44d3993b26e1682dba0b2244631ce1ba95b 100644
--- a/mc_frontend/src/app/exercise/exercise.page.spec.ts
+++ b/mc_frontend/src/app/exercise/exercise.page.spec.ts
@@ -7,7 +7,6 @@ import {ActivatedRoute, RouterModule} from '@angular/router';
 import {TranslateTestingModule} from '../translate-testing/translate-testing.module';
 import {APP_BASE_HREF} from '@angular/common';
 import {of} from 'rxjs';
-import {AnnisResponse} from '../models/annisResponse';
 import {ExerciseType, MoodleExerciseType} from '../models/enum';
 import Spy = jasmine.Spy;
 import configMC from '../../configMC';
@@ -37,14 +36,17 @@ describe('ExercisePage', () => {
             schemas: [CUSTOM_ELEMENTS_SCHEMA],
         })
             .compileComponents().then();
+    }));
+
+    beforeEach(() => {
         fixture = TestBed.createComponent(ExercisePage);
         exercisePage = fixture.componentInstance;
         h5pSpy = spyOn(exercisePage.exerciseService, 'initH5P').and.returnValue(Promise.resolve());
         checkSpy = spyOn(exercisePage.corpusService, 'checkAnnisResponse').and.callFake(() => Promise.reject());
         getRequestSpy = spyOn(exercisePage.helperService, 'makeGetRequest').and.returnValue(Promise.resolve(
-            new AnnisResponse({exercise_type: MoodleExerciseType.cloze.toString()})));
+            {exercise_type: MoodleExerciseType.cloze.toString()}));
         fixture.detectChanges();
-    }));
+    });
 
     it('should create', () => {
         expect(exercisePage).toBeTruthy();
@@ -66,7 +68,7 @@ describe('ExercisePage', () => {
         exercisePage.helperService.applicationState.next(exercisePage.helperService.deepCopy(MockMC.applicationState));
         exercisePage.loadExercise().then(() => {
             expect(exercisePage.corpusService.exercise.type).toBe(ExerciseType.cloze);
-            getRequestSpy.and.returnValue(Promise.resolve(new AnnisResponse({exercise_type: MoodleExerciseType.markWords.toString()})));
+            getRequestSpy.and.returnValue(Promise.resolve({exercise_type: MoodleExerciseType.markWords.toString()}));
             exercisePage.loadExercise().then(() => {
                 expect(h5pSpy).toHaveBeenCalledWith(configMC.excerciseTypePathMarkWords);
                 getRequestSpy.and.callFake(() => Promise.reject());
diff --git a/mc_frontend/src/app/exercise/exercise.page.ts b/mc_frontend/src/app/exercise/exercise.page.ts
index 141d4dd615656bec6d4dc29f6a5da754dcf8134a..f8c18e48493e7c9f26811bd48cb815b99bb7111a 100644
--- a/mc_frontend/src/app/exercise/exercise.page.ts
+++ b/mc_frontend/src/app/exercise/exercise.page.ts
@@ -6,13 +6,13 @@ import {ActivatedRoute} from '@angular/router';
 import {TranslateService} from '@ngx-translate/core';
 import {ExerciseService} from 'src/app/exercise.service';
 import {HttpClient, HttpParams} from '@angular/common/http';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {ExerciseType, MoodleExerciseType} from 'src/app/models/enum';
 import {CorpusService} from 'src/app/corpus.service';
 import {ApplicationState} from '../models/applicationState';
 import {take} from 'rxjs/operators';
 import configMC from '../../configMC';
 import {Storage} from '@ionic/storage';
+import {AnnisResponse} from '../../../openapi';
 
 @Component({
     selector: 'app-exercise',
diff --git a/mc_frontend/src/app/helper.service.spec.ts b/mc_frontend/src/app/helper.service.spec.ts
index 4fc17a5674eae66163be073f968aaff41a34783f..f5edab8561221bed131165322ab6fef62d294838 100644
--- a/mc_frontend/src/app/helper.service.spec.ts
+++ b/mc_frontend/src/app/helper.service.spec.ts
@@ -198,7 +198,7 @@ describe('HelperService', () => {
         helperService.saveApplicationState(helperService.deepCopy(MockMC.applicationState)).then(() => {
             helperService.storage.get(configMC.localStorageKeyApplicationState).then((jsonString: string) => {
                 const state: ApplicationState = JSON.parse(jsonString) as ApplicationState;
-                expect(state.mostRecentSetup.annisResponse.nodes.length).toBe(1);
+                expect(state.mostRecentSetup.annisResponse.graph_data.nodes.length).toBe(1);
                 done();
             });
         });
diff --git a/mc_frontend/src/app/helper.service.ts b/mc_frontend/src/app/helper.service.ts
index f1d3ebd9d544aefe10c79a19cbf9021cab9cb6dc..52040e34a5fe294a41025e0a9066ca99c7bb6346 100644
--- a/mc_frontend/src/app/helper.service.ts
+++ b/mc_frontend/src/app/helper.service.ts
@@ -11,6 +11,7 @@ import {Language} from 'src/app/models/language';
 import {ReplaySubject} from 'rxjs';
 import {TextData} from './models/textData';
 import configMC from '../configMC';
+import {GraphData} from '../../openapi/model/graphData';
 
 @Injectable({
     providedIn: 'root'
@@ -281,7 +282,8 @@ export class HelperService {
                     exerciseList: []
                 });
                 if (jsonString) {
-                    const state: ApplicationState = JSON.parse(jsonString) as ApplicationState;
+                    const jsonObject: any = this.updateAnnisResponse(jsonString);
+                    const state: ApplicationState = jsonObject as ApplicationState;
                     state.exerciseList = state.exerciseList ? state.exerciseList : [];
                     this.applicationStateCache = state;
                 }
@@ -364,4 +366,21 @@ export class HelperService {
             position
         }).then((toast: HTMLIonToastElement) => toast.present());
     }
+
+    updateAnnisResponse(jsonString: string): any {
+        const jsonObject: any = JSON.parse(jsonString);
+        // backwards compatibility
+        [jsonObject.currentSetup, jsonObject.mostRecentSetup].forEach((textdata: any) => {
+            if (textdata && textdata.annisResponse && !textdata.annisResponse.graph_data) {
+                const annisResp: any = textdata.annisResponse;
+                const props: string[] = ['links', 'nodes', 'directed', 'graph', 'multigraph'];
+                annisResp.graph_data = {};
+                props.forEach((prop: string) => {
+                    annisResp.graph_data[prop] = annisResp[prop];
+                    delete annisResp[prop];
+                });
+            }
+        });
+        return jsonObject;
+    }
 }
diff --git a/mc_frontend/src/app/models/annisResponse.ts b/mc_frontend/src/app/models/annisResponse.ts
deleted file mode 100644
index 8877b41e34f95a3366cd2e4e58d218fc4fc51843..0000000000000000000000000000000000000000
--- a/mc_frontend/src/app/models/annisResponse.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/* tslint:disable:variable-name */
-import {Solution} from 'src/app/models/solution';
-import {LinkMC} from 'src/app/models/linkMC';
-import {NodeMC} from 'src/app/models/nodeMC';
-import {FrequencyItem} from 'src/app/models/frequencyItem';
-import {TextComplexity} from 'src/app/models/textComplexity';
-
-export class AnnisResponse {
-    public directed: boolean;
-    public exercise_id: string;
-    public exercise_type: string;
-    public frequency_analysis: FrequencyItem[];
-    public links: LinkMC[];
-    public multigraph: boolean;
-    public nodes: NodeMC[];
-    public solutions: Solution[];
-    public text_complexity: TextComplexity;
-    public uri: string;
-
-    constructor(init?: Partial<AnnisResponse>) {
-        Object.assign(this, init);
-    }
-}
diff --git a/mc_frontend/src/app/models/exerciseMC.ts b/mc_frontend/src/app/models/exerciseMC.ts
index 9c3fc433a3b7f55812332b64bc5d9c4a191f04fb..f69b749036873a67cca98c2c69b74f65aae4bc7d 100644
--- a/mc_frontend/src/app/models/exerciseMC.ts
+++ b/mc_frontend/src/app/models/exerciseMC.ts
@@ -1,5 +1,6 @@
 /* tslint:disable:variable-name */
-import {Solution} from 'src/app/models/solution';
+
+import {Solution} from '../../../openapi';
 
 export class ExerciseMC {
     public conll: string;
diff --git a/mc_frontend/src/app/models/frequencyItem.ts b/mc_frontend/src/app/models/frequencyItem.ts
deleted file mode 100644
index 9ec4f326d8fc794d312a8529185b5b9e54839cb2..0000000000000000000000000000000000000000
--- a/mc_frontend/src/app/models/frequencyItem.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export class FrequencyItem {
-    public values: string[];
-    public phenomena: string[];
-    public count: number;
-
-    constructor(init?: Partial<FrequencyItem>) {
-        Object.assign(this, init);
-    }
-}
-
diff --git a/mc_frontend/src/app/models/linkMC.ts b/mc_frontend/src/app/models/linkMC.ts
deleted file mode 100644
index 6a7f74ddd1911f7c5a46810b0b1360c823a30017..0000000000000000000000000000000000000000
--- a/mc_frontend/src/app/models/linkMC.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/* tslint:disable:variable-name */
-export class LinkMC {
-    public annis_component_name: string;
-    public annis_component_type: string;
-    public source: string;
-    public target: string;
-    public udep_deprel: string;
-    constructor(init?: Partial<LinkMC>) {
-        Object.assign(this, init);
-    }
-}
diff --git a/mc_frontend/src/app/models/mockMC.ts b/mc_frontend/src/app/models/mockMC.ts
index 94d0c26e16dced1a21ac801384f4f36472930cb9..7d9a5ab6f3e45275dc80e091ddcc6de4124e0586 100644
--- a/mc_frontend/src/app/models/mockMC.ts
+++ b/mc_frontend/src/app/models/mockMC.ts
@@ -1,16 +1,14 @@
 import {CorpusMC} from './corpusMC';
 import {ExerciseMC} from './exerciseMC';
 import {PartOfSpeechValue, Phenomenon} from './enum';
-import {FrequencyItem} from './frequencyItem';
 import {ApplicationState} from './applicationState';
 import {TextData} from './textData';
-import {AnnisResponse} from './annisResponse';
-import {NodeMC} from './nodeMC';
 import {TestResultMC} from './testResultMC';
 import StatementBase from './xAPI/StatementBase';
 import Result from './xAPI/Result';
 import Score from './xAPI/Score';
 import {TextRange} from './textRange';
+import {AnnisResponse, FrequencyItem} from '../../../openapi';
 
 export default class MockMC {
     static apiResponseCorporaGet: CorpusMC[] = [new CorpusMC({
@@ -18,20 +16,28 @@ export default class MockMC {
         source_urn: 'urn',
         title: 'title',
     })];
-    static apiResponseFrequencyAnalysisGet: FrequencyItem[] = [new FrequencyItem({
+    static apiResponseFrequencyAnalysisGet: FrequencyItem[] = [{
         phenomena: [Phenomenon.partOfSpeech.toString()],
         values: [PartOfSpeechValue.adjective.toString()]
-    })];
-    static apiResponseTextGet: AnnisResponse = new AnnisResponse({
-        nodes: [new NodeMC({udep_lemma: 'lemma', annis_tok: 'tok'})],
-        links: []
-    });
+    }];
+    static apiResponseTextGet: AnnisResponse = {
+        graph_data: {
+            nodes: [{udep_lemma: 'lemma', annis_tok: 'tok'}],
+            links: []
+        }
+    };
     static applicationState: ApplicationState = new ApplicationState({
         currentSetup: new TextData({
             currentCorpus: new CorpusMC({citations: {}}),
             currentTextRange: new TextRange({start: ['1', '2'], end: ['1', '2']})
         }),
-        mostRecentSetup: new TextData({annisResponse: new AnnisResponse({nodes: [new NodeMC()], links: []})}),
+        mostRecentSetup: new TextData({
+            annisResponse: {
+                graph_data: {
+                    nodes: [{}], links: []
+                }
+            }
+        }),
         exerciseList: [new ExerciseMC()]
     });
     static popoverController: any = {create: () => Promise.resolve({present: () => Promise.resolve()})};
diff --git a/mc_frontend/src/app/models/nodeMC.ts b/mc_frontend/src/app/models/nodeMC.ts
deleted file mode 100644
index 1d6c1df06f326f3ff85ca3076f9ac5508f898dbc..0000000000000000000000000000000000000000
--- a/mc_frontend/src/app/models/nodeMC.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/* tslint:disable:variable-name */
-export class NodeMC {
-    public annis_node_name: string;
-    public annis_node_type: string;
-    public annis_tok: string;
-    public annis_type: string;
-    public id: string;
-    public udep_lemma: string;
-    public udep_upostag: string;
-    public udep_xpostag: string;
-    public udep_feats: string;
-    public solution: string;
-    public is_oov: boolean;
-    constructor(init?: Partial<NodeMC>) {
-        Object.assign(this, init);
-    }
-}
diff --git a/mc_frontend/src/app/models/solution.ts b/mc_frontend/src/app/models/solution.ts
deleted file mode 100644
index 027f47650fafdf56e01eeeaff0dcea88f12a1952..0000000000000000000000000000000000000000
--- a/mc_frontend/src/app/models/solution.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import {SolutionElement} from 'src/app/models/solutionElement';
-
-export class Solution {
-    public target: SolutionElement;
-    public value: SolutionElement;
-
-    constructor(init?: Partial<Solution>) {
-        Object.assign(this, init);
-    }
-}
diff --git a/mc_frontend/src/app/models/solutionElement.ts b/mc_frontend/src/app/models/solutionElement.ts
deleted file mode 100644
index dbfe8cb92b246fe4fd559c0e6c0a0b7d6de6bb7b..0000000000000000000000000000000000000000
--- a/mc_frontend/src/app/models/solutionElement.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export class SolutionElement {
-    public sentence_id: number;
-    public token_id: number;
-    public content: string;
-    public salt_id: string;
-
-    constructor(init?: Partial<SolutionElement>) {
-        Object.assign(this, init);
-    }
-}
\ No newline at end of file
diff --git a/mc_frontend/src/app/models/textData.ts b/mc_frontend/src/app/models/textData.ts
index 98efa67eb3d777d92cbae0b9cba8491e9feb137c..b897b53841921009f209186ae1e3b435504866d3 100644
--- a/mc_frontend/src/app/models/textData.ts
+++ b/mc_frontend/src/app/models/textData.ts
@@ -1,7 +1,7 @@
 import {CorpusMC} from 'src/app/models/corpusMC';
 import {TextRange} from 'src/app/models/textRange';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {Author} from 'src/app/models/author';
+import {AnnisResponse} from '../../../openapi';
 
 export class TextData {
     public annisResponse: AnnisResponse;
diff --git a/mc_frontend/src/app/preview/preview.page.html b/mc_frontend/src/app/preview/preview.page.html
index 57bc606246fbe30f1fc33e34854d17e9ea607e1e..c9b890b7f5273812386e8aece5698225c6d97726 100644
--- a/mc_frontend/src/app/preview/preview.page.html
+++ b/mc_frontend/src/app/preview/preview.page.html
@@ -71,8 +71,8 @@ beginning that it is going to be a download (instead of an ordinary link or clic
                     </ion-button>
                 </ion-col>
                 <ion-col style="text-align: left;">
-                    <ion-button id="showShareLinkButton" style="max-width: 6em" (click)="showShareLink = !showShareLink">
-                        <ion-icon slot="start" name="share"></ion-icon>
+                    <ion-button id="showShareLinkButton" style="max-width: 8em" (click)="showShareLink = !showShareLink">
+                        <ion-icon slot="start" name="share-social-outline"></ion-icon>
                         {{ "SHARE" | translate}}
                     </ion-button>
                 </ion-col>
diff --git a/mc_frontend/src/app/preview/preview.page.spec.ts b/mc_frontend/src/app/preview/preview.page.spec.ts
index 1bf652a0c82291746f7d34df13212ea575d195d3..795eb621c1bc178ee49fa87ad913e4ef71869afd 100644
--- a/mc_frontend/src/app/preview/preview.page.spec.ts
+++ b/mc_frontend/src/app/preview/preview.page.spec.ts
@@ -8,26 +8,21 @@ import {RouterModule} from '@angular/router';
 import {TranslateTestingModule} from '../translate-testing/translate-testing.module';
 import {FormsModule} from '@angular/forms';
 import {APP_BASE_HREF} from '@angular/common';
-import {CorpusService} from '../corpus.service';
 import {ToastController} from '@ionic/angular';
 import MockMC from '../models/mockMC';
-import {AnnisResponse} from '../models/annisResponse';
-import {Solution} from '../models/solution';
 import {ExerciseType} from '../models/enum';
-import {SolutionElement} from '../models/solutionElement';
 import Spy = jasmine.Spy;
-import {NodeMC} from '../models/nodeMC';
 import {TestResultMC} from '../models/testResultMC';
 import H5PeventDispatcherMock from '../models/h5pEventDispatcherMock';
 import Result from '../models/xAPI/Result';
 import configMC from '../../configMC';
+import {AnnisResponse, Solution} from '../../../openapi';
 
 declare var H5P: any;
 
 describe('PreviewPage', () => {
     let previewPage: PreviewPage;
     let fixture: ComponentFixture<PreviewPage>;
-    let corpusService: CorpusService;
     let checkAnnisResponseSpy: Spy;
     let xapiSpy: Spy;
 
@@ -48,15 +43,14 @@ describe('PreviewPage', () => {
             schemas: [CUSTOM_ELEMENTS_SCHEMA],
         })
             .compileComponents().then();
-        corpusService = TestBed.inject(CorpusService);
+    }));
+
+    beforeEach(() => {
         fixture = TestBed.createComponent(PreviewPage);
         previewPage = fixture.componentInstance;
         xapiSpy = spyOn(previewPage, 'setXAPIeventHandler');
-        checkAnnisResponseSpy = spyOn(corpusService, 'checkAnnisResponse').and.callFake(() => Promise.reject());
+        checkAnnisResponseSpy = spyOn(previewPage.corpusService, 'checkAnnisResponse').and.callFake(() => Promise.reject());
         fixture.detectChanges();
-    }));
-
-    beforeEach(() => {
     });
 
     it('should create', () => {
@@ -65,7 +59,7 @@ describe('PreviewPage', () => {
 
     it('should copy the link', () => {
         previewPage.helperService.isVocabularyCheck = true;
-        previewPage.corpusService.annisResponse = new AnnisResponse({solutions: []});
+        previewPage.corpusService.annisResponse = {solutions: []};
         fixture.detectChanges();
         const button: HTMLIonButtonElement = document.querySelector('#showShareLinkButton');
         button.click();
@@ -78,7 +72,7 @@ describe('PreviewPage', () => {
 
     it('should initialize H5P', () => {
         spyOn(previewPage.exerciseService, 'initH5P').and.returnValue(Promise.resolve());
-        previewPage.corpusService.annisResponse = new AnnisResponse({exercise_id: '', solutions: [new Solution()]});
+        previewPage.corpusService.annisResponse = {exercise_id: '', solutions: [{}]};
         previewPage.currentSolutions = previewPage.corpusService.annisResponse.solutions;
         previewPage.initH5P();
         expect(previewPage.solutionIndicesString.length).toBe(0);
@@ -104,7 +98,7 @@ describe('PreviewPage', () => {
             checkAnnisResponseSpy.and.returnValue(Promise.resolve());
             spyOn(previewPage, 'initH5P');
             spyOn(previewPage, 'processAnnisResponse');
-            previewPage.currentSolutions = [new Solution()];
+            previewPage.currentSolutions = [{}];
             previewPage.ngOnInit().then(() => {
                 expect(previewPage.currentSolutions.length).toBe(0);
                 iframe = document.querySelector(previewPage.exerciseService.h5pIframeString);
@@ -116,41 +110,40 @@ describe('PreviewPage', () => {
     });
 
     it('should process an ANNIS response', () => {
-        previewPage.corpusService.annisResponse = new AnnisResponse({});
-        const ar: AnnisResponse = new AnnisResponse({
-            solutions: [new Solution({target: new SolutionElement({content: 'content'})})]
-        });
+        previewPage.corpusService.annisResponse = {graph_data: {links: [], nodes: []}};
+        const solution: Solution = {target: {content: 'content', sentence_id: 1, token_id: 1}};
+        const ar: AnnisResponse = {solutions: [solution], graph_data: {links: [], nodes: []}};
         previewPage.processAnnisResponse(ar);
         expect(previewPage.corpusService.annisResponse.solutions.length).toBe(1);
         previewPage.corpusService.currentUrn = 'urn:';
         previewPage.processAnnisResponse(ar);
-        expect(previewPage.corpusService.annisResponse.nodes).toEqual(ar.nodes);
+        expect(previewPage.corpusService.annisResponse.graph_data.nodes).toEqual(ar.graph_data.nodes);
     });
 
     it('should process solutions', () => {
         const solutions: Solution[] = [
-            new Solution({
-                target: new SolutionElement({content: 'content2', salt_id: 'id'}),
-                value: new SolutionElement({salt_id: 'id'})
-            }),
-            new Solution({
-                target: new SolutionElement({content: 'content1', salt_id: 'id'}),
-                value: new SolutionElement({salt_id: 'id'})
-            }),
-            new Solution({
-                target: new SolutionElement({content: 'content1', salt_id: 'id'}),
-                value: new SolutionElement({salt_id: 'id'})
-            }),
-            new Solution({
-                target: new SolutionElement({content: 'content3', salt_id: 'id'}),
-                value: new SolutionElement({salt_id: 'id'})
-            })];
+            {
+                target: {content: 'content2', salt_id: 'id', sentence_id: 1, token_id: 1},
+                value: {salt_id: 'id', content: '', sentence_id: 1, token_id: 1}
+            },
+            {
+                target: {content: 'content1', salt_id: 'id', sentence_id: 1, token_id: 1},
+                value: {salt_id: 'id', content: '', sentence_id: 1, token_id: 1}
+            },
+            {
+                target: {content: 'content1', salt_id: 'id', sentence_id: 1, token_id: 1},
+                value: {salt_id: 'id', content: '', sentence_id: 1, token_id: 1}
+            },
+            {
+                target: {content: 'content3', salt_id: 'id', sentence_id: 1, token_id: 1},
+                value: {salt_id: 'id', content: '', sentence_id: 1, token_id: 1}
+            }];
         previewPage.corpusService.exercise.type = ExerciseType.markWords;
         previewPage.exerciseService.excludeOOV = true;
-        previewPage.corpusService.annisResponse = new AnnisResponse({
-            nodes: [new NodeMC({is_oov: false, id: 'id'})],
+        previewPage.corpusService.annisResponse = {
+            graph_data: {nodes: [{is_oov: false, id: 'id'}], links: []},
             solutions
-        });
+        };
         previewPage.processSolutions(solutions);
         expect(previewPage.currentSolutions[2]).toBe(solutions[0]);
     });
@@ -170,8 +163,8 @@ describe('PreviewPage', () => {
     });
 
     it('should switch OOV', () => {
-        previewPage.currentSolutions = [new Solution()];
-        previewPage.corpusService.annisResponse = new AnnisResponse();
+        previewPage.currentSolutions = [{}];
+        previewPage.corpusService.annisResponse = {};
         spyOn(previewPage, 'processSolutions');
         spyOn(previewPage, 'initH5P');
         previewPage.switchOOV();
diff --git a/mc_frontend/src/app/preview/preview.page.ts b/mc_frontend/src/app/preview/preview.page.ts
index decf3eff7bfc5898db949b9fb3a366dd05b96fa6..3ada6e1cb182c21c5e5365984870aadee21da5c2 100644
--- a/mc_frontend/src/app/preview/preview.page.ts
+++ b/mc_frontend/src/app/preview/preview.page.ts
@@ -1,5 +1,4 @@
 /* tslint:disable:no-string-literal */
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {ExerciseType, FileType} from 'src/app/models/enum';
 import {HelperService} from 'src/app/helper.service';
 import {NavController, ToastController} from '@ionic/angular';
@@ -7,12 +6,12 @@ import {ExerciseService} from 'src/app/exercise.service';
 import {CorpusService} from 'src/app/corpus.service';
 import {Component, OnDestroy, OnInit} from '@angular/core';
 import {TranslateService} from '@ngx-translate/core';
-import {Solution} from 'src/app/models/solution';
 import {HttpClient} from '@angular/common/http';
 import {XAPIevent} from 'src/app/models/xAPIevent';
 import {TestResultMC} from 'src/app/models/testResultMC';
 import configMC from '../../configMC';
 import {Storage} from '@ionic/storage';
+import {AnnisResponse, Solution} from '../../../openapi';
 
 declare var H5P: any;
 
@@ -90,19 +89,19 @@ export class PreviewPage implements OnDestroy, OnInit {
         this.processSolutions(ar.solutions);
         this.corpusService.annisResponse.uri = ar.uri;
         const isUrn: boolean = this.corpusService.currentUrn && this.corpusService.currentUrn.startsWith('urn:');
-        this.corpusService.annisResponse.nodes = isUrn ? this.corpusService.annisResponse.nodes : ar.nodes;
-        this.corpusService.annisResponse.links = isUrn ? this.corpusService.annisResponse.links : ar.links;
+        this.corpusService.annisResponse.graph_data.nodes = isUrn ? this.corpusService.annisResponse.graph_data.nodes : ar.graph_data.nodes;
+        this.corpusService.annisResponse.graph_data.links = isUrn ? this.corpusService.annisResponse.graph_data.links : ar.graph_data.links;
     }
 
     processSolutions(solutions: Solution[]): void {
         const isCloze: boolean = this.corpusService.exercise.type === ExerciseType.cloze;
         if (this.exerciseService.excludeOOV) {
-            const nodeIdSet: Set<string> = new Set(this.corpusService.annisResponse.nodes.filter(
+            const nodeIdSet: Set<string> = new Set(this.corpusService.annisResponse.graph_data.nodes.filter(
                 x => !x.is_oov).map(x => x.id));
             solutions = this.corpusService.annisResponse.solutions.filter(
                 x => nodeIdSet.has(x.target.salt_id) && (isCloze || nodeIdSet.has(x.value.salt_id)));
         }
-        let newSolutions: Solution[] = [];
+        let newSolutions: Solution[];
         if (isCloze) {
             this.maxGapLength = Math.max.apply(Math, solutions.map(x => x.target.content.length));
             this.solutionNodeIdSet = new Set(solutions.map(x => x.target.salt_id));
diff --git a/mc_frontend/src/app/ranking/ranking.page.spec.ts b/mc_frontend/src/app/ranking/ranking.page.spec.ts
index d13b82bc5a6abb5da7ce461d396fb1ff52b30c65..8c51fafbc38e3b0ab0e2a034987c5a9ca8271af6 100644
--- a/mc_frontend/src/app/ranking/ranking.page.spec.ts
+++ b/mc_frontend/src/app/ranking/ranking.page.spec.ts
@@ -7,16 +7,13 @@ import {IonicStorageModule} from '@ionic/storage';
 import {RouterModule} from '@angular/router';
 import {TranslateTestingModule} from '../translate-testing/translate-testing.module';
 import {APP_BASE_HREF} from '@angular/common';
-import {CorpusService} from '../corpus.service';
 import {Sentence} from '../models/sentence';
 import Spy = jasmine.Spy;
-import {AnnisResponse} from '../models/annisResponse';
-import {NodeMC} from '../models/nodeMC';
+import {AnnisResponse} from '../../../openapi';
 
 describe('RankingPage', () => {
     let rankingPage: RankingPage;
     let fixture: ComponentFixture<RankingPage>;
-    let corpusService: CorpusService;
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
@@ -33,7 +30,6 @@ describe('RankingPage', () => {
             schemas: [CUSTOM_ELEMENTS_SCHEMA],
         })
             .compileComponents().then();
-        corpusService = TestBed.inject(CorpusService);
     }));
 
     beforeEach(() => {
@@ -48,8 +44,8 @@ describe('RankingPage', () => {
 
     it('should show the text', (done) => {
         rankingPage.helperService.isVocabularyCheck = false;
-        const vocCheckSpy: Spy = spyOn(rankingPage.vocService, 'getVocabularyCheck').and.returnValue(Promise.resolve(
-            new AnnisResponse({nodes: [new NodeMC({id: 'id/id:1-2/id'})]})));
+        const ar: AnnisResponse = {graph_data: {nodes: [{id: 'id/id:1-2/id'}], links: []}};
+        const vocCheckSpy: Spy = spyOn(rankingPage.vocService, 'getVocabularyCheck').and.returnValue(Promise.resolve(ar));
         spyOn(rankingPage.corpusService, 'processAnnisResponse');
         spyOn(rankingPage.helperService, 'goToShowTextPage').and.returnValue(Promise.resolve(true));
         rankingPage.showText([new Sentence({id: 1})]).then(() => {
diff --git a/mc_frontend/src/app/ranking/ranking.page.ts b/mc_frontend/src/app/ranking/ranking.page.ts
index 331a3e5afcf99a5b1c29e3d6b4d2094e686e95b3..b359f58cbe53c4a6d13e1f9e60bae75848a797a8 100644
--- a/mc_frontend/src/app/ranking/ranking.page.ts
+++ b/mc_frontend/src/app/ranking/ranking.page.ts
@@ -1,12 +1,12 @@
 import {Component} from '@angular/core';
 import {VocabularyService} from 'src/app/vocabulary.service';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import {ExerciseService} from 'src/app/exercise.service';
 import {HelperService} from 'src/app/helper.service';
 import {NavController, ToastController} from '@ionic/angular';
 import {CorpusService} from 'src/app/corpus.service';
 import {HttpErrorResponse} from '@angular/common/http';
 import {Sentence} from 'src/app/models/sentence';
+import {AnnisResponse} from '../../../openapi';
 
 @Component({
     selector: 'app-ranking',
@@ -30,8 +30,8 @@ export class RankingPage {
         return new Promise<void>((resolve, reject) => {
             this.corpusService.currentUrn = this.corpusService.baseUrn + `@${rank[0].id}-${rank[rank.length - 1].id}`;
             this.vocService.getVocabularyCheck(this.corpusService.currentUrn, true).then((ar: AnnisResponse) => {
-                const urnStart: string = ar.nodes[0].id.split('/')[1];
-                const urnEnd: string = ar.nodes.slice(-1)[0].id.split('/')[1];
+                const urnStart: string = ar.graph_data.nodes[0].id.split('/')[1];
+                const urnEnd: string = ar.graph_data.nodes.slice(-1)[0].id.split('/')[1];
                 this.corpusService.currentUrn = urnStart.concat('-', urnEnd.split(':').slice(-1)[0]);
                 this.corpusService.processAnnisResponse(ar);
                 this.helperService.isVocabularyCheck = true;
diff --git a/mc_frontend/src/app/semantics/semantics.page.spec.ts b/mc_frontend/src/app/semantics/semantics.page.spec.ts
index 5c86a7ee3c435ac390a5fe0f1c876615c1c53a39..b30aa61e9f96ccf2fc1a0a69f875c4e311a0c5ff 100644
--- a/mc_frontend/src/app/semantics/semantics.page.spec.ts
+++ b/mc_frontend/src/app/semantics/semantics.page.spec.ts
@@ -31,10 +31,13 @@ describe('SemanticsPage', () => {
             ],
             schemas: [CUSTOM_ELEMENTS_SCHEMA],
         }).compileComponents().then();
+    }));
+
+    beforeEach(() => {
         fixture = TestBed.createComponent(SemanticsPage);
         semanticsPage = fixture.componentInstance;
         fixture.detectChanges();
-    }));
+    });
 
     it('should create', () => {
         expect(semanticsPage).toBeTruthy();
diff --git a/mc_frontend/src/app/show-text/show-text.page.html b/mc_frontend/src/app/show-text/show-text.page.html
index a439707ee16c7693b37a76969628606d018ef356..c5b202f402384e9f61716e8034567f29c74d5d1f 100644
--- a/mc_frontend/src/app/show-text/show-text.page.html
+++ b/mc_frontend/src/app/show-text/show-text.page.html
@@ -39,8 +39,9 @@
             <ion-col class="text" size="8">
                 <div *ngIf="highlightOOV; else noOOV">
                     <!-- do not add whitespace/newline etc. between the span elements!                   -->
-                    <span *ngFor="let node of corpusService.annisResponse.nodes; index as i"><span class="tok"
-                                                                                                   [class.oov]="node.is_oov">{{node.annis_tok}}</span>{{ getWhiteSpace(i) }}</span>
+                    <span *ngFor="let node of corpusService.annisResponse.graph_data.nodes; index as i"><span
+                            class="tok"
+                            [class.oov]="node.is_oov">{{node.annis_tok}}</span>{{ getWhiteSpace(i) }}</span>
                 </div>
                 <ng-template #noOOV>
                     <span class="tok">{{corpusService.currentText}}</span>
diff --git a/mc_frontend/src/app/show-text/show-text.page.spec.ts b/mc_frontend/src/app/show-text/show-text.page.spec.ts
index 87ccf6a424cc4bb7ee7897266c7c54b45b121174..04152dcc223db583cfdc4ebbb56235da4cdfc2d5 100644
--- a/mc_frontend/src/app/show-text/show-text.page.spec.ts
+++ b/mc_frontend/src/app/show-text/show-text.page.spec.ts
@@ -8,8 +8,6 @@ import {RouterModule} from '@angular/router';
 import {TranslateTestingModule} from '../translate-testing/translate-testing.module';
 import {FormsModule} from '@angular/forms';
 import {APP_BASE_HREF} from '@angular/common';
-import {AnnisResponse} from '../models/annisResponse';
-import {NodeMC} from '../models/nodeMC';
 import {VocabularyCorpus} from '../models/enum';
 import Spy = jasmine.Spy;
 import MockMC from '../models/mockMC';
@@ -68,13 +66,13 @@ describe('ShowTextPage', () => {
     });
 
     it('should get whitespace', () => {
-        showTextPage.corpusService.annisResponse = new AnnisResponse({nodes: []});
+        showTextPage.corpusService.annisResponse = {graph_data: {nodes: [], links: []}};
         let result: string = showTextPage.getWhiteSpace(0);
         expect(result.length).toBe(0);
-        showTextPage.corpusService.annisResponse.nodes = [new NodeMC(), new NodeMC()];
+        showTextPage.corpusService.annisResponse.graph_data.nodes = [{}, {}];
         result = showTextPage.getWhiteSpace(0);
         expect(result.length).toBe(1);
-        showTextPage.corpusService.annisResponse.nodes[1].annis_tok = '.';
+        showTextPage.corpusService.annisResponse.graph_data.nodes[1].annis_tok = '.';
         result = showTextPage.getWhiteSpace(0);
         expect(result.length).toBe(0);
     });
diff --git a/mc_frontend/src/app/show-text/show-text.page.ts b/mc_frontend/src/app/show-text/show-text.page.ts
index dc87fee98a7480299c00e710937af79deea276eb..890e818c9e0e16fd28ee6ea607b0af4bed81f678 100644
--- a/mc_frontend/src/app/show-text/show-text.page.ts
+++ b/mc_frontend/src/app/show-text/show-text.page.ts
@@ -84,9 +84,10 @@ export class ShowTextPage implements OnInit {
     }
 
     getWhiteSpace(index: number): string {
-        if (this.corpusService.annisResponse.nodes[index + 1]) {
-            if (this.corpusService.annisResponse.nodes[index + 1].annis_tok &&
-                this.corpusService.annisResponse.nodes[index + 1].annis_tok.search(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g) >= 0) {
+        if (this.corpusService.annisResponse.graph_data.nodes[index + 1]) {
+            if (this.corpusService.annisResponse.graph_data.nodes[index + 1].annis_tok &&
+                this.corpusService.annisResponse.graph_data.nodes[index + 1].annis_tok
+                    .search(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g) >= 0) {
                 return '';
             }
             return ' ';
diff --git a/mc_frontend/src/app/vocabulary.service.spec.ts b/mc_frontend/src/app/vocabulary.service.spec.ts
index 311b97df79ceef570b5c506104ccc346e4e3661a..b800c5fa662f3e1d85a90516626e62d554205878 100644
--- a/mc_frontend/src/app/vocabulary.service.spec.ts
+++ b/mc_frontend/src/app/vocabulary.service.spec.ts
@@ -6,9 +6,9 @@ import {IonicStorageModule} from '@ionic/storage';
 import {TranslateTestingModule} from './translate-testing/translate-testing.module';
 import {VocabularyCorpus} from './models/enum';
 import {Sentence} from './models/sentence';
-import {AnnisResponse} from './models/annisResponse';
 import {HttpErrorResponse} from '@angular/common/http';
 import Spy = jasmine.Spy;
+import {AnnisResponse} from '../../openapi';
 
 describe('VocabularyService', () => {
     let vocabularyService: VocabularyService;
@@ -37,7 +37,8 @@ describe('VocabularyService', () => {
         vocabularyService.getVocabularyCheck('', false).then(() => {
         }, async (response: HttpErrorResponse) => {
             expect(response.status).toBe(500);
-            requestSpy.and.returnValue(Promise.resolve(new AnnisResponse()));
+            const ar: AnnisResponse = {};
+            requestSpy.and.returnValue(Promise.resolve(ar));
             const result: AnnisResponse | Sentence[] = await vocabularyService.getVocabularyCheck('', true);
             expect(result.hasOwnProperty('length')).toBe(false);
             done();
diff --git a/mc_frontend/src/app/vocabulary.service.ts b/mc_frontend/src/app/vocabulary.service.ts
index d2fb10ed4ec404e2db7185623b79476cbd7fa7a3..13ff907c2d2d76d60d27fb69dc73c2107ccdbca0 100644
--- a/mc_frontend/src/app/vocabulary.service.ts
+++ b/mc_frontend/src/app/vocabulary.service.ts
@@ -7,8 +7,8 @@ import {Sentence} from 'src/app/models/sentence';
 import {HelperService} from 'src/app/helper.service';
 import {TestResultMC} from 'src/app/models/testResultMC';
 import {ToastController} from '@ionic/angular';
-import {AnnisResponse} from 'src/app/models/annisResponse';
 import configMC from '../configMC';
+import {AnnisResponse} from '../../openapi';
 
 @Injectable({
     providedIn: 'root'