diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2ebf1b039849189c1e8816ba2c62554b18d8bebe..0109464c6406fc0e45163a81228f3eca0013978c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,39 +1,43 @@
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.13)
 
 
 set(
-    CMAKE_TOOLCHAIN_FILE
-    "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake"
-    CACHE
-    FILEPATH
-    "Default toolchain"
-)
+	CMAKE_TOOLCHAIN_FILE
+	"${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake"
+	CACHE
+	FILEPATH
+	"Default toolchain"
+	)
 
 
 option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
 include("cmake/HunterGate.cmake")
 HunterGate(
-    URL "https://github.com/cpp-pm/hunter/archive/v0.23.244.tar.gz"
-    SHA1 "2c0f491fd0b80f7b09e3d21adb97237161ef9835"
-    LOCAL
-)
+	URL "https://github.com/cpp-pm/hunter/archive/v0.23.244.tar.gz"
+	SHA1 "2c0f491fd0b80f7b09e3d21adb97237161ef9835"
+	LOCAL
+	)
 
 option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${HUNTER_ENABLED})
 option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog."
-        ${HUNTER_ENABLED})
+	${HUNTER_ENABLED})
 option(USE_BUNDLED_OLM "Use the bundled version of libolm." ${HUNTER_ENABLED})
 option(USE_BUNDLED_GTEST "Use the bundled version of Google Test."
-        ${HUNTER_ENABLED})
+	${HUNTER_ENABLED})
 option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json."
-        ${HUNTER_ENABLED})
+	${HUNTER_ENABLED})
 option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL."
-        ${HUNTER_ENABLED})
+	${HUNTER_ENABLED})
 option(USE_BUNDLED_SODIUM "Use the bundled version of libsodium."
-        ${HUNTER_ENABLED})
+	${HUNTER_ENABLED})
 option(USE_BUNDLED_ZLIB "Use the bundled version of zlib."
-        ${HUNTER_ENABLED})
+	${HUNTER_ENABLED})
 
-project(matrix_client VERSION 0.3.0 LANGUAGES CXX C)
+project(matrix_client
+	VERSION 0.3.0
+	DESCRIPTION "Client API library for Matrix, built on top of Boost.Asio"
+	HOMEPAGE_URL https://github.com/Nheko-Reborn/mtxclient
+	LANGUAGES CXX C)
 
 option(ASAN "Compile with address sanitizers" OFF)
 option(BUILD_LIB_TESTS "Build tests" ON)
@@ -42,56 +46,56 @@ option(COVERAGE "Calculate test coverage" OFF)
 option(IWYU "Check headers with include-what-you-use" OFF)
 option(BUILD_SHARED_LIBS "Specifies whether to build mtxclient as a shared library lib or not" ON)
 
-set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
 
 if(NOT MSVC)
-  set(
-    CMAKE_CXX_FLAGS
-    "${CMAKE_CXX_FLAGS} \
-        -Wall \
-        -Wextra \
-        -pipe \
-        -pedantic \
-        -fsized-deallocation \
-        -fdiagnostics-color=always \
-        -Wunreachable-code"
-    )
+	set(
+		CMAKE_CXX_FLAGS
+		"${CMAKE_CXX_FLAGS} \
+		-Wall \
+		-Wextra \
+		-pipe \
+		-pedantic \
+		-fsized-deallocation \
+		-fdiagnostics-color=always \
+		-Wunreachable-code"
+		)
 endif()
 
 if(MSVC)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj -bigobj")
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj -bigobj")
 endif()
 
 if(ASAN)
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
 endif()
 
 if(NOT MSVC AND NOT APPLE)
-  set(THREADS_PREFER_PTHREAD_FLAG ON)
-  find_package(Threads REQUIRED)
+	set(THREADS_PREFER_PTHREAD_FLAG ON)
+	find_package(Threads REQUIRED)
 endif()
 
 include(FeatureSummary)
 
 if(USE_BUNDLED_OPENSSL)
