diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 28f8bc2e70cab671d9c7af5fb1b6c3796d3501c0..6b54663578b60be402531c52f156a88b774f8311 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -183,7 +183,7 @@ test-pages:
   before_script:
     - apk update
     - apk add doxygen git texlive-full py3-jinja2 py3-pygments
-    - git clone git://github.com/mosra/m.css
+    - git clone https://github.com/mosra/m.css.git
   script:
     - ./m.css/documentation/doxygen.py Doxyfile-mcss
     - mv generated-docs/html/ public/
@@ -201,7 +201,7 @@ pages:
   before_script:
     - apk update
     - apk add doxygen git texlive-full py3-jinja2 py3-pygments
-    - git clone git://github.com/mosra/m.css
+    - git clone https://github.com/mosra/m.css.git
   script:
     - ./m.css/documentation/doxygen.py Doxyfile-mcss
     - mv generated-docs/html/ public/
diff --git a/include/mtx/events/collections.hpp b/include/mtx/events/collections.hpp
index 410eb7f73ac15a35aee250e9a20d329ac8ba2a4d..58c04ee21a4b8a29daf0b4edf33cef7af228541c 100644
--- a/include/mtx/events/collections.hpp
+++ b/include/mtx/events/collections.hpp
@@ -112,6 +112,7 @@ using StrippedEvents = std::variant<events::StrippedEvent<states::Aliases>,
                                     events::StrippedEvent<states::Avatar>,
                                     events::StrippedEvent<states::CanonicalAlias>,
                                     events::StrippedEvent<states::Create>,
+                                    events::StrippedEvent<states::Encryption>,
                                     events::StrippedEvent<states::GuestAccess>,
                                     events::StrippedEvent<states::HistoryVisibility>,
                                     events::StrippedEvent<states::JoinRules>,
diff --git a/include/mtx/requests.hpp b/include/mtx/requests.hpp
index c3b5127ef28fbe54def5944ee16294ea0ca90017..c510b97259500f03f6179534a3a92d0e04f246a0 100644
--- a/include/mtx/requests.hpp
+++ b/include/mtx/requests.hpp
@@ -52,6 +52,21 @@ struct CreateRoom
     Preset preset = Preset::PrivateChat;
     //! Whether or not the room will be visible by non members.
     common::RoomVisibility visibility = common::RoomVisibility::Private;
+
+    //! A list of state events to set in the new room. This allows the user to override the default
+    //! state events set in the new room. The expected format of the state events are an object with
+    //! type, state_key and content keys set.
+    std::vector<events::collections::StrippedEvents> initial_state;
+
+    //! The room version to set for the room. If not provided, the homeserver is to use its
+    //! configured default. If provided, the homeserver will return a 400 error with the errcode
+    //! M_UNSUPPORTED_ROOM_VERSION if it does not support the room version.
+    std::string room_version;
+
+    //! Extra keys, such as m.federate, to be added to the content of the m.room.create event. The
+    //! server will overwrite the following keys: creator, room_version. Future versions of the
+    //! specification may allow the server to overwrite other keys.
+    std::optional<events::state::Create> creation_content;
 };
 
 void
diff --git a/lib/structs/requests.cpp b/lib/structs/requests.cpp
index de4ab9704d42fb10d794b847135a23f01ceb2674..c907c2733e70c30ec72b250afa8f503a7cadb6e8 100644
--- a/lib/structs/requests.cpp
+++ b/lib/structs/requests.cpp
@@ -46,6 +46,22 @@ to_json(json &obj, const CreateRoom &request)
     obj["is_direct"]  = request.is_direct;
     obj["preset"]     = presetToString(request.preset);
     obj["visibility"] = visibilityToString(request.visibility);
+
+    if (!request.room_version.empty())
+        obj["room_version"] = request.room_version;
+
+    if (request.creation_content)
+        obj["creation_content"] = *request.creation_content;
+
+    if (!request.initial_state.empty()) {
+        auto arr = nlohmann::json::array();
+        for (const auto &ev : request.initial_state) {
+            auto event_json = std::visit([](auto e) { return json(e); }, ev);
+            event_json.erase("sender");
+            arr.push_back(std::move(event_json));
+        }
+        obj["initial_state"] = std::move(arr);
+    }
 }
 
 void
