diff --git a/README.md b/README.md
index 5d62b3c5832932bceb7ce2631a50cf129b0c6598..1075569286f43d6e835d510fefe6c27b27da9328 100644
--- a/README.md
+++ b/README.md
@@ -221,7 +221,7 @@ $config2 = new TestcenterInstanceConfig('http://testcenter2/api', new User('supe
 $connector = new MultiTestcenterConnector([$config1, $config2]);
 
 try {
-    $result = $connector->addWorkspace('New Workspace');
+    $result = $connector->addWorkspaceAsync('New Workspace');
 } catch (\Exception $e) {
     // handle exception
 }
@@ -229,3 +229,6 @@ try {
 
 Als Rückgabewerte erhält man ein assoziatives Array mit der Base-URL der jeweiligen Testcenter Instanz als Key und
 deren Antwort als Wert.
+
+Um mehrere Testcenter Instanzen parallel anzusprechen, müssen die asynchronen Varianten der Methoden verwendet werden.
+Bei den synchronen Methoden werden die Testcenter Instanzen sequentiell nacheinander angesprochen.
diff --git a/composer.json b/composer.json
index bff20102c2af056db33eed4103379765d68a1ff9..a0f4d609dba9b7b17f35e7f3ce3d4c0d59ad4a99 100644
--- a/composer.json
+++ b/composer.json
@@ -24,6 +24,7 @@
         "ext-json": "*",
         "ext-xmlwriter": "*",
         "ext-zip": "*",
+        "ext-curl": "*",
         "guzzlehttp/guzzle": "^7.8"
     },
     "scripts": {
diff --git a/e2e/AddAUserTest.php b/e2e/AddAUserTest.php
index 20e2d34793fdfad157b432d09b66a4d953e8c53d..15a05ed906ef34ea041d070a0eb1443d559e5274 100644
--- a/e2e/AddAUserTest.php
+++ b/e2e/AddAUserTest.php
@@ -1,11 +1,15 @@
 <?php
 
-use \Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$user = new User('super', 'user123', User::ADMIN_ROLE);
-$connector = new TestcenterConnector($user, 'http://localhost/api');
-$result = $connector->addUser('userabc', 'passwabc');
+$config = getTestcenterConfig();
+
+$username = getenv('TEST_USERNAME') ? getenv('TEST_USERNAME') : 'userabc';
+$password = getenv('TEST_PASSWORD') ? getenv('TEST_PASSWORD') : 'passwabc';
+
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
+$result = $connector->addUser($username, $password);
 var_dump($result);
diff --git a/e2e/AddAWorkspaceTest.php b/e2e/AddAWorkspaceTest.php
index d7c5e57fef8eba78b51e891cb9fdcf48aa5bbd71..26a9f9076f739b0bb05a4b9b85a9e5be0ba27937 100644
--- a/e2e/AddAWorkspaceTest.php
+++ b/e2e/AddAWorkspaceTest.php
@@ -1,12 +1,15 @@
 <?php
 
-use \Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$user = new User('super', 'user123', User::ADMIN_ROLE);
-$connector = new TestcenterConnector($user, 'http://localhost/api');
-$workspaceId = $connector->addWorkspace('New test workspace 2');
+$config = getTestcenterConfig();
 
-var_dump($workspaceId);
\ No newline at end of file
+$workspaceName = getenv('WORKSPACE_NAME') ?? 'New test workspace 2';
+
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
+$workspaceId = $connector->addWorkspace($workspaceName);
+
+var_dump($workspaceId);
diff --git a/e2e/AddSuperAdminTest.php b/e2e/AddSuperAdminTest.php
index 60096bd42c6fc2f8d0296cabda605fbf97d31883..78dd5ebab01994b089b513542ebff8ae19a3505e 100644
--- a/e2e/AddSuperAdminTest.php
+++ b/e2e/AddSuperAdminTest.php
@@ -1,11 +1,12 @@
 <?php
 
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
-use Kompetenztestde\TestcenterConnector\Models\User;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$user = new User('super', 'user123', User::ADMIN_ROLE);
-$connector = new TestcenterConnector($user, 'http://localhost/api');
+$config = getTestcenterConfig();
+
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $result = $connector->addSuperAdmin('super2', 'user123');
 var_dump($result);
diff --git a/e2e/AddTestTest.php b/e2e/AddTestTest.php
index e7d4d05be8dc6a057cbde5079c37202c2b8c8f8f..7ff30f52745b1dc9a99fdbb2c7227ea17387106b 100644
--- a/e2e/AddTestTest.php
+++ b/e2e/AddTestTest.php
@@ -1,10 +1,10 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\Models\FileTest;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
 $booklet = file_get_contents('./testAssets/Booklet.xml');
 $bookletAddress = 'data://application/xml;base64,' . base64_encode($booklet);
@@ -29,7 +29,7 @@ $test = new FileTest(
     ]
 );
 
-$user = new User('super', 'user123', User::ADMIN_ROLE);
-$connector = new TestcenterConnector($user, 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $response = $connector->addTest(5, $test);
 var_dump($response);
diff --git a/e2e/ChangeUserPasswordTest.php b/e2e/ChangeUserPasswordTest.php
index dea1ea6577c7863bd03cf9219776c6ed98cf18dd..f986db82772581f6231e767fffdd3e03102471d7 100644
--- a/e2e/ChangeUserPasswordTest.php
+++ b/e2e/ChangeUserPasswordTest.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $response = $connector->changeUserPassword(1, 'user1234');
 var_dump($response);
diff --git a/e2e/ChangeUserRolesTest.php b/e2e/ChangeUserRolesTest.php
index 1b909623b8e698245da66e19a1d3d14216764662..49b60300700f21ddd09e76d294a2edcd152e1781 100644
--- a/e2e/ChangeUserRolesTest.php
+++ b/e2e/ChangeUserRolesTest.php
@@ -1,15 +1,16 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\UserRole;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
 $roles = [
     new UserRole(1, UserRole::RW)
 ];
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $response = $connector->changeUserRoles(2, $roles);
 var_dump($response);
diff --git a/e2e/CreateTesttakersTest.php b/e2e/CreateTesttakersTest.php
index 33ff66e0cb8118c65b60cd18b4be8c37410fb8b0..e1b1e2d7d91e95b29831f686d56c04a710cf7395 100644
--- a/e2e/CreateTesttakersTest.php
+++ b/e2e/CreateTesttakersTest.php
@@ -1,15 +1,15 @@
 <?php
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\Models\Pupil;
 use Kompetenztestde\TestcenterConnector\Models\Teacher;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 use Kompetenztestde\TestcenterConnector\Models\TestGroup;
 
-$user = new User('super', 'user123', User::ADMIN_ROLE);
-$connector = new TestcenterConnector($user, 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 
 $logins = [
     new Pupil('login1', 'password1'),
@@ -17,4 +17,6 @@ $logins = [
 ];
 $group = new TestGroup('test-group', 'Test Group', $logins);
 
-$connector->addTestGroup(1, $group, 'BOOKLET.SAMPLE-1');
\ No newline at end of file
+$workspaceId = getenv('WORKSPACE_ID') !== false ? intval(getenv('WORKSPACE_ID')) : 1;
+
+$connector->addTestGroupAsync($workspaceId, $group, 'BOOKLET.SAMPLE-1')->wait();
diff --git a/e2e/DeleteData.php b/e2e/DeleteData.php
index cffbd26c854cc2c8e7131e5e0418f79230b5a860..76ca2570a928899000839c1be730dc6f28e1dbe4 100644
--- a/e2e/DeleteData.php
+++ b/e2e/DeleteData.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $result = $connector->deleteData(3, ['group1']);
-var_dump($result);
\ No newline at end of file
+var_dump($result);
diff --git a/e2e/DeleteFilesTest.php b/e2e/DeleteFilesTest.php
index 2dae0514cde2fa8583cba2c73f537f782c6f961d..db6a86182f38a085c5a4c993c48f65ef84974e73 100644
--- a/e2e/DeleteFilesTest.php
+++ b/e2e/DeleteFilesTest.php
@@ -1,15 +1,13 @@
 <?php
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
 use Kompetenztestde\TestcenterConnector\Models\File;
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
-$connector = new TestcenterConnector(
-    new User('super', 'user123', User::ADMIN_ROLE),
-    'http://localhost/api'
-);
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 
 $files = [
     new File('SAMPLE_TESTTAKERS.XML', File::TESTTAKERS_FILE)
diff --git a/e2e/DeleteSomeUsers.php b/e2e/DeleteSomeUsers.php
index 577bc46c669488d116f765c32da4d822469b7cb0..9acdf5d2186831d9f635383e85b35dd15d81b969 100644
--- a/e2e/DeleteSomeUsers.php
+++ b/e2e/DeleteSomeUsers.php
@@ -1,12 +1,13 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $result = $connector->deleteUsers([3]);
 if ($result) {
     echo 'successful';
-}
\ No newline at end of file
+}
diff --git a/e2e/DeleteSomeWorkspacesTest.php b/e2e/DeleteSomeWorkspacesTest.php
index 0ebf2fc6d16306e13e8ec54c02396d565c975438..a96c43c3d6c69299eee121d7c1bde74d3a06465d 100644
--- a/e2e/DeleteSomeWorkspacesTest.php
+++ b/e2e/DeleteSomeWorkspacesTest.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $result = $connector->deleteWorkspace([3, 4]);
-var_dump($result);
\ No newline at end of file
+var_dump($result);
diff --git a/e2e/E2eTestHelper.php b/e2e/E2eTestHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ab0ecf6cbe7f0b2e1922621edbd4fa95b553f09
--- /dev/null
+++ b/e2e/E2eTestHelper.php
@@ -0,0 +1,14 @@
+<?php
+
+use Kompetenztestde\TestcenterConnector\Models\User;
+use Kompetenztestde\TestcenterConnector\TestcenterInstanceConfig;
+
+function getTestcenterConfig($suffix = ''): TestcenterInstanceConfig
+{
+    $baseUrl = getenv("TESTCENTER_BASE_URL$suffix") ? getenv("TESTCENTER_BASE_URL$suffix") : 'http://localhost/api';
+    $username = getenv("TESTCENTER_USER$suffix") ? getenv("TESTCENTER_USER$suffix") : 'super';
+    $password = getenv("TESTCENTER_PASSWORD$suffix") ? getenv("TESTCENTER_PASSWORD$suffix") : 'user123';
+
+    $user = new User($username, $password, User::ADMIN_ROLE);
+    return new TestcenterInstanceConfig($baseUrl, $user);
+}
diff --git a/e2e/GetAListOfUsers.php b/e2e/GetAListOfUsers.php
index 0d5119ee25865e0b34ce0817def8735a4d5b1413..cb1449c78d96b168a60bc97947484b57545051bf 100644
--- a/e2e/GetAListOfUsers.php
+++ b/e2e/GetAListOfUsers.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $users = $connector->getUsers();
-var_dump($users);
\ No newline at end of file
+var_dump($users);
diff --git a/e2e/GetAListOfWorkspacesTest.php b/e2e/GetAListOfWorkspacesTest.php
index 1d249745bd17c22f5da4882bd507968713a6b6d7..b71ed04657b6151c42de9278ade2fb57a8d39fa7 100644
--- a/e2e/GetAListOfWorkspacesTest.php
+++ b/e2e/GetAListOfWorkspacesTest.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $workspaces = $connector->getWorkspaces();
 var_dump($workspaces);
diff --git a/e2e/GetLogEntriesTest.php b/e2e/GetLogEntriesTest.php
index 1faf99896e49ac87b745b6736afaff8065fb168a..599957f6750b71f8b301baa3a1b8ab38041551a7 100644
--- a/e2e/GetLogEntriesTest.php
+++ b/e2e/GetLogEntriesTest.php
@@ -1,10 +1,12 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
-$logEntries = $connector->getLogs(1, ['sample_group']);
-var_dump($logEntries);
\ No newline at end of file
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
+$workspaceId = getenv('WORKSPACE_ID') !== false ? intval(getenv('WORKSPACE_ID')) : 1;
+$logEntries = $connector->getLogs($workspaceId, ['sample_group']);
+var_dump($logEntries);
diff --git a/e2e/GetResponsesTest.php b/e2e/GetResponsesTest.php
index b2daf2a76a59be298c64ac474c7dbfc279c7f443..175164e626b3f0d9338c263a33c8b1c6af77914f 100644
--- a/e2e/GetResponsesTest.php
+++ b/e2e/GetResponsesTest.php
@@ -3,9 +3,12 @@
 use Kompetenztestde\TestcenterConnector\Api\TestcenterApiClient;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$apiClient = new TestcenterApiClient('http://localhost/api');
+$config = getTestcenterConfig();
+$apiClient = new TestcenterApiClient($config->getBaseUrl());
 
-$session = $apiClient->startAdminSession('super', 'user123');
+$user = $config->getUser();
+$session = $apiClient->startAdminSession($user->getUsername(), $user->getPassword());
 $responses = $apiClient->getReportOfItemResponses($session->getToken(), 4, ['test-group-2']);
-var_dump($responses);
\ No newline at end of file
+var_dump($responses);
diff --git a/e2e/GetResultsTest.php b/e2e/GetResultsTest.php
index 25b409df40b3888f2b0bb165a9d9c4730e73d70a..9891128f0c98610d1735e070106d80d7c59634ae 100644
--- a/e2e/GetResultsTest.php
+++ b/e2e/GetResultsTest.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $results = $connector->getResults(1);
-var_dump($results);
\ No newline at end of file
+var_dump($results);
diff --git a/e2e/GetSystemCheckReports.php b/e2e/GetSystemCheckReports.php
index 146f2bc228fe326fd189bb2c461ca9b67949ad2e..0d85ba8d4bb381e84fb09dd57fc85ca3fe43e2b2 100644
--- a/e2e/GetSystemCheckReports.php
+++ b/e2e/GetSystemCheckReports.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $report = $connector->getSystemCheckReport(1, ['SYSCHECK.SAMPLE']);
-var_dump($report);
\ No newline at end of file
+var_dump($report);
diff --git a/e2e/GetSystemConfig.php b/e2e/GetSystemConfig.php
index 8645f6652eed397c4d84ba74f66d9e6a19d81f9b..4c06f1b313b66bc54cddf63d72f6a5f26fd79a75 100644
--- a/e2e/GetSystemConfig.php
+++ b/e2e/GetSystemConfig.php
@@ -1,10 +1,12 @@
 <?php
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
 use Kompetenztestde\TestcenterConnector\Api\TestcenterApiClient;
 
-$apiClient = new TestcenterApiClient('http://localhost/api');
+$config = getTestcenterConfig();
+$apiClient = new TestcenterApiClient($config->getBaseUrl());
 $config = $apiClient->getSystemConfig();
 
-var_dump($config);
\ No newline at end of file
+var_dump($config);
diff --git a/e2e/GetUserWorkspacesTest.php b/e2e/GetUserWorkspacesTest.php
index 85c53ee38487bd303ecc928e6f52791e09c48f0b..4f0d27c877c41f42fa1efef635b3085b741fc574 100644
--- a/e2e/GetUserWorkspacesTest.php
+++ b/e2e/GetUserWorkspacesTest.php
@@ -1,10 +1,11 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $workspaces = $connector->getWorkspacesByUser(1);
 var_dump($workspaces);
diff --git a/e2e/MultiAddWorkspaceTest.php b/e2e/MultiAddWorkspaceTest.php
index 6dd406c032a2aa967fd63830ab324dc895ca9232..0931620ed6170ae79d8b999fe425f98e81782f8a 100644
--- a/e2e/MultiAddWorkspaceTest.php
+++ b/e2e/MultiAddWorkspaceTest.php
@@ -1,13 +1,12 @@
 <?php
 
 use Kompetenztestde\TestcenterConnector\MultiTestcenterConnector;
-use Kompetenztestde\TestcenterConnector\TestcenterInstanceConfig;
-use Kompetenztestde\TestcenterConnector\Models\User;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$config1 = new TestcenterInstanceConfig('http://testcenter1/api', new User('super', 'user123', User::ADMIN_ROLE));
-$config2 = new TestcenterInstanceConfig('http://testcenter2/api', new User('super', 'user123', User::ADMIN_ROLE));
+$config1 = getTestcenterConfig('_1');
+$config2 = getTestcenterConfig('_2');
 
 $connector = new MultiTestcenterConnector([$config1, $config2]);
 $result = $connector->addWorkspace('test-workspace-123');
diff --git a/e2e/MultiGetResponsesTest.php b/e2e/MultiGetResponsesTest.php
index 90e34528a0f39589fa70ff4401c2d5fb90ae4047..baea1d661c709228eaa81daf97ac22122a1deaf6 100644
--- a/e2e/MultiGetResponsesTest.php
+++ b/e2e/MultiGetResponsesTest.php
@@ -1,14 +1,23 @@
 <?php
 
 use Kompetenztestde\TestcenterConnector\MultiTestcenterConnector;
-use Kompetenztestde\TestcenterConnector\TestcenterInstanceConfig;
-use Kompetenztestde\TestcenterConnector\Models\User;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$config1 = new TestcenterInstanceConfig('http://testcenter1/api', new User('super', 'user123', User::ADMIN_ROLE));
-$config2 = new TestcenterInstanceConfig('http://testcenter2/api', new User('super', 'user123', User::ADMIN_ROLE));
+$config1 = getTestcenterConfig('_1');
+$config2 = getTestcenterConfig('_2');
 
-$connector = new MultiTestcenterConnector([$config1, $config2]);
-$result = $connector->getGroupResults('Sample Workspace', 'test_group');
-var_dump($result);
+$connector1 = new MultiTestcenterConnector([$config1, $config2]);
+$start1 = microtime(true);
+$result = $connector1->getGroupResultsAsync('Sample Workspace', 'test_group');
+$end1 = microtime(true);
+$duration = number_format($end1 - $start1, 2);
+echo("Duration async: $duration\n");
+
+$connector2 = new MultiTestcenterConnector([$config1, $config2]);
+$start2 = microtime(true);
+$result = $connector2->getGroupResults('Sample Workspace', 'test_group');
+$end2 = microtime(true);
+$duration = number_format($end2 - $start2, 2);
+echo("Duration sync: $duration\n");
diff --git a/e2e/RenameAWorkspaceTest.php b/e2e/RenameAWorkspaceTest.php
index cd3d6345dd4edc52183936a154adca29194c33a8..7582b6e1155e56009cff4e9f739590b72c1cae7e 100644
--- a/e2e/RenameAWorkspaceTest.php
+++ b/e2e/RenameAWorkspaceTest.php
@@ -1,12 +1,13 @@
 <?php
 
-use Kompetenztestde\TestcenterConnector\Models\User;
 use Kompetenztestde\TestcenterConnector\TestcenterConnector;
 
 require __DIR__ . '/../vendor/autoload.php';
+require __DIR__ . '/E2eTestHelper.php';
 
-$connector = new TestcenterConnector(new User('super', 'user123', User::ADMIN_ROLE), 'http://localhost/api');
+$config = getTestcenterConfig();
+$connector = new TestcenterConnector($config->getUser(), $config->getBaseUrl());
 $result = $connector->renameWorkspace(3, 'renamed-workspace');
 if ($result) {
     echo 'successful';
-}
\ No newline at end of file
+}
diff --git a/src/Api/TestcenterApiClient.php b/src/Api/TestcenterApiClient.php
index 19f16ea58f459b09adc371d43bd2b22cae042349..1c71b1fa4b1be0b7c088ba30d7707935c9e4181b 100644
--- a/src/Api/TestcenterApiClient.php
+++ b/src/Api/TestcenterApiClient.php
@@ -3,8 +3,10 @@
 namespace Kompetenztestde\TestcenterConnector\Api;
 
 use GuzzleHttp;
+use GuzzleHttp\Client;
 use GuzzleHttp\Exception\GuzzleException;
 use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Promise\PromiseInterface;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\FileDeletionResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\Result;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\StartSessionResponse;
@@ -21,6 +23,7 @@ use Kompetenztestde\TestcenterConnector\Api\Schemas\Workspace;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\SessionResponse;
 use Kompetenztestde\TestcenterConnector\Exceptions\ApiException;
 use Kompetenztestde\TestcenterConnector\Exceptions\TestcenterConnectorException;
+use Psr\Http\Message\ResponseInterface;
 
 class TestcenterApiClient
 {
@@ -36,7 +39,7 @@ class TestcenterApiClient
 
     /**
      * @param string $baseUrl
-     * @param GuzzleHttp\Client $httpClient
+     * @param Client|null $httpClient
      */
     public function __construct(string $baseUrl, GuzzleHttp\Client $httpClient = null)
     {
@@ -59,30 +62,42 @@ class TestcenterApiClient
      * @throws ApiException
      */
     public function startAdminSession(string $username, string $password): StartSessionResponse
+    {
+        return $this->startAdminSessionAsync($username, $password)->wait();
+    }
+
+    /**
+     * Asynchronously start a session as admin by username and password
+     *
+     * @param string $username
+     * @param string $password
+     * @return PromiseInterface resolves to {@see StartSessionResponse} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function startAdminSessionAsync(string $username, string $password): PromiseInterface
     {
         $body = [
             'name' => $username,
             'password' => $password
         ];
 
-        try {
-            $response = $this->httpClient->put($this->baseUrl . '/session/admin', [
-                'json' => $body
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        $promise = $this->httpClient->putAsync($this->baseUrl . '/session/admin', [
+            'json' => $body
+        ]);
 
-        // TODO: Testcenter API documentation says status code 202 but in source it says 204
-        if ($response->getStatusCode() === 204) {
-            throw new TestcenterConnectorException('User exists but has no admin privileges.');
-        }
+        return $promise->then(function (ResponseInterface $response) {
+            // TODO: Testcenter API documentation says status code 202 but in source it says 204
+            if ($response->getStatusCode() === 204) {
+                throw new TestcenterConnectorException('User exists but has no admin privileges.');
+            }
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        $sessionResponse = new StartSessionResponse($data);
-        return $sessionResponse;
+            return new StartSessionResponse($data);
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -102,6 +117,25 @@ class TestcenterApiClient
         string $filename,
         string $content
     ): FileUploadResponse {
+        return $this->uploadFileAsync($authToken, $workspaceId, $filename, $content)->wait();
+    }
+
+    /**
+     * Asynchronously uploads a Resource, Unit, Booklet, SysCheck or Testtakers file
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param string $filename
+     * @param string $content
+     * @return PromiseInterface resolves to {@see FileUploadResponse} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function uploadFileAsync(
+        string $authToken,
+        int $workspaceId,
+        string $filename,
+        string $content
+    ): PromiseInterface {
         $requestOptions = [
             'headers' => [
                 'AuthToken' => $authToken
@@ -115,28 +149,27 @@ class TestcenterApiClient
             ]
         ];
 
-        try {
-            $response = $this->httpClient->post(
-                $this->baseUrl . '/workspace/' . $workspaceId . '/file',
-                $requestOptions
-            );
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
-
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
-
-        $fileValidationResults = [];
-        foreach ($data as $filename => $fileResponse) {
-            $errors = isset($fileResponse->error) ? $fileResponse->error : [];
-            $warnings = isset($fileResponse->warning) ? $fileResponse->warning : [];
-            $result = new FileValidationResult($filename, $warnings, $errors);
-            $fileValidationResults[] = $result;
-        }
+        $promise = $this->httpClient->postAsync(
+            $this->baseUrl . '/workspace/' . $workspaceId . '/file',
+            $requestOptions
+        );
+
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
+
+            $fileValidationResults = [];
+            foreach ($data as $filename => $fileResponse) {
+                $errors = $fileResponse->error ?? [];
+                $warnings = $fileResponse->warning ?? [];
+                $result = new FileValidationResult($filename, $warnings, $errors);
+                $fileValidationResults[] = $result;
+            }
 
-        $fileResponse = new FileUploadResponse($fileValidationResults);
-        return $fileResponse;
+            return new FileUploadResponse($fileValidationResults);
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -151,23 +184,40 @@ class TestcenterApiClient
      */
     public function deleteFiles(string $authToken, int $workspaceId, array $filenames): FileDeletionResponse
     {
-        try {
-            $response = $this->httpClient->delete("{$this->baseUrl}/workspace/$workspaceId/files", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => [
-                    'f' => $filenames
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        return $this->deleteFilesAsync($authToken, $workspaceId, $filenames)->wait();
+    }
+
+    /**
+     * Asynchronously delete one or more files in a workspace
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param array $filenames
+     * @return PromiseInterface resolves to {@see FileDeletionResponses} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function deleteFilesAsync(
+        string $authToken,
+        int $workspaceId,
+        array $filenames
+    ): PromiseInterface {
+        $promise = $this->httpClient->deleteAsync("{$this->baseUrl}/workspace/$workspaceId/files", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => [
+                'f' => $filenames
+            ]
+        ]);
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        return new FileDeletionResponse($data);
+            return new FileDeletionResponse($data);
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -179,31 +229,44 @@ class TestcenterApiClient
      * @throws ApiException
      * @throws TestcenterConnectorException
      */
-    public function addAWorkspace(string $authToken, string $name)
+    public function addAWorkspace(string $authToken, string $name): ?int
+    {
+        return $this->addAWorkspaceAsync($authToken, $name)->wait();
+    }
+
+    /**
+     * Asynchronously add a new workspace to the testcenter
+     *
+     * @param string $authToken
+     * @param string $name
+     * @return PromiseInterface resolves to workspace id (int) or null or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function addAWorkspaceAsync(string $authToken, string $name): PromiseInterface
     {
         $body = [
             'name' => $name
         ];
-        try {
-            $response = $this->httpClient->put($this->baseUrl . '/workspace', [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => $body
-            ]);
-        } catch (GuzzleException $e) {
+        $promise = $this->httpClient->putAsync($this->baseUrl . '/workspace', [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => $body
+        ]);
+
+        return $promise->then(function (ResponseInterface $response) {
+            // check if workspace id is returned (version 15.1.7+)
+            $body = $response->getBody()->getContents();
+            $workspaceId = filter_var($body, FILTER_VALIDATE_INT);
+
+            if ($workspaceId !== false) {
+                return $workspaceId;
+            } else {
+                return null;
+            }
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
-
-        // check if workspace id is returned (version 15.1.7+)
-        $body = $response->getBody()->getContents();
-        $workspaceId = filter_var($body, FILTER_VALIDATE_INT);
-
-        if ($workspaceId !== false) {
-            return $workspaceId;
-        } else {
-            return null;
-        }
+        });
     }
 
     /**
@@ -216,26 +279,38 @@ class TestcenterApiClient
      */
     public function getAListOfWorkspaces(string $authToken): array
     {
-        try {
-            $response = $this->httpClient->get($this->baseUrl . '/workspaces', [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        return $this->getAListOfWorkspacesAsync($authToken)->wait();
+    }
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+    /**
+     * Asynchronously get an array of all workspaces in the testcenter
+     *
+     * @param string $authToken super admin authentication token
+     * @return PromiseInterface resolves to array of {@see Workspace} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function getAListOfWorkspacesAsync(string $authToken): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync($this->baseUrl . '/workspaces', [
+            'headers' => [
+                'AuthToken' => $authToken
+            ]
+        ]);
 
-        $workspaces = [];
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        foreach ($data as $workspaceData) {
-            $workspaces[] = new Workspace($workspaceData);
-        }
+            $workspaces = [];
+
+            foreach ($data as $workspaceData) {
+                $workspaces[] = new Workspace($workspaceData);
+            }
 
-        return $workspaces;
+            return $workspaces;
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -249,37 +324,49 @@ class TestcenterApiClient
      */
     public function getAListOfUserWorkspaces(string $authToken, int $userId): array
     {
-        try {
-            $response = $this->httpClient->get("{$this->baseUrl}/user/$userId/workspaces", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        return $this->getAListOfUserWorkspacesAsync($authToken, $userId)->wait();
+    }
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+    /**
+     * Asynchronously get a list of workspaces a user has access to
+     *
+     * @param string $authToken
+     * @param int $userId
+     * @return PromiseInterface resolves to array of {@see UserWorkspace} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function getAListOfUserWorkspacesAsync(string $authToken, int $userId): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync("{$this->baseUrl}/user/$userId/workspaces", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ]
+        ]);
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        if ($data === null) {
-            throw new TestcenterConnectorException('Invalid json response');
-        }
+            if ($data === null) {
+                throw new TestcenterConnectorException('Invalid json response');
+            }
 
-        if (!is_array($data)) {
-            throw new TestcenterConnectorException('Expected response to be an array');
-        }
+            if (!is_array($data)) {
+                throw new TestcenterConnectorException('Expected response to be an array');
+            }
 
-        $workspaces = [];
+            $workspaces = [];
 
-        foreach ($data as $workspaceData) {
-            if (!is_object($workspaceData)) {
-                throw new TestcenterConnectorException('Expected workspace data to be an object');
+            foreach ($data as $workspaceData) {
+                if (!is_object($workspaceData)) {
+                    throw new TestcenterConnectorException('Expected workspace data to be an object');
+                }
+                $workspaces[] = new UserWorkspace($workspaceData);
             }
-            $workspaces[] = new UserWorkspace($workspaceData);
-        }
 
-        return $workspaces;
+            return $workspaces;
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -294,22 +381,39 @@ class TestcenterApiClient
      */
     public function changeUserRoles(string $authToken, int $workspaceId, array $userRoles): bool
     {
+        return $this->changeUserRolesAsync($authToken, $workspaceId, $userRoles)->wait();
+    }
+
+    /**
+     * Asynchronously set roles of one or multiple users in a workspace
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param array $userRoles
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function changeUserRolesAsync(
+        string $authToken,
+        int $workspaceId,
+        array $userRoles
+    ): PromiseInterface {
         $body = [
             'u' => $userRoles
         ];
 
-        try {
-            $this->httpClient->patch($this->baseUrl . '/workspace/' . $workspaceId . '/users', [
-                'json' => $body,
-                'headers' => [
-                    'AuthToken' => $authToken
-                ]
-            ]);
+        $promise = $this->httpClient->patchAsync($this->baseUrl . '/workspace/' . $workspaceId . '/users', [
+            'json' => $body,
+            'headers' => [
+                'AuthToken' => $authToken
+            ]
+        ]);
 
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
@@ -322,20 +426,31 @@ class TestcenterApiClient
      */
     public function getASession(string $authToken): SessionResponse
     {
-        try {
-            $response = $this->httpClient->get($this->baseUrl . '/session', [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        return $this->getASessionAsync($authToken)->wait();
+    }
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+    /**
+     * Asynchronously returns session data according to an authToken
+     *
+     * @param string $authToken
+     * @return PromiseInterface resolves to {@see SessionResponse} or rejects with {@see TestcenterConnectorException}
+     *                          or {@see ApiException}
+     */
+    public function getASessionAsync(string $authToken): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync($this->baseUrl . '/session', [
+            'headers' => [
+                'AuthToken' => $authToken
+            ]
+        ]);
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        return new SessionResponse($data);
+            return new SessionResponse($data);
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -350,33 +465,49 @@ class TestcenterApiClient
      */
     public function getReportOfItemResponses(string $authToken, int $workspaceId, array $dataIds): array
     {
-        $dataIds = implode(',', $dataIds);
-        try {
-            $response = $this->httpClient->get($this->baseUrl . '/workspace/' . $workspaceId . '/report/response', [
-                'headers' => [
-                    'AuthToken' => $authToken,
-                    'Accept' => 'application/json'
-                ],
-                'query' => [
-                    'dataIds' => $dataIds
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
-
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+        return $this->getReportOfItemResponsesAsync($authToken, $workspaceId, $dataIds)->wait();
+    }
 
-        if ($data !== null) {
-            $responses = array_map(function ($responsesData) {
-                return new UnitResponse($responsesData);
-            }, $data);
-        } else {
-            $responses = [];
-        }
+    /**
+     * Asynchronously get responses from a workspace for a list of test groups
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param string[] $dataIds
+     * @return PromiseInterface resolves to array of {@see UnitResponse} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function getReportOfItemResponsesAsync(
+        string $authToken,
+        int $workspaceId,
+        array $dataIds
+    ): PromiseInterface {
+        $dataIds = implode(',', $dataIds);
+        $promise = $this->httpClient->getAsync($this->baseUrl . '/workspace/' . $workspaceId . '/report/response', [
+            'headers' => [
+                'AuthToken' => $authToken,
+                'Accept' => 'application/json'
+            ],
+            'query' => [
+                'dataIds' => $dataIds
+            ]
+        ]);
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
+
+            if ($data !== null) {
+                $responses = array_map(function ($responsesData) {
+                    return new UnitResponse($responsesData);
+                }, $data);
+            } else {
+                $responses = [];
+            }
 
-        return $responses;
+            return $responses;
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -391,34 +522,49 @@ class TestcenterApiClient
      */
     public function getReportOfLogs(string $authToken, int $workspaceId, array $dataIds): array
     {
-        try {
-            $response = $this->httpClient->get($this->baseUrl . '/workspace/' . $workspaceId . '/report/log', [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'query' => [
-                    'dataIds' => implode(',', $dataIds)
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
-
-        $logEntries = [];
-
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+        return $this->getReportOfLogsAsync($authToken, $workspaceId, $dataIds)->wait();
+    }
 
-        // testcenter returns an empty body if there are no log entries instead of an empty array
-        if ($data === null) {
-            $logEntries = [];
-        } else {
-            $logEntries = array_map(function ($entryData) {
-                return new LogEntry($entryData);
-            }, $data);
-        }
+    /**
+     * Asynchronously get logs for login groups in a given workspace
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param string[] $dataIds
+     * @return PromiseInterface resolves to array of {@see LogEntry} or rejects with {@see TestcenterConnectorException}
+     *                          or {@see ApiException}
+     */
+    public function getReportOfLogsAsync(
+        string $authToken,
+        int $workspaceId,
+        array $dataIds
+    ): PromiseInterface {
+        $promise = $this->httpClient->getAsync($this->baseUrl . '/workspace/' . $workspaceId . '/report/log', [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'query' => [
+                'dataIds' => implode(',', $dataIds)
+            ]
+        ]);
+
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
+
+            // testcenter returns an empty body if there are no log entries instead of an empty array
+            if ($data === null) {
+                $logEntries = [];
+            } else {
+                $logEntries = array_map(function ($entryData) {
+                    return new LogEntry($entryData);
+                }, $data);
+            }
 
-        return $logEntries;
+            return $logEntries;
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -433,24 +579,40 @@ class TestcenterApiClient
      */
     public function addAUser(string $authToken, string $username, string $password): bool
     {
+        return $this->addAUserAsync($authToken, $username, $password)->wait();
+    }
+
+    /**
+     * Asynchronously add a new user
+     *
+     * @param string $authToken
+     * @param string $username
+     * @param string $password
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function addAUserAsync(
+        string $authToken,
+        string $username,
+        string $password
+    ): PromiseInterface {
         $body = [
             'n' => $username,
             'p' => $password
         ];
 
-        try {
-            //Sending a PUT request without saving the return value
-            $response = $this->httpClient->put("{$this->baseUrl}/user", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => $body
-            ]);
+        $promise = $this->httpClient->putAsync("{$this->baseUrl}/user", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => $body
+        ]);
 
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
@@ -463,42 +625,54 @@ class TestcenterApiClient
      */
     public function getAListOfUsers(string $authToken): array
     {
-        try {
-            $response = $this->httpClient->get("{$this->baseUrl}/users", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
-
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+        return $this->getAListOfUsersAsync($authToken)->wait();
+    }
 
-        if ($data === null) {
-            throw new TestcenterConnectorException('Invalid json response');
-        }
+    /**
+     * Asynchronously get a list of all registered users
+     *
+     * @param string $authToken
+     * @return PromiseInterface resolves to array of {@see User} or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function getAListOfUsersAsync(string $authToken): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync("{$this->baseUrl}/users", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ]
+        ]);
 
-        if (!is_array($data)) {
-            throw new TestcenterConnectorException('Expected array response');
-        }
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        $users = [];
+            if ($data === null) {
+                throw new TestcenterConnectorException('Invalid json response');
+            }
 
-        foreach ($data as $userData) {
-            if (!is_object($userData)) {
-                throw new TestcenterConnectorException('Expected object user data');
+            if (!is_array($data)) {
+                throw new TestcenterConnectorException('Expected array response');
             }
 
-            $users[] = new User($userData);
-        }
+            $users = [];
+
+            foreach ($data as $userData) {
+                if (!is_object($userData)) {
+                    throw new TestcenterConnectorException('Expected object user data');
+                }
 
-        return $users;
+                $users[] = new User($userData);
+            }
+
+            return $users;
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
-     * Change the password of an user
+     * Change the password of a user
      *
      * @param string $authToken
      * @param int $userId
@@ -509,20 +683,34 @@ class TestcenterApiClient
      */
     public function changeUserPassword(string $authToken, int $userId, string $newPassword): bool
     {
-        try {
-            $this->httpClient->patch("{$this->baseUrl}/user/$userId/password", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => [
-                    'p' => $newPassword
-                ]
-            ]);
+        return $this->changeUserPasswordAsync($authToken, $userId, $newPassword)->wait();
+    }
 
+    /**
+     * Asynchronously change the password of a user
+     *
+     * @param string $authToken
+     * @param int $userId
+     * @param string $newPassword
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function changeUserPasswordAsync(string $authToken, int $userId, string $newPassword): PromiseInterface
+    {
+        $promise = $this->httpClient->patchAsync("{$this->baseUrl}/user/$userId/password", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => [
+                'p' => $newPassword
+            ]
+        ]);
+
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
@@ -538,20 +726,40 @@ class TestcenterApiClient
      */
     public function changeSuperAdminStatus(string $authToken, int $userId, bool $newStatus, string $password): bool
     {
-        try {
-            $newStatus = $newStatus ? 'on' : 'off';
-            $response = $this->httpClient->patch("{$this->baseUrl}/user/$userId/super-admin/$newStatus", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => [
-                    'p' => $password
-                ]
-            ]);
+        return $this->changeSuperAdminStatusAsync($authToken, $userId, $newStatus, $password)->wait();
+    }
+
+    /**
+     * Asynchronously change the super admin status of a user
+     *
+     * @param string $authToken
+     * @param int $userId
+     * @param bool $newStatus
+     * @param string $password
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function changeSuperAdminStatusAsync(
+        string $authToken,
+        int $userId,
+        bool $newStatus,
+        string $password
+    ): PromiseInterface {
+        $newStatus = $newStatus ? 'on' : 'off';
+        $promise = $this->httpClient->patchAsync("{$this->baseUrl}/user/$userId/super-admin/$newStatus", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => [
+                'p' => $password
+            ]
+        ]);
+
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
@@ -565,21 +773,32 @@ class TestcenterApiClient
      */
     public function deleteSomeUsers(string $authToken, array $userIds): bool
     {
-        try {
-            //Sending a DELETE request without saving the return value
-            $this->httpClient->delete("{$this->baseUrl}/users", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => [
-                    'u' => $userIds
-                ]
-            ]);
+        return $this->deleteSomeUsersAsync($authToken, $userIds)->wait();
+    }
+
+    /**
+     * Asynchronously delete one or more users
+     * @param string $authToken
+     * @param array $userIds
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function deleteSomeUsersAsync(string $authToken, array $userIds): PromiseInterface
+    {
+        $promise = $this->httpClient->deleteAsync("{$this->baseUrl}/users", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => [
+                'u' => $userIds
+            ]
+        ]);
 
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
@@ -592,25 +811,39 @@ class TestcenterApiClient
      * @throws TestcenterConnectorException
      */
     public function deleteSomeWorkspaces(string $authToken, array $workspaceIds): bool
+    {
+        return $this->deleteSomeWorkspacesAsync($authToken, $workspaceIds)->wait();
+    }
+
+    /**
+     * Asynchronously delete one or many workspaces
+     *
+     * @param string $authToken
+     * @param array $workspaceIds
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function deleteSomeWorkspacesAsync(string $authToken, array $workspaceIds): PromiseInterface
     {
         $requestBody = [
             'ws' => $workspaceIds
         ];
 
-        try {
-            $response = $this->httpClient->delete("{$this->baseUrl}/workspaces", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => $requestBody
-            ]);
+        $promise = $this->httpClient->deleteAsync("{$this->baseUrl}/workspaces", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => $requestBody
+        ]);
 
+        return $promise->then(function (ResponseInterface $response) {
             return ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300);
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
+
     /**
      * Rename a workspace
      *
@@ -622,24 +855,37 @@ class TestcenterApiClient
      * @throws TestcenterConnectorException
      */
     public function renameAWorkspace(string $authToken, int $workspaceId, string $name): bool
+    {
+        return $this->renameAWorkspaceAsync($authToken, $workspaceId, $name)->wait();
+    }
+
+    /**
+     * Asynchronously rename a workspace
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param string $name
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function renameAWorkspaceAsync(string $authToken, int $workspaceId, string $name): PromiseInterface
     {
         $body = [
             'name' => $name
         ];
 
-        try {
-            //Sending a PATCH request without saving the return value
-            $this->httpClient->patch("{$this->baseUrl}/workspace/$workspaceId", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => $body
-            ]);
+        $promise = $this->httpClient->patchAsync("{$this->baseUrl}/workspace/$workspaceId", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => $body
+        ]);
 
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
@@ -653,25 +899,38 @@ class TestcenterApiClient
      */
     public function getResults(string $authToken, int $workspaceId): array
     {
-        try {
-            $response = $this->httpClient->get("{$this->baseUrl}/workspace/$workspaceId/results", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        return $this->getResultsAsync($authToken, $workspaceId)->wait();
+    }
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+    /**
+     * Asynchronously get statistical results from a workspace
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @return PromiseInterface resolves to array of {@see Result} or rejects with {@see TestcenterConnectorException}
+     *                          or {@see ApiException}
+     */
+    public function getResultsAsync(string $authToken, int $workspaceId): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync("{$this->baseUrl}/workspace/$workspaceId/results", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ]
+        ]);
 
-        $results = [];
-        foreach ($data as $resultData) {
-            $results[] = new Result($resultData);
-        }
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        return $results;
+            $results = [];
+            foreach ($data as $resultData) {
+                $results[] = new Result($resultData);
+            }
+
+            return $results;
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -686,28 +945,39 @@ class TestcenterApiClient
      */
     public function getReportOfSystemChecks(string $authToken, int $workspaceId, array $dataIds): array
     {
-        try {
-            $response = $this->httpClient->get("{$this->baseUrl}/workspace/$workspaceId/report/sys-check", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'query' => [
-                    'dataIds' => implode(',', $dataIds)
-                ]
-            ]);
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
-
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+        return $this->getReportOfSystemChecksAsync($authToken, $workspaceId, $dataIds)->wait();
+    }
 
+    /**
+     * Asynchronously returns a System Check report
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param string[] $dataIds
+     * @return PromiseInterface resolves to array of {@see SysCheckReport} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function getReportOfSystemChecksAsync(string $authToken, int $workspaceId, array $dataIds): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync("{$this->baseUrl}/workspace/$workspaceId/report/sys-check", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'query' => [
+                'dataIds' => implode(',', $dataIds)
+            ]
+        ]);
 
-        $reports = array_map(function ($reportData) {
-            return new SysCheckReport($reportData);
-        }, $data);
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        return $reports;
+            return array_map(function ($reportData) {
+                return new SysCheckReport($reportData);
+            }, $data);
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -718,20 +988,30 @@ class TestcenterApiClient
      * @throws ApiException
      * @throws TestcenterConnectorException
      */
-    public function getSystemConfig()
+    public function getSystemConfig(): SystemConfigResponse
     {
-        try {
-            $response = $this->httpClient->get("{$this->baseUrl}/system/config");
-        } catch (GuzzleException $e) {
-            $this->handleException($e);
-        }
+        return $this->getSystemConfigAsync()->wait();
+    }
 
-        $body = $response->getBody()->getContents();
-        $data = json_decode($body);
+    /**
+     * Asynchronously returns publicly available parts of system config:
+     * version number, customTexts for UI, broadcastingService infos, serverTimestamp
+     *
+     * @return PromiseInterface resolves to {@see SystemConfigResponse} or rejects with
+     *                          {@see TestcenterConnectorException} or {@see ApiException}
+     */
+    public function getSystemConfigAsync(): PromiseInterface
+    {
+        $promise = $this->httpClient->getAsync("{$this->baseUrl}/system/config");
 
-        $config = new SystemConfigResponse($data);
+        return $promise->then(function (ResponseInterface $response) {
+            $body = $response->getBody()->getContents();
+            $data = json_decode($body);
 
-        return $config;
+            return new SystemConfigResponse($data);
+        }, function (GuzzleException $e) {
+            $this->handleException($e);
+        });
     }
 
     /**
@@ -746,21 +1026,34 @@ class TestcenterApiClient
      */
     public function deleteData(string $authToken, int $workspaceId, array $groups): bool
     {
-        try {
-            //Sending a DELETE request without saving the return value
-            $this->httpClient->delete("{$this->baseUrl}/workspace/$workspaceId/responses", [
-                'headers' => [
-                    'AuthToken' => $authToken
-                ],
-                'json' => [
-                    'groups' => $groups
-                ]
-            ]);
+        return $this->deleteDataAsync($authToken, $workspaceId, $groups)->wait();
+    }
+
+    /**
+     * Asynchronously delete responses from a workspace
+     *
+     * @param string $authToken
+     * @param int $workspaceId
+     * @param string[] $groups
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException} or
+     *                          {@see ApiException}
+     */
+    public function deleteDataAsync(string $authToken, int $workspaceId, array $groups): PromiseInterface
+    {
+        $promise = $this->httpClient->deleteAsync("{$this->baseUrl}/workspace/$workspaceId/responses", [
+            'headers' => [
+                'AuthToken' => $authToken
+            ],
+            'json' => [
+                'groups' => $groups
+            ]
+        ]);
 
+        return $promise->then(function (ResponseInterface $response) {
             return true;
-        } catch (GuzzleException $e) {
+        }, function (GuzzleException $e) {
             $this->handleException($e);
-        }
+        });
     }
 
     /**
diff --git a/src/MultiTestcenterConnector.php b/src/MultiTestcenterConnector.php
index 34ef1241da72fcc85fe24f675520652db8497a1a..77cb0f43521a9ab621643bee03a29529ac52e42f 100644
--- a/src/MultiTestcenterConnector.php
+++ b/src/MultiTestcenterConnector.php
@@ -3,10 +3,59 @@
 namespace Kompetenztestde\TestcenterConnector;
 
 use Exception;
+use Kompetenztestde\TestcenterConnector\Api\Schemas\FileDeletionResponse;
+use Kompetenztestde\TestcenterConnector\Api\Schemas\FileUploadResponse;
+use Kompetenztestde\TestcenterConnector\Api\Schemas\UserRole;
+use Kompetenztestde\TestcenterConnector\Models\File;
+use Kompetenztestde\TestcenterConnector\Models\TestGroup;
+use Kompetenztestde\TestcenterConnector\Models\TestInterface;
+use Kompetenztestde\TestcenterConnector\Models\Workspace;
+use Kompetenztestde\TestcenterConnector\Xml\XmlGenerator;
+use Throwable;
+use GuzzleHttp\Client;
+use GuzzleHttp\Promise\Utils;
 use LogicException;
 use BadMethodCallException;
 use Kompetenztestde\TestcenterConnector\Exceptions\TestcenterConnectorException;
 
+// phpcs:disable
+/**
+ * Wrapper class for {@see TestcenterConnector} to communicate with multiple testcenter instances in parallel
+ *
+ * Async methods are called in parallel other methods have to get called one after another. When working with multiple
+ * testcenter instances the use of async methods is recommended for improved performance.
+ *
+ * On success the methods return an associative array of the return values of each instance with the instance base url
+ * as keys. If any call fails a {@see TestcenterConnectorException} is thrown
+ *
+ * @method FileUploadResponse[] addTestGroup(int|string $workspace, TestGroup $group, string $bookletId, XmlGenerator $xmlGenerator = null)
+ * @method FileUploadResponse[] addTestGroupAsync(int|string $workspace, TestGroup $group, string $bookletId, XmlGenerator $xmlGenerator = null)
+ * @method int[] addWorkspace(string $name, ?bool $setRole = false)
+ * @method int[] addWorkspaceAsync(string $name, ?bool $setRole = false)
+ * @method Workspace[] getWorkspaces()
+ * @method Workspace[] getWorkspacesAsync()
+ * @method bool[] deleteWorkspace(int|string[] $workspaces)
+ * @method bool[] deleteWorkspaceAsync(int|string[] $workspaces)
+ * @method FileUploadResponse[] addTest(int|string $workspace, TestInterface $test)
+ * @method array getGroupResults(int|string $workspace, string $groupId)
+ * @method array getGroupResultsAsync(int|string $workspace, string $groupId)
+ * @method bool[] deleteUsers(int|string[] $users)
+ * @method bool[] addUser(string $username, string $password)
+ * @method int[] addSuperAdmin(string $username, string $password)
+ * @method array getUsers()
+ * @method bool[] renameWorkspace(int|string $workspace, string $name)
+ * @method bool[] deleteData(int|string $workspace, string[] $groups)
+ * @method array getSystemCheckReport(int|string $workspace, string[] $dataIds)
+ * @method array getResults(int|string $workspace)
+ * @method array getResultsAsync(int|string $workspace)
+ * @method array getWorkspacesByUser(int|string $user)
+ * @method bool[] changeUserRoles(int|string $workspace, UserRole[] $userRoles)
+ * @method array getLogs(int|string $workspace, string[] $groupIds)
+ * @method array getLogsAsync(int|string $workspace, string[] $groupIds)
+ * @method FileDeletionResponse[] deleteFiles(int|string $workspace, File[] $files)
+ * @method bool[] changeUserPassword(int|string $user, string $newPassword)
+ */
+// phpcs:enable
 class MultiTestcenterConnector
 {
     /**
@@ -20,11 +69,17 @@ class MultiTestcenterConnector
      */
     public function __construct(?array $instanceConfigs, array $testcenterConnectors = null)
     {
+        $guzzleClient = new Client();
         if ($instanceConfigs !== null) {
             foreach ($instanceConfigs as $config) {
                 $this->connectors[] = new TestcenterConnector(
                     $config->getUser(),
-                    $config->getBaseUrl()
+                    $config->getBaseUrl(),
+                    null,
+                    null,
+                    null,
+                    null,
+                    $guzzleClient
                 );
             }
         } elseif ($testcenterConnectors !== null) {
@@ -41,20 +96,38 @@ class MultiTestcenterConnector
     {
         $results = [];
         $errors = [];
-        foreach ($this->connectors as $connector) {
-            if (method_exists($connector, $method)) {
-                try {
-                    $results[$connector->getBaseUrl()] = call_user_func_array([$connector, $method], $args);
-                } catch (Exception $e) {
-                    $errors[$connector->getBaseUrl()] = "Error: " . $e->getMessage();
+
+        if (preg_match('/.*Async$/', $method)) {
+            $promises = [];
+
+            foreach ($this->connectors as $connector) {
+                if (method_exists($connector, $method)) {
+                    $promises[$connector->getBaseUrl()] = call_user_func_array([$connector, $method], $args);
+                } else {
+                    throw new BadMethodCallException("Method $method does not exist in TestcenterConnector.");
+                }
+            }
+            try {
+                $results = Utils::unwrap($promises);
+            } catch (Throwable $e) {
+                throw new TestcenterConnectorException("Errors occurred: " . print_r($e, true));
+            }
+        } else {
+            foreach ($this->connectors as $connector) {
+                if (method_exists($connector, $method)) {
+                    try {
+                        $results[$connector->getBaseUrl()] = call_user_func_array([$connector, $method], $args);
+                    } catch (Exception $e) {
+                        $errors[$connector->getBaseUrl()] = "Error: " . $e->getMessage();
+                    }
+                } else {
+                    throw new BadMethodCallException("Method $method does not exist in TestcenterConnector.");
                 }
-            } else {
-                throw new BadMethodCallException("Method $method does not exist in TestcenterConnector.");
             }
-        }
 
-        if (!empty($errors)) {
-            throw new TestcenterConnectorException("Errors occurred: " . print_r($errors, true));
+            if (!empty($errors)) {
+                throw new TestcenterConnectorException("Errors occurred: " . print_r($errors, true));
+            }
         }
 
         return $results;
diff --git a/src/TestcenterConnector.php b/src/TestcenterConnector.php
index 8669cc7fd809bc08225762f897e302913000ebb4..246f7bc5607f5834914548c1df6a0119fad6b328 100644
--- a/src/TestcenterConnector.php
+++ b/src/TestcenterConnector.php
@@ -3,11 +3,20 @@
 namespace Kompetenztestde\TestcenterConnector;
 
 use Exception;
+use GuzzleHttp\Client;
+use GuzzleHttp\Promise\FulfilledPromise;
+use GuzzleHttp\Promise\PromiseInterface;
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\Result;
+use Kompetenztestde\TestcenterConnector\Api\Schemas\SessionResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\SysCheckReport;
+use Kompetenztestde\TestcenterConnector\Api\Schemas\SystemConfigResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\UnitResponse;
 use Kompetenztestde\TestcenterConnector\Models\TestGroup;
+use Kompetenztestde\TestcenterConnector\Models\Workspace;
 use LogicException;
+use Throwable;
 use ZipArchive;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\FileDeletionResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\FileUploadResponse;
@@ -63,14 +72,15 @@ class TestcenterConnector
         TestcenterApiClient $apiClient = null,
         string $authToken = null,
         string $authenticatedUsername = null,
-        callable $authTokenChangedCallback = null
+        callable $authTokenChangedCallback = null,
+        Client $guzzleClient = null
     ) {
         if ($apiClient === null && $baseUrl === null) {
             throw new LogicException('Either apiClient or baseUrl arguments are needed.');
         }
 
         if ($apiClient === null) {
-            $this->apiClient = new TestcenterApiClient($baseUrl);
+            $this->apiClient = new TestcenterApiClient($baseUrl, $guzzleClient);
         } else {
             $this->apiClient = $apiClient;
         }
@@ -96,31 +106,68 @@ class TestcenterConnector
         Models\TestGroup $group,
         string $bookletId,
         XmlGenerator $xmlGenerator = null
-    ) {
+    ): FileUploadResponse {
+        return $this->addTestGroupAsync($workspace, $group, $bookletId, $xmlGenerator)->wait();
+    }
+
+    /**
+     * Asynchronously create a new group of testtakers and monitor logins for a booklet
+     *
+     * @param int|string $workspace ID or name of the workspace to create the group in
+     * @param TestGroup $group group to create
+     * @param string $bookletId ID of the booklet the group should be assigned to
+     * @param XmlGenerator|null $xmlGenerator optional xml generator instance (for testing purposes)
+     * @return PromiseInterface resolves to {@see FileUploadResponse} or rejects with
+     *                          {@see TestcenterConnectorException}
+     */
+    public function addTestGroupAsync(
+        $workspace,
+        Models\TestGroup $group,
+        string $bookletId,
+        XmlGenerator $xmlGenerator = null
+    ): PromiseInterface {
+        $generatedTesttakers = null;
+
         if ($xmlGenerator === null) {
             $xmlGenerator = new XmlGenerator();
         }
 
-        try {
-            $config = $this->apiClient->getSystemConfig();
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
-        $version = isset($config) ? $config->getVersion() : '15.1.4';
-
-        // generate testtakers xml
-        $xmlGroup = $this->getXmlTestGroup($group, $bookletId);
-        $testtakers = $xmlGenerator->generateTesttakersXml($xmlGroup, $version);
-
-        // upload testtakers xml
-        try {
-            $this->setAuthToken();
-            $workspaceId = $this->getWorkspaceId($workspace);
-            $filename = 'testtakers-' . $group->getId() . '.xml';
-            return $this->apiClient->uploadFile($this->authToken, $workspaceId, $filename, $testtakers);
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+        return $this->apiClient->getSystemConfigAsync()
+            ->then(
+                function (
+                    SystemConfigResponse $config
+                ) use (
+                    $group,
+                    $bookletId,
+                    $xmlGenerator,
+                    $workspace,
+                    &$generatedTesttakers
+                ) {
+                    $version = $config->getVersion() ?? '15.1.4';
+
+                    // generate testtakers xml
+                    $xmlGroup = $this->getXmlTestGroup($group, $bookletId);
+                    $generatedTesttakers = $xmlGenerator->generateTesttakersXml($xmlGroup, $version);
+
+                    // upload testtakers xml
+                    return $this->setAuthTokenAsync();
+                }
+            )
+            ->then(function () use ($workspace, $group, &$generatedTesttakers) {
+                $workspaceId = $this->getWorkspaceId($workspace);
+                $filename = 'testtakers-' . $group->getId() . '.xml';
+                return $this->apiClient->uploadFileAsync(
+                    $this->authToken,
+                    $workspaceId,
+                    $filename,
+                    $generatedTesttakers
+                );
+            })
+            ->then(function (FileUploadResponse $response) {
+                return $response;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     /**
@@ -133,32 +180,57 @@ class TestcenterConnector
      */
     public function addWorkspace(string $name, bool $setRole = false): int
     {
-        try {
-            $this->setAuthToken();
-
-            $workspaceId = $this->apiClient->addAWorkspace($this->authToken, $name);
-
-            if ($workspaceId === null) {
-                $workspaceId = $this->getWorkspaceId($name);
-            }
-
-            if ($setRole) {
-                $userId = $this->getUserId($this->user->getUsername());
-                $success = $this->apiClient->changeUserRoles(
-                    $this->authToken,
-                    $workspaceId,
-                    [new UserRole($userId, UserRole::RW)]
-                );
+        return $this->addWorkspaceAsync($name, $setRole)->wait();
+    }
 
+    /**
+     * Asynchronously create a new workspace
+     *
+     * @param string $name Name of the new workspace (must be unique)
+     * @param bool $setRole set RW role for the current user for the new workspace (default: false)
+     * @return PromiseInterface resolves to workspace id (int) or rejects with {@see TestcenterConnectorException}
+     */
+    public function addWorkspaceAsync(string $name, bool $setRole = false): PromiseInterface
+    {
+        $createdWorkspaceId = null;
+        return $this->setAuthTokenAsync()
+            ->then(function () use ($name) {
+                return $this->apiClient->addAWorkspaceAsync($this->authToken, $name);
+            })
+            ->then(function ($workspaceId) use ($name) {
+                if ($workspaceId === null) {
+                    return $this->getWorkspaceIdAsync($name);
+                }
+                return $workspaceId;
+            })
+            ->then(function ($workspaceId) use ($setRole, &$createdWorkspaceId) {
+                $createdWorkspaceId = $workspaceId;
+                if ($setRole) {
+                    return $this->getUserIdAsync($this->user->getUsername());
+                }
+                return true;
+            })
+            ->then(function ($userId) use (&$createdWorkspaceId, $setRole) {
+                if ($setRole) {
+                    return $this->apiClient->changeUserRolesAsync(
+                        $this->authToken,
+                        $createdWorkspaceId,
+                        [new UserRole($userId, UserRole::RW)]
+                    );
+                }
+                return true;
+            })
+            ->then(function ($success) use (&$createdWorkspaceId) {
                 if (!$success) {
-                    throw new TestcenterConnectorException('Failed to set RW role for new workspace for current user.');
+                    throw new TestcenterConnectorException(
+                        'Failed to set RW role for new workspace for current user.'
+                    );
                 }
-            }
 
-            return $workspaceId;
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+                return $createdWorkspaceId;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     /**
@@ -169,17 +241,28 @@ class TestcenterConnector
      */
     public function getWorkspaces(): array
     {
-        try {
-            $this->setAuthToken();
-
-            $workspaces = $this->apiClient->getAListOfWorkspaces($this->authToken);
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+        return $this->getWorkspacesAsync()->wait();
+    }
 
-        return array_map(function ($workspace) {
-            return new Models\Workspace($workspace->getId(), $workspace->getName());
-        }, $workspaces);
+    /**
+     * Asynchronously get a list of all workspaces
+     *
+     * @return PromiseInterface resolves to array of {@see Workspace} on success or rejects with
+     *                          {@see TestcenterConnectorException}
+     */
+    public function getWorkspacesAsync(): PromiseInterface
+    {
+        return $this->setAuthTokenAsync()
+            ->then(function () {
+                return $this->apiClient->getAListOfWorkspacesAsync($this->authToken);
+            })
+            ->then(function ($workspaces) {
+                return array_map(function ($workspace) {
+                    return new Models\Workspace($workspace->getId(), $workspace->getName());
+                }, $workspaces);
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     /**
@@ -191,13 +274,29 @@ class TestcenterConnector
      */
     public function deleteWorkspace(array $workspaces): bool
     {
-        try {
-            $this->setAuthToken();
-            $workspaceIds = $this->getWorkspaceIds($workspaces);
-            return $this->apiClient->deleteSomeWorkspaces($this->authToken, $workspaceIds);
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+        return $this->deleteWorkspaceAsync($workspaces)->wait();
+    }
+
+    /**
+     * Asynchronously delete workspaces
+     *
+     * @param int|string[] $workspaces array of workspace ids or names to delete
+     * @return PromiseInterface resolves to true on success or rejects with {@see TestcenterConnectorException}
+     */
+    public function deleteWorkspaceAsync(array $workspaces): PromiseInterface
+    {
+        return $this->setAuthTokenAsync()
+            ->then(function () use ($workspaces) {
+                return $this->getWorkspaceIdsAsync($workspaces);
+            })
+            ->then(function ($workspaceIds) {
+                return $this->apiClient->deleteSomeWorkspacesAsync($this->authToken, $workspaceIds);
+            })
+            ->then(function ($success) {
+                return $success;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     /**
@@ -264,13 +363,31 @@ class TestcenterConnector
      */
     public function getGroupResults($workspace, string $groupId): array
     {
-        try {
-            $this->setAuthToken();
-            $workspaceId = $this->getWorkspaceId($workspace);
-            return $this->apiClient->getReportOfItemResponses($this->authToken, $workspaceId, [$groupId]);
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+        return $this->getGroupResultsAsync($workspace, $groupId)->wait();
+    }
+
+    /**
+     * Asynchronously get results of a single group
+     *
+     * @param int|string $workspace id or name of the workspace to get results from
+     * @param string $groupId id of the test group to get results from
+     * @return PromiseInterface resolves to array of {@see UnitResponse} on success or rejects with
+     *                          {@see TestcenterConnectorException}
+     */
+    public function getGroupResultsAsync($workspace, string $groupId): PromiseInterface
+    {
+        return $this->setAuthTokenAsync()
+            ->then(function () use ($workspace) {
+                return $this->getWorkspaceIdAsync($workspace);
+            })
+            ->then(function ($workspaceId) use ($groupId) {
+                return $this->apiClient->getReportOfItemResponsesAsync($this->authToken, $workspaceId, [$groupId]);
+            })
+            ->then(function ($responses) {
+                return $responses;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     private function getXmlTestGroup(Models\TestGroup $group, string $bookletId): Xml\TestGroup
@@ -312,7 +429,7 @@ class TestcenterConnector
         }
     }
 
-     /**
+    /**
      * Adds an user
      *
      * @param string $username name of the new user
@@ -468,7 +585,7 @@ class TestcenterConnector
     }
 
     /**
-     * Retrieves a list of unit- and booklet results for a given workspace and groups
+     * Retrieves a list of unit- and booklet results for a given workspace
      *
      * @param int|string $workspace id or name of the workspace
      * @return Result[]
@@ -476,14 +593,29 @@ class TestcenterConnector
      */
     public function getResults($workspace): array
     {
-        try {
-            $this->setAuthToken();
-            $workspaceId = $this->getWorkspaceId($workspace);
-            $reports = $this->apiClient->getResults($this->authToken, $workspaceId);
-            return $reports;
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+        return $this->getResultsAsync($workspace)->wait();
+    }
+
+    /**
+     * Asynchronously retrieves a list of unit- and booklet results for a given workspace
+     *
+     * @param int|string $workspace id or name of the workspace
+     * @return PromiseInterface resolves to array of {@see Result} or rejects with {@see TestcenterConnectorException}
+     */
+    public function getResultsAsync($workspace): PromiseInterface
+    {
+        return $this->setAuthTokenAsync()
+            ->then(function () use ($workspace) {
+                return $this->getWorkspaceIdAsync($workspace);
+            })
+            ->then(function (int $workspaceId) {
+                return $this->apiClient->getResultsAsync($this->authToken, $workspaceId);
+            })
+            ->then(function ($results) {
+                return $results;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     /**
@@ -533,13 +665,30 @@ class TestcenterConnector
      */
     public function getLogs($workspace, array $groupIds): array
     {
-        try {
-            $this->setAuthToken();
-            $workspaceId = $this->getWorkspaceId($workspace);
-            return $this->apiClient->getReportOfLogs($this->authToken, $workspaceId, $groupIds);
-        } catch (ApiException $e) {
-            throw new TestcenterConnectorException('API Exception: ' . $e);
-        }
+        return $this->getLogsAsync($workspace, $groupIds)->wait();
+    }
+
+    /**
+     * Asynchronously get logs of a test group in a workspace
+     *
+     * @param int|string $workspace id or name of the workspace
+     * @param string[] $groupIds ids of the test groups to get logs from
+     * @return PromiseInterface resolves to array of {@see LogEntry} or rejects with {@see TestcenterConnectorException}
+     */
+    public function getLogsAsync($workspace, array $groupIds): PromiseInterface
+    {
+        return $this->setAuthTokenAsync()
+            ->then(function () use ($workspace) {
+                return $this->getWorkspaceId($workspace);
+            })
+            ->then(function (int $workspaceId) use ($groupIds) {
+                return $this->apiClient->getReportOfLogsAsync($this->authToken, $workspaceId, $groupIds);
+            })
+            ->then(function ($logs) {
+                return $logs;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
     }
 
     /**
@@ -599,45 +748,59 @@ class TestcenterConnector
      */
     private function setAuthToken(): void
     {
-        if (!$this->hasRunningSession()) {
-            switch ($this->user->getRole()) {
-                case User::ADMIN_ROLE:
-                    $sessionResponse = $this->apiClient->startAdminSession(
-                        $this->user->getUsername(),
-                        $this->user->getPassword()
-                    );
-                    break;
-                case User::LOGIN_ROLE:
-                    throw new Exception("Not implemented");
-                default:
-                    throw new TestcenterConnectorException("{$this->user->getRole()} is not an allowed user role.");
-            }
+        $this->setAuthTokenAsync()->wait();
+    }
+
+    private function setAuthTokenAsync(): PromiseInterface
+    {
+        return $this->hasRunningSessionAsync()
+            ->then(function (bool $result) {
+                if (!$result) {
+                    switch ($this->user->getRole()) {
+                        case User::ADMIN_ROLE:
+                            return $this->apiClient->startAdminSessionAsync(
+                                $this->user->getUsername(),
+                                $this->user->getPassword()
+                            );
+                        case User::LOGIN_ROLE:
+                            throw new Exception("Not implemented");
+                        default:
+                            throw new TestcenterConnectorException(
+                                "{$this->user->getRole()} is not an allowed user role."
+                            );
+                    }
+                }
+                return null;
+            })
+            ->then(function ($sessionResponse) {
+                if (!$sessionResponse) {
+                    return;
+                }
 
-            $this->authToken = $sessionResponse->getToken();
-            $this->authenticatedUsername = $sessionResponse->getDisplayName();
+                $this->authToken = $sessionResponse->getToken();
+                $this->authenticatedUsername = $sessionResponse->getDisplayName();
 
-            if ($this->authTokenChangedCallback !== null) {
-                call_user_func($this->authTokenChangedCallback, $this->authToken);
-            }
-        }
+                if ($this->authTokenChangedCallback !== null) {
+                    call_user_func($this->authTokenChangedCallback, $this->authToken);
+                }
+            });
     }
 
-    /**
-     * @return bool
-     * @throws TestcenterConnectorException
-     */
-    private function hasRunningSession(): bool
+    private function hasRunningSessionAsync(): PromiseInterface
     {
         if ($this->authToken === null || $this->user->getUsername() !== $this->authenticatedUsername) {
-            return false;
+            return new FulfilledPromise(false);
         }
 
-        try {
-            $sessionResponse = $this->apiClient->getASession($this->authToken);
-            return $sessionResponse->getToken() === $this->authToken;
-        } catch (ApiException $e) {
-            return false;
-        }
+        return $this->apiClient->getASessionAsync($this->authToken)
+            ->then(
+                function (SessionResponse $sessionResponse) {
+                    return $sessionResponse->getToken() === $this->authToken;
+                },
+                function (ApiException $e) {
+                    return false;
+                }
+            );
     }
 
     /**
@@ -649,20 +812,29 @@ class TestcenterConnector
      * @throws TestcenterConnectorException
      */
     private function getWorkspaceId($workspace): int
+    {
+        return $this->getWorkspaceIdAsync($workspace)->wait();
+    }
+
+    private function getWorkspaceIdAsync($workspace): PromiseInterface
     {
         if (is_string($workspace)) {
-            $workspaces = $this->apiClient->getAListOfWorkspaces($this->authToken);
-            foreach ($workspaces as $currentWorkspace) {
-                if ($currentWorkspace->getName() === $workspace) {
-                    return $currentWorkspace->getId();
-                }
-            }
+            return $this->apiClient->getAListOfWorkspacesAsync($this->authToken)
+                ->then(function ($workspaces) use ($workspace) {
+                    foreach ($workspaces as $currentWorkspace) {
+                        if ($currentWorkspace->getName() === $workspace) {
+                            return $currentWorkspace->getId();
+                        }
+                    }
 
-            throw new TestcenterConnectorException("Workspace with name '$workspace' does not exist.");
+                    throw new TestcenterConnectorException("Workspace with name '$workspace' does not exist.");
+                });
         } elseif (is_int($workspace)) {
-            return $workspace;
+            return new FulfilledPromise($workspace);
         } else {
-            throw new LogicException('workspace must be either int or string');
+            return new Promise(function () {
+                throw new LogicException('workspace must be either int or string');
+            });
         }
     }
 
@@ -670,42 +842,61 @@ class TestcenterConnector
      * Get ids for an array of workspaces
      *
      * @param int|string[] $workspaces ids or names of workspaces
-     * @return int[]
-     * @throws ApiException
-     * @throws TestcenterConnectorException
+     * @return PromiseInterface resolves to array of workspace ids (int) or rejects with
+     *                          {@see TestcenterConnectorException} or {@see LogicException}
      */
-    private function getWorkspaceIds($workspaces): array
+    private function getWorkspaceIdsAsync($workspaces): PromiseInterface
     {
-        $instanceWorkspaces = null;
-        $workspaceIds = [];
-
-        foreach ($workspaces as $workspace) {
-            if (is_string($workspace)) {
-                if ($instanceWorkspaces === null) {
-                    $instanceWorkspaces = $this->apiClient->getAListOfWorkspaces($this->authToken);
-                }
-
-                $workspaceId = null;
-                foreach ($instanceWorkspaces as $currentWorkspace) {
-                    if ($currentWorkspace->getName() === $workspace) {
-                        $workspaceId = $currentWorkspace->getId();
-                        break;
+        if (!$this->containsWorkspaceName($workspaces)) {
+            return new FulfilledPromise($workspaces);
+        }
+
+        return $this->apiClient->getAListOfWorkspacesAsync($this->authToken)
+            ->then(function ($instanceWorkspaces) use ($workspaces) {
+                $workspaceIds = [];
+
+                foreach ($workspaces as $workspace) {
+                    if (is_string($workspace)) {
+                        $workspaceId = null;
+                        foreach ($instanceWorkspaces as $instanceWorkspace) {
+                            if ($instanceWorkspace->getName() === $workspace) {
+                                $workspaceId = $instanceWorkspace->getId();
+                                break;
+                            }
+                        }
+
+                        if ($workspaceId !== null) {
+                            $workspaceIds[] = $workspaceId;
+                        } else {
+                            throw new TestcenterConnectorException("Workspace with name '$workspace' does not exist.");
+                        }
+                    } elseif (is_int($workspace)) {
+                        $workspaceIds[] = $workspace;
+                    } else {
+                        throw new LogicException('workspace must be either int or string');
                     }
                 }
 
-                if ($workspaceId !== null) {
-                    $workspaceIds[] = $workspaceId;
-                } else {
-                    throw new TestcenterConnectorException("Workspace with name '$workspace' does not exist.");
-                }
-            } elseif (is_int($workspace)) {
-                $workspaceIds[] = $workspace;
-            } else {
-                throw new LogicException('workspace must be either int or string');
+                return $workspaceIds;
+            }, function ($e) {
+                $this->handleAsyncError($e);
+            });
+    }
+
+    /**
+     * Check if an array of workspaces contains a workspace by name
+     *
+     * @param int|string[] $workspaces array of workspace ids or names
+     * @return bool true if at least one instance is not an integer, false if all are integers
+     */
+    private function containsWorkspaceName($workspaces): bool
+    {
+        foreach ($workspaces as $workspace) {
+            if (!is_int($workspace)) {
+                return true;
             }
         }
-
-        return $workspaceIds;
+        return false;
     }
 
     /**
@@ -718,19 +909,38 @@ class TestcenterConnector
      */
     private function getUserId($user): int
     {
-        if (is_string($user)) {
-            $users = $this->apiClient->getAListOfUsers($this->authToken);
-            foreach ($users as $currentUser) {
-                if ($currentUser->getName() === $user) {
-                    return $currentUser->getId();
-                }
-            }
+        return $this->getUserIdAsync($user)->wait();
+    }
 
-            throw new TestcenterConnectorException("User with name '$user' does not exist.");
+    /**
+     * Asynchronously get a users id
+     *
+     * @param int|string $user id or name of a user
+     * @return PromiseInterface resolves to user id (int) or rejects with {@see TestcenterConnectorException} or
+     *                          {@see LogicException}
+     */
+    private function getUserIdAsync($user): PromiseInterface
+    {
+        if (is_string($user)) {
+            return $this->apiClient->getAListOfUsersAsync($this->authToken)
+                ->then(function ($users) use ($user) {
+                    foreach ($users as $currentUser) {
+                        if ($currentUser->getName() === $user) {
+                            return $currentUser->getId();
+                        }
+                    }
+                    return null;
+                })
+                ->then(function ($userId) use ($user) {
+                    if ($userId === null) {
+                        throw new TestcenterConnectorException("User with name '$user' does not exist.");
+                    }
+                    return $userId;
+                });
         } elseif (is_int($user)) {
-            return $user;
+            return new FulfilledPromise($user);
         } else {
-            throw new LogicException('user must be either int or string');
+            return new RejectedPromise(new LogicException('user must be either int or string'));
         }
     }
 
@@ -775,4 +985,21 @@ class TestcenterConnector
 
         return $userIds;
     }
+
+    /**
+     * Handle an error received in a promise's then block
+     *
+     * @throws Throwable
+     * @throws TestcenterConnectorException
+     */
+    private function handleAsyncError($error)
+    {
+        if ($error instanceof ApiException) {
+            throw new TestcenterConnectorException('API Exception: ' . $error);
+        } elseif ($error instanceof Throwable) {
+            throw $error;
+        } else {
+            throw new Exception($error);
+        }
+    }
 }
diff --git a/tests/TestcenterConnectorTest.php b/tests/TestcenterConnectorTest.php
index ab554c0458d334ad7ed29d819100b34954f38757..0a4b67aa3c99819153c9eb4652134797668442d0 100644
--- a/tests/TestcenterConnectorTest.php
+++ b/tests/TestcenterConnectorTest.php
@@ -3,12 +3,15 @@
 namespace Kompetenztestde\TestcenterConnector;
 
 use Generator;
+use GuzzleHttp\Promise\FulfilledPromise;
+use GuzzleHttp\Promise\Promise;
 use GuzzleHttp\Psr7\Response;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\FileDeletionResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\FileUploadResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\LogEntry;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\SessionResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\StartSessionResponse;
+use Kompetenztestde\TestcenterConnector\Api\Schemas\SystemConfigResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\UnitResponse;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\UserRole;
 use Kompetenztestde\TestcenterConnector\Api\Schemas\UserWorkspace;
@@ -90,30 +93,27 @@ class TestcenterConnectorTest extends TestCase
             ->method('generateTesttakersXml')
             ->willReturn($this->testtakersContent);
 
-        $startSessionResponse = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
-            ]
-        );
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->with(
-                $this->identicalTo($this->user->getUsername()),
-                $this->identicalTo($this->user->getPassword())
-            )
-            ->willReturn($startSessionResponse);
-
-        $this->apiClient->expects($this->once())
-            ->method('uploadFile')
+            ->method('uploadFileAsync')
             ->with(
                 $this->identicalTo($this->authToken),
                 $this->identicalTo($this->workspaceId),
                 $this->identicalTo('testtakers-test-group.xml'),
                 $this->testtakersContent
             )
-            ->willReturn(new FileUploadResponse([]));
+            ->willReturn(new FulfilledPromise(new FileUploadResponse([])));
+
+        $configResponse = new SystemConfigResponse(
+            (object) [
+                'version' => '15.5.0'
+            ]
+        );
+
+        $this->apiClient->expects($this->once())
+            ->method('getSystemConfigAsync')
+            ->willReturn(new FulfilledPromise($configResponse));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $connector->addTestGroup($this->workspaceId, $this->group, $this->bookletId, $this->xmlGenerator);
@@ -125,7 +125,8 @@ class TestcenterConnectorTest extends TestCase
             new Pupil('login1')
         ];
         $group = new TestGroup('test-group', 'Test Group', $logins, 'grouppassword');
-        $expectedGroup = new XmlTestGroup($this->group->getId(),
+        $expectedGroup = new XmlTestGroup(
+            $this->group->getId(),
             $this->group->getLabel(),
             [
                 new XmlTestLogin('grouppassword-login1', null, XmlLoginMode::RUN_HOT_RETURN)
@@ -138,20 +139,21 @@ class TestcenterConnectorTest extends TestCase
             ->with($this->equalTo($expectedGroup))
             ->willReturn($this->testtakersContent);
 
-        $startSessionResponse = new StartSessionResponse(
+        $this->successfulLogin();
+
+        $this->apiClient->expects($this->once())
+            ->method('uploadFileAsync')
+            ->willReturn(new FulfilledPromise(new FileUploadResponse([])));
+
+        $configResponse = new SystemConfigResponse(
             (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
+                'version' => '15.5.0'
             ]
         );
 
         $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
-
-        $this->apiClient->expects($this->once())
-            ->method('uploadFile')
-            ->willReturn(new FileUploadResponse([]));
+            ->method('getSystemConfigAsync')
+            ->willReturn(new FulfilledPromise($configResponse));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $connector->addTestGroup($this->workspaceId, $group, 'test-booklet', $this->xmlGenerator);
@@ -166,30 +168,17 @@ class TestcenterConnectorTest extends TestCase
             ->method('generateTesttakersXml')
             ->willReturn($this->testtakersContent);
 
-        $startSessionResponse = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
-            ]
-        );
-
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->with(
-                $this->identicalTo('super'),
-                $this->identicalTo('user123')
-            )
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->exactly(2))
-            ->method('uploadFile')
+            ->method('uploadFileAsync')
             ->with(
                 $this->identicalTo($this->authToken),
                 $this->identicalTo($this->workspaceId),
                 $this->identicalTo('testtakers-test-group.xml'),
                 $this->testtakersContent
             )
-            ->willReturn(new FileUploadResponse([]));
+            ->willReturn(new FulfilledPromise(new FileUploadResponse([])));
 
         $sessionResponse = new SessionResponse(
             (object) [
@@ -199,9 +188,19 @@ class TestcenterConnectorTest extends TestCase
         );
 
         $this->apiClient->expects($this->once())
-            ->method('getASession')
+            ->method('getASessionAsync')
             ->with($this->identicalTo($this->authToken))
-            ->willReturn($sessionResponse);
+            ->willReturn(new FulfilledPromise($sessionResponse));
+
+        $configResponse = new SystemConfigResponse(
+            (object) [
+                'version' => '15.5.0'
+            ]
+        );
+
+        $this->apiClient->expects($this->any())
+            ->method('getSystemConfigAsync')
+            ->willReturn(new FulfilledPromise($configResponse));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $connector->addTestGroup($this->workspaceId, $this->group, $this->bookletId, $this->xmlGenerator);
@@ -218,37 +217,38 @@ class TestcenterConnectorTest extends TestCase
             ->method('generateTesttakersXml')
             ->willReturn($this->testtakersContent);
 
-        $startSessionResponse = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
-            ]
-        );
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->exactly(2))
-            ->method('startAdminSession')
-            ->with(
-                $this->identicalTo('super'),
-                $this->identicalTo('user123')
-            )
-            ->willReturn($startSessionResponse);
-
-        $this->apiClient->expects($this->exactly(2))
-            ->method('uploadFile')
+            ->method('uploadFileAsync')
             ->with(
                 $this->identicalTo($this->authToken),
                 $this->identicalTo($this->workspaceId),
                 $this->identicalTo('testtakers-test-group.xml'),
                 $this->testtakersContent
             )
-            ->willReturn(new FileUploadResponse([]));
+            ->willReturn(new FulfilledPromise(new FileUploadResponse([])));
+
+        $sessionResponse = new Promise(function () {
+            throw new ApiException("Expired", 409);
+        });
 
         $this->apiClient->expects($this->once())
-            ->method('getASession')
+            ->method('getASessionAsync')
             ->with(
                 $this->identicalTo($this->authToken)
             )
-            ->willThrowException(new ApiException("Expired", 409));
+            ->willReturn($sessionResponse);
+
+        $configResponse = new SystemConfigResponse(
+            (object) [
+                'version' => '15.5.0'
+            ]
+        );
+
+        $this->apiClient->expects($this->any())
+            ->method('getSystemConfigAsync')
+            ->willReturn(new FulfilledPromise($configResponse));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $connector->addTestGroup($this->workspaceId, $this->group, $this->bookletId, $this->xmlGenerator);
@@ -257,23 +257,15 @@ class TestcenterConnectorTest extends TestCase
 
     public function testAddWorkspace(): void
     {
-        $startSessionResponse = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('addAWorkspace')
+            ->method('addAWorkspaceAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo('test-workspace')
             )
-            ->willReturn(2);
+            ->willReturn(new FulfilledPromise(2));
 
         $this->apiClient->expects($this->never())->method('changeUserRoles');
 
@@ -285,40 +277,34 @@ class TestcenterConnectorTest extends TestCase
 
     public function testAddWorkspaceSetRole(): void
     {
-        $startSessionResponse = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('addAWorkspace')
+            ->method('addAWorkspaceAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo('test-workspace')
             )
-            ->willReturn(2);
+            ->willReturn(new FulfilledPromise(2));
+
+        $users = [new \Kompetenztestde\TestcenterConnector\Api\Schemas\User((object) [
+            'name' => 'super',
+            'id' => 1
+        ])];
 
         $this->apiClient->expects($this->once())
-            ->method('getAListOfUsers')
+            ->method('getAListOfUsersAsync')
             ->with($this->authToken)
-            ->willReturn([new \Kompetenztestde\TestcenterConnector\Api\Schemas\User((object) [
-                'name' => 'super',
-                'id' => 1
-            ])]);
+            ->willReturn(new FulfilledPromise($users));
 
         $this->apiClient->expects($this->once())
-            ->method('changeUserRoles')
+            ->method('changeUserRolesAsync')
             ->with(
                 $this->authToken,
                 2,
                 [new UserRole(1, UserRole::RW)]
             )
-            ->willReturn(true);
+            ->willReturn(new FulfilledPromise(true));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $workspaceId = $connector->addWorkspace('test-workspace', true);
@@ -328,23 +314,15 @@ class TestcenterConnectorTest extends TestCase
 
     public function testAddWorkspaceLegacy(): void
     {
-        $startSessionResponse = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => 'super'
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('addAWorkspace')
+            ->method('addAWorkspaceAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo('test-workspace')
             )
-            ->willReturn(null);
+            ->willReturn(new FulfilledPromise(null));
 
         $workspaces = [
             new Workspace(
@@ -362,9 +340,9 @@ class TestcenterConnectorTest extends TestCase
         ];
 
         $this->apiClient->expects($this->once())
-            ->method('getAListOfWorkspaces')
+            ->method('getAListOfWorkspacesAsync')
             ->with($this->equalTo($this->authToken))
-            ->willReturn($workspaces);
+            ->willReturn(new FulfilledPromise($workspaces));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $workspaceId = $connector->addWorkspace('test-workspace');
@@ -374,13 +352,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testGetWorkspaces(): void
     {
-        $startSessionResponse = new StartSessionResponse((object) [
-            'token' => $this->authToken,
-            'displayName' => 'super'
-        ]);
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $workspaces = [
             new Workspace((object) [
@@ -394,8 +366,8 @@ class TestcenterConnectorTest extends TestCase
         ];
 
         $this->apiClient->expects($this->once())
-            ->method('getAListOfWorkspaces')
-            ->willReturn($workspaces);
+            ->method('getAListOfWorkspacesAsync')
+            ->willReturn(new FulfilledPromise($workspaces));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $workspaces = $connector->getWorkspaces();
@@ -409,20 +381,15 @@ class TestcenterConnectorTest extends TestCase
 
     public function testDeleteWorkspace(): void
     {
-        $startSessionResponse = new StartSessionResponse((object) [
-            'token' => $this->authToken,
-            'displayName' => 'super'
-        ]);
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('deleteSomeWorkspaces')
+            ->method('deleteSomeWorkspacesAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo([1])
-            );
+            )
+            ->willReturn(new FulfilledPromise(true));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $connector->deleteWorkspace([1]);
@@ -430,16 +397,10 @@ class TestcenterConnectorTest extends TestCase
 
     public function testDeleteWorkspaceError(): void
     {
-        $startSessionResponse = new StartSessionResponse((object) [
-            'token' => $this->authToken,
-            'displayName' => 'super'
-        ]);
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('deleteSomeWorkspaces')
+            ->method('deleteSomeWorkspacesAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo([1])
@@ -472,13 +433,7 @@ class TestcenterConnectorTest extends TestCase
             ]));
 
 
-        $startSessionResponse = new StartSessionResponse(
-            (object) ['token' => $this->authToken, 'displayName' => 'super']
-        );
-
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($startSessionResponse);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
             ->method('uploadFile')
@@ -516,24 +471,16 @@ class TestcenterConnectorTest extends TestCase
             )
         ];
 
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
-        $this->apiClient->expects($this->once())
-            ->method('getReportOfItemResponses')
+            ->method('getReportOfItemResponsesAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo(1),
                 $this->equalTo(['test-group'])
             )
-            ->willReturn($testResponses);
+            ->willReturn(new FulfilledPromise($testResponses));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $results = $connector->getGroupResults(1, 'test-group');
@@ -543,15 +490,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testGetWorkspacesByUser(): void
     {
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
+        $this->successfulLogin();
 
         $workspaces = [
             new UserWorkspace(
@@ -587,15 +526,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testChangeUserRoles(): void
     {
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
+        $this->successfulLogin();
 
         $userRoles = [
             new UserRole(
@@ -624,15 +555,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testGetLogs(): void
     {
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
+        $this->successfulLogin();
 
         $logs = [
             new LogEntry(
@@ -648,13 +571,13 @@ class TestcenterConnectorTest extends TestCase
             )
         ];
         $this->apiClient->expects($this->once())
-            ->method('getReportOfLogs')
+            ->method('getReportOfLogsAsync')
             ->with(
                 $this->equalTo($this->authToken),
                 $this->equalTo(1),
                 $this->equalTo(['test_group'])
             )
-            ->willReturn($logs);
+            ->willReturn(new FulfilledPromise($logs));
 
         $connector = new TestcenterConnector($this->user, null, $this->apiClient);
         $returnedLogs = $connector->getLogs(1, ['test_group']);
@@ -664,15 +587,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testDeleteFiles(): void
     {
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
+        $this->successfulLogin();
 
         $deletionResponse = new FileDeletionResponse(
             (object) [
@@ -709,15 +624,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testChangeUserPassword(): void
     {
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
             ->method('changeUserPassword')
@@ -736,15 +643,7 @@ class TestcenterConnectorTest extends TestCase
 
     public function testAddSuperAdmin(): void
     {
-        $response = new StartSessionResponse(
-            (object) [
-                'token' => $this->authToken,
-                'displayName' => $this->user->getUsername()
-            ]
-        );
-        $this->apiClient->expects($this->once())
-            ->method('startAdminSession')
-            ->willReturn($response);
+        $this->successfulLogin();
 
         $this->apiClient->expects($this->once())
             ->method('addAUser')
@@ -817,4 +716,15 @@ class TestcenterConnectorTest extends TestCase
             yield $key => $item;
         }
     }
-}
\ No newline at end of file
+
+    private function successfulLogin()
+    {
+        $startSessionResponse = new StartSessionResponse((object) [
+            'token' => $this->authToken,
+            'displayName' => $this->user->getUsername()
+        ]);
+        $this->apiClient->expects($this->atLeastOnce())
+            ->method('startAdminSessionAsync')
+            ->willReturn(new FulfilledPromise($startSessionResponse));
+    }
+}