-        hunter_add_package(OpenSSL)
+	hunter_add_package(OpenSSL)
 endif()
 find_package(OpenSSL REQUIRED)
 set_package_properties(OpenSSL PROPERTIES
-    DESCRIPTION "Open source SSL and TLS implementation and cryptographic library"
-    URL "https://www.openssl.org/"
-    TYPE REQUIRED
-)
+	DESCRIPTION "Open source SSL and TLS implementation and cryptographic library"
+	URL "https://www.openssl.org/"
+	TYPE REQUIRED
+	)
 
 if(USE_BUNDLED_ZLIB)
-        hunter_add_package(ZLIB)
+	hunter_add_package(ZLIB)
 endif()
 find_package(ZLIB)
 set_package_properties(ZLIB PROPERTIES
-    DESCRIPTION "A free compression library unencumbered by patents"
-    URL "https://www.zlib.net/"
-    TYPE REQUIRED
-)
+	DESCRIPTION "A free compression library unencumbered by patents"
+	URL "https://www.zlib.net/"
+	TYPE REQUIRED
+	)
 
 if(USE_BUNDLED_OLM)
 	include(FetchContent)
@@ -111,7 +115,7 @@ else()
 endif()
 
 if(USE_BUNDLED_SODIUM)
-        hunter_add_package(libsodium)
+	hunter_add_package(libsodium)
 	find_package(libsodium 1.0.14 REQUIRED)
 else()
 	find_package(sodium 1.0.14 REQUIRED)
@@ -123,75 +127,78 @@ else()
 endif()
 
 if(USE_BUNDLED_JSON)
-        hunter_add_package(nlohmann_json)
+	hunter_add_package(nlohmann_json)
 endif()
 find_package(nlohmann_json 3.2.0)
 set_package_properties(nlohmann_json PROPERTIES
-    DESCRIPTION "JSON for Modern C++, a C++11 header-only JSON class"
-    URL "https://nlohmann.github.io/json/"
-    TYPE REQUIRED
-)
+	DESCRIPTION "JSON for Modern C++, a C++11 header-only JSON class"
+	URL "https://nlohmann.github.io/json/"
+	TYPE REQUIRED
+	)
 
 if(USE_BUNDLED_BOOST)
-        hunter_add_package(Boost COMPONENTS iostreams system thread)
+	hunter_add_package(Boost COMPONENTS iostreams system thread)
 endif()
