diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java
index 780729bf5a1effb97371964883d3e9356297544a..c45f02562fa72036b24749ab4fdca47fbf87ed41 100644
--- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java
+++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java
@@ -24,6 +24,7 @@ import java.util.Objects;
  */
 public class ParserUtils {
 
+  public static final String UNKNOWN_GROUP_NAME = "Unknown";
   private static boolean verboseLoading = false;
   private static final Logger logger = LogManager.getLogger(ParserUtils.class);
 
@@ -172,7 +173,7 @@ public class ParserUtils {
    */
   public static void createUnknownGroup(OpenHAB2Model model, Collection<Item> danglingItems) {
     Group unknownGroup = new Group();
-    unknownGroup.setID("Unknown");
+    unknownGroup.setID(UNKNOWN_GROUP_NAME);
     model.addGroup(unknownGroup);
     danglingItems.forEach(unknownGroup::addItem);
     logger.info("Created new {}", unknownGroup.prettyPrint().trim());
@@ -234,4 +235,21 @@ public class ParserUtils {
     return result;
   }
 
+  public static Item parseItem(String definition)
+      throws IllegalArgumentException, IOException, Parser.Exception {
+    StringReader reader = new StringReader(definition);
+    EraserScanner scanner = new EraserScanner(reader);
+    EraserParser parser = new EraserParser();
+    Root root = (Root) parser.parse(scanner);
+    reader.close();
+    int size = root.getOpenHAB2Model().items().size();
+    if (size == 0) {
+      throw new IllegalArgumentException("Model does not contain any items!");
+    }
+    if (size > 1) {
+      logger.warn("Model does contain {} items, ignoring all but the first.", size);
+    }
+    return root.getOpenHAB2Model().items().get(0);
+  }
+
 }
diff --git a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java
index ae15f566876bb233706d34a9b563fa85af195244..c1c8c2f40e4a3847e4dbfa7ea4b95a406df4d1d2 100644
--- a/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java
+++ b/eraser.spark/src/main/java/de/tudresden/inf/st/eraser/spark/Application.java
@@ -3,12 +3,15 @@ package de.tudresden.inf.st.eraser.spark;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import de.tudresden.inf.st.eraser.jastadd.model.*;
 import de.tudresden.inf.st.eraser.util.JavaUtils;
+import de.tudresden.inf.st.eraser.util.ParserUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.eclipse.jetty.http.HttpStatus;
 import spark.Request;
 import spark.Response;
 import spark.Spark;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -41,14 +44,20 @@ public class Application {
     Spark.path("/", () -> Spark.before((request, response) -> logger.debug(request.pathInfo())));
 
     Spark.path("/activity", () -> {
+
+      //--- GET /activity ---
       Spark.get("",
           (request, response) -> wrapActivityList(root.getMachineLearningRoot().getActivityList()),
           mapper::writeValueAsString);
+
+      //--- GET /activity/current ---
       Spark.get("/current",
           (request, response) -> JavaUtils.ifPresentOrElseReturn(root.currentActivity(),
               this::wrapActivity,
               () -> makeError(response, 204, "No activity recognized.")),
           mapper::writeValueAsString);
+
+      //--- PUT /activity/current ---
       Spark.put("/current", (request, response) -> {
         logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength());
         if (!root.getMachineLearningRoot().hasActivityRecognition()) {
@@ -62,6 +71,8 @@ public class Application {
           return makeError(response, 501, "Activity not editable for " + activityRecognition.getClass().getSimpleName());
         }
       });
+
+      //--- GET /activity/:identifier ---
       Spark.get("/:identifier",
           (request, response) ->
               JavaUtils.ifPresentOrElseReturn(root.resolveActivity(paramAsInt(request, "identifier")),
@@ -71,9 +82,13 @@ public class Application {
     });
 
     Spark.path("/events", () -> {
+
+      //--- GET /events ---
       Spark.get("",
           (request, response) -> wrapChangeEventList(root.getMachineLearningRoot().getChangeEventList()),
           mapper::writeValueAsString);
+
+      //--- GET /events/:identifier ---
       Spark.get("/:identifier",
           (request, response) ->
               JavaUtils.ifPresentOrElseReturn(root.resolveChangeEvent(paramAsInt(request, "identifier")),
@@ -83,33 +98,63 @@ public class Application {
     });
 
     Spark.path("/model", () -> {
+
+      //--- GET /model/full ---
       Spark.get("/full", (request, response) -> {
         response.type("text/plain");
         return root.prettyPrint();
       });
-      Spark.get("/items",
-          (request, response) -> wrapItemList(root.getOpenHAB2Model().items()),
-          mapper::writeValueAsString);
-      Spark.put("/items/:identifier/state", (request, response) -> {
-        logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength());
-        return safeItemRoute(request, response,
-            item -> {
-              try {
-                item.setStateFromString(request.body());
-                return "OK";
-              } catch (Exception e) {
-                logger.catching(e);
-                return makeError(response, 500, e.getMessage());
-              }
-            });
-      });
-      Spark.get("/items/:identifier/history",
-          (request, response) -> {
+      Spark.path("/items", () -> {
+
+        //--- GET /model/items ---
+        Spark.get("",
+            (request, response) -> wrapItemList(root.getOpenHAB2Model().items()),
+            mapper::writeValueAsString);
+        Spark.path("/:identifier", () -> {
+
+          Spark.put("", (request, response) -> {
+            OpenHAB2Model openHAB2Model = root.getOpenHAB2Model();
+            Item item = ParserUtils.parseItem(request.body());
+            if (!openHAB2Model.resolveItem(item.getID()).isPresent()) {
+              JavaUtils.ifPresentOrElse(
+                  root.getOpenHAB2Model().resolveGroup(ParserUtils.UNKNOWN_GROUP_NAME),
+                  group -> group.addItem(item),
+                  () -> ParserUtils.createUnknownGroup(root.getOpenHAB2Model(), Collections.singletonList(item)));
+              response.status(201);
+            }
+            return "OK";
+          });
+
+          //--- GET /model/items/:identifier/state ---
+          Spark.get("/state", (request, response) ->
+              safeItemRoute(request, response, Item::getStateAsString));
+
+          //--- PUT /model/items/:identifier/state ---
+          Spark.put("/state", (request, response) -> {
             logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength());
-            return safeItemRoute(request, response, item -> makeHistory(item, response));
+            return safeItemRoute(request, response,
+                item -> {
+                  try {
+                    item.setStateFromString(request.body());
+                    return "OK";
+                  } catch (Exception e) {
+                    logger.catching(e);
+                    return makeError(response, 500, e.getMessage());
+                  }
+                });
           });
+        });
+
+        //--- GET /model/items/:identifier/history ---
+        Spark.get("/:identifier/history",
+            (request, response) -> {
+              logger.info("request body: '{}', params: '{}', length={}", request.body(), request.params(), request.contentLength());
+              return safeItemRoute(request, response, item -> makeHistory(item, response));
+            });
+      });
     });
 
+    //--- POST /system/exit ---
     Spark.post("/system/exit", (request, response) -> {
       try {
         lock.lock();
diff --git a/integration_test/.gitignore b/integration_test/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..54c5c0b024dab04da2a43d4135ed67ed2a670b46
--- /dev/null
+++ b/integration_test/.gitignore
@@ -0,0 +1,2 @@
+/build/
+logs/
diff --git a/integration_test/build.gradle b/integration_test/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2d905f3f6d29a82267538d94376175d6556787d7
--- /dev/null
+++ b/integration_test/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'application'
+
+dependencies {
+    testCompile project(':eraser-base')
+    testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.8'
+    testCompile group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.5.8'
+    testCompile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
+}
+
+run {
+    mainClassName = 'de.tudresden.inf.st.eraser.integration_test.Main'
+    standardInput = System.in
+    if (project.hasProperty("appArgs")) {
+        args Eval.me(appArgs)
+    }
+}
+
+sourceSets {
+    main {
+        java {
+            srcDir 'src/main/java'
+        }
+    }
+}
diff --git a/integration_test/src/main/java/de/tudresden/inf/st/eraser/integration_test/Main.java b/integration_test/src/main/java/de/tudresden/inf/st/eraser/integration_test/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..c362161e99ef67c70a79278d93263cce1a91eb2f
--- /dev/null
+++ b/integration_test/src/main/java/de/tudresden/inf/st/eraser/integration_test/Main.java
@@ -0,0 +1,8 @@
+package de.tudresden.inf.st.eraser.integration_test;
+
+public class Main {
+
+  public static void main(String[] args) {
+    System.out.println("Hello World!");
+  }
+}
diff --git a/integration_test/src/main/resources/log4j2.xml b/integration_test/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..867ec439d0a32dcb5f8b3e2d0c7485d7d8da418c
--- /dev/null
+++ b/integration_test/src/main/resources/log4j2.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration>
+    <Appenders>
+        <Console name="Console">
+            <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/>
+        </Console>
+        <RollingFile name="RollingFile" fileName="logs/eraser.log"
+                    filePattern="logs/eraser-%i.log">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
+            <Policies>
+                <OnStartupTriggeringPolicy/>
+            </Policies>
+            <DefaultRolloverStrategy max="20"/>
+        </RollingFile>
+    </Appenders>
+    <Loggers>
+        <Root level="debug">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="RollingFile"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/integration_test/src/test/java/de/tudresden/inf/st/eraser/integration_test/ItemTest.java b/integration_test/src/test/java/de/tudresden/inf/st/eraser/integration_test/ItemTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1aff8c0ae416aba4f0f1c96801e12445c666076
--- /dev/null
+++ b/integration_test/src/test/java/de/tudresden/inf/st/eraser/integration_test/ItemTest.java
@@ -0,0 +1,148 @@
+package de.tudresden.inf.st.eraser.integration_test;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import de.tudresden.inf.st.eraser.openhab2.data.ItemData;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.fluent.Form;
+import org.apache.http.client.fluent.Request;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.function.Function;
+
+import static org.hamcrest.Matchers.either;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Integration test to check openHAB-binding for eraser works well together with eraser itself.
+ *
+ * @author rschoene - Initial contribution
+ */
+@RunWith(Parameterized.class)
+public class ItemTest {
+
+  private static final String OPENHAB_ITEM_URI = "http://localhost:8080/rest/items/";
+  private static final String ERASER_ITEM_URI = "http://localhost:4567/model/items/";
+
+  private static final String DIMMER_ITEM = "dimmer_item";
+  private static final String NUMBER_ITEM = "number_item";
+  private static final String SWITCH_ITEM = "switch_item";
+  private static final String COLOR_ITEM = "color_item";
+
+  @BeforeClass
+  public static void ensureItemsAreCreated() throws IOException {
+    ensureItemCreated(DIMMER_ITEM, "Dimmer");
+    ensureItemCreated(NUMBER_ITEM, "Number");
+    ensureItemCreated(SWITCH_ITEM, "Switch");
+    ensureItemCreated(COLOR_ITEM, "Color");
+  }
+
+  private static void ensureItemCreated(String name, String type) throws IOException {
+    // Create at openHAB
+    HttpResponse responseOpenHAB = Request.Put(OPENHAB_ITEM_URI + name)
+        .bodyForm(Form.form().add("type", type).add("name", name).build())
+        .execute().returnResponse();
+    assertThat(
+        responseOpenHAB.getStatusLine().getStatusCode(),
+        either(equalTo(HttpStatus.SC_CREATED))
+            .or(equalTo(HttpStatus.SC_OK)));
+
+    // Create at eraser
+    HttpResponse responseEraser = Request.Put(ERASER_ITEM_URI + name)
+        .bodyString(type + " Item: id=\"" + name + "\"", ContentType.TEXT_PLAIN)
+        .execute().returnResponse();
+    assertThat(
+        responseEraser.getStatusLine().getStatusCode(),
+        equalTo(HttpStatus.SC_CREATED));
+  }
+
+  @Test
+  public void itemAvailable() throws IOException {
+    String name = "Tradfri_2_small_tv";
+    ItemData itemData = Request.Get( OPENHAB_ITEM_URI + name )
+        .execute().handleResponse(
+            response -> retrieveResourceFromResponse(response, ItemData.class));
+    assertThat(itemData.type, equalTo("Dimmer"));
+  }
+
+  @Test
+  public void dimmerSetStateAtOpenHAB() throws IOException {
+    String name = "Tradfri_2_small_tv";
+    double newValue = 3.0;
+    // set item state with openHAB REST API
+    HttpResponse httpResponse = Request.Put(OPENHAB_ITEM_URI + name + "/state")
+        .bodyString(Double.toString(newValue), ContentType.TEXT_PLAIN)
+        .execute().returnResponse();
+    assertThat(
+        httpResponse.getStatusLine().getStatusCode(),
+        equalTo(HttpStatus.SC_ACCEPTED));
+
+    // check whether state was set correctly
+    String responseOpenHAB = Request.Get(OPENHAB_ITEM_URI + name + "/state")
+        .execute().returnContent().asString();
+    assertThat(Double.parseDouble(responseOpenHAB), equalTo(newValue));
+
+    // check whether state was updated on eraser side
+    String responseEraser = Request.Get(ERASER_ITEM_URI + name + "/state")
+        .execute().returnContent().asString();
+    assertThat(Double.parseDouble(responseEraser), equalTo(newValue));
+  }
+
+  @Test
+  public void dimmerSetStateAtEraser() throws IOException {
+    String name = "Tradfri_2_small_tv";
+    double newValue = 25.0;
+    // set item state with eraser REST API
+    String uri = ERASER_ITEM_URI + name + "/state";
+    System.out.println(uri);
+    HttpResponse httpResponse = Request.Put(uri)
+        .bodyString(Double.toString(newValue), ContentType.TEXT_PLAIN)
+        .execute().returnResponse();
+    assertThat(
+        httpResponse.getStatusLine().getStatusCode(),
+        equalTo(HttpStatus.SC_OK));
+
+    // check whether state was set correctly
+    String responseEraser = Request.Get(ERASER_ITEM_URI + name + "/state")
+        .execute().returnContent().asString();
+    assertThat(Double.parseDouble(responseEraser), equalTo(newValue));
+
+    // check whether state was updated on openHAB side
+    String responseOpenHAB = Request.Get(OPENHAB_ITEM_URI + name + "/state")
+        .execute().returnContent().asString();
+    assertThat(Double.parseDouble(responseOpenHAB), equalTo(newValue));
+  }
+
+  private static <T> T retrieveResourceFromResponse(HttpResponse response, Class<T> clazz)
+      throws IOException {
+    String jsonFromResponse = EntityUtils.toString(response.getEntity());
+    ObjectMapper mapper = new ObjectMapper()
+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+    return mapper.readValue(jsonFromResponse, clazz);
+  }
+
+  private static <T> Object[] d(String name, T initialValue, Function<T, String> toString,
+                                Function<String, T> fromString) {
+    return new Object[]{name, initialValue, toString, fromString};
+  }
+
+  @Parameterized.Parameters(name= "{index}: {0}")
+  public static Iterable<Object[]> data() {
+    return Arrays.asList(
+        d(DIMMER_ITEM, 25.0, d -> Double.toString(d), Double::parseDouble),
+        d(NUMBER_ITEM, 4, i -> Integer.toString(i), Integer::parseInt),
+        d(SWITCH_ITEM, "ON", Function.identity(), Function.identity()),
+        d(COLOR_ITEM, "1,2,3", Function.identity(), Function.identity())
+    );
+  }
+
+}
diff --git a/settings.gradle b/settings.gradle
index 44b66ee077cceb9a26f2cfed97b319d4a30741a3..4492d856ad2d58bf8a3fe0eedca93b30b9c05bb2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,3 +19,4 @@ include ':feedbackloop.learner'
 include ':influx_test'
 include ':eraser.spark'
 include ':eraser.starter'
+include ':integration_test'