diff --git a/experimental/uloapi/pom.xml b/experimental/uloapi/pom.xml
index dae17b2eb51192c5aaa54a271f81905f5f27d998..950618486c30fc1581c3df7abe8d5c39ddc75010 100644
--- a/experimental/uloapi/pom.xml
+++ b/experimental/uloapi/pom.xml
@@ -18,7 +18,7 @@
                 <configuration>
                     <archive>
                         <manifest>
-                            <mainClass>info.mathhub.uloapi.rest.Main</mainClass>
+                            <mainClass>info.mathhub.uloapi.html.Main</mainClass>
                         </manifest>
                     </archive>
                     <descriptorRefs>
@@ -41,7 +41,7 @@
                 <artifactId>exec-maven-plugin</artifactId>
                 <version>1.2.1</version>
                 <configuration>
-                    <mainClass>info.mathhub.uloapi.rest.Main</mainClass>
+                    <mainClass>info.mathhub.uloapi.html.Main</mainClass>
                 </configuration>
             </plugin>
         </plugins>
@@ -78,5 +78,10 @@
             <artifactId>graphdb-free-runtime</artifactId>
             <version>9.2.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.sparkjava</groupId>
+            <artifactId>spark-template-freemarker</artifactId>
+            <version>2.7.1</version>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Errors.java b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Errors.java
new file mode 100644
index 0000000000000000000000000000000000000000..23b78eadfd3d130fecbec56679087e406b34e2df
--- /dev/null
+++ b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Errors.java
@@ -0,0 +1,59 @@
+package info.mathhub.uloapi.html;
+
+import org.eclipse.jetty.http.HttpStatus;
+import spark.ModelAndView;
+import spark.Request;
+import spark.Response;
+import spark.Route;
+import spark.template.freemarker.FreeMarkerEngine;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Errors {
+    private Errors() {};
+
+    /**
+     * Singleton instance of the Freemarker engine for use with errors.
+     * Used in {@link Errors#createError}.
+     */
+    private static final FreeMarkerEngine freemarker = new FreeMarkerEngine();
+
+    public static final Route notFound = (Request request, Response response) -> {
+        return createError(HttpStatus.NOT_FOUND_404).handle(request, response);
+    };
+
+    public static final Route notAcceptable = (Request request, Response response) -> {
+        return createError(HttpStatus.NOT_ACCEPTABLE_406).handle(request, response);
+    };
+
+    public static final Route notImplemented = (Request request, Response response) -> {
+        return createError(HttpStatus.NOT_IMPLEMENTED_501).handle(request, response);
+    };
+
+    public static final Route internalServerError = (Request request, Response response) -> {
+        return createError(HttpStatus.INTERNAL_SERVER_ERROR_500).handle(request, response);
+    };
+
+    private static Route createError(int status) {
+        return (Request request, Response response) -> {
+            // set content type
+
+            response.type("text/html");
+
+            // prepare model
+
+            final Map<String, Object> model = new HashMap<>();
+            model.put("status", status);
+            model.put("description", HttpStatus.getMessage(status));
+
+            // render
+
+            final ModelAndView modelAndView = new ModelAndView(model, "error.flt");
+
+            synchronized (Errors.freemarker) {
+                return Errors.freemarker.render(modelAndView);
+            }
+        };
+    }
+}
diff --git a/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Filters.java b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Filters.java
new file mode 100644
index 0000000000000000000000000000000000000000..22f1bc13562079d4c7b76cfa83fa0217d38456ca
--- /dev/null
+++ b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Filters.java
@@ -0,0 +1,13 @@
+package info.mathhub.uloapi.html;
+
+import spark.Filter;
+import spark.Request;
+import spark.Response;
+
+public class Filters {
+    private Filters() {};
+
+    public static final Filter setHtmlContentType = (Request request, Response response) -> {
+        response.type("text/html");
+    };
+}
diff --git a/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Main.java b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..16812fb32ad8e3c5bd90787a42a645e01c747c35
--- /dev/null
+++ b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Main.java
@@ -0,0 +1,21 @@
+package info.mathhub.uloapi.html;
+
+import spark.template.freemarker.FreeMarkerEngine;
+
+import static spark.Spark.*;
+
+/**
+ * Entry point of the JSON/REST API for the underlying ULO/RDF store.
+ */
+public class Main {
+    private Main() {};
+
+    public static void main(String[] args) {
+        get("/", Routes.index, new FreeMarkerEngine());
+
+        notFound(Errors.notFound);
+        internalServerError(Errors.internalServerError);
+
+        after(Filters.setHtmlContentType);
+    }
+}
diff --git a/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Routes.java b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Routes.java
new file mode 100644
index 0000000000000000000000000000000000000000..4371cf7151bf824d625ce8b335da31855f30d9ec
--- /dev/null
+++ b/experimental/uloapi/src/main/java/info/mathhub/uloapi/html/Routes.java
@@ -0,0 +1,18 @@
+package info.mathhub.uloapi.html;
+
+import info.mathhub.uloapi.query.Query;
+import spark.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class contains all routes of our application.
+ */
+public class Routes {
+    private Routes() {};
+
+    public static final TemplateViewRoute index = (Request request, Response response) -> {
+        return new ModelAndView(null, "index.flt");
+    };
+}
diff --git a/experimental/uloapi/src/main/resources/spark/template/freemarker/base.flt b/experimental/uloapi/src/main/resources/spark/template/freemarker/base.flt
new file mode 100644
index 0000000000000000000000000000000000000000..00db730910a21bcef2d729cd062a81f5ccd55be8
--- /dev/null
+++ b/experimental/uloapi/src/main/resources/spark/template/freemarker/base.flt
@@ -0,0 +1,32 @@
+<#macro page_title>
+    Page Title
+</#macro>
+
+<#macro page_main>
+    <h1>Page Title</h1>
+    <p>Page Body</p>
+</#macro>
+
+<#macro display_page>
+    <!DOCTYPE html>
+    <html>
+        <head>
+            <title><@page_title/></title>
+            <style>
+                * {
+                    font-family: "Open Sans", Helvetica, Arial;
+                    font-size: 12pt;
+                }
+
+                a {
+                    color: blue;
+                }
+            </style>
+        </head>
+        <body>
+            <main>
+                <@page_main/>
+            </main>
+        </body>
+    </html>
+</#macro>
\ No newline at end of file
diff --git a/experimental/uloapi/src/main/resources/spark/template/freemarker/error.flt b/experimental/uloapi/src/main/resources/spark/template/freemarker/error.flt
new file mode 100644
index 0000000000000000000000000000000000000000..aab46438a25ba64d3a5c06602f3fa4719e9cf52f
--- /dev/null
+++ b/experimental/uloapi/src/main/resources/spark/template/freemarker/error.flt
@@ -0,0 +1,12 @@
+<#include "base.flt">
+
+<#macro page_title>
+    Error ${status}
+</#macro>
+
+<#macro page_main>
+    <h1>Error ${status}: ${description}</h1>
+    <p>Grow old or die trying.</p>
+</#macro>
+
+<@display_page/>
diff --git a/experimental/uloapi/src/main/resources/spark/template/freemarker/index.flt b/experimental/uloapi/src/main/resources/spark/template/freemarker/index.flt
new file mode 100644
index 0000000000000000000000000000000000000000..9f0757657d6a33ef790a760c752b7fff48827c55
--- /dev/null
+++ b/experimental/uloapi/src/main/resources/spark/template/freemarker/index.flt
@@ -0,0 +1,27 @@
+<#include "base.flt">
+
+<#macro page_title>
+    ULO/RDF Endpoint
+</#macro>
+
+<#macro page_main>
+    <h1>ULO/RDF Endpoint</h1>
+    <p>This is a technology demo of an ULO/RDF Endpoint.</p>
+    <h2>Introduction</h2>
+    <p>
+        Working with RDF triplets is somewhat easy with the <a href="https://rdf4j.org">RDF4J</a> library.
+        This demo uses RDF4J to illustrate that (1) a connection to an existing database can be established
+        and (2) this database is filled with some sample data from <a href="https://mathhub.info/">MathHub</a>
+        repositories.
+    </p>
+    <p>
+        It is no incident that this demo is hacked together with a JVM-based stack. Potential integration
+        into MMT should be easier with lessons learned from this demo.
+    </p>
+    <h2>Demos</h2>
+    <ul>
+        <li><a href="/statistics">Show (not every interesting) Statistics</a> about the imported ULO/RDF dataset.</li>
+    </ul>
+</#macro>
+
+<@display_page/>
\ No newline at end of file