-find_package(Boost 1.70 COMPONENTS iostreams system thread)
+find_package(Boost 1.70 COMPONENTS REQUIRED iostreams system thread)
 set_package_properties(Boost PROPERTIES
-DESCRIPTION "Free peer-reviewed portable C++ source libraries"
-URL "https://www.boost.org/"
-TYPE REQUIRED
-)
-
-add_library(matrix_client
-    lib/http/client.cpp
-    lib/http/session.cpp
-    lib/crypto/client.cpp
-    lib/crypto/utils.cpp
-    lib/utils.cpp
-    lib/log.cpp
-    lib/structs/common.cpp
-    lib/structs/errors.cpp
-    lib/structs/events.cpp
-    lib/structs/requests.cpp
-    lib/structs/events/aliases.cpp
-    lib/structs/events/avatar.cpp
-    lib/structs/events/canonical_alias.cpp
-    lib/structs/events/common.cpp
-    lib/structs/events/collections.cpp
-    lib/structs/events/create.cpp
-    lib/structs/events/encrypted.cpp
-    lib/structs/events/encryption.cpp
-    lib/structs/events/guest_access.cpp
-    lib/structs/events/history_visibility.cpp
-    lib/structs/events/join_rules.cpp
-    lib/structs/events/member.cpp
-    lib/structs/events/name.cpp
-    lib/structs/events/pinned_events.cpp
-    lib/structs/events/power_levels.cpp
-    lib/structs/events/redaction.cpp
-    lib/structs/events/tag.cpp
-    lib/structs/events/tombstone.cpp
-    lib/structs/events/topic.cpp
-    lib/structs/events/messages/audio.cpp
-    lib/structs/events/messages/emote.cpp
-    lib/structs/events/messages/file.cpp
-    lib/structs/events/messages/image.cpp
-    lib/structs/events/messages/notice.cpp
-    lib/structs/events/messages/text.cpp
-    lib/structs/events/messages/video.cpp
-    lib/structs/responses/common.cpp
-    lib/structs/responses/create_room.cpp
-    lib/structs/responses/crypto.cpp
-    lib/structs/responses/empty.cpp
-    lib/structs/responses/login.cpp
-    lib/structs/responses/media.cpp
-    lib/structs/responses/messages.cpp
-    lib/structs/responses/notifications.cpp
-    lib/structs/responses/profile.cpp
-    lib/structs/responses/register.cpp
-    lib/structs/responses/sync.cpp
-    lib/structs/responses/version.cpp
-    lib/structs/responses/well-known.cpp)
+	DESCRIPTION "Free peer-reviewed portable C++ source libraries"
+	URL "https://www.boost.org/"
+	TYPE REQUIRED
+	)
+
+add_library(matrix_client)
+
+target_sources(matrix_client
+	PRIVATE
+	lib/http/client.cpp
+	lib/http/session.cpp
+	lib/crypto/client.cpp
+	lib/crypto/utils.cpp
+	lib/utils.cpp
+	lib/log.cpp
+	lib/structs/common.cpp
+	lib/structs/errors.cpp
+	lib/structs/events.cpp
+	lib/structs/requests.cpp
+	lib/structs/events/aliases.cpp
+	lib/structs/events/avatar.cpp
+	lib/structs/events/canonical_alias.cpp
+	lib/structs/events/common.cpp
+	lib/structs/events/collections.cpp
+	lib/structs/events/create.cpp
+	lib/structs/events/encrypted.cpp
+	lib/structs/events/encryption.cpp
+	lib/structs/events/guest_access.cpp
+	lib/structs/events/history_visibility.cpp
+	lib/structs/events/join_rules.cpp
+	lib/structs/events/member.cpp
+	lib/structs/events/name.cpp
+	lib/structs/events/pinned_events.cpp
+	lib/structs/events/power_levels.cpp
+	lib/structs/events/redaction.cpp
+	lib/structs/events/tag.cpp
+	lib/structs/events/tombstone.cpp
+	lib/structs/events/topic.cpp
+	lib/structs/events/messages/audio.cpp
+	lib/structs/events/messages/emote.cpp
+	lib/structs/events/messages/file.cpp
+	lib/structs/events/messages/image.cpp
+	lib/structs/events/messages/notice.cpp
+	lib/structs/events/messages/text.cpp
+	lib/structs/events/messages/video.cpp
+	lib/structs/responses/common.cpp
+	lib/structs/responses/create_room.cpp
+	lib/structs/responses/crypto.cpp
+	lib/structs/responses/empty.cpp
+	lib/structs/responses/login.cpp
+	lib/structs/responses/media.cpp
+	lib/structs/responses/messages.cpp
+	lib/structs/responses/notifications.cpp
+	lib/structs/responses/profile.cpp
+	lib/structs/responses/register.cpp
+	lib/structs/responses/sync.cpp
+	lib/structs/responses/version.cpp
+	lib/structs/responses/well-known.cpp)
 add_library(MatrixClient::MatrixClient ALIAS matrix_client)
 target_include_directories(
 	matrix_client
@@ -224,35 +231,35 @@ else()
 endif()
 
 if(NOT MSVC AND NOT APPLE)
-  target_link_libraries(matrix_client PUBLIC Threads::Threads)
+	target_link_libraries(matrix_client PUBLIC Threads::Threads)
 endif()
 
 if(COVERAGE)
-  include(CodeCoverage)
-  add_custom_target(ctest COMMAND ${CMAKE_CTEST_COMMAND})
-  target_compile_options(matrix_client PUBLIC
-          -O0        # no optimization
-          -g         # generate debug info
-          --coverage # sets all required flags
-          -fprofile-arcs -ftest-coverage # just to be sure, for clang!
-          )
-  target_link_options(matrix_client PUBLIC --coverage)
-  setup_target_for_coverage(test_coverage ctest coverage)
+	include(CodeCoverage)
+	add_custom_target(ctest COMMAND ${CMAKE_CTEST_COMMAND})
+	target_compile_options(matrix_client PUBLIC
+		-O0        # no optimization
+		-g         # generate debug info
+		--coverage # sets all required flags
+		-fprofile-arcs -ftest-coverage # just to be sure, for clang!
+		)
+	target_link_options(matrix_client PUBLIC --coverage)
+	setup_target_for_coverage(test_coverage ctest coverage)
 endif()
 
 
 if(IWYU)
-  find_program(iwyu_path NAMES include-what-you-use iwyu)
-  if(iwyu_path)
-    set_property(TARGET matrix_client
-                 PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path})
-  else()
-    message(WARNING "Could not find the program include-what-you-use")
-  endif()
+	find_program(iwyu_path NAMES include-what-you-use iwyu)
+	if(iwyu_path)
+		set_property(TARGET matrix_client
+			PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path})
+	else()
+		message(WARNING "Could not find the program include-what-you-use")
+	endif()
 endif()
 
 if(BUILD_LIB_EXAMPLES)