diff --git a/tests/client_api.cpp b/tests/client_api.cpp
index a5c000464e8ed8cbfae5f81f39c28db1e1acd3a8..7f044cdcaf01faeb92f16c6401fb176aa840b98e 100644
--- a/tests/client_api.cpp
+++ b/tests/client_api.cpp
@@ -8,6 +8,7 @@
 #include "mtx/events/encrypted.hpp"
 #include "mtx/requests.hpp"
 #include "mtx/responses.hpp"
+#include "mtxclient/crypto/types.hpp"
 #include "mtxclient/http/client.hpp"
 
 #include "test_helpers.hpp"
@@ -289,6 +290,58 @@ TEST(ClientAPI, CreateRoom)
     mtx_client->close();
 }
 
+TEST(ClientAPI, CreateRoomInitialState)
+{
+    mtx::requests::CreateRoom req;
+
+    mtx::events::StrippedEvent<mtx::events::state::Encryption> enc;
+    enc.type                         = mtx::events::EventType::RoomEncryption;
+    enc.content.algorithm            = mtx::crypto::MEGOLM_ALGO;
+    enc.content.rotation_period_ms   = 1000ULL * 60ULL * 60ULL * 777ULL;
+    enc.content.rotation_period_msgs = 777;
+
+    req.initial_state.emplace_back(enc);
+
+    std::shared_ptr<Client> mtx_client = make_test_client();
+
+    mtx_client->login(
+      "alice", "secret", [mtx_client](const mtx::responses::Login &, RequestErr err) {
+          check_error(err);
+      });
+
+    while (mtx_client->access_token().empty())
+        sleep();
+
+    mtx_client->create_room(
+      req, [mtx_client, enc](const mtx::responses::CreateRoom &res, RequestErr err) {
+          check_error(err);
+          ASSERT_TRUE(res.room_id.localpart().size() > 10);
+          EXPECT_EQ(res.room_id.hostname(), server_name());
+
+          mtx_client->get_state(
+            res.room_id.to_string(), [enc](const mtx::responses::StateEvents &res, RequestErr err) {
+                check_error(err);
+                ASSERT_TRUE(res.events.size() > 0);
+                bool found_enc_event = false;
+
+                for (const auto &e : res.events) {
+                    auto ev =
+                      std::get_if<mtx::events::StateEvent<mtx::events::state::Encryption>>(&e);
+                    if (ev) {
+                        found_enc_event = true;
+                        EXPECT_EQ(ev->content.algorithm, enc.content.algorithm);
+                        EXPECT_EQ(ev->content.rotation_period_ms, enc.content.rotation_period_ms);
+                        EXPECT_EQ(ev->content.rotation_period_msgs,
+                                  enc.content.rotation_period_msgs);
+                    }
+                }
+                EXPECT_TRUE(found_enc_event);
+            });
+      });
+
+    mtx_client->close();
+}
+
 TEST(ClientAPI, Members)
 {
     std::shared_ptr<Client> mtx_client = make_test_client();
diff --git a/tests/connection.cpp b/tests/connection.cpp
index 957d1ae9150144814fbb253b42b56cbc0e4326f8..c05fe10a45850a476fc1a8d41544c0b85447d0dc 100644
--- a/tests/connection.cpp
+++ b/tests/connection.cpp
@@ -16,7 +16,7 @@ TEST(Basic, Connection)
 {
     auto client = make_test_client();
 
-    client->versions([](const mtx::responses::Versions &, RequestErr err) { ASSERT_FALSE(err); });
+    client->versions([](const mtx::responses::Versions &, RequestErr err) { check_error(err); });
     client->close();
 }
 
@@ -30,7 +30,7 @@ TEST(Basic, ServerWithPort)
     EXPECT_EQ(alice->server(), server);
     EXPECT_EQ(alice->port(), 8008);
 
-    alice->versions([](const mtx::responses::Versions &, RequestErr err) { ASSERT_FALSE(err); });
+    alice->versions([](const mtx::responses::Versions &, RequestErr err) { check_error(err); });
     alice->close();
 }