diff --git a/build/integration/.gitignore b/build/integration/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..18b981bf7ed87b284739e6a4c011883436674339
--- /dev/null
+++ b/build/integration/.gitignore
@@ -0,0 +1,3 @@
+vendor
+output
+composer.lock
diff --git a/build/integration/composer.json b/build/integration/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..98b2f294c7ae8a223dd2ad6fa5accfc8aab81d0d
--- /dev/null
+++ b/build/integration/composer.json
@@ -0,0 +1,7 @@
+{
+  "require-dev": {
+    "phpunit/phpunit": "~4.6",
+    "guzzlehttp/guzzle": "~5.0",
+    "behat/behat": "2.4.*@stable"
+  }
+}
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01ca0d18790178fb3ee4c77d1a2565e57e8caf4d
--- /dev/null
+++ b/build/integration/config/behat.yml
@@ -0,0 +1,17 @@
+default:
+    paths:
+        features: ../features
+        bootstrap: %behat.paths.features%/bootstrap
+
+    context:
+      parameters:
+        baseUrl:  http://localhost:8080/ocs/
+        admin:
+          - admin
+          - admin
+
+ci:
+    formatter:
+        name:       pretty,junit
+        parameters:
+          output_path: null,./output
diff --git a/build/integration/features/bootstrap/FeatureContext.php b/build/integration/features/bootstrap/FeatureContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7a04e1ca761413de18669b379baaa5dd76652b6
--- /dev/null
+++ b/build/integration/features/bootstrap/FeatureContext.php
@@ -0,0 +1,142 @@
+<?php
+
+use Behat\Behat\Context\BehatContext;
+use GuzzleHttp\Client;
+use GuzzleHttp\Message\ResponseInterface;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+/**
+ * Features context.
+ */
+class FeatureContext extends BehatContext {
+
+	/** @var string */
+	private $baseUrl = '';
+
+	/** @var ResponseInterface */
+	private $response = null;
+
+	/** @var string */
+	private $currentUser = '';
+
+	/** @var int */
+	private $apiVersion = 1;
+
+	/**
+	 * Initializes context.
+	 * Every scenario gets it's own context object.
+	 *
+	 * @param array $parameters context parameters (set them up through behat.yml)
+	 */
+	public function __construct(array $parameters) {
+
+		// Initialize your context here
+		$this->baseUrl = $parameters['baseUrl'];
+		$this->adminUser = $parameters['admin'];
+
+		// in case of ci deployment we take the server url from the environment
+		$testServerUrl = getenv('TEST_SERVER_URL');
+		if ($testServerUrl !== false) {
+			$this->baseUrl = $testServerUrl;
+		}
+	}
+
+	/**
+	 * @When /^sending "([^"]*)" to "([^"]*)"$/
+	 */
+	public function sendingTo($verb, $url) {
+		$this->sendingToWith($verb, $url, null);
+	}
+
+	/**
+	 * @Then /^the status code should be "([^"]*)"$/
+	 */
+	public function theStatusCodeShouldBe($statusCode) {
+		PHPUnit_Framework_Assert::assertEquals($statusCode, $this->response->getStatusCode());
+	}
+
+	/**
+	 * @Given /^As an "([^"]*)"$/
+	 */
+	public function asAn($user) {
+		$this->currentUser = $user;
+	}
+
+	/**
+	 * @Given /^using api version "([^"]*)"$/
+	 */
+	public function usingApiVersion($version) {
+		$this->apiVersion = $version;
+	}
+
+	/**
+	 * @Given /^user "([^"]*)" exists$/
+	 */
+	public function userExists($user) {
+		$fullUrl = $this->baseUrl . "v2.php/cloud/users/$user";
+		$client = new Client();
+		$options = [];
+		if ($this->currentUser === 'admin') {
+			$options['auth'] = $this->adminUser;
+		}
+
+		$this->response = $client->get($fullUrl, $options);
+		PHPUnit_Framework_Assert::assertEquals(200, $this->response->getStatusCode());
+	}
+
+	/**
+	 * @Given /^user "([^"]*)" does not exist$/
+	 */
+	public function userDoesNotExist($user) {
+		try {
+			$this->userExists($user);
+		} catch (\GuzzleHttp\Exception\ClientException $ex) {
+			PHPUnit_Framework_Assert::assertEquals(404, $ex->getResponse()->getStatusCode());
+		}
+	}
+
+	/**
+	 * @When /^creating the user "([^"]*)r"$/
+	 */
+	public function creatingTheUser($user) {
+		$fullUrl = $this->baseUrl . "v2.php/cloud/users/$user";
+		$client = new Client();
+		$options = [];
+		if ($this->currentUser === 'admin') {
+			$options['auth'] = $this->adminUser;
+		}
+
+		$this->response = $client->post($fullUrl, [
+			'form_params' => [
+				'userid' => $user,
+				'password' => '123456'
+			]
+		]);
+		PHPUnit_Framework_Assert::assertEquals(200, $this->response->getStatusCode());
+	}
+
+	/**
+	 * @When /^sending "([^"]*)" to "([^"]*)" with$/
+	 * @param \Behat\Gherkin\Node\TableNode|null $formData
+	 */
+	public function sendingToWith($verb, $url, $body) {
+		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
+		$client = new Client();
+		$options = [];
+		if ($this->currentUser === 'admin') {
+			$options['auth'] = $this->adminUser;
+		}
+		if ($body instanceof \Behat\Gherkin\Node\TableNode) {
+			$fd = $body->getRowsHash();
+			$options['body'] = $fd;
+		}
+
+		try {
+			$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
+		} catch (\GuzzleHttp\Exception\ClientException $ex) {
+			$this->response = $ex->getResponse();
+		}
+	}
+
+}
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
new file mode 100644
index 0000000000000000000000000000000000000000..9e3d2df50bb435475b6cf343de2c21902e45afdd
--- /dev/null
+++ b/build/integration/features/provisioning-v1.feature
@@ -0,0 +1,32 @@
+Feature: provisioning
+  Background:
+    Given using api version "1"
+
+  Scenario: Getting an not existing user
+    Given As an "admin"
+    When sending "GET" to "/cloud/users/test"
+    Then the status code should be "200"
+
+  Scenario: Listing all users
+    Given As an "admin"
+    When sending "GET" to "/cloud/users"
+    Then the status code should be "200"
+
+  Scenario: Create a user
+    Given As an "admin"
+    And user "brand-new-user" does not exist
+    When sending "POST" to "/cloud/users" with
+      | userid | brand-new-user |
+      | password | 123456 |
+
+    Then the status code should be "200"
+    And user "brand-new-user" exists
+
+
+  Scenario: Delete a user
+    Given As an "admin"
+    And user "brand-new-user" exists
+    When sending "POST" to "/cloud/users" with
+      | userid | brand-new-user |
+    Then the status code should be "200"
+    And user "brand-new-user" does not exist
diff --git a/build/integration/features/provisioning-v2.feature b/build/integration/features/provisioning-v2.feature
new file mode 100644
index 0000000000000000000000000000000000000000..72ceed5d6a53d7bc516d29e42f6bbfb78422e2b8
--- /dev/null
+++ b/build/integration/features/provisioning-v2.feature
@@ -0,0 +1,9 @@
+Feature: provisioning
+  Background:
+    Given using api version "2"
+
+  Scenario: Getting an not existing user
+    Given As an "admin"
+    When sending "GET" to "/cloud/users/test"
+    Then the status code should be "404"
+
diff --git a/build/integration/run.sh b/build/integration/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..08f10b86c5f99082a01f1bb9f5dd1576443c1cca
--- /dev/null
+++ b/build/integration/run.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+composer install
+
+# TODO: avoid port collision on jenkins - use $EXECUTOR_NUMBER
+if [ -z "$EXECUTOR_NUMBER" ]; then
+    EXECUTOR_NUMBER=0
+fi
+PORT=$((8080 + $EXECUTOR_NUMBER))
+#PORT=8080
+echo $PORT
+php -S localhost:$PORT -t ../.. &
+PHPPID=$!
+echo $PHPPID
+
+export TEST_SERVER_URL="http://localhost:$PORT/ocs/"
+vendor/bin/behat --profile ci
+
+kill $PHPPID