-  add_subdirectory(examples)
+	add_subdirectory(examples)
 endif()
 
 feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
@@ -264,150 +271,150 @@ include(GNUInstallDirs)
 set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/MatrixClient)
 
 install(TARGETS matrix_client
-        EXPORT matrix_client-targets
-        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
-        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+	EXPORT matrix_client-targets
+	LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+	ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
 set_target_properties(matrix_client PROPERTIES EXPORT_NAME MatrixClient)
 
 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 install(EXPORT matrix_client-targets
-               FILE
-               MatrixClientTargets.cmake
-               NAMESPACE
-               MatrixClient::
-               DESTINATION
-               ${INSTALL_CONFIGDIR})
+	FILE
+	MatrixClientTargets.cmake
+	NAMESPACE
+	MatrixClient::
+	DESTINATION
+	${INSTALL_CONFIGDIR})
 
 include(CMakePackageConfigHelpers)
 write_basic_package_version_file(
-  ${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfigVersion.cmake
-  VERSION
-  ${PROJECT_VERSION}
-  COMPATIBILITY
-  AnyNewerVersion)
+	${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfigVersion.cmake
+	VERSION
+	${PROJECT_VERSION}
+	COMPATIBILITY
+	AnyNewerVersion)
 
 configure_package_config_file(
-  ${CMAKE_CURRENT_LIST_DIR}/cmake/MatrixClientConfig.cmake.in
-  ${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfig.cmake
-  INSTALL_DESTINATION
-  ${INSTALL_CONFIGDIR})
+	${CMAKE_CURRENT_LIST_DIR}/cmake/MatrixClientConfig.cmake.in
+	${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfig.cmake
+	INSTALL_DESTINATION
+	${INSTALL_CONFIGDIR})
 
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfig.cmake
-              ${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfigVersion.cmake
-              DESTINATION
-              ${INSTALL_CONFIGDIR})
+	${CMAKE_CURRENT_BINARY_DIR}/MatrixClientConfigVersion.cmake
+	DESTINATION
+	${INSTALL_CONFIGDIR})
 
 export(EXPORT
-       matrix_client-targets
-       FILE
-       ${CMAKE_CURRENT_BINARY_DIR}/MatrixClientTargets.cmake
-       NAMESPACE
-       MatrixClient::)
+	matrix_client-targets
+	FILE
+	${CMAKE_CURRENT_BINARY_DIR}/MatrixClientTargets.cmake
+	NAMESPACE
+	MatrixClient::)
 export(PACKAGE MatrixClient)
 
 set_property(TARGET matrix_client PROPERTY SOVERSION ${PROJECT_VERSION})
 
 if(BUILD_LIB_TESTS)
