diff --git a/mc_openapi/__main__.py b/mc_openapi/__main__.py
index 4d7e3e897dd7ab7dd99fa3f5025f5a297228af98..3d4bf9315506f0bdf811effcb280862e419da1a3 100644
--- a/mc_openapi/__main__.py
+++ b/mc_openapi/__main__.py
@@ -2,6 +2,9 @@
 import argparse
 import logging
 from logging.config import dictConfig
+import re
+
+from tabulate import tabulate
 
 from mc_openapi.app_config import app
 from mc_openapi.doml_mc import DOMLVersion, init_model, verify_csp_compatibility, verify_model, synthesize_model
@@ -88,8 +91,16 @@ else:
 
         # Check CSP Compatibility
         if args.csp:
-            verify_csp_compatibility(dmc)
-            # TODO: Do something with the results
+            csp = verify_csp_compatibility(dmc)
+            for csp_k, csp_v in csp.items():
+                # Format items in minreq
+                if csp_k == 'minreq':
+                    for row in csp_v:
+                        for index, col in enumerate(row):
+                            if index > 0 and isinstance(col, list):
+                                row[index] = "\n".join(col)
+
+                print(tabulate(csp_v, headers='firstrow', tablefmt='fancy_grid'))
         else:
             result, msg = verify_model(dmc, domlr_src, args.threads, args.consistency, args.skip_builtin)
 
diff --git a/mc_openapi/app_config.py b/mc_openapi/app_config.py
index f6234dd5585799ceb66b993316301db9b8ebc97f..869bdba07f84d0bffa6bb94762306111e6b425da 100644
--- a/mc_openapi/app_config.py
+++ b/mc_openapi/app_config.py
@@ -1,8 +1,12 @@
 import connexion
-
-from logging.config import dictConfig
+from flask import render_template
 
 app = connexion.App(__name__, specification_dir='openapi/')
 app.add_api('model_checker.yaml')
 
+@app.route("/")
+def home():
+    return render_template('home.html')
+
+
 application = app.app
diff --git a/mc_openapi/doml_mc/csp_compatibility/allowlist_check_v1.py b/mc_openapi/doml_mc/csp_compatibility/allowlist_check_v1.py
index 7f641c04e64b7518dadda730ec83325935e4c4ae..3c8b0c4aadfd7f7ad42b2daba1160d18ab849fe1 100644
--- a/mc_openapi/doml_mc/csp_compatibility/allowlist_check_v1.py
+++ b/mc_openapi/doml_mc/csp_compatibility/allowlist_check_v1.py
@@ -3,7 +3,6 @@
 from mc_openapi.doml_mc.intermediate_model.doml_element import DOMLElement, IntermediateModel
 from mc_openapi.doml_mc.intermediate_model.metamodel import DOMLVersion
 from difflib import SequenceMatcher
-from tabulate import tabulate
 
 import re
 
@@ -11,22 +10,25 @@ class CSPCompatibilityValidator:
     def __init__(self, data: dict) -> None:
         self.data = data
 
-    def check(self, model: IntermediateModel, doml_version: DOMLVersion) -> list[str]:
+    def check(self, model: IntermediateModel, doml_version: DOMLVersion) -> dict[str, list]:
         """Returns a list of CSP supported by the model"""
 
+        ret = {}
+
         # Check KeyPair
         keypairs = self.check_keypair(model)
         if len(keypairs) > 1:
-            print(tabulate(keypairs, headers='firstrow', tablefmt='fancy_grid'))
-
+            ret['keypairs'] = keypairs
         # ComputingNode and inheritors
         arch, os, minreq = self.check_computing_nodes(model)
         if len(arch) > 1:
-            print(tabulate(arch, headers='firstrow', tablefmt='fancy_grid'))
+            ret['arch'] = arch
         if len(os) > 1:
-            print(tabulate(os, headers='firstrow', tablefmt='fancy_grid'))
+            ret['os'] = os
         if len(minreq) > 1:
-            print(tabulate(minreq, headers='firstrow', tablefmt='fancy_grid'))
+            ret['minreq'] = minreq
+
+        return ret
 
     def check_keypair(self, model: IntermediateModel):
         elems = model.values()
@@ -107,13 +109,18 @@ class CSPCompatibilityValidator:
                         CHECKS[req] = el.associations.get(req) is not None or el.attributes.get(req) is not None
                 
                 all_req_valid = all([v for v in CHECKS.values()])
-                value = '✅' if all_req_valid else '❌'
+                value = ['✅' if all_req_valid else '❌']
                 if not all_req_valid:
-                    value += " Missing:\n" + "\n".join([
-                        k.replace("_", ".", 1).replace("::", ".")
+                    value += [
+                        re.sub(r"\s*,\s*", " -> ", k)
+                        .replace("_", ".", 1)
+                        .replace("::", ".")
+                        .replace("[", "")
+                        .replace("]", "")
+                        .replace("'", "")
                         for k, v in CHECKS.items() 
                         if v is False
-                    ])
+                    ]
                 ROW.append(value)
             MINREQ_TABLE.append(ROW)
 