-  enable_testing()
-
-  if(USE_BUNDLED_GTEST)
-	  hunter_add_package(GTest)
-  endif()
-  find_package(GTest REQUIRED)
-
-  file(COPY tests/fixtures DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
-
-  add_executable(client_api tests/client_api.cpp)
-  target_link_libraries(client_api
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-  target_include_directories(client_api PRIVATE
-                             ${CMAKE_CURRENT_SOURCE_DIR}/tests)
-
-  add_executable(media_api tests/media_api.cpp)
-  target_link_libraries(media_api
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-  target_include_directories(media_api PRIVATE
-                             ${CMAKE_CURRENT_SOURCE_DIR}/tests)
-
-  add_executable(e2ee tests/e2ee.cpp)
-  target_link_libraries(e2ee
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-  target_include_directories(e2ee PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
-
-  add_executable(utils tests/utils.cpp)
-  target_link_libraries(utils
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-  target_include_directories(utils PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
-
-  add_executable(connection tests/connection.cpp)
-  target_link_libraries(connection
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-  target_include_directories(connection PRIVATE
-                             ${CMAKE_CURRENT_SOURCE_DIR}/tests)
-
-  add_executable(identifiers tests/identifiers.cpp)
-  target_link_libraries(identifiers
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_executable(events tests/events.cpp)
-  target_link_libraries(events
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_executable(messages tests/messages.cpp)
-  target_link_libraries(messages
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_executable(responses tests/responses.cpp)
-  target_link_libraries(responses
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_executable(requests tests/requests.cpp)
-  target_link_libraries(requests
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_executable(errors tests/errors.cpp)
-  target_link_libraries(errors
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_executable(crypto tests/crypto.cpp)
-  target_link_libraries(crypto
-                        MatrixClient::MatrixClient
-                        GTest::GTest
-                        GTest::Main)
-
-  add_test(BasicConnectivity connection)
-  add_test(ClientAPI client_api)
-  add_test(MediaAPI media_api)
-  add_test(Encryption e2ee)
-  add_test(Utilities utils)
-  add_test(Identifiers identifiers)
-  add_test(Errors errors)
-  add_test(CryptoStructs crypto)
-  add_test(StateEvents events)
-  add_test(RoomEvents messages)
-  add_test(Responses responses)
-  add_test(Requests requests)
+	enable_testing()
+
+	if(USE_BUNDLED_GTEST)
+		hunter_add_package(GTest)
+	endif()
+	find_package(GTest REQUIRED)
+
+	file(COPY tests/fixtures DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+	add_executable(client_api tests/client_api.cpp)
+	target_link_libraries(client_api
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+	target_include_directories(client_api PRIVATE
+		${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+	add_executable(media_api tests/media_api.cpp)
+	target_link_libraries(media_api
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+	target_include_directories(media_api PRIVATE
+		${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+	add_executable(e2ee tests/e2ee.cpp)
+	target_link_libraries(e2ee
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+	target_include_directories(e2ee PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+	add_executable(utils tests/utils.cpp)
+	target_link_libraries(utils
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+	target_include_directories(utils PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+	add_executable(connection tests/connection.cpp)
+	target_link_libraries(connection
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+	target_include_directories(connection PRIVATE
+		${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+	add_executable(identifiers tests/identifiers.cpp)
+	target_link_libraries(identifiers
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_executable(events tests/events.cpp)
+	target_link_libraries(events
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_executable(messages tests/messages.cpp)
+	target_link_libraries(messages
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_executable(responses tests/responses.cpp)
+	target_link_libraries(responses
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_executable(requests tests/requests.cpp)
+	target_link_libraries(requests
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_executable(errors tests/errors.cpp)
+	target_link_libraries(errors
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_executable(crypto tests/crypto.cpp)
+	target_link_libraries(crypto
+		MatrixClient::MatrixClient
+		GTest::GTest
+		GTest::Main)
+
+	add_test(BasicConnectivity connection)
+	add_test(ClientAPI client_api)
+	add_test(MediaAPI media_api)
+	add_test(Encryption e2ee)
+	add_test(Utilities utils)
+	add_test(Identifiers identifiers)
+	add_test(Errors errors)
+	add_test(CryptoStructs crypto)
+	add_test(StateEvents events)
+	add_test(RoomEvents messages)
+	add_test(Responses responses)
+	add_test(Requests requests)
 endif()
diff --git a/include/mtx/events/member.hpp b/include/mtx/events/member.hpp
index 3815f1d5979c64138c4f9eb1283bcbbd36adb39b..78e95816cf22a80e7153031c1db0db7823c27d36 100644
--- a/include/mtx/events/member.hpp
+++ b/include/mtx/events/member.hpp
@@ -42,6 +42,9 @@ struct Member
         //! with the intention of being a direct chat.
         bool is_direct = false;
 
+        //! reason for the membership change, empty in most cases
+        std::string reason;
+
         /* ThirdPartyInvite third_party_invite; */
 };
 
diff --git a/include/mtx/requests.hpp b/include/mtx/requests.hpp
index 86d8d94560a3b361657c4319c12b0050f8e8eabc..8836497a1e014b25f762e60001d0d764d3cddef6 100644
--- a/include/mtx/requests.hpp
+++ b/include/mtx/requests.hpp
@@ -102,14 +102,18 @@ struct DisplayName
 void
 to_json(json &obj, const DisplayName &request);
 
-//! Request payload for the `POST /_matrix/client/r0/rooms/{roomId}/invite` endpoint.
-struct RoomInvite
+//! Request payload for the `POST /_matrix/client/r0/rooms/{roomId}/invite` endpoint as well as ban,
+//! unban and kick.
+struct RoomMembershipChange
 {
         std::string user_id;
+
+        //! optional kick, invite or ban reason
+        std::string reason;
 };
 
 void
-to_json(json &obj, const RoomInvite &request);
+to_json(json &obj, const RoomMembershipChange &request);
 
 //! Request payload for the `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}` endpoint.
 struct TypingNotification
diff --git a/include/mtxclient/crypto/client.hpp b/include/mtxclient/crypto/client.hpp
index cb626241a49615485a9c069e97e5df29525a0543..b79d92e6e7a1baedf3115c69269dfadff87954b2 100644
--- a/include/mtxclient/crypto/client.hpp
+++ b/include/mtxclient/crypto/client.hpp
@@ -5,7 +5,6 @@
 #include <new>
 
 #include <nlohmann/json.hpp>
-#include <sodium.h>
 
 #include <mtx/identifiers.hpp>
 #include <mtx/requests.hpp>
diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp
index 16a9c59e266a861939da648f6cbf232d0dc65b6a..c095654c2860cffae130bf4b93aec6ac04f26b7e 100644
--- a/include/mtxclient/http/client.hpp
+++ b/include/mtxclient/http/client.hpp
@@ -192,7 +192,23 @@ public:
         //! Invite a user to a room.
         void invite_user(const std::string &room_id,
                          const std::string &user_id,
-                         Callback<mtx::responses::RoomInvite> cb);
+                         Callback<mtx::responses::RoomInvite> cb,
+                         const std::string &reason = "");
+        //! Kick a user from a room.
+        void kick_user(const std::string &room_id,
+                       const std::string &user_id,
+                       Callback<mtx::responses::Empty> cb,
+                       const std::string &reason = "");
+        //! Ban a user from a room.
+        void ban_user(const std::string &room_id,
+                      const std::string &user_id,
+                      Callback<mtx::responses::Empty> cb,
+                      const std::string &reason = "");
+        //! Unban a user from a room.
+        void unban_user(const std::string &room_id,
+                        const std::string &user_id,
+                        Callback<mtx::responses::Empty> cb,
+                        const std::string &reason = "");
 
         //! Perform sync.
         void sync(const SyncOpts &opts, Callback<mtx::responses::Sync> cb);
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index eb0ccb2c40c632132b93d0d8708d0aa67a4f4cf8..c8d4322f0188a78bc218ab8ffa1d5df314606af0 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -374,14 +374,62 @@ Client::leave_room(const std::string &room_id, Callback<nlohmann::json> callback
 void
 Client::invite_user(const std::string &room_id,
                     const std::string &user_id,
-                    Callback<mtx::responses::RoomInvite> callback)
+                    Callback<mtx::responses::RoomInvite> callback,
+                    const std::string &reason)
 {
-        mtx::requests::RoomInvite req;
+        mtx::requests::RoomMembershipChange req;
         req.user_id = user_id;
+        req.reason  = reason;
 
         auto api_path = "/client/r0/rooms/" + room_id + "/invite";
 
-        post<mtx::requests::RoomInvite, mtx::responses::RoomInvite>(api_path, req, callback);
+        post<mtx::requests::RoomMembershipChange, mtx::responses::RoomInvite>(
+          api_path, req, callback);
+}
+
+void
+Client::kick_user(const std::string &room_id,
+                  const std::string &user_id,
+                  Callback<mtx::responses::Empty> callback,
+                  const std::string &reason)
+{
+        mtx::requests::RoomMembershipChange req;
+        req.user_id = user_id;
+        req.reason  = reason;
+
+        auto api_path = "/client/r0/rooms/" + room_id + "/kick";
+
+        post<mtx::requests::RoomMembershipChange, mtx::responses::Empty>(api_path, req, callback);
+}
+
+void
+Client::ban_user(const std::string &room_id,
+                 const std::string &user_id,
+                 Callback<mtx::responses::Empty> callback,
+                 const std::string &reason)
+{
+        mtx::requests::RoomMembershipChange req;
+        req.user_id = user_id;
+        req.reason  = reason;
+
+        auto api_path = "/client/r0/rooms/" + room_id + "/ban";
+
+        post<mtx::requests::RoomMembershipChange, mtx::responses::Empty>(api_path, req, callback);
+}
+
+void
+Client::unban_user(const std::string &room_id,
+                   const std::string &user_id,
+                   Callback<mtx::responses::Empty> callback,
+                   const std::string &reason)
+{
+        mtx::requests::RoomMembershipChange req;
+        req.user_id = user_id;
+        req.reason  = reason;
+
+        auto api_path = "/client/r0/rooms/" + room_id + "/unban";
+
+        post<mtx::requests::RoomMembershipChange, mtx::responses::Empty>(api_path, req, callback);
 }
 
 void
diff --git a/lib/structs/events/member.cpp b/lib/structs/events/member.cpp
index 4115b6d087e08a1e8a6300e4cc9c14ce4cfa8603..788c407f3f5f1551a9ff5c9e85eb6a5bfb1b5c76 100644
--- a/lib/structs/events/member.cpp
+++ b/lib/structs/events/member.cpp
@@ -56,6 +56,9 @@ from_json(const json &obj, Member &member)
 
         if (obj.find("is_direct") != obj.end())
                 member.is_direct = obj.at("is_direct").get<bool>();
+
+        if (obj.find("reason") != obj.end())
+                member.reason = obj.at("reason").get<std::string>();
 }
 
 void
@@ -65,6 +68,7 @@ to_json(json &obj, const Member &member)
         obj["avatar_url"]  = member.avatar_url;
         obj["displayname"] = member.display_name;
         obj["is_direct"]   = member.is_direct;
+        obj["reason"]      = member.reason;
 }
 
 } // namespace state
diff --git a/lib/structs/requests.cpp b/lib/structs/requests.cpp
index d3ea07af101e6f6bd43d3d3f0f8310daa264c15d..0ce5c75c0e0570f0662c850b48b3a6372a01a118 100644
--- a/lib/structs/requests.cpp
+++ b/lib/structs/requests.cpp
@@ -91,9 +91,12 @@ to_json(json &obj, const DisplayName &request)
 }
 
 void
-to_json(json &obj, const RoomInvite &request)
+to_json(json &obj, const RoomMembershipChange &request)
 {
         obj["user_id"] = request.user_id;
+
+        if (!request.reason.empty())
+                obj["reason"] = request.reason;
 }
 
 void
diff --git a/tests/client_api.cpp b/tests/client_api.cpp
index fddcb1d9b113922cf1b9b5b4273a4890f39daf4f..705efbbb40ff240368948a604bfb0507a57200d4 100644
--- a/tests/client_api.cpp
+++ b/tests/client_api.cpp
@@ -518,6 +518,110 @@ TEST(ClientAPI, InviteRoom)
         bob->close();
 }
 
+TEST(ClientAPI, KickRoom)
+{
+        auto alice = std::make_shared<Client>("localhost");
+        auto bob   = std::make_shared<Client>("localhost");
+
+        alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
+                check_error(err);
+        });
+
+        bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
+                check_error(err);
+        });
+
+        while (alice->access_token().empty() || bob->access_token().empty())
+                sleep();
+
+        mtx::requests::CreateRoom req;
+        req.name   = "Name";
+        req.topic  = "Topic";
+        req.invite = {};
+        alice->create_room(
+          req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
+                  check_error(err);
+                  auto room_id = res.room_id.to_string();
+
+                  alice->invite_user(
+                    room_id,
+                    "@bob:localhost",
+                    [room_id, alice, bob](const mtx::responses::Empty &, RequestErr err) {
+                            check_error(err);
+
+                            bob->join_room(
+                              room_id, [alice, room_id](const nlohmann::json &, RequestErr err) {
+                                      check_error(err);
+
+                                      alice->kick_user(room_id,
+                                                       "@bob:localhost",
+                                                       [](const mtx::responses::Empty &,
+                                                          RequestErr err) { check_error(err); });
+                              });
+                    });
+          });
+
+        alice->close();
+        bob->close();
+}
+
+TEST(ClientAPI, BanRoom)
+{
+        auto alice = std::make_shared<Client>("localhost");
+        auto bob   = std::make_shared<Client>("localhost");
+
+        alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
+                check_error(err);
+        });
+
+        bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
+                check_error(err);
+        });
+
+        while (alice->access_token().empty() || bob->access_token().empty())
+                sleep();
+
+        mtx::requests::CreateRoom req;
+        req.name   = "Name";
+        req.topic  = "Topic";
+        req.invite = {};
+        alice->create_room(
+          req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
+                  check_error(err);
+                  auto room_id = res.room_id.to_string();
+
+                  alice->invite_user(
+                    room_id,
+                    "@bob:localhost",
+                    [room_id, alice, bob](const mtx::responses::Empty &, RequestErr err) {
+                            check_error(err);
+
+                            bob->join_room(
+                              room_id, [alice, room_id](const nlohmann::json &, RequestErr err) {
+                                      check_error(err);
+
+                                      alice->ban_user(
+                                        room_id,
+                                        "@bob:localhost",
+                                        [alice, room_id](const mtx::responses::Empty &,
+                                                         RequestErr err) {
+                                                check_error(err);
+                                                alice->unban_user(
+                                                  room_id,
+                                                  "@bob:localhost",
+                                                  [](const mtx::responses::Empty &,
+                                                     RequestErr err) { check_error(err); },
+                                                  "You not bad anymore!");
+                                        },
+                                        "You bad!");
+                              });
+                    });
+          });
+
+        alice->close();
+        bob->close();
+}
+
 TEST(ClientAPI, InvalidInvite)
 {
         auto alice = std::make_shared<Client>("localhost");