diff --git a/mc_openapi/doml_mc/main.py b/mc_openapi/doml_mc/main.py
index 189bb72a7eb7a000736a534b2f934cd6ad818990..ac542d106cde4ed108defe1fa555027c2a15558f 100644
--- a/mc_openapi/doml_mc/main.py
+++ b/mc_openapi/doml_mc/main.py
@@ -8,7 +8,7 @@ from mc_openapi.doml_mc import ModelChecker
 from mc_openapi.doml_mc.common_reqs import CommonRequirements
 from mc_openapi.doml_mc.consistency_reqs import get_association_multiplicity_reqs, get_association_type_reqs, get_attribute_multiplicity_reqs, get_attribute_type_reqs, get_inverse_association_reqs
 from mc_openapi.doml_mc.csp_compatibility import \
-    CSPCompatibilityValidator as csp_comp
+    CSPCompatibilityValidator as CSPCompatibility
 from mc_openapi.doml_mc.domlr_parser import (DOMLRTransformer, Parser,
                                              SynthesisDOMLRTransformer)
 from mc_openapi.doml_mc.imc import RequirementStore
@@ -155,6 +155,5 @@ def synthesize_model(dmc: ModelChecker, external_domlr: str, max_tries: int):
 
     
 def verify_csp_compatibility(dmc: ModelChecker):
-    csp_comp.check(dmc.intermediate_model, dmc.doml_version)
-    # TODO: Refactor CSP to output a datastructure/table to print via CLI or REST
-    exit(0)
\ No newline at end of file
+    return CSPCompatibility.check(dmc.intermediate_model, dmc.doml_version)
+    
\ No newline at end of file
diff --git a/mc_openapi/handlers.py b/mc_openapi/handlers.py
index 488b6a87fafbd59977e318003ec170af58702256..5308e80b2eaf2221b51d226647985448918d31af 100644
--- a/mc_openapi/handlers.py
+++ b/mc_openapi/handlers.py
@@ -1,6 +1,8 @@
 import datetime
 import logging
 import os
+
+from flask import render_template
 from mc_openapi.doml_mc.html_output import html_template
 
 from mc_openapi.doml_mc.intermediate_model.metamodel import DOMLVersion
@@ -71,7 +73,12 @@ def csp(body, version=None):
         dmc = init_model(doml_xmi, doml_version)
         
         # TODO: Do something with the results
-        verify_csp_compatibility(dmc)
+        return verify_csp_compatibility(dmc)
+    
 
     except Exception as e:
         return make_error("The supplied DOMLX model is malformed or its DOML version is unsupported.", debug_msg=str(e)), 400
+
+def csp_html(body, version=None):
+    ret = csp(body, version)
+    return render_template('csp.html.jinja', **ret).replace('\n', '')
diff --git a/mc_openapi/openapi/model_checker.yaml b/mc_openapi/openapi/model_checker.yaml
index 1cb4f6e761567db33bcd8f01ed62d7025427668c..3a20209a933620f3a45da32c76595d088a1f4f4b 100644
--- a/mc_openapi/openapi/model_checker.yaml
+++ b/mc_openapi/openapi/model_checker.yaml
@@ -113,6 +113,58 @@ paths:
             schema:
               type: string
         required: true
+      parameters:
+      - in: query
+        name: version
+        schema:
+          type: string
+        description: >
+          The DOML version you're currently using, written as e.g. `2.0` or `V2_0`.
+          If not specified, it will try to infer it by trying to parse it for each supported
+          DOML version, starting with the most recent.
+        required: false
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                type: object
+          description: OK - CSP compatibility successfully completed.
+        "400":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/error'
+          description: malformed request
+        "500":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/error'
+          description: internal error
+  /csp_html:
+    post:
+      summary: Verify compatibility of a DOML model with a set of Cloud Service Providers.
+      description: Send a DOML model in XMI format and a requirement to check.
+        The response says whether the model can be deployed on the supported CSPs.
+        It produces a compatibility matrix and lists required properties for each CSP.
+      operationId: mc_openapi.handlers.csp_html
+      requestBody:
+        content:
+          application/xml:
+            schema:
+              type: string
+        required: true
+      parameters:
+      - in: query
+        name: version
+        schema:
+          type: string
+        description: >
+          The DOML version you're currently using, written as e.g. `2.0` or `V2_0`.
+          If not specified, it will try to infer it by trying to parse it for each supported
+          DOML version, starting with the most recent.
+        required: false
       responses:
         "200":
           content:
diff --git a/mc_openapi/templates/csp.html.jinja b/mc_openapi/templates/csp.html.jinja
new file mode 100644
index 0000000000000000000000000000000000000000..d4bb921294a7e03f5d90a74b2a2e749b9004991f
--- /dev/null
+++ b/mc_openapi/templates/csp.html.jinja
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <title>CSP Compatibility Report</title>
+    <meta charset="utf8">
+    <style type="text/css">
+        {% include "style.css" %}
+    </style>
+</head>
+<body>
+    <h1>CSP Compatibility Report</h1>
+
+    {% if keypairs %}
+    <h2>Keypairs</h2>
+        <table>
+        {% for row in keypairs %}
+        {% set outer_loop = loop %}
+            <tr>
+            {% for column in row %}
+            {% if outer_loop.first or loop.first %}
+                <th>{{ column }}</th>
+            {% else %}
+                <td>{{ column }}</td>
+            {% endif %}
+            {% endfor %}
+            </tr>
+        {% endfor %}
+        </table>
+    {% endif %}
+
+    {% if os %}
+    <h2>OS</h2>
+        <table>
+        {% for row in os %}
+        {% set outer_loop = loop %}
+            <tr>
+            {% for column in row %}
+            {% if outer_loop.first or loop.first %}
+                <th>{{ column }}</th>
+            {% else %}
+                <td>{{ column }}</td>
+            {% endif %}
+            {% endfor %}
+            </tr>
+        {% endfor %}
+        </table>
+    {% endif %}
+
+    {% if arch %}
+        <h2>Architectures</h2>
+        <table>
+        {% for row in arch %}
+        {% set outer_loop = loop %}
+            <tr>
+            {% for column in row %}
+            {% if outer_loop.first or loop.first %}
+                <th>{{ column }}</th>
+            {% else %}
+                <td>{{ column }}</td>
+            {% endif %}
+            {% endfor %}
+            </tr>
+        {% endfor %}
+        </table>
+    {% endif %}
+
+    {% if minreq %}
+        <h2>Minimum Requirements</h2>
+        <table>
+        {% for row in minreq %}
+        {% set outer_loop = loop %}
+            <tr>
+            {% for column in row %}
+            {% if outer_loop.first or loop.first %}
+                <th>{{ column }}</th>
+            {% else %}
+            <td>
+                <ul>
+                    {% for item in column %}
+                    <li>
+                        {{ item }}
+                    </li>
+                    {% endfor %}
+                </ul>
+            </td>
+            {% endif %}
+            {% endfor %}
+            </tr>
+        {% endfor %}
+        </table>
+    {% endif %}
+</body>
+</html>
\ No newline at end of file
diff --git a/mc_openapi/templates/home.html b/mc_openapi/templates/home.html
new file mode 100644
index 0000000000000000000000000000000000000000..c71017d9ae33da28f92e61be9f978b0d2ab538d5
--- /dev/null
+++ b/mc_openapi/templates/home.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <title>DOML Model Checker - Dashboard</title>
+    <meta charset="utf-8">
+</head>
+<body>
+    <h1>DOML Model Checker - Dashboard</h1>
+    <input type="file" id="domlx" name="domlx" accept="text/xml,.domlx" />
+
+    <script>
+        const input = document.querySelector("#domlx")
+        input.addEventListener('change', readFile, false)
+
+        async function readFile(event) {
+            const file = event.target.files[0]
+            if (file) {
+                const res = await fetch('/csp_html', {
+                    method: 'POST',
+                    headers: {
+                        "Content-Type": "application/xml",
+                    },
+                    redirect: "follow",
+                    body: await file.text()
+                })
+                if (res.status === 200) {
+                    newHtml = await res.text()
+                    // Fix escaped quotes for tags
+                    newHtml = newHtml.replaceAll("\\\"", "\"")
+                    // Unescape emojis
+                    // See: https://stackoverflow.com/questions/51640509/
+                    newHtml = newHtml.replace(/\\u[\dA-F]{4}/gi, function(match) {
+                        return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
+                    })
+                    // Remove trailing and heading "
+                    newHtml = newHtml.substr(1, newHtml.length - 3)
+                    document.write(newHtml)
+                }
+            }
+        }
+
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/mc_openapi/templates/style.css b/mc_openapi/templates/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..9336cc2e4ebf42fe28f07e82dafa1ea864973111
--- /dev/null
+++ b/mc_openapi/templates/style.css
@@ -0,0 +1,44 @@
+* {
+    color: black;
+    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+}
+
+/* spacing */
+
+table {
+    table-layout: fixed;
+    width: 100%;
+    border-collapse: collapse;
+    border: 1px solid black;
+}
+
+th,
+td {
+    padding: 0.5em;
+    border: 1px solid black;
+    text-align: left;
+}
+
+th {
+    background-color: #e7e7e7;
+}
+
+td {
+    vertical-align: top;
+}
+
+ul {
+    padding: 0;
+    margin: 0;
+    list-style: none;
+}
+
+li {
+    word-wrap: anywhere;
+    margin-bottom: 1em;
+}
+
+li:last-child {
+    margin-bottom: 0;
+}
+  
\ No newline at end of file