diff --git a/Cargo.lock b/Cargo.lock
index e7ffe5bd..997cedc6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2081,7 +2081,7 @@ dependencies = [
 [[package]]
 name = "ruma"
 version = "0.4.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "assign",
  "js_int",
@@ -2102,7 +2102,7 @@ dependencies = [
 [[package]]
 name = "ruma-api"
 version = "0.18.5"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "bytes",
  "http",
@@ -2119,7 +2119,7 @@ dependencies = [
 [[package]]
 name = "ruma-api-macros"
 version = "0.18.5"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -2130,7 +2130,7 @@ dependencies = [
 [[package]]
 name = "ruma-appservice-api"
 version = "0.4.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "ruma-api",
  "ruma-common",
@@ -2144,7 +2144,7 @@ dependencies = [
 [[package]]
 name = "ruma-client-api"
 version = "0.12.3"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "assign",
  "bytes",
@@ -2164,7 +2164,7 @@ dependencies = [
 [[package]]
 name = "ruma-common"
 version = "0.6.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "indexmap",
  "js_int",
@@ -2179,7 +2179,7 @@ dependencies = [
 [[package]]
 name = "ruma-events"
 version = "0.24.6"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "indoc",
  "js_int",
@@ -2196,7 +2196,7 @@ dependencies = [
 [[package]]
 name = "ruma-events-macros"
 version = "0.24.6"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -2207,7 +2207,7 @@ dependencies = [
 [[package]]
 name = "ruma-federation-api"
 version = "0.3.1"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "js_int",
  "ruma-api",
@@ -2222,7 +2222,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers"
 version = "0.20.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "percent-encoding",
  "rand 0.8.4",
@@ -2237,7 +2237,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-macros"
 version = "0.20.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "quote",
  "ruma-identifiers-validation",
@@ -2247,7 +2247,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-validation"
 version = "0.5.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "thiserror",
 ]
@@ -2255,7 +2255,7 @@ dependencies = [
 [[package]]
 name = "ruma-identity-service-api"
 version = "0.3.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "js_int",
  "ruma-api",
@@ -2268,7 +2268,7 @@ dependencies = [
 [[package]]
 name = "ruma-push-gateway-api"
 version = "0.3.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "js_int",
  "ruma-api",
@@ -2283,12 +2283,12 @@ dependencies = [
 [[package]]
 name = "ruma-serde"
 version = "0.5.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "base64 0.13.0",
  "bytes",
  "form_urlencoded",
- "itoa 0.4.8",
+ "itoa 1.0.1",
  "js_int",
  "ruma-serde-macros",
  "serde",
@@ -2298,7 +2298,7 @@ dependencies = [
 [[package]]
 name = "ruma-serde-macros"
 version = "0.5.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -2309,7 +2309,7 @@ dependencies = [
 [[package]]
 name = "ruma-signatures"
 version = "0.9.0"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "base64 0.13.0",
  "ed25519-dalek",
@@ -2326,7 +2326,7 @@ dependencies = [
 [[package]]
 name = "ruma-state-res"
 version = "0.4.1"
-source = "git+https://github.com/ruma/ruma?rev=aed09886946f8817a478981cae1b6b8b5d4e7b7d#aed09886946f8817a478981cae1b6b8b5d4e7b7d"
+source = "git+https://github.com/ruma/ruma?rev=fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2#fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2"
 dependencies = [
  "itertools",
  "js_int",
diff --git a/Cargo.toml b/Cargo.toml
index ab7b47d0..b9affa76 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,7 +21,7 @@ tower-http = { version = "0.2.1", features = ["add-extension", "cors", "compress
 
 # Used for matrix spec type definitions and helpers
 #ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
-ruma = { git = "https://github.com/ruma/ruma", rev = "aed09886946f8817a478981cae1b6b8b5d4e7b7d", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-pre-spec", "unstable-exhaustive-types"] }
+ruma = { git = "https://github.com/ruma/ruma", rev = "fa2e3662a456bd8957b3e1293c1dfaf66e85c2f2", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-pre-spec", "unstable-exhaustive-types"] }
 #ruma = { git = "https://github.com/timokoesters/ruma", rev = "50c1db7e0a3a21fc794b0cce3b64285a4c750c71", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
 #ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
 
diff --git a/src/client_server/account.rs b/src/client_server/account.rs
index c15d820e..1ff0fa08 100644
--- a/src/client_server/account.rs
+++ b/src/client_server/account.rs
@@ -8,14 +8,12 @@ use crate::{
 };
 use ruma::{
     api::client::{
-        error::ErrorKind,
-        r0::{
-            account::{
-                change_password, deactivate, get_3pids, get_username_availability, register,
-                whoami, ThirdPartyIdRemovalStatus,
-            },
-            uiaa::{AuthFlow, AuthType, UiaaInfo},
+        account::{
+            change_password, deactivate, get_3pids, get_username_availability, register, whoami,
+            ThirdPartyIdRemovalStatus,
         },
+        error::ErrorKind,
+        uiaa::{AuthFlow, AuthType, UiaaInfo},
     },
     events::{
         room::member::{MembershipState, RoomMemberEventContent},
@@ -42,8 +40,8 @@ const GUEST_NAME_LENGTH: usize = 10;
 /// Note: This will not reserve the username, so the username might become invalid when trying to register
 pub async fn get_register_available_route(
     db: DatabaseGuard,
-    body: Ruma<get_username_availability::Request<'_>>,
-) -> Result<get_username_availability::Response> {
+    body: Ruma<get_username_availability::v3::Request<'_>>,
+) -> Result<get_username_availability::v3::Response> {
     // Validate user id
     let user_id =
         UserId::parse_with_server_name(body.username.to_lowercase(), db.globals.server_name())
@@ -67,7 +65,7 @@ pub async fn get_register_available_route(
     // TODO add check for appservice namespaces
 
     // If no if check is true we have an username that's available to be used.
-    Ok(get_username_availability::Response { available: true })
+    Ok(get_username_availability::v3::Response { available: true })
 }
 
 /// # `POST /_matrix/client/r0/register`
@@ -85,8 +83,8 @@ pub async fn get_register_available_route(
 /// - If `inhibit_login` is false: Creates a device and returns device id and access_token
 pub async fn register_route(
     db: DatabaseGuard,
-    body: Ruma<register::Request<'_>>,
-) -> Result<register::Response> {
+    body: Ruma<register::v3::Request<'_>>,
+) -> Result<register::v3::Response> {
     if !db.globals.allow_registration() && !body.from_appservice {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
@@ -206,7 +204,7 @@ pub async fn register_route(
 
     // Inhibit login does not work for guests
     if !is_guest && body.inhibit_login {
-        return Ok(register::Response {
+        return Ok(register::v3::Response {
             access_token: None,
             user_id,
             device_id: None,
@@ -244,7 +242,7 @@ pub async fn register_route(
 
     db.flush()?;
 
-    Ok(register::Response {
+    Ok(register::v3::Response {
         access_token: Some(token),
         user_id,
         device_id: Some(device_id),
@@ -267,8 +265,8 @@ pub async fn register_route(
 /// - Triggers device list updates
 pub async fn change_password_route(
     db: DatabaseGuard,
-    body: Ruma<change_password::Request<'_>>,
-) -> Result<change_password::Response> {
+    body: Ruma<change_password::v3::Request<'_>>,
+) -> Result<change_password::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -321,7 +319,7 @@ pub async fn change_password_route(
 
     db.flush()?;
 
-    Ok(change_password::Response {})
+    Ok(change_password::v3::Response {})
 }
 
 /// # `GET _matrix/client/r0/account/whoami`
@@ -329,9 +327,9 @@ pub async fn change_password_route(
 /// Get user_id of the sender user.
 ///
 /// Note: Also works for Application Services
-pub async fn whoami_route(body: Ruma<whoami::Request>) -> Result<whoami::Response> {
+pub async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoami::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
-    Ok(whoami::Response {
+    Ok(whoami::v3::Response {
         user_id: sender_user.clone(),
     })
 }
@@ -348,8 +346,8 @@ pub async fn whoami_route(body: Ruma<whoami::Request>) -> Result<whoami::Respons
 /// - Removes ability to log in again
 pub async fn deactivate_route(
     db: DatabaseGuard,
-    body: Ruma<deactivate::Request<'_>>,
-) -> Result<deactivate::Response> {
+    body: Ruma<deactivate::v3::Request<'_>>,
+) -> Result<deactivate::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -442,7 +440,7 @@ pub async fn deactivate_route(
 
     db.flush()?;
 
-    Ok(deactivate::Response {
+    Ok(deactivate::v3::Response {
         id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
     })
 }
@@ -452,8 +450,10 @@ pub async fn deactivate_route(
 /// Get a list of third party identifiers associated with this account.
 ///
 /// - Currently always returns empty list
-pub async fn third_party_route(body: Ruma<get_3pids::Request>) -> Result<get_3pids::Response> {
+pub async fn third_party_route(
+    body: Ruma<get_3pids::v3::Request>,
+) -> Result<get_3pids::v3::Response> {
     let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
-    Ok(get_3pids::Response::new(Vec::new()))
+    Ok(get_3pids::v3::Response::new(Vec::new()))
 }
diff --git a/src/client_server/alias.rs b/src/client_server/alias.rs
index 509372c4..75cf85e5 100644
--- a/src/client_server/alias.rs
+++ b/src/client_server/alias.rs
@@ -4,8 +4,8 @@ use ruma::{
     api::{
         appservice,
         client::{
+            alias::{create_alias, delete_alias, get_alias},
             error::ErrorKind,
-            r0::alias::{create_alias, delete_alias, get_alias},
         },
         federation,
     },
@@ -17,8 +17,8 @@ use ruma::{
 /// Creates a new room alias on this server.
 pub async fn create_alias_route(
     db: DatabaseGuard,
-    body: Ruma<create_alias::Request<'_>>,
-) -> Result<create_alias::Response> {
+    body: Ruma<create_alias::v3::Request<'_>>,
+) -> Result<create_alias::v3::Response> {
     if body.room_alias.server_name() != db.globals.server_name() {
         return Err(Error::BadRequest(
             ErrorKind::InvalidParam,
@@ -35,7 +35,7 @@ pub async fn create_alias_route(
 
     db.flush()?;
 
-    Ok(create_alias::Response::new())
+    Ok(create_alias::v3::Response::new())
 }
 
 /// # `DELETE /_matrix/client/r0/directory/room/{roomAlias}`
@@ -46,8 +46,8 @@ pub async fn create_alias_route(
 /// - TODO: Update canonical alias event
 pub async fn delete_alias_route(
     db: DatabaseGuard,
-    body: Ruma<delete_alias::Request<'_>>,
-) -> Result<delete_alias::Response> {
+    body: Ruma<delete_alias::v3::Request<'_>>,
+) -> Result<delete_alias::v3::Response> {
     if body.room_alias.server_name() != db.globals.server_name() {
         return Err(Error::BadRequest(
             ErrorKind::InvalidParam,
@@ -61,7 +61,7 @@ pub async fn delete_alias_route(
 
     db.flush()?;
 
-    Ok(delete_alias::Response::new())
+    Ok(delete_alias::v3::Response::new())
 }
 
 /// # `GET /_matrix/client/r0/directory/room/{roomAlias}`
@@ -71,15 +71,15 @@ pub async fn delete_alias_route(
 /// - TODO: Suggest more servers to join via
 pub async fn get_alias_route(
     db: DatabaseGuard,
-    body: Ruma<get_alias::Request<'_>>,
-) -> Result<get_alias::Response> {
+    body: Ruma<get_alias::v3::Request<'_>>,
+) -> Result<get_alias::v3::Response> {
     get_alias_helper(&db, &body.room_alias).await
 }
 
 pub(crate) async fn get_alias_helper(
     db: &Database,
     room_alias: &RoomAliasId,
-) -> Result<get_alias::Response> {
+) -> Result<get_alias::v3::Response> {
     if room_alias.server_name() != db.globals.server_name() {
         let response = db
             .sending
@@ -90,7 +90,10 @@ pub(crate) async fn get_alias_helper(
             )
             .await?;
 
-        return Ok(get_alias::Response::new(response.room_id, response.servers));
+        return Ok(get_alias::v3::Response::new(
+            response.room_id,
+            response.servers,
+        ));
     }
 
     let mut room_id = None;
@@ -141,7 +144,7 @@ pub(crate) async fn get_alias_helper(
         }
     };
 
-    Ok(get_alias::Response::new(
+    Ok(get_alias::v3::Response::new(
         room_id,
         vec![db.globals.server_name().to_owned()],
     ))
diff --git a/src/client_server/backup.rs b/src/client_server/backup.rs
index 14c239b1..808d8868 100644
--- a/src/client_server/backup.rs
+++ b/src/client_server/backup.rs
@@ -1,12 +1,12 @@
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::api::client::{
-    error::ErrorKind,
-    r0::backup::{
+    backup::{
         add_backup_key_session, add_backup_key_sessions, add_backup_keys, create_backup,
         delete_backup, delete_backup_key_session, delete_backup_key_sessions, delete_backup_keys,
         get_backup, get_backup_key_session, get_backup_key_sessions, get_backup_keys,
         get_latest_backup, update_backup,
     },
+    error::ErrorKind,
 };
 
 /// # `POST /_matrix/client/r0/room_keys/version`
@@ -14,8 +14,8 @@ use ruma::api::client::{
 /// Creates a new backup.
 pub async fn create_backup_route(
     db: DatabaseGuard,
-    body: Ruma<create_backup::Request>,
-) -> Result<create_backup::Response> {
+    body: Ruma<create_backup::v3::Request>,
+) -> Result<create_backup::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let version = db
         .key_backups
@@ -23,7 +23,7 @@ pub async fn create_backup_route(
 
     db.flush()?;
 
-    Ok(create_backup::Response { version })
+    Ok(create_backup::v3::Response { version })
 }
 
 /// # `PUT /_matrix/client/r0/room_keys/version/{version}`
@@ -31,15 +31,15 @@ pub async fn create_backup_route(
 /// Update information about an existing backup. Only `auth_data` can be modified.
 pub async fn update_backup_route(
     db: DatabaseGuard,
-    body: Ruma<update_backup::Request<'_>>,
-) -> Result<update_backup::Response> {
+    body: Ruma<update_backup::v3::Request<'_>>,
+) -> Result<update_backup::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     db.key_backups
         .update_backup(sender_user, &body.version, &body.algorithm, &db.globals)?;
 
     db.flush()?;
 
-    Ok(update_backup::Response {})
+    Ok(update_backup::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/room_keys/version`
@@ -47,8 +47,8 @@ pub async fn update_backup_route(
 /// Get information about the latest backup version.
 pub async fn get_latest_backup_route(
     db: DatabaseGuard,
-    body: Ruma<get_latest_backup::Request>,
-) -> Result<get_latest_backup::Response> {
+    body: Ruma<get_latest_backup::v3::Request>,
+) -> Result<get_latest_backup::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let (version, algorithm) =
@@ -59,7 +59,7 @@ pub async fn get_latest_backup_route(
                 "Key backup does not exist.",
             ))?;
 
-    Ok(get_latest_backup::Response {
+    Ok(get_latest_backup::v3::Response {
         algorithm,
         count: (db.key_backups.count_keys(sender_user, &version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &version)?,
@@ -72,8 +72,8 @@ pub async fn get_latest_backup_route(
 /// Get information about an existing backup.
 pub async fn get_backup_route(
     db: DatabaseGuard,
-    body: Ruma<get_backup::Request<'_>>,
-) -> Result<get_backup::Response> {
+    body: Ruma<get_backup::v3::Request<'_>>,
+) -> Result<get_backup::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let algorithm = db
         .key_backups
@@ -83,7 +83,7 @@ pub async fn get_backup_route(
             "Key backup does not exist.",
         ))?;
 
-    Ok(get_backup::Response {
+    Ok(get_backup::v3::Response {
         algorithm,
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
@@ -98,15 +98,15 @@ pub async fn get_backup_route(
 /// - Deletes both information about the backup, as well as all key data related to the backup
 pub async fn delete_backup_route(
     db: DatabaseGuard,
-    body: Ruma<delete_backup::Request<'_>>,
-) -> Result<delete_backup::Response> {
+    body: Ruma<delete_backup::v3::Request<'_>>,
+) -> Result<delete_backup::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.key_backups.delete_backup(sender_user, &body.version)?;
 
     db.flush()?;
 
-    Ok(delete_backup::Response {})
+    Ok(delete_backup::v3::Response {})
 }
 
 /// # `PUT /_matrix/client/r0/room_keys/keys`
@@ -118,8 +118,8 @@ pub async fn delete_backup_route(
 /// - Returns the new number of keys in this backup and the etag
 pub async fn add_backup_keys_route(
     db: DatabaseGuard,
-    body: Ruma<add_backup_keys::Request<'_>>,
-) -> Result<add_backup_keys::Response> {
+    body: Ruma<add_backup_keys::v3::Request<'_>>,
+) -> Result<add_backup_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if Some(&body.version)
@@ -149,7 +149,7 @@ pub async fn add_backup_keys_route(
 
     db.flush()?;
 
-    Ok(add_backup_keys::Response {
+    Ok(add_backup_keys::v3::Response {
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
     })
@@ -164,8 +164,8 @@ pub async fn add_backup_keys_route(
 /// - Returns the new number of keys in this backup and the etag
 pub async fn add_backup_key_sessions_route(
     db: DatabaseGuard,
-    body: Ruma<add_backup_key_sessions::Request<'_>>,
-) -> Result<add_backup_key_sessions::Response> {
+    body: Ruma<add_backup_key_sessions::v3::Request<'_>>,
+) -> Result<add_backup_key_sessions::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if Some(&body.version)
@@ -193,7 +193,7 @@ pub async fn add_backup_key_sessions_route(
 
     db.flush()?;
 
-    Ok(add_backup_key_sessions::Response {
+    Ok(add_backup_key_sessions::v3::Response {
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
     })
@@ -208,8 +208,8 @@ pub async fn add_backup_key_sessions_route(
 /// - Returns the new number of keys in this backup and the etag
 pub async fn add_backup_key_session_route(
     db: DatabaseGuard,
-    body: Ruma<add_backup_key_session::Request<'_>>,
-) -> Result<add_backup_key_session::Response> {
+    body: Ruma<add_backup_key_session::v3::Request<'_>>,
+) -> Result<add_backup_key_session::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if Some(&body.version)
@@ -235,7 +235,7 @@ pub async fn add_backup_key_session_route(
 
     db.flush()?;
 
-    Ok(add_backup_key_session::Response {
+    Ok(add_backup_key_session::v3::Response {
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
     })
@@ -246,13 +246,13 @@ pub async fn add_backup_key_session_route(
 /// Retrieves all keys from the backup.
 pub async fn get_backup_keys_route(
     db: DatabaseGuard,
-    body: Ruma<get_backup_keys::Request<'_>>,
-) -> Result<get_backup_keys::Response> {
+    body: Ruma<get_backup_keys::v3::Request<'_>>,
+) -> Result<get_backup_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let rooms = db.key_backups.get_all(sender_user, &body.version)?;
 
-    Ok(get_backup_keys::Response { rooms })
+    Ok(get_backup_keys::v3::Response { rooms })
 }
 
 /// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
@@ -260,15 +260,15 @@ pub async fn get_backup_keys_route(
 /// Retrieves all keys from the backup for a given room.
 pub async fn get_backup_key_sessions_route(
     db: DatabaseGuard,
-    body: Ruma<get_backup_key_sessions::Request<'_>>,
-) -> Result<get_backup_key_sessions::Response> {
+    body: Ruma<get_backup_key_sessions::v3::Request<'_>>,
+) -> Result<get_backup_key_sessions::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let sessions = db
         .key_backups
         .get_room(sender_user, &body.version, &body.room_id)?;
 
-    Ok(get_backup_key_sessions::Response { sessions })
+    Ok(get_backup_key_sessions::v3::Response { sessions })
 }
 
 /// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@@ -276,8 +276,8 @@ pub async fn get_backup_key_sessions_route(
 /// Retrieves a key from the backup.
 pub async fn get_backup_key_session_route(
     db: DatabaseGuard,
-    body: Ruma<get_backup_key_session::Request<'_>>,
-) -> Result<get_backup_key_session::Response> {
+    body: Ruma<get_backup_key_session::v3::Request<'_>>,
+) -> Result<get_backup_key_session::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let key_data = db
@@ -288,7 +288,7 @@ pub async fn get_backup_key_session_route(
             "Backup key not found for this user's session.",
         ))?;
 
-    Ok(get_backup_key_session::Response { key_data })
+    Ok(get_backup_key_session::v3::Response { key_data })
 }
 
 /// # `DELETE /_matrix/client/r0/room_keys/keys`
@@ -296,15 +296,15 @@ pub async fn get_backup_key_session_route(
 /// Delete the keys from the backup.
 pub async fn delete_backup_keys_route(
     db: DatabaseGuard,
-    body: Ruma<delete_backup_keys::Request<'_>>,
-) -> Result<delete_backup_keys::Response> {
+    body: Ruma<delete_backup_keys::v3::Request<'_>>,
+) -> Result<delete_backup_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.key_backups.delete_all_keys(sender_user, &body.version)?;
 
     db.flush()?;
 
-    Ok(delete_backup_keys::Response {
+    Ok(delete_backup_keys::v3::Response {
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
     })
@@ -315,8 +315,8 @@ pub async fn delete_backup_keys_route(
 /// Delete the keys from the backup for a given room.
 pub async fn delete_backup_key_sessions_route(
     db: DatabaseGuard,
-    body: Ruma<delete_backup_key_sessions::Request<'_>>,
-) -> Result<delete_backup_key_sessions::Response> {
+    body: Ruma<delete_backup_key_sessions::v3::Request<'_>>,
+) -> Result<delete_backup_key_sessions::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.key_backups
@@ -324,7 +324,7 @@ pub async fn delete_backup_key_sessions_route(
 
     db.flush()?;
 
-    Ok(delete_backup_key_sessions::Response {
+    Ok(delete_backup_key_sessions::v3::Response {
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
     })
@@ -335,8 +335,8 @@ pub async fn delete_backup_key_sessions_route(
 /// Delete a key from the backup.
 pub async fn delete_backup_key_session_route(
     db: DatabaseGuard,
-    body: Ruma<delete_backup_key_session::Request<'_>>,
-) -> Result<delete_backup_key_session::Response> {
+    body: Ruma<delete_backup_key_session::v3::Request<'_>>,
+) -> Result<delete_backup_key_session::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.key_backups
@@ -344,7 +344,7 @@ pub async fn delete_backup_key_session_route(
 
     db.flush()?;
 
-    Ok(delete_backup_key_session::Response {
+    Ok(delete_backup_key_session::v3::Response {
         count: (db.key_backups.count_keys(sender_user, &body.version)? as u32).into(),
         etag: db.key_backups.get_etag(sender_user, &body.version)?,
     })
diff --git a/src/client_server/capabilities.rs b/src/client_server/capabilities.rs
index b1e072e7..ac2e59f6 100644
--- a/src/client_server/capabilities.rs
+++ b/src/client_server/capabilities.rs
@@ -1,6 +1,6 @@
 use crate::{Result, Ruma};
 use ruma::{
-    api::client::r0::capabilities::{
+    api::client::capabilities::{
         get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability,
     },
     RoomVersionId,
@@ -11,8 +11,8 @@ use std::collections::BTreeMap;
 ///
 /// Get information on the supported feature set and other relevent capabilities of this server.
 pub async fn get_capabilities_route(
-    _body: Ruma<get_capabilities::Request>,
-) -> Result<get_capabilities::Response> {
+    _body: Ruma<get_capabilities::v3::Request>,
+) -> Result<get_capabilities::v3::Response> {
     let mut available = BTreeMap::new();
     available.insert(RoomVersionId::V5, RoomVersionStability::Stable);
     available.insert(RoomVersionId::V6, RoomVersionStability::Stable);
@@ -23,5 +23,5 @@ pub async fn get_capabilities_route(
         available,
     };
 
-    Ok(get_capabilities::Response { capabilities })
+    Ok(get_capabilities::v3::Response { capabilities })
 }
diff --git a/src/client_server/config.rs b/src/client_server/config.rs
index 83bb7a59..a9a2fb14 100644
--- a/src/client_server/config.rs
+++ b/src/client_server/config.rs
@@ -1,11 +1,11 @@
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::{
     api::client::{
-        error::ErrorKind,
-        r0::config::{
+        config::{
             get_global_account_data, get_room_account_data, set_global_account_data,
             set_room_account_data,
         },
+        error::ErrorKind,
     },
     events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
     serde::Raw,
@@ -18,8 +18,8 @@ use serde_json::{json, value::RawValue as RawJsonValue};
 /// Sets some account data for the sender user.
 pub async fn set_global_account_data_route(
     db: DatabaseGuard,
-    body: Ruma<set_global_account_data::Request<'_>>,
-) -> Result<set_global_account_data::Response> {
+    body: Ruma<set_global_account_data::v3::Request<'_>>,
+) -> Result<set_global_account_data::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let data: serde_json::Value = serde_json::from_str(body.data.get())
@@ -40,7 +40,7 @@ pub async fn set_global_account_data_route(
 
     db.flush()?;
 
-    Ok(set_global_account_data::Response {})
+    Ok(set_global_account_data::v3::Response {})
 }
 
 /// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
@@ -48,8 +48,8 @@ pub async fn set_global_account_data_route(
 /// Sets some room account data for the sender user.
 pub async fn set_room_account_data_route(
     db: DatabaseGuard,
-    body: Ruma<set_room_account_data::Request<'_>>,
-) -> Result<set_room_account_data::Response> {
+    body: Ruma<set_room_account_data::v3::Request<'_>>,
+) -> Result<set_room_account_data::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let data: serde_json::Value = serde_json::from_str(body.data.get())
@@ -70,7 +70,7 @@ pub async fn set_room_account_data_route(
 
     db.flush()?;
 
-    Ok(set_room_account_data::Response {})
+    Ok(set_room_account_data::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
@@ -78,8 +78,8 @@ pub async fn set_room_account_data_route(
 /// Gets some account data for the sender user.
 pub async fn get_global_account_data_route(
     db: DatabaseGuard,
-    body: Ruma<get_global_account_data::Request<'_>>,
-) -> Result<get_global_account_data::Response> {
+    body: Ruma<get_global_account_data::v3::Request<'_>>,
+) -> Result<get_global_account_data::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let event: Box<RawJsonValue> = db
@@ -91,7 +91,7 @@ pub async fn get_global_account_data_route(
         .map_err(|_| Error::bad_database("Invalid account data event in db."))?
         .content;
 
-    Ok(get_global_account_data::Response { account_data })
+    Ok(get_global_account_data::v3::Response { account_data })
 }
 
 /// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
@@ -99,8 +99,8 @@ pub async fn get_global_account_data_route(
 /// Gets some room account data for the sender user.
 pub async fn get_room_account_data_route(
     db: DatabaseGuard,
-    body: Ruma<get_room_account_data::Request<'_>>,
-) -> Result<get_room_account_data::Response> {
+    body: Ruma<get_room_account_data::v3::Request<'_>>,
+) -> Result<get_room_account_data::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let event: Box<RawJsonValue> = db
@@ -116,7 +116,7 @@ pub async fn get_room_account_data_route(
         .map_err(|_| Error::bad_database("Invalid account data event in db."))?
         .content;
 
-    Ok(get_room_account_data::Response { account_data })
+    Ok(get_room_account_data::v3::Response { account_data })
 }
 
 #[derive(Deserialize)]
diff --git a/src/client_server/context.rs b/src/client_server/context.rs
index 167d0cc5..2f6a2eac 100644
--- a/src/client_server/context.rs
+++ b/src/client_server/context.rs
@@ -1,9 +1,6 @@
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::{
-    api::client::{
-        error::ErrorKind,
-        r0::{context::get_context, filter::LazyLoadOptions},
-    },
+    api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
     events::EventType,
 };
 use std::{collections::HashSet, convert::TryFrom};
@@ -17,8 +14,8 @@ use tracing::error;
 /// joined, depending on history_visibility)
 pub async fn get_context_route(
     db: DatabaseGuard,
-    body: Ruma<get_context::Request<'_>>,
-) -> Result<get_context::Response> {
+    body: Ruma<get_context::v3::Request<'_>>,
+) -> Result<get_context::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -178,7 +175,7 @@ pub async fn get_context_route(
         }
     }
 
-    let resp = get_context::Response {
+    let resp = get_context::v3::Response {
         start: start_token,
         end: end_token,
         events_before,
diff --git a/src/client_server/device.rs b/src/client_server/device.rs
index 76172d21..09c94064 100644
--- a/src/client_server/device.rs
+++ b/src/client_server/device.rs
@@ -1,10 +1,8 @@
 use crate::{database::DatabaseGuard, utils, Error, Result, Ruma};
 use ruma::api::client::{
+    device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
     error::ErrorKind,
-    r0::{
-        device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
-        uiaa::{AuthFlow, AuthType, UiaaInfo},
-    },
+    uiaa::{AuthFlow, AuthType, UiaaInfo},
 };
 
 use super::SESSION_ID_LENGTH;
@@ -14,8 +12,8 @@ use super::SESSION_ID_LENGTH;
 /// Get metadata on all devices of the sender user.
 pub async fn get_devices_route(
     db: DatabaseGuard,
-    body: Ruma<get_devices::Request>,
-) -> Result<get_devices::Response> {
+    body: Ruma<get_devices::v3::Request>,
+) -> Result<get_devices::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let devices: Vec<device::Device> = db
@@ -24,7 +22,7 @@ pub async fn get_devices_route(
         .filter_map(|r| r.ok()) // Filter out buggy devices
         .collect();
 
-    Ok(get_devices::Response { devices })
+    Ok(get_devices::v3::Response { devices })
 }
 
 /// # `GET /_matrix/client/r0/devices/{deviceId}`
@@ -32,8 +30,8 @@ pub async fn get_devices_route(
 /// Get metadata on a single device of the sender user.
 pub async fn get_device_route(
     db: DatabaseGuard,
-    body: Ruma<get_device::Request<'_>>,
-) -> Result<get_device::Response> {
+    body: Ruma<get_device::v3::Request<'_>>,
+) -> Result<get_device::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let device = db
@@ -41,7 +39,7 @@ pub async fn get_device_route(
         .get_device_metadata(sender_user, &body.body.device_id)?
         .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
 
-    Ok(get_device::Response { device })
+    Ok(get_device::v3::Response { device })
 }
 
 /// # `PUT /_matrix/client/r0/devices/{deviceId}`
@@ -49,8 +47,8 @@ pub async fn get_device_route(
 /// Updates the metadata on a given device of the sender user.
 pub async fn update_device_route(
     db: DatabaseGuard,
-    body: Ruma<update_device::Request<'_>>,
-) -> Result<update_device::Response> {
+    body: Ruma<update_device::v3::Request<'_>>,
+) -> Result<update_device::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut device = db
@@ -65,7 +63,7 @@ pub async fn update_device_route(
 
     db.flush()?;
 
-    Ok(update_device::Response {})
+    Ok(update_device::v3::Response {})
 }
 
 /// # `DELETE /_matrix/client/r0/devices/{deviceId}`
@@ -79,8 +77,8 @@ pub async fn update_device_route(
 /// - Triggers device list updates
 pub async fn delete_device_route(
     db: DatabaseGuard,
-    body: Ruma<delete_device::Request<'_>>,
-) -> Result<delete_device::Response> {
+    body: Ruma<delete_device::v3::Request<'_>>,
+) -> Result<delete_device::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -121,7 +119,7 @@ pub async fn delete_device_route(
 
     db.flush()?;
 
-    Ok(delete_device::Response {})
+    Ok(delete_device::v3::Response {})
 }
 
 /// # `PUT /_matrix/client/r0/devices/{deviceId}`
@@ -137,8 +135,8 @@ pub async fn delete_device_route(
 /// - Triggers device list updates
 pub async fn delete_devices_route(
     db: DatabaseGuard,
-    body: Ruma<delete_devices::Request<'_>>,
-) -> Result<delete_devices::Response> {
+    body: Ruma<delete_devices::v3::Request<'_>>,
+) -> Result<delete_devices::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -181,5 +179,5 @@ pub async fn delete_devices_route(
 
     db.flush()?;
 
-    Ok(delete_devices::Response {})
+    Ok(delete_devices::v3::Response {})
 }
diff --git a/src/client_server/directory.rs b/src/client_server/directory.rs
index 62bf566a..ad88254e 100644
--- a/src/client_server/directory.rs
+++ b/src/client_server/directory.rs
@@ -2,14 +2,12 @@ use crate::{database::DatabaseGuard, Database, Error, Result, Ruma};
 use ruma::{
     api::{
         client::{
-            error::ErrorKind,
-            r0::{
-                directory::{
-                    get_public_rooms, get_public_rooms_filtered, get_room_visibility,
-                    set_room_visibility,
-                },
-                room,
+            directory::{
+                get_public_rooms, get_public_rooms_filtered, get_room_visibility,
+                set_room_visibility,
             },
+            error::ErrorKind,
+            room,
         },
         federation,
     },
@@ -36,8 +34,8 @@ use tracing::{info, warn};
 /// - Rooms are ordered by the number of joined members
 pub async fn get_public_rooms_filtered_route(
     db: DatabaseGuard,
-    body: Ruma<get_public_rooms_filtered::Request<'_>>,
-) -> Result<get_public_rooms_filtered::Response> {
+    body: Ruma<get_public_rooms_filtered::v3::Request<'_>>,
+) -> Result<get_public_rooms_filtered::v3::Response> {
     get_public_rooms_filtered_helper(
         &db,
         body.server.as_deref(),
@@ -56,8 +54,8 @@ pub async fn get_public_rooms_filtered_route(
 /// - Rooms are ordered by the number of joined members
 pub async fn get_public_rooms_route(
     db: DatabaseGuard,
-    body: Ruma<get_public_rooms::Request<'_>>,
-) -> Result<get_public_rooms::Response> {
+    body: Ruma<get_public_rooms::v3::Request<'_>>,
+) -> Result<get_public_rooms::v3::Response> {
     let response = get_public_rooms_filtered_helper(
         &db,
         body.server.as_deref(),
@@ -68,7 +66,7 @@ pub async fn get_public_rooms_route(
     )
     .await?;
 
-    Ok(get_public_rooms::Response {
+    Ok(get_public_rooms::v3::Response {
         chunk: response.chunk,
         prev_batch: response.prev_batch,
         next_batch: response.next_batch,
@@ -83,8 +81,8 @@ pub async fn get_public_rooms_route(
 /// - TODO: Access control checks
 pub async fn set_room_visibility_route(
     db: DatabaseGuard,
-    body: Ruma<set_room_visibility::Request<'_>>,
-) -> Result<set_room_visibility::Response> {
+    body: Ruma<set_room_visibility::v3::Request<'_>>,
+) -> Result<set_room_visibility::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     match &body.visibility {
@@ -103,7 +101,7 @@ pub async fn set_room_visibility_route(
 
     db.flush()?;
 
-    Ok(set_room_visibility::Response {})
+    Ok(set_room_visibility::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/directory/list/room/{roomId}`
@@ -111,9 +109,9 @@ pub async fn set_room_visibility_route(
 /// Gets the visibility of a given room in the room directory.
 pub async fn get_room_visibility_route(
     db: DatabaseGuard,
-    body: Ruma<get_room_visibility::Request<'_>>,
-) -> Result<get_room_visibility::Response> {
-    Ok(get_room_visibility::Response {
+    body: Ruma<get_room_visibility::v3::Request<'_>>,
+) -> Result<get_room_visibility::v3::Response> {
+    Ok(get_room_visibility::v3::Response {
         visibility: if db.rooms.is_public_room(&body.room_id)? {
             room::Visibility::Public
         } else {
@@ -129,7 +127,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
     since: Option<&str>,
     filter: &IncomingFilter,
     _network: &IncomingRoomNetwork,
-) -> Result<get_public_rooms_filtered::Response> {
+) -> Result<get_public_rooms_filtered::v3::Response> {
     if let Some(other_server) = server.filter(|server| *server != db.globals.server_name().as_str())
     {
         let response = db
@@ -148,7 +146,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
             )
             .await?;
 
-        return Ok(get_public_rooms_filtered::Response {
+        return Ok(get_public_rooms_filtered::v3::Response {
             chunk: response.chunk,
             prev_batch: response.prev_batch,
             next_batch: response.next_batch,
@@ -189,7 +187,6 @@ pub(crate) async fn get_public_rooms_filtered_helper(
             let room_id = room_id?;
 
             let chunk = PublicRoomsChunk {
-                aliases: Vec::new(),
                 canonical_alias: db
                     .rooms
                     .room_state_get(&room_id, &EventType::RoomCanonicalAlias, "")?
@@ -328,7 +325,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
         Some(format!("n{}", num_since + limit))
     };
 
-    Ok(get_public_rooms_filtered::Response {
+    Ok(get_public_rooms_filtered::v3::Response {
         chunk,
         prev_batch,
         next_batch,
diff --git a/src/client_server/filter.rs b/src/client_server/filter.rs
index a606aeb4..379950f4 100644
--- a/src/client_server/filter.rs
+++ b/src/client_server/filter.rs
@@ -1,7 +1,7 @@
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::api::client::{
     error::ErrorKind,
-    r0::filter::{create_filter, get_filter},
+    filter::{create_filter, get_filter},
 };
 
 /// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
@@ -11,15 +11,15 @@ use ruma::api::client::{
 /// - A user can only access their own filters
 pub async fn get_filter_route(
     db: DatabaseGuard,
-    body: Ruma<get_filter::Request<'_>>,
-) -> Result<get_filter::Response> {
+    body: Ruma<get_filter::v3::Request<'_>>,
+) -> Result<get_filter::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let filter = match db.users.get_filter(sender_user, &body.filter_id)? {
         Some(filter) => filter,
         None => return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found.")),
     };
 
-    Ok(get_filter::Response::new(filter))
+    Ok(get_filter::v3::Response::new(filter))
 }
 
 /// # `PUT /_matrix/client/r0/user/{userId}/filter`
@@ -27,10 +27,10 @@ pub async fn get_filter_route(
 /// Creates a new filter to be used by other endpoints.
 pub async fn create_filter_route(
     db: DatabaseGuard,
-    body: Ruma<create_filter::Request<'_>>,
-) -> Result<create_filter::Response> {
+    body: Ruma<create_filter::v3::Request<'_>>,
+) -> Result<create_filter::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
-    Ok(create_filter::Response::new(
+    Ok(create_filter::v3::Response::new(
         db.users.create_filter(sender_user, &body.filter)?,
     ))
 }
diff --git a/src/client_server/keys.rs b/src/client_server/keys.rs
index 2ea62a87..525c7790 100644
--- a/src/client_server/keys.rs
+++ b/src/client_server/keys.rs
@@ -5,13 +5,11 @@ use ruma::{
     api::{
         client::{
             error::ErrorKind,
-            r0::{
-                keys::{
-                    claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures,
-                    upload_signing_keys,
-                },
-                uiaa::{AuthFlow, AuthType, UiaaInfo},
+            keys::{
+                claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures,
+                upload_signing_keys,
             },
+            uiaa::{AuthFlow, AuthType, UiaaInfo},
         },
         federation,
     },
@@ -29,8 +27,8 @@ use std::collections::{BTreeMap, HashMap, HashSet};
 /// - If there are no device keys yet: Adds device keys (TODO: merge with existing keys?)
 pub async fn upload_keys_route(
     db: DatabaseGuard,
-    body: Ruma<upload_keys::Request>,
-) -> Result<upload_keys::Response> {
+    body: Ruma<upload_keys::v3::Request>,
+) -> Result<upload_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -59,7 +57,7 @@ pub async fn upload_keys_route(
 
     db.flush()?;
 
-    Ok(upload_keys::Response {
+    Ok(upload_keys::v3::Response {
         one_time_key_counts: db.users.count_one_time_keys(sender_user, sender_device)?,
     })
 }
@@ -73,8 +71,8 @@ pub async fn upload_keys_route(
 /// - The master and self-signing keys contain signatures that the user is allowed to see
 pub async fn get_keys_route(
     db: DatabaseGuard,
-    body: Ruma<get_keys::Request<'_>>,
-) -> Result<get_keys::Response> {
+    body: Ruma<get_keys::v3::Request<'_>>,
+) -> Result<get_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let response = get_keys_helper(
@@ -93,8 +91,8 @@ pub async fn get_keys_route(
 /// Claims one-time keys
 pub async fn claim_keys_route(
     db: DatabaseGuard,
-    body: Ruma<claim_keys::Request>,
-) -> Result<claim_keys::Response> {
+    body: Ruma<claim_keys::v3::Request>,
+) -> Result<claim_keys::v3::Response> {
     let response = claim_keys_helper(&body.one_time_keys, &db).await?;
 
     db.flush()?;
@@ -109,8 +107,8 @@ pub async fn claim_keys_route(
 /// - Requires UIAA to verify password
 pub async fn upload_signing_keys_route(
     db: DatabaseGuard,
-    body: Ruma<upload_signing_keys::Request<'_>>,
-) -> Result<upload_signing_keys::Response> {
+    body: Ruma<upload_signing_keys::v3::Request<'_>>,
+) -> Result<upload_signing_keys::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -160,7 +158,7 @@ pub async fn upload_signing_keys_route(
 
     db.flush()?;
 
-    Ok(upload_signing_keys::Response {})
+    Ok(upload_signing_keys::v3::Response {})
 }
 
 /// # `POST /_matrix/client/r0/keys/signatures/upload`
@@ -168,12 +166,14 @@ pub async fn upload_signing_keys_route(
 /// Uploads end-to-end key signatures from the sender user.
 pub async fn upload_signatures_route(
     db: DatabaseGuard,
-    body: Ruma<upload_signatures::Request>,
-) -> Result<upload_signatures::Response> {
+    body: Ruma<upload_signatures::v3::Request>,
+) -> Result<upload_signatures::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     for (user_id, signed_keys) in &body.signed_keys {
         for (key_id, signed_key) in signed_keys {
+            let signed_key = serde_json::to_value(signed_key).unwrap();
+
             for signature in signed_key
                 .get("signatures")
                 .ok_or(Error::BadRequest(
@@ -219,7 +219,9 @@ pub async fn upload_signatures_route(
 
     db.flush()?;
 
-    Ok(upload_signatures::Response {})
+    Ok(upload_signatures::v3::Response {
+        failures: BTreeMap::new(), // TODO: integrate
+    })
 }
 
 /// # `POST /_matrix/client/r0/keys/changes`
@@ -229,8 +231,8 @@ pub async fn upload_signatures_route(
 /// - TODO: left users
 pub async fn get_key_changes_route(
     db: DatabaseGuard,
-    body: Ruma<get_key_changes::Request<'_>>,
-) -> Result<get_key_changes::Response> {
+    body: Ruma<get_key_changes::v3::Request<'_>>,
+) -> Result<get_key_changes::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut device_list_updates = HashSet::new();
@@ -266,7 +268,7 @@ pub async fn get_key_changes_route(
                 .filter_map(|r| r.ok()),
         );
     }
-    Ok(get_key_changes::Response {
+    Ok(get_key_changes::v3::Response {
         changed: device_list_updates.into_iter().collect(),
         left: Vec::new(), // TODO
     })
@@ -277,7 +279,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
     device_keys_input: &BTreeMap<Box<UserId>, Vec<Box<DeviceId>>>,
     allowed_signatures: F,
     db: &Database,
-) -> Result<get_keys::Response> {
+) -> Result<get_keys::v3::Response> {
     let mut master_keys = BTreeMap::new();
     let mut self_signing_keys = BTreeMap::new();
     let mut user_signing_keys = BTreeMap::new();
@@ -386,7 +388,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
         }
     }
 
-    Ok(get_keys::Response {
+    Ok(get_keys::v3::Response {
         master_keys,
         self_signing_keys,
         user_signing_keys,
@@ -397,7 +399,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
 
 fn add_unsigned_device_display_name(
     keys: &mut Raw<ruma::encryption::DeviceKeys>,
-    metadata: ruma::api::client::r0::device::Device,
+    metadata: ruma::api::client::device::Device,
 ) -> serde_json::Result<()> {
     if let Some(display_name) = metadata.display_name {
         let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
@@ -416,7 +418,7 @@ fn add_unsigned_device_display_name(
 pub(crate) async fn claim_keys_helper(
     one_time_keys_input: &BTreeMap<Box<UserId>, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>,
     db: &Database,
-) -> Result<claim_keys::Response> {
+) -> Result<claim_keys::v3::Response> {
     let mut one_time_keys = BTreeMap::new();
 
     let mut get_over_federation = BTreeMap::new();
@@ -468,7 +470,7 @@ pub(crate) async fn claim_keys_helper(
         }
     }
 
-    Ok(claim_keys::Response {
+    Ok(claim_keys::v3::Response {
         failures,
         one_time_keys,
     })
diff --git a/src/client_server/media.rs b/src/client_server/media.rs
index dcdea05a..71dbed68 100644
--- a/src/client_server/media.rs
+++ b/src/client_server/media.rs
@@ -4,7 +4,7 @@ use crate::{
 };
 use ruma::api::client::{
     error::ErrorKind,
-    r0::media::{
+    media::{
         create_content, get_content, get_content_as_filename, get_content_thumbnail,
         get_media_config,
     },
@@ -17,9 +17,9 @@ const MXC_LENGTH: usize = 32;
 /// Returns max upload size.
 pub async fn get_media_config_route(
     db: DatabaseGuard,
-    _body: Ruma<get_media_config::Request>,
-) -> Result<get_media_config::Response> {
-    Ok(get_media_config::Response {
+    _body: Ruma<get_media_config::v3::Request>,
+) -> Result<get_media_config::v3::Response> {
+    Ok(get_media_config::v3::Response {
         upload_size: db.globals.max_request_size().into(),
     })
 }
@@ -32,8 +32,8 @@ pub async fn get_media_config_route(
 /// - Media will be saved in the media/ directory
 pub async fn create_content_route(
     db: DatabaseGuard,
-    body: Ruma<create_content::Request<'_>>,
-) -> Result<create_content::Response> {
+    body: Ruma<create_content::v3::Request<'_>>,
+) -> Result<create_content::v3::Response> {
     let mxc = format!(
         "mxc://{}/{}",
         db.globals.server_name(),
@@ -56,7 +56,7 @@ pub async fn create_content_route(
 
     db.flush()?;
 
-    Ok(create_content::Response {
+    Ok(create_content::v3::Response {
         content_uri: mxc.try_into().expect("Invalid mxc:// URI"),
         blurhash: None,
     })
@@ -67,13 +67,13 @@ pub async fn get_remote_content(
     mxc: &str,
     server_name: &ruma::ServerName,
     media_id: &str,
-) -> Result<get_content::Response, Error> {
+) -> Result<get_content::v3::Response, Error> {
     let content_response = db
         .sending
         .send_federation_request(
             &db.globals,
             server_name,
-            get_content::Request {
+            get_content::v3::Request {
                 allow_remote: false,
                 server_name,
                 media_id,
@@ -101,8 +101,8 @@ pub async fn get_remote_content(
 /// - Only allows federation if `allow_remote` is true
 pub async fn get_content_route(
     db: DatabaseGuard,
-    body: Ruma<get_content::Request<'_>>,
-) -> Result<get_content::Response> {
+    body: Ruma<get_content::v3::Request<'_>>,
+) -> Result<get_content::v3::Response> {
     let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
 
     if let Some(FileMeta {
@@ -111,7 +111,7 @@ pub async fn get_content_route(
         file,
     }) = db.media.get(&db.globals, &mxc).await?
     {
-        Ok(get_content::Response {
+        Ok(get_content::v3::Response {
             file,
             content_type,
             content_disposition,
@@ -132,8 +132,8 @@ pub async fn get_content_route(
 /// - Only allows federation if `allow_remote` is true
 pub async fn get_content_as_filename_route(
     db: DatabaseGuard,
-    body: Ruma<get_content_as_filename::Request<'_>>,
-) -> Result<get_content_as_filename::Response> {
+    body: Ruma<get_content_as_filename::v3::Request<'_>>,
+) -> Result<get_content_as_filename::v3::Response> {
     let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
 
     if let Some(FileMeta {
@@ -142,7 +142,7 @@ pub async fn get_content_as_filename_route(
         file,
     }) = db.media.get(&db.globals, &mxc).await?
     {
-        Ok(get_content_as_filename::Response {
+        Ok(get_content_as_filename::v3::Response {
             file,
             content_type,
             content_disposition: Some(format!("inline; filename={}", body.filename)),
@@ -151,7 +151,7 @@ pub async fn get_content_as_filename_route(
         let remote_content_response =
             get_remote_content(&db, &mxc, &body.server_name, &body.media_id).await?;
 
-        Ok(get_content_as_filename::Response {
+        Ok(get_content_as_filename::v3::Response {
             content_disposition: Some(format!("inline: filename={}", body.filename)),
             content_type: remote_content_response.content_type,
             file: remote_content_response.file,
@@ -168,8 +168,8 @@ pub async fn get_content_as_filename_route(
 /// - Only allows federation if `allow_remote` is true
 pub async fn get_content_thumbnail_route(
     db: DatabaseGuard,
-    body: Ruma<get_content_thumbnail::Request<'_>>,
-) -> Result<get_content_thumbnail::Response> {
+    body: Ruma<get_content_thumbnail::v3::Request<'_>>,
+) -> Result<get_content_thumbnail::v3::Response> {
     let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
 
     if let Some(FileMeta {
@@ -188,14 +188,14 @@ pub async fn get_content_thumbnail_route(
         )
         .await?
     {
-        Ok(get_content_thumbnail::Response { file, content_type })
+        Ok(get_content_thumbnail::v3::Response { file, content_type })
     } else if &*body.server_name != db.globals.server_name() && body.allow_remote {
         let get_thumbnail_response = db
             .sending
             .send_federation_request(
                 &db.globals,
                 &body.server_name,
-                get_content_thumbnail::Request {
+                get_content_thumbnail::v3::Request {
                     allow_remote: false,
                     height: body.height,
                     width: body.width,
diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs
index 447f829e..0f5e7c2c 100644
--- a/src/client_server/membership.rs
+++ b/src/client_server/membership.rs
@@ -8,7 +8,7 @@ use ruma::{
     api::{
         client::{
             error::ErrorKind,
-            r0::membership::{
+            membership::{
                 ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
                 join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
                 unban_user, IncomingThirdPartySigned,
@@ -44,8 +44,8 @@ use tracing::{debug, error, warn};
 /// - If the server does not know about the room: asks other servers over federation
 pub async fn join_room_by_id_route(
     db: DatabaseGuard,
-    body: Ruma<join_room_by_id::Request<'_>>,
-) -> Result<join_room_by_id::Response> {
+    body: Ruma<join_room_by_id::v3::Request<'_>>,
+) -> Result<join_room_by_id::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut servers: HashSet<_> = db
@@ -84,8 +84,8 @@ pub async fn join_room_by_id_route(
 /// - If the server does not know about the room: asks other servers over federation
 pub async fn join_room_by_id_or_alias_route(
     db: DatabaseGuard,
-    body: Ruma<join_room_by_id_or_alias::Request<'_>>,
-) -> Result<join_room_by_id_or_alias::Response> {
+    body: Ruma<join_room_by_id_or_alias::v3::Request<'_>>,
+) -> Result<join_room_by_id_or_alias::v3::Response> {
     let sender_user = body.sender_user.as_deref().expect("user is authenticated");
     let body = body.body;
 
@@ -124,7 +124,7 @@ pub async fn join_room_by_id_or_alias_route(
 
     db.flush()?;
 
-    Ok(join_room_by_id_or_alias::Response {
+    Ok(join_room_by_id_or_alias::v3::Response {
         room_id: join_room_response.room_id,
     })
 }
@@ -136,15 +136,15 @@ pub async fn join_room_by_id_or_alias_route(
 /// - This should always work if the user is currently joined.
 pub async fn leave_room_route(
     db: DatabaseGuard,
-    body: Ruma<leave_room::Request<'_>>,
-) -> Result<leave_room::Response> {
+    body: Ruma<leave_room::v3::Request<'_>>,
+) -> Result<leave_room::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.rooms.leave_room(sender_user, &body.room_id, &db).await?;
 
     db.flush()?;
 
-    Ok(leave_room::Response::new())
+    Ok(leave_room::v3::Response::new())
 }
 
 /// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
@@ -152,14 +152,14 @@ pub async fn leave_room_route(
 /// Tries to send an invite event into the room.
 pub async fn invite_user_route(
     db: DatabaseGuard,
-    body: Ruma<invite_user::Request<'_>>,
-) -> Result<invite_user::Response> {
+    body: Ruma<invite_user::v3::Request<'_>>,
+) -> Result<invite_user::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
-    if let invite_user::IncomingInvitationRecipient::UserId { user_id } = &body.recipient {
+    if let invite_user::v3::IncomingInvitationRecipient::UserId { user_id } = &body.recipient {
         invite_helper(sender_user, user_id, &body.room_id, &db, false).await?;
         db.flush()?;
-        Ok(invite_user::Response {})
+        Ok(invite_user::v3::Response {})
     } else {
         Err(Error::BadRequest(ErrorKind::NotFound, "User not found."))
     }
@@ -170,8 +170,8 @@ pub async fn invite_user_route(
 /// Tries to send a kick event into the room.
 pub async fn kick_user_route(
     db: DatabaseGuard,
-    body: Ruma<kick_user::Request<'_>>,
-) -> Result<kick_user::Response> {
+    body: Ruma<kick_user::v3::Request<'_>>,
+) -> Result<kick_user::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut event: RoomMemberEventContent = serde_json::from_str(
@@ -221,7 +221,7 @@ pub async fn kick_user_route(
 
     db.flush()?;
 
-    Ok(kick_user::Response::new())
+    Ok(kick_user::v3::Response::new())
 }
 
 /// # `POST /_matrix/client/r0/rooms/{roomId}/ban`
@@ -229,8 +229,8 @@ pub async fn kick_user_route(
 /// Tries to send a ban event into the room.
 pub async fn ban_user_route(
     db: DatabaseGuard,
-    body: Ruma<ban_user::Request<'_>>,
-) -> Result<ban_user::Response> {
+    body: Ruma<ban_user::v3::Request<'_>>,
+) -> Result<ban_user::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     // TODO: reason
@@ -291,7 +291,7 @@ pub async fn ban_user_route(
 
     db.flush()?;
 
-    Ok(ban_user::Response::new())
+    Ok(ban_user::v3::Response::new())
 }
 
 /// # `POST /_matrix/client/r0/rooms/{roomId}/unban`
@@ -299,8 +299,8 @@ pub async fn ban_user_route(
 /// Tries to send an unban event into the room.
 pub async fn unban_user_route(
     db: DatabaseGuard,
-    body: Ruma<unban_user::Request<'_>>,
-) -> Result<unban_user::Response> {
+    body: Ruma<unban_user::v3::Request<'_>>,
+) -> Result<unban_user::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut event: RoomMemberEventContent = serde_json::from_str(
@@ -349,7 +349,7 @@ pub async fn unban_user_route(
 
     db.flush()?;
 
-    Ok(unban_user::Response::new())
+    Ok(unban_user::v3::Response::new())
 }
 
 /// # `POST /_matrix/client/r0/rooms/{roomId}/forget`
@@ -362,15 +362,15 @@ pub async fn unban_user_route(
 /// be called from every device
 pub async fn forget_room_route(
     db: DatabaseGuard,
-    body: Ruma<forget_room::Request<'_>>,
-) -> Result<forget_room::Response> {
+    body: Ruma<forget_room::v3::Request<'_>>,
+) -> Result<forget_room::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.rooms.forget(&body.room_id, sender_user)?;
 
     db.flush()?;
 
-    Ok(forget_room::Response::new())
+    Ok(forget_room::v3::Response::new())
 }
 
 /// # `POST /_matrix/client/r0/joined_rooms`
@@ -378,11 +378,11 @@ pub async fn forget_room_route(
 /// Lists all rooms the user has joined.
 pub async fn joined_rooms_route(
     db: DatabaseGuard,
-    body: Ruma<joined_rooms::Request>,
-) -> Result<joined_rooms::Response> {
+    body: Ruma<joined_rooms::v3::Request>,
+) -> Result<joined_rooms::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
-    Ok(joined_rooms::Response {
+    Ok(joined_rooms::v3::Response {
         joined_rooms: db
             .rooms
             .rooms_joined(sender_user)
@@ -398,8 +398,8 @@ pub async fn joined_rooms_route(
 /// - Only works if the user is currently joined
 pub async fn get_member_events_route(
     db: DatabaseGuard,
-    body: Ruma<get_member_events::Request<'_>>,
-) -> Result<get_member_events::Response> {
+    body: Ruma<get_member_events::v3::Request<'_>>,
+) -> Result<get_member_events::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     // TODO: check history visibility?
@@ -410,7 +410,7 @@ pub async fn get_member_events_route(
         ));
     }
 
-    Ok(get_member_events::Response {
+    Ok(get_member_events::v3::Response {
         chunk: db
             .rooms
             .room_state_full(&body.room_id)?
@@ -429,8 +429,8 @@ pub async fn get_member_events_route(
 /// - TODO: An appservice just needs a puppet joined
 pub async fn joined_members_route(
     db: DatabaseGuard,
-    body: Ruma<joined_members::Request<'_>>,
-) -> Result<joined_members::Response> {
+    body: Ruma<joined_members::v3::Request<'_>>,
+) -> Result<joined_members::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if !db.rooms.is_joined(sender_user, &body.room_id)? {
@@ -447,14 +447,14 @@ pub async fn joined_members_route(
 
         joined.insert(
             user_id,
-            joined_members::RoomMember {
+            joined_members::v3::RoomMember {
                 display_name,
                 avatar_url,
             },
         );
     }
 
-    Ok(joined_members::Response { joined })
+    Ok(joined_members::v3::Response { joined })
 }
 
 #[tracing::instrument(skip(db))]
@@ -464,7 +464,7 @@ async fn join_room_by_id_helper(
     room_id: &RoomId,
     servers: &HashSet<Box<ServerName>>,
     _third_party_signed: Option<&IncomingThirdPartySigned>,
-) -> Result<join_room_by_id::Response> {
+) -> Result<join_room_by_id::v3::Response> {
     let sender_user = sender_user.expect("user is authenticated");
 
     let mutex_state = Arc::clone(
@@ -489,7 +489,7 @@ async fn join_room_by_id_helper(
                 .send_federation_request(
                     &db.globals,
                     remote_server,
-                    federation::membership::create_join_event_template::v1::Request {
+                    federation::membership::prepare_join_event::v1::Request {
                         room_id,
                         user_id: sender_user,
                         ver: &[RoomVersionId::V5, RoomVersionId::V6],
@@ -720,7 +720,7 @@ async fn join_room_by_id_helper(
 
     db.flush()?;
 
-    Ok(join_room_by_id::Response::new(room_id.to_owned()))
+    Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
 }
 
 fn validate_and_add_event_id(
diff --git a/src/client_server/message.rs b/src/client_server/message.rs
index 93d5b3bb..b5c41490 100644
--- a/src/client_server/message.rs
+++ b/src/client_server/message.rs
@@ -2,7 +2,7 @@ use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, Error, Result, Ruma
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::message::{get_message_events, send_message_event},
+        message::{get_message_events, send_message_event},
     },
     events::EventType,
 };
@@ -20,8 +20,8 @@ use std::{
 /// - Tries to send the event into the room, auth rules will determine if it is allowed
 pub async fn send_message_event_route(
     db: DatabaseGuard,
-    body: Ruma<send_message_event::Request<'_>>,
-) -> Result<send_message_event::Response> {
+    body: Ruma<send_message_event::v3::Request<'_>>,
+) -> Result<send_message_event::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_deref();
 
@@ -61,7 +61,7 @@ pub async fn send_message_event_route(
             .map_err(|_| Error::bad_database("Invalid txnid bytes in database."))?
             .try_into()
             .map_err(|_| Error::bad_database("Invalid event id in txnid data."))?;
-        return Ok(send_message_event::Response { event_id });
+        return Ok(send_message_event::v3::Response { event_id });
     }
 
     let mut unsigned = BTreeMap::new();
@@ -93,7 +93,9 @@ pub async fn send_message_event_route(
 
     db.flush()?;
 
-    Ok(send_message_event::Response::new((*event_id).to_owned()))
+    Ok(send_message_event::v3::Response::new(
+        (*event_id).to_owned(),
+    ))
 }
 
 /// # `GET /_matrix/client/r0/rooms/{roomId}/messages`
@@ -104,8 +106,8 @@ pub async fn send_message_event_route(
 /// joined, depending on history_visibility)
 pub async fn get_message_events_route(
     db: DatabaseGuard,
-    body: Ruma<get_message_events::Request<'_>>,
-) -> Result<get_message_events::Response> {
+    body: Ruma<get_message_events::v3::Request<'_>>,
+) -> Result<get_message_events::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -132,12 +134,12 @@ pub async fn get_message_events_route(
 
     let next_token;
 
-    let mut resp = get_message_events::Response::new();
+    let mut resp = get_message_events::v3::Response::new();
 
     let mut lazy_loaded = HashSet::new();
 
     match body.dir {
-        get_message_events::Direction::Forward => {
+        get_message_events::v3::Direction::Forward => {
             let events_after: Vec<_> = db
                 .rooms
                 .pdus_after(sender_user, &body.room_id, from)?
@@ -174,7 +176,7 @@ pub async fn get_message_events_route(
             resp.end = next_token.map(|count| count.to_string());
             resp.chunk = events_after;
         }
-        get_message_events::Direction::Backward => {
+        get_message_events::v3::Direction::Backward => {
             let events_before: Vec<_> = db
                 .rooms
                 .pdus_until(sender_user, &body.room_id, from)?
diff --git a/src/client_server/presence.rs b/src/client_server/presence.rs
index 7549b1a7..9e6ce0b8 100644
--- a/src/client_server/presence.rs
+++ b/src/client_server/presence.rs
@@ -1,5 +1,5 @@
 use crate::{database::DatabaseGuard, utils, Result, Ruma};
-use ruma::api::client::r0::presence::{get_presence, set_presence};
+use ruma::api::client::presence::{get_presence, set_presence};
 use std::time::Duration;
 
 /// # `PUT /_matrix/client/r0/presence/{userId}/status`
@@ -7,8 +7,8 @@ use std::time::Duration;
 /// Sets the presence state of the sender user.
 pub async fn set_presence_route(
     db: DatabaseGuard,
-    body: Ruma<set_presence::Request<'_>>,
-) -> Result<set_presence::Response> {
+    body: Ruma<set_presence::v3::Request<'_>>,
+) -> Result<set_presence::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     for room_id in db.rooms.rooms_joined(sender_user) {
@@ -38,7 +38,7 @@ pub async fn set_presence_route(
 
     db.flush()?;
 
-    Ok(set_presence::Response {})
+    Ok(set_presence::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/presence/{userId}/status`
@@ -48,8 +48,8 @@ pub async fn set_presence_route(
 /// - Only works if you share a room with the user
 pub async fn get_presence_route(
     db: DatabaseGuard,
-    body: Ruma<get_presence::Request<'_>>,
-) -> Result<get_presence::Response> {
+    body: Ruma<get_presence::v3::Request<'_>>,
+) -> Result<get_presence::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut presence_event = None;
@@ -71,7 +71,7 @@ pub async fn get_presence_route(
     }
 
     if let Some(presence) = presence_event {
-        Ok(get_presence::Response {
+        Ok(get_presence::v3::Response {
             // TODO: Should ruma just use the presenceeventcontent type here?
             status_msg: presence.content.status_msg,
             currently_active: presence.content.currently_active,
diff --git a/src/client_server/profile.rs b/src/client_server/profile.rs
index 33bfbb5c..30000272 100644
--- a/src/client_server/profile.rs
+++ b/src/client_server/profile.rs
@@ -3,7 +3,7 @@ use ruma::{
     api::{
         client::{
             error::ErrorKind,
-            r0::profile::{
+            profile::{
                 get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
             },
         },
@@ -21,8 +21,8 @@ use std::sync::Arc;
 /// - Also makes sure other users receive the update using presence EDUs
 pub async fn set_displayname_route(
     db: DatabaseGuard,
-    body: Ruma<set_display_name::Request<'_>>,
-) -> Result<set_display_name::Response> {
+    body: Ruma<set_display_name::v3::Request<'_>>,
+) -> Result<set_display_name::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.users
@@ -108,7 +108,7 @@ pub async fn set_displayname_route(
 
     db.flush()?;
 
-    Ok(set_display_name::Response {})
+    Ok(set_display_name::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/profile/{userId}/displayname`
@@ -118,8 +118,8 @@ pub async fn set_displayname_route(
 /// - If user is on another server: Fetches displayname over federation
 pub async fn get_displayname_route(
     db: DatabaseGuard,
-    body: Ruma<get_display_name::Request<'_>>,
-) -> Result<get_display_name::Response> {
+    body: Ruma<get_display_name::v3::Request<'_>>,
+) -> Result<get_display_name::v3::Response> {
     if body.user_id.server_name() != db.globals.server_name() {
         let response = db
             .sending
@@ -133,12 +133,12 @@ pub async fn get_displayname_route(
             )
             .await?;
 
-        return Ok(get_display_name::Response {
+        return Ok(get_display_name::v3::Response {
             displayname: response.displayname,
         });
     }
 
-    Ok(get_display_name::Response {
+    Ok(get_display_name::v3::Response {
         displayname: db.users.displayname(&body.user_id)?,
     })
 }
@@ -150,8 +150,8 @@ pub async fn get_displayname_route(
 /// - Also makes sure other users receive the update using presence EDUs
 pub async fn set_avatar_url_route(
     db: DatabaseGuard,
-    body: Ruma<set_avatar_url::Request<'_>>,
-) -> Result<set_avatar_url::Response> {
+    body: Ruma<set_avatar_url::v3::Request<'_>>,
+) -> Result<set_avatar_url::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.users
@@ -239,7 +239,7 @@ pub async fn set_avatar_url_route(
 
     db.flush()?;
 
-    Ok(set_avatar_url::Response {})
+    Ok(set_avatar_url::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/profile/{userId}/avatar_url`
@@ -249,8 +249,8 @@ pub async fn set_avatar_url_route(
 /// - If user is on another server: Fetches avatar_url and blurhash over federation
 pub async fn get_avatar_url_route(
     db: DatabaseGuard,
-    body: Ruma<get_avatar_url::Request<'_>>,
-) -> Result<get_avatar_url::Response> {
+    body: Ruma<get_avatar_url::v3::Request<'_>>,
+) -> Result<get_avatar_url::v3::Response> {
     if body.user_id.server_name() != db.globals.server_name() {
         let response = db
             .sending
@@ -264,13 +264,13 @@ pub async fn get_avatar_url_route(
             )
             .await?;
 
-        return Ok(get_avatar_url::Response {
+        return Ok(get_avatar_url::v3::Response {
             avatar_url: response.avatar_url,
             blurhash: response.blurhash,
         });
     }
 
-    Ok(get_avatar_url::Response {
+    Ok(get_avatar_url::v3::Response {
         avatar_url: db.users.avatar_url(&body.user_id)?,
         blurhash: db.users.blurhash(&body.user_id)?,
     })
@@ -283,8 +283,8 @@ pub async fn get_avatar_url_route(
 /// - If user is on another server: Fetches profile over federation
 pub async fn get_profile_route(
     db: DatabaseGuard,
-    body: Ruma<get_profile::Request<'_>>,
-) -> Result<get_profile::Response> {
+    body: Ruma<get_profile::v3::Request<'_>>,
+) -> Result<get_profile::v3::Response> {
     if body.user_id.server_name() != db.globals.server_name() {
         let response = db
             .sending
@@ -298,7 +298,7 @@ pub async fn get_profile_route(
             )
             .await?;
 
-        return Ok(get_profile::Response {
+        return Ok(get_profile::v3::Response {
             displayname: response.displayname,
             avatar_url: response.avatar_url,
             blurhash: response.blurhash,
@@ -313,7 +313,7 @@ pub async fn get_profile_route(
         ));
     }
 
-    Ok(get_profile::Response {
+    Ok(get_profile::v3::Response {
         avatar_url: db.users.avatar_url(&body.user_id)?,
         blurhash: db.users.blurhash(&body.user_id)?,
         displayname: db.users.displayname(&body.user_id)?,
diff --git a/src/client_server/push.rs b/src/client_server/push.rs
index 67b70d28..90f4e028 100644
--- a/src/client_server/push.rs
+++ b/src/client_server/push.rs
@@ -2,7 +2,7 @@ use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::push::{
+        push::{
             delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled,
             get_pushrules_all, set_pusher, set_pushrule, set_pushrule_actions,
             set_pushrule_enabled, RuleKind,
@@ -17,8 +17,8 @@ use ruma::{
 /// Retrieves the push rules event for this user.
 pub async fn get_pushrules_all_route(
     db: DatabaseGuard,
-    body: Ruma<get_pushrules_all::Request>,
-) -> Result<get_pushrules_all::Response> {
+    body: Ruma<get_pushrules_all::v3::Request>,
+) -> Result<get_pushrules_all::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let event: PushRulesEvent = db
@@ -29,7 +29,7 @@ pub async fn get_pushrules_all_route(
             "PushRules event not found.",
         ))?;
 
-    Ok(get_pushrules_all::Response {
+    Ok(get_pushrules_all::v3::Response {
         global: event.content.global,
     })
 }
@@ -39,8 +39,8 @@ pub async fn get_pushrules_all_route(
 /// Retrieves a single specified push rule for this user.
 pub async fn get_pushrule_route(
     db: DatabaseGuard,
-    body: Ruma<get_pushrule::Request<'_>>,
-) -> Result<get_pushrule::Response> {
+    body: Ruma<get_pushrule::v3::Request<'_>>,
+) -> Result<get_pushrule::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let event: PushRulesEvent = db
@@ -77,7 +77,7 @@ pub async fn get_pushrule_route(
     };
 
     if let Some(rule) = rule {
-        Ok(get_pushrule::Response { rule })
+        Ok(get_pushrule::v3::Response { rule })
     } else {
         Err(Error::BadRequest(
             ErrorKind::NotFound,
@@ -91,8 +91,8 @@ pub async fn get_pushrule_route(
 /// Creates a single specified push rule for this user.
 pub async fn set_pushrule_route(
     db: DatabaseGuard,
-    body: Ruma<set_pushrule::Request<'_>>,
-) -> Result<set_pushrule::Response> {
+    body: Ruma<set_pushrule::v3::Request<'_>>,
+) -> Result<set_pushrule::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let body = body.body;
 
@@ -179,7 +179,7 @@ pub async fn set_pushrule_route(
 
     db.flush()?;
 
-    Ok(set_pushrule::Response {})
+    Ok(set_pushrule::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
@@ -187,8 +187,8 @@ pub async fn set_pushrule_route(
 /// Gets the actions of a single specified push rule for this user.
 pub async fn get_pushrule_actions_route(
     db: DatabaseGuard,
-    body: Ruma<get_pushrule_actions::Request<'_>>,
-) -> Result<get_pushrule_actions::Response> {
+    body: Ruma<get_pushrule_actions::v3::Request<'_>>,
+) -> Result<get_pushrule_actions::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if body.scope != "global" {
@@ -233,7 +233,7 @@ pub async fn get_pushrule_actions_route(
 
     db.flush()?;
 
-    Ok(get_pushrule_actions::Response {
+    Ok(get_pushrule_actions::v3::Response {
         actions: actions.unwrap_or_default(),
     })
 }
@@ -243,8 +243,8 @@ pub async fn get_pushrule_actions_route(
 /// Sets the actions of a single specified push rule for this user.
 pub async fn set_pushrule_actions_route(
     db: DatabaseGuard,
-    body: Ruma<set_pushrule_actions::Request<'_>>,
-) -> Result<set_pushrule_actions::Response> {
+    body: Ruma<set_pushrule_actions::v3::Request<'_>>,
+) -> Result<set_pushrule_actions::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if body.scope != "global" {
@@ -302,7 +302,7 @@ pub async fn set_pushrule_actions_route(
 
     db.flush()?;
 
-    Ok(set_pushrule_actions::Response {})
+    Ok(set_pushrule_actions::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled`
@@ -310,8 +310,8 @@ pub async fn set_pushrule_actions_route(
 /// Gets the enabled status of a single specified push rule for this user.
 pub async fn get_pushrule_enabled_route(
     db: DatabaseGuard,
-    body: Ruma<get_pushrule_enabled::Request<'_>>,
-) -> Result<get_pushrule_enabled::Response> {
+    body: Ruma<get_pushrule_enabled::v3::Request<'_>>,
+) -> Result<get_pushrule_enabled::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if body.scope != "global" {
@@ -361,7 +361,7 @@ pub async fn get_pushrule_enabled_route(
 
     db.flush()?;
 
-    Ok(get_pushrule_enabled::Response { enabled })
+    Ok(get_pushrule_enabled::v3::Response { enabled })
 }
 
 /// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled`
@@ -369,8 +369,8 @@ pub async fn get_pushrule_enabled_route(
 /// Sets the enabled status of a single specified push rule for this user.
 pub async fn set_pushrule_enabled_route(
     db: DatabaseGuard,
-    body: Ruma<set_pushrule_enabled::Request<'_>>,
-) -> Result<set_pushrule_enabled::Response> {
+    body: Ruma<set_pushrule_enabled::v3::Request<'_>>,
+) -> Result<set_pushrule_enabled::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if body.scope != "global" {
@@ -433,7 +433,7 @@ pub async fn set_pushrule_enabled_route(
 
     db.flush()?;
 
-    Ok(set_pushrule_enabled::Response {})
+    Ok(set_pushrule_enabled::v3::Response {})
 }
 
 /// # `DELETE /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
@@ -441,8 +441,8 @@ pub async fn set_pushrule_enabled_route(
 /// Deletes a single specified push rule for this user.
 pub async fn delete_pushrule_route(
     db: DatabaseGuard,
-    body: Ruma<delete_pushrule::Request<'_>>,
-) -> Result<delete_pushrule::Response> {
+    body: Ruma<delete_pushrule::v3::Request<'_>>,
+) -> Result<delete_pushrule::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if body.scope != "global" {
@@ -495,7 +495,7 @@ pub async fn delete_pushrule_route(
 
     db.flush()?;
 
-    Ok(delete_pushrule::Response {})
+    Ok(delete_pushrule::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/pushers`
@@ -503,11 +503,11 @@ pub async fn delete_pushrule_route(
 /// Gets all currently active pushers for the sender user.
 pub async fn get_pushers_route(
     db: DatabaseGuard,
-    body: Ruma<get_pushers::Request>,
-) -> Result<get_pushers::Response> {
+    body: Ruma<get_pushers::v3::Request>,
+) -> Result<get_pushers::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
-    Ok(get_pushers::Response {
+    Ok(get_pushers::v3::Response {
         pushers: db.pusher.get_pushers(sender_user)?,
     })
 }
@@ -519,8 +519,8 @@ pub async fn get_pushers_route(
 /// - TODO: Handle `append`
 pub async fn set_pushers_route(
     db: DatabaseGuard,
-    body: Ruma<set_pusher::Request>,
-) -> Result<set_pusher::Response> {
+    body: Ruma<set_pusher::v3::Request>,
+) -> Result<set_pusher::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let pusher = body.pusher.clone();
 
@@ -528,5 +528,5 @@ pub async fn set_pushers_route(
 
     db.flush()?;
 
-    Ok(set_pusher::Response::default())
+    Ok(set_pusher::v3::Response::default())
 }
diff --git a/src/client_server/read_marker.rs b/src/client_server/read_marker.rs
index cc6928d1..9422f218 100644
--- a/src/client_server/read_marker.rs
+++ b/src/client_server/read_marker.rs
@@ -1,9 +1,6 @@
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::{
-    api::client::{
-        error::ErrorKind,
-        r0::{read_marker::set_read_marker, receipt::create_receipt},
-    },
+    api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
     events::EventType,
     receipt::ReceiptType,
     MilliSecondsSinceUnixEpoch,
@@ -18,8 +15,8 @@ use std::collections::BTreeMap;
 /// - If `read_receipt` is set: Update private marker and public read receipt EDU
 pub async fn set_read_marker_route(
     db: DatabaseGuard,
-    body: Ruma<set_read_marker::Request<'_>>,
-) -> Result<set_read_marker::Response> {
+    body: Ruma<set_read_marker::v3::Request<'_>>,
+) -> Result<set_read_marker::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let fully_read_event = ruma::events::fully_read::FullyReadEvent {
@@ -75,7 +72,7 @@ pub async fn set_read_marker_route(
 
     db.flush()?;
 
-    Ok(set_read_marker::Response {})
+    Ok(set_read_marker::v3::Response {})
 }
 
 /// # `POST /_matrix/client/r0/rooms/{roomId}/receipt/{receiptType}/{eventId}`
@@ -83,8 +80,8 @@ pub async fn set_read_marker_route(
 /// Sets private read marker and public read receipt EDU.
 pub async fn create_receipt_route(
     db: DatabaseGuard,
-    body: Ruma<create_receipt::Request<'_>>,
-) -> Result<create_receipt::Response> {
+    body: Ruma<create_receipt::v3::Request<'_>>,
+) -> Result<create_receipt::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     db.rooms.edus.private_read_set(
@@ -126,5 +123,5 @@ pub async fn create_receipt_route(
 
     db.flush()?;
 
-    Ok(create_receipt::Response {})
+    Ok(create_receipt::v3::Response {})
 }
diff --git a/src/client_server/redact.rs b/src/client_server/redact.rs
index 1e05bfe2..4843993a 100644
--- a/src/client_server/redact.rs
+++ b/src/client_server/redact.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
 
 use crate::{database::DatabaseGuard, pdu::PduBuilder, Result, Ruma};
 use ruma::{
-    api::client::r0::redact::redact_event,
+    api::client::redact::redact_event,
     events::{room::redaction::RoomRedactionEventContent, EventType},
 };
 
@@ -15,8 +15,8 @@ use serde_json::value::to_raw_value;
 /// - TODO: Handle txn id
 pub async fn redact_event_route(
     db: DatabaseGuard,
-    body: Ruma<redact_event::Request<'_>>,
-) -> Result<redact_event::Response> {
+    body: Ruma<redact_event::v3::Request<'_>>,
+) -> Result<redact_event::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let body = body.body;
 
@@ -52,5 +52,5 @@ pub async fn redact_event_route(
     db.flush()?;
 
     let event_id = (*event_id).to_owned();
-    Ok(redact_event::Response { event_id })
+    Ok(redact_event::v3::Response { event_id })
 }
diff --git a/src/client_server/report.rs b/src/client_server/report.rs
index 6274172c..1e47792e 100644
--- a/src/client_server/report.rs
+++ b/src/client_server/report.rs
@@ -1,6 +1,6 @@
 use crate::{database::DatabaseGuard, utils::HtmlEscape, Error, Result, Ruma};
 use ruma::{
-    api::client::{error::ErrorKind, r0::room::report_content},
+    api::client::{error::ErrorKind, room::report_content},
     events::room::message,
     int,
 };
@@ -11,8 +11,8 @@ use ruma::{
 ///
 pub async fn report_event_route(
     db: DatabaseGuard,
-    body: Ruma<report_content::Request<'_>>,
-) -> Result<report_content::Response> {
+    body: Ruma<report_content::v3::Request<'_>>,
+) -> Result<report_content::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let pdu = match db.rooms.get_pdu(&body.event_id)? {
@@ -68,5 +68,5 @@ pub async fn report_event_route(
 
     db.flush()?;
 
-    Ok(report_content::Response {})
+    Ok(report_content::v3::Response {})
 }
diff --git a/src/client_server/room.rs b/src/client_server/room.rs
index 54559e26..99838ceb 100644
--- a/src/client_server/room.rs
+++ b/src/client_server/room.rs
@@ -4,7 +4,7 @@ use crate::{
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::room::{self, aliases, create_room, get_room_event, upgrade_room},
+        room::{self, aliases, create_room, get_room_event, upgrade_room},
     },
     events::{
         room::{
@@ -47,8 +47,10 @@ use tracing::{info, warn};
 /// - Send invite events
 pub async fn create_room_route(
     db: DatabaseGuard,
-    body: Ruma<create_room::Request<'_>>,
-) -> Result<create_room::Response> {
+    body: Ruma<create_room::v3::Request<'_>>,
+) -> Result<create_room::v3::Response> {
+    use create_room::v3::RoomPreset;
+
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let room_id = RoomId::new(db.globals.server_name());
@@ -207,15 +209,15 @@ pub async fn create_room_route(
         .preset
         .clone()
         .unwrap_or_else(|| match &body.visibility {
-            room::Visibility::Private => create_room::RoomPreset::PrivateChat,
-            room::Visibility::Public => create_room::RoomPreset::PublicChat,
-            _ => create_room::RoomPreset::PrivateChat, // Room visibility should not be custom
+            room::Visibility::Private => RoomPreset::PrivateChat,
+            room::Visibility::Public => RoomPreset::PublicChat,
+            _ => RoomPreset::PrivateChat, // Room visibility should not be custom
         });
 
     let mut users = BTreeMap::new();
     users.insert(sender_user.clone(), int!(100));
 
-    if preset == create_room::RoomPreset::TrustedPrivateChat {
+    if preset == RoomPreset::TrustedPrivateChat {
         for invite_ in &body.invite {
             users.insert(invite_.clone(), int!(100));
         }
@@ -281,7 +283,7 @@ pub async fn create_room_route(
         PduBuilder {
             event_type: EventType::RoomJoinRules,
             content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
-                create_room::RoomPreset::PublicChat => JoinRule::Public,
+                RoomPreset::PublicChat => JoinRule::Public,
                 // according to spec "invite" is the default
                 _ => JoinRule::Invite,
             }))
@@ -319,7 +321,7 @@ pub async fn create_room_route(
         PduBuilder {
             event_type: EventType::RoomGuestAccess,
             content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
-                create_room::RoomPreset::PublicChat => GuestAccess::Forbidden,
+                RoomPreset::PublicChat => GuestAccess::Forbidden,
                 _ => GuestAccess::CanJoin,
             }))
             .expect("event is valid, we just created it"),
@@ -408,7 +410,7 @@ pub async fn create_room_route(
 
     db.flush()?;
 
-    Ok(create_room::Response::new(room_id))
+    Ok(create_room::v3::Response::new(room_id))
 }
 
 /// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}`
@@ -418,8 +420,8 @@ pub async fn create_room_route(
 /// - You have to currently be joined to the room (TODO: Respect history visibility)
 pub async fn get_room_event_route(
     db: DatabaseGuard,
-    body: Ruma<get_room_event::Request<'_>>,
-) -> Result<get_room_event::Response> {
+    body: Ruma<get_room_event::v3::Request<'_>>,
+) -> Result<get_room_event::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if !db.rooms.is_joined(sender_user, &body.room_id)? {
@@ -429,7 +431,7 @@ pub async fn get_room_event_route(
         ));
     }
 
-    Ok(get_room_event::Response {
+    Ok(get_room_event::v3::Response {
         event: db
             .rooms
             .get_pdu(&body.event_id)?
@@ -445,8 +447,8 @@ pub async fn get_room_event_route(
 /// - Only users joined to the room are allowed to call this TODO: Allow any user to call it if history_visibility is world readable
 pub async fn get_room_aliases_route(
     db: DatabaseGuard,
-    body: Ruma<aliases::Request<'_>>,
-) -> Result<aliases::Response> {
+    body: Ruma<aliases::v3::Request<'_>>,
+) -> Result<aliases::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if !db.rooms.is_joined(sender_user, &body.room_id)? {
@@ -456,7 +458,7 @@ pub async fn get_room_aliases_route(
         ));
     }
 
-    Ok(aliases::Response {
+    Ok(aliases::v3::Response {
         aliases: db
             .rooms
             .room_aliases(&body.room_id)
@@ -477,8 +479,8 @@ pub async fn get_room_aliases_route(
 /// - Modifies old room power levels to prevent users from speaking
 pub async fn upgrade_room_route(
     db: DatabaseGuard,
-    body: Ruma<upgrade_room::Request<'_>>,
-) -> Result<upgrade_room::Response> {
+    body: Ruma<upgrade_room::v3::Request<'_>>,
+) -> Result<upgrade_room::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if !matches!(body.new_version, RoomVersionId::V5 | RoomVersionId::V6) {
@@ -702,5 +704,5 @@ pub async fn upgrade_room_route(
     db.flush()?;
 
     // Return the replacement room id
-    Ok(upgrade_room::Response { replacement_room })
+    Ok(upgrade_room::v3::Response { replacement_room })
 }
diff --git a/src/client_server/search.rs b/src/client_server/search.rs
index 5860484e..c83ff2c0 100644
--- a/src/client_server/search.rs
+++ b/src/client_server/search.rs
@@ -1,7 +1,12 @@
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
-use ruma::api::client::{error::ErrorKind, r0::search::search_events};
+use ruma::api::client::{
+    error::ErrorKind,
+    search::search_events::v3::{
+        self as search_events_v3, EventContextResult, ResultCategories, ResultRoomEvents,
+        SearchResult,
+    },
+};
 
-use search_events::{EventContextResult, ResultCategories, ResultRoomEvents, SearchResult};
 use std::collections::BTreeMap;
 
 /// # `POST /_matrix/client/r0/search`
@@ -11,8 +16,8 @@ use std::collections::BTreeMap;
 /// - Only works if the user is currently joined to the room (TODO: Respect history visibility)
 pub async fn search_events_route(
     db: DatabaseGuard,
-    body: Ruma<search_events::Request<'_>>,
-) -> Result<search_events::Response> {
+    body: Ruma<search_events_v3::Request<'_>>,
+) -> Result<search_events_v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let search_criteria = body.search_categories.room_events.as_ref().unwrap();
@@ -97,7 +102,7 @@ pub async fn search_events_route(
         Some((skip + limit).to_string())
     };
 
-    Ok(search_events::Response::new(ResultCategories {
+    Ok(search_events_v3::Response::new(ResultCategories {
         room_events: ResultRoomEvents {
             count: Some((results.len() as u32).into()), // TODO: set this to none. Element shouldn't depend on it
             groups: BTreeMap::new(),                    // TODO
diff --git a/src/client_server/session.rs b/src/client_server/session.rs
index c2259c26..2e1ed544 100644
--- a/src/client_server/session.rs
+++ b/src/client_server/session.rs
@@ -3,10 +3,8 @@ use crate::{database::DatabaseGuard, utils, Error, Result, Ruma};
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::{
-            session::{get_login_types, login, logout, logout_all},
-            uiaa::IncomingUserIdentifier,
-        },
+        session::{get_login_types, login, logout, logout_all},
+        uiaa::IncomingUserIdentifier,
     },
     UserId,
 };
@@ -24,10 +22,10 @@ struct Claims {
 /// Get the supported login types of this server. One of these should be used as the `type` field
 /// when logging in.
 pub async fn get_login_types_route(
-    _body: Ruma<get_login_types::Request>,
-) -> Result<get_login_types::Response> {
-    Ok(get_login_types::Response::new(vec![
-        get_login_types::LoginType::Password(Default::default()),
+    _body: Ruma<get_login_types::v3::Request>,
+) -> Result<get_login_types::v3::Response> {
+    Ok(get_login_types::v3::Response::new(vec![
+        get_login_types::v3::LoginType::Password(Default::default()),
     ]))
 }
 
@@ -44,12 +42,12 @@ pub async fn get_login_types_route(
 /// supported login types.
 pub async fn login_route(
     db: DatabaseGuard,
-    body: Ruma<login::Request<'_>>,
-) -> Result<login::Response> {
+    body: Ruma<login::v3::Request<'_>>,
+) -> Result<login::v3::Response> {
     // Validate login method
     // TODO: Other login methods
     let user_id = match &body.login_info {
-        login::IncomingLoginInfo::Password(login::IncomingPassword {
+        login::v3::IncomingLoginInfo::Password(login::v3::IncomingPassword {
             identifier,
             password,
         }) => {
@@ -86,7 +84,7 @@ pub async fn login_route(
 
             user_id
         }
-        login::IncomingLoginInfo::Token(login::IncomingToken { token }) => {
+        login::v3::IncomingLoginInfo::Token(login::v3::IncomingToken { token }) => {
             if let Some(jwt_decoding_key) = db.globals.jwt_decoding_key() {
                 let token = jsonwebtoken::decode::<Claims>(
                     token,
@@ -144,7 +142,7 @@ pub async fn login_route(
 
     db.flush()?;
 
-    Ok(login::Response {
+    Ok(login::v3::Response {
         user_id,
         access_token: token,
         home_server: Some(db.globals.server_name().to_owned()),
@@ -163,8 +161,8 @@ pub async fn login_route(
 /// - Triggers device list updates
 pub async fn logout_route(
     db: DatabaseGuard,
-    body: Ruma<logout::Request>,
-) -> Result<logout::Response> {
+    body: Ruma<logout::v3::Request>,
+) -> Result<logout::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -172,7 +170,7 @@ pub async fn logout_route(
 
     db.flush()?;
 
-    Ok(logout::Response::new())
+    Ok(logout::v3::Response::new())
 }
 
 /// # `POST /_matrix/client/r0/logout/all`
@@ -188,8 +186,8 @@ pub async fn logout_route(
 /// from each device of this user.
 pub async fn logout_all_route(
     db: DatabaseGuard,
-    body: Ruma<logout_all::Request>,
-) -> Result<logout_all::Response> {
+    body: Ruma<logout_all::v3::Request>,
+) -> Result<logout_all::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     for device_id in db.users.all_device_ids(sender_user).flatten() {
@@ -198,5 +196,5 @@ pub async fn logout_all_route(
 
     db.flush()?;
 
-    Ok(logout_all::Response::new())
+    Ok(logout_all::v3::Response::new())
 }
diff --git a/src/client_server/state.rs b/src/client_server/state.rs
index e334e7de..a97b1872 100644
--- a/src/client_server/state.rs
+++ b/src/client_server/state.rs
@@ -6,7 +6,7 @@ use crate::{
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::state::{get_state_events, get_state_events_for_key, send_state_event},
+        state::{get_state_events, get_state_events_for_key, send_state_event},
     },
     events::{
         room::{
@@ -28,8 +28,8 @@ use ruma::{
 /// - If event is new canonical_alias: Rejects if alias is incorrect
 pub async fn send_state_event_for_key_route(
     db: DatabaseGuard,
-    body: Ruma<send_state_event::Request<'_>>,
-) -> Result<send_state_event::Response> {
+    body: Ruma<send_state_event::v3::Request<'_>>,
+) -> Result<send_state_event::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let event_id = send_state_event_for_key_helper(
@@ -45,7 +45,7 @@ pub async fn send_state_event_for_key_route(
     db.flush()?;
 
     let event_id = (*event_id).to_owned();
-    Ok(send_state_event::Response { event_id })
+    Ok(send_state_event::v3::Response { event_id })
 }
 
 /// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}`
@@ -57,8 +57,8 @@ pub async fn send_state_event_for_key_route(
 /// - If event is new canonical_alias: Rejects if alias is incorrect
 pub async fn send_state_event_for_empty_key_route(
     db: DatabaseGuard,
-    body: Ruma<send_state_event::Request<'_>>,
-) -> Result<RumaResponse<send_state_event::Response>> {
+    body: Ruma<send_state_event::v3::Request<'_>>,
+) -> Result<RumaResponse<send_state_event::v3::Response>> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     // Forbid m.room.encryption if encryption is disabled
@@ -82,7 +82,7 @@ pub async fn send_state_event_for_empty_key_route(
     db.flush()?;
 
     let event_id = (*event_id).to_owned();
-    Ok(send_state_event::Response { event_id }.into())
+    Ok(send_state_event::v3::Response { event_id }.into())
 }
 
 /// # `GET /_matrix/client/r0/rooms/{roomid}/state`
@@ -92,8 +92,8 @@ pub async fn send_state_event_for_empty_key_route(
 /// - If not joined: Only works if current room history visibility is world readable
 pub async fn get_state_events_route(
     db: DatabaseGuard,
-    body: Ruma<get_state_events::Request<'_>>,
-) -> Result<get_state_events::Response> {
+    body: Ruma<get_state_events::v3::Request<'_>>,
+) -> Result<get_state_events::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     #[allow(clippy::blocks_in_if_conditions)]
@@ -121,7 +121,7 @@ pub async fn get_state_events_route(
         ));
     }
 
-    Ok(get_state_events::Response {
+    Ok(get_state_events::v3::Response {
         room_state: db
             .rooms
             .room_state_full(&body.room_id)?
@@ -138,8 +138,8 @@ pub async fn get_state_events_route(
 /// - If not joined: Only works if current room history visibility is world readable
 pub async fn get_state_events_for_key_route(
     db: DatabaseGuard,
-    body: Ruma<get_state_events_for_key::Request<'_>>,
-) -> Result<get_state_events_for_key::Response> {
+    body: Ruma<get_state_events_for_key::v3::Request<'_>>,
+) -> Result<get_state_events_for_key::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     #[allow(clippy::blocks_in_if_conditions)]
@@ -175,7 +175,7 @@ pub async fn get_state_events_for_key_route(
             "State event not found.",
         ))?;
 
-    Ok(get_state_events_for_key::Response {
+    Ok(get_state_events_for_key::v3::Response {
         content: serde_json::from_str(event.content.get())
             .map_err(|_| Error::bad_database("Invalid event content in database"))?,
     })
@@ -188,8 +188,8 @@ pub async fn get_state_events_for_key_route(
 /// - If not joined: Only works if current room history visibility is world readable
 pub async fn get_state_events_for_empty_key_route(
     db: DatabaseGuard,
-    body: Ruma<get_state_events_for_key::Request<'_>>,
-) -> Result<RumaResponse<get_state_events_for_key::Response>> {
+    body: Ruma<get_state_events_for_key::v3::Request<'_>>,
+) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     #[allow(clippy::blocks_in_if_conditions)]
@@ -225,7 +225,7 @@ pub async fn get_state_events_for_empty_key_route(
             "State event not found.",
         ))?;
 
-    Ok(get_state_events_for_key::Response {
+    Ok(get_state_events_for_key::v3::Response {
         content: serde_json::from_str(event.content.get())
             .map_err(|_| Error::bad_database("Invalid event content in database"))?,
     }
diff --git a/src/client_server/sync.rs b/src/client_server/sync.rs
index eef65da4..eec4cf6d 100644
--- a/src/client_server/sync.rs
+++ b/src/client_server/sync.rs
@@ -1,6 +1,6 @@
 use crate::{database::DatabaseGuard, Database, Error, Result, Ruma, RumaResponse};
 use ruma::{
-    api::client::r0::{
+    api::client::{
         filter::{IncomingFilterDefinition, LazyLoadOptions},
         sync::sync_events,
         uiaa::UiaaResponse,
@@ -56,8 +56,8 @@ use tracing::error;
 /// `since` will be cached
 pub async fn sync_events_route(
     db: DatabaseGuard,
-    body: Ruma<sync_events::Request<'_>>,
-) -> Result<sync_events::Response, RumaResponse<UiaaResponse>> {
+    body: Ruma<sync_events::v3::Request<'_>>,
+) -> Result<sync_events::v3::Response, RumaResponse<UiaaResponse>> {
     let sender_user = body.sender_user.expect("user is authenticated");
     let sender_device = body.sender_device.expect("user is authenticated");
     let body = body.body;
@@ -130,8 +130,8 @@ async fn sync_helper_wrapper(
     db: Arc<DatabaseGuard>,
     sender_user: Box<UserId>,
     sender_device: Box<DeviceId>,
-    body: sync_events::IncomingRequest,
-    tx: Sender<Option<Result<sync_events::Response>>>,
+    body: sync_events::v3::IncomingRequest,
+    tx: Sender<Option<Result<sync_events::v3::Response>>>,
 ) {
     let since = body.since.clone();
 
@@ -172,9 +172,15 @@ async fn sync_helper(
     db: Arc<DatabaseGuard>,
     sender_user: Box<UserId>,
     sender_device: Box<DeviceId>,
-    body: sync_events::IncomingRequest,
+    body: sync_events::v3::IncomingRequest,
     // bool = caching allowed
-) -> Result<(sync_events::Response, bool), Error> {
+) -> Result<(sync_events::v3::Response, bool), Error> {
+    use sync_events::v3::{
+        DeviceLists, Ephemeral, GlobalAccountData, IncomingFilter, InviteState, InvitedRoom,
+        JoinedRoom, LeftRoom, Presence, RoomAccountData, RoomSummary, Rooms, State, Timeline,
+        ToDevice, UnreadNotificationsCount,
+    };
+
     // TODO: match body.set_presence {
     db.rooms.edus.ping_presence(&sender_user)?;
 
@@ -187,8 +193,8 @@ async fn sync_helper(
     // Load filter
     let filter = match body.filter {
         None => IncomingFilterDefinition::default(),
-        Some(sync_events::IncomingFilter::FilterDefinition(filter)) => filter,
-        Some(sync_events::IncomingFilter::FilterId(filter_id)) => db
+        Some(IncomingFilter::FilterDefinition(filter)) => filter,
+        Some(IncomingFilter::FilterId(filter_id)) => db
             .users
             .get_filter(&sender_user, &filter_id)?
             .unwrap_or_default(),
@@ -666,8 +672,8 @@ async fn sync_helper(
         db.rooms
             .associate_token_shortstatehash(&room_id, next_batch, current_shortstatehash)?;
 
-        let joined_room = sync_events::JoinedRoom {
-            account_data: sync_events::RoomAccountData {
+        let joined_room = JoinedRoom {
+            account_data: RoomAccountData {
                 events: db
                     .account_data
                     .changes_since(Some(&room_id), &sender_user, since)?
@@ -679,27 +685,27 @@ async fn sync_helper(
                     })
                     .collect(),
             },
-            summary: sync_events::RoomSummary {
+            summary: RoomSummary {
                 heroes,
                 joined_member_count: joined_member_count.map(|n| (n as u32).into()),
                 invited_member_count: invited_member_count.map(|n| (n as u32).into()),
             },
-            unread_notifications: sync_events::UnreadNotificationsCount {
+            unread_notifications: UnreadNotificationsCount {
                 highlight_count,
                 notification_count,
             },
-            timeline: sync_events::Timeline {
+            timeline: Timeline {
                 limited: limited || joined_since_last_sync,
                 prev_batch,
                 events: room_events,
             },
-            state: sync_events::State {
+            state: State {
                 events: state_events
                     .iter()
                     .map(|pdu| pdu.to_sync_state_event())
                     .collect(),
             },
-            ephemeral: sync_events::Ephemeral { events: edus },
+            ephemeral: Ephemeral { events: edus },
         };
 
         if !joined_room.is_empty() {
@@ -767,14 +773,14 @@ async fn sync_helper(
 
         left_rooms.insert(
             room_id.clone(),
-            sync_events::LeftRoom {
-                account_data: sync_events::RoomAccountData { events: Vec::new() },
-                timeline: sync_events::Timeline {
+            LeftRoom {
+                account_data: RoomAccountData { events: Vec::new() },
+                timeline: Timeline {
                     limited: false,
                     prev_batch: Some(next_batch_string.clone()),
                     events: Vec::new(),
                 },
-                state: sync_events::State {
+                state: State {
                     events: left_state_events,
                 },
             },
@@ -807,8 +813,8 @@ async fn sync_helper(
 
         invited_rooms.insert(
             room_id.clone(),
-            sync_events::InvitedRoom {
-                invite_state: sync_events::InviteState {
+            InvitedRoom {
+                invite_state: InviteState {
                     events: invite_state_events,
                 },
             },
@@ -840,21 +846,21 @@ async fn sync_helper(
     db.users
         .remove_to_device_events(&sender_user, &sender_device, since)?;
 
-    let response = sync_events::Response {
+    let response = sync_events::v3::Response {
         next_batch: next_batch_string,
-        rooms: sync_events::Rooms {
+        rooms: Rooms {
             leave: left_rooms,
             join: joined_rooms,
             invite: invited_rooms,
             knock: BTreeMap::new(), // TODO
         },
-        presence: sync_events::Presence {
+        presence: Presence {
             events: presence_updates
                 .into_iter()
                 .map(|(_, v)| Raw::new(&v).expect("PresenceEvent always serializes successfully"))
                 .collect(),
         },
-        account_data: sync_events::GlobalAccountData {
+        account_data: GlobalAccountData {
             events: db
                 .account_data
                 .changes_since(None, &sender_user, since)?
@@ -866,12 +872,12 @@ async fn sync_helper(
                 })
                 .collect(),
         },
-        device_lists: sync_events::DeviceLists {
+        device_lists: DeviceLists {
             changed: device_list_updates.into_iter().collect(),
             left: device_list_left.into_iter().collect(),
         },
         device_one_time_keys_count: db.users.count_one_time_keys(&sender_user, &sender_device)?,
-        to_device: sync_events::ToDevice {
+        to_device: ToDevice {
             events: db
                 .users
                 .get_to_device_events(&sender_user, &sender_device)?,
diff --git a/src/client_server/tag.rs b/src/client_server/tag.rs
index 29bd9a0b..21cff0bb 100644
--- a/src/client_server/tag.rs
+++ b/src/client_server/tag.rs
@@ -1,6 +1,6 @@
 use crate::{database::DatabaseGuard, Result, Ruma};
 use ruma::{
-    api::client::r0::tag::{create_tag, delete_tag, get_tags},
+    api::client::tag::{create_tag, delete_tag, get_tags},
     events::{
         tag::{TagEvent, TagEventContent},
         EventType,
@@ -15,8 +15,8 @@ use std::collections::BTreeMap;
 /// - Inserts the tag into the tag event of the room account data.
 pub async fn update_tag_route(
     db: DatabaseGuard,
-    body: Ruma<create_tag::Request<'_>>,
-) -> Result<create_tag::Response> {
+    body: Ruma<create_tag::v3::Request<'_>>,
+) -> Result<create_tag::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut tags_event = db
@@ -42,7 +42,7 @@ pub async fn update_tag_route(
 
     db.flush()?;
 
-    Ok(create_tag::Response {})
+    Ok(create_tag::v3::Response {})
 }
 
 /// # `DELETE /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}`
@@ -52,8 +52,8 @@ pub async fn update_tag_route(
 /// - Removes the tag from the tag event of the room account data.
 pub async fn delete_tag_route(
     db: DatabaseGuard,
-    body: Ruma<delete_tag::Request<'_>>,
-) -> Result<delete_tag::Response> {
+    body: Ruma<delete_tag::v3::Request<'_>>,
+) -> Result<delete_tag::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let mut tags_event = db
@@ -76,7 +76,7 @@ pub async fn delete_tag_route(
 
     db.flush()?;
 
-    Ok(delete_tag::Response {})
+    Ok(delete_tag::v3::Response {})
 }
 
 /// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags`
@@ -86,11 +86,11 @@ pub async fn delete_tag_route(
 /// - Gets the tag event of the room account data.
 pub async fn get_tags_route(
     db: DatabaseGuard,
-    body: Ruma<get_tags::Request<'_>>,
-) -> Result<get_tags::Response> {
+    body: Ruma<get_tags::v3::Request<'_>>,
+) -> Result<get_tags::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
-    Ok(get_tags::Response {
+    Ok(get_tags::v3::Response {
         tags: db
             .account_data
             .get(Some(&body.room_id), sender_user, EventType::Tag)?
diff --git a/src/client_server/thirdparty.rs b/src/client_server/thirdparty.rs
index 524f3bad..c2c1adfd 100644
--- a/src/client_server/thirdparty.rs
+++ b/src/client_server/thirdparty.rs
@@ -1,5 +1,5 @@
 use crate::{Result, Ruma};
-use ruma::api::client::r0::thirdparty::get_protocols;
+use ruma::api::client::thirdparty::get_protocols;
 
 use std::collections::BTreeMap;
 
@@ -7,10 +7,10 @@ use std::collections::BTreeMap;
 ///
 /// TODO: Fetches all metadata about protocols supported by the homeserver.
 pub async fn get_protocols_route(
-    _body: Ruma<get_protocols::Request>,
-) -> Result<get_protocols::Response> {
+    _body: Ruma<get_protocols::v3::Request>,
+) -> Result<get_protocols::v3::Response> {
     // TODO
-    Ok(get_protocols::Response {
+    Ok(get_protocols::v3::Response {
         protocols: BTreeMap::new(),
     })
 }
diff --git a/src/client_server/to_device.rs b/src/client_server/to_device.rs
index e57998f6..6d4fc0ca 100644
--- a/src/client_server/to_device.rs
+++ b/src/client_server/to_device.rs
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
 use crate::{database::DatabaseGuard, Error, Result, Ruma};
 use ruma::{
     api::{
-        client::{error::ErrorKind, r0::to_device::send_event_to_device},
+        client::{error::ErrorKind, to_device::send_event_to_device},
         federation::{self, transactions::edu::DirectDeviceContent},
     },
     events::EventType,
@@ -15,8 +15,8 @@ use ruma::{
 /// Send a to-device event to a set of client devices.
 pub async fn send_event_to_device_route(
     db: DatabaseGuard,
-    body: Ruma<send_event_to_device::Request<'_>>,
-) -> Result<send_event_to_device::Response> {
+    body: Ruma<send_event_to_device::v3::Request<'_>>,
+) -> Result<send_event_to_device::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
     let sender_device = body.sender_device.as_deref();
 
@@ -28,7 +28,7 @@ pub async fn send_event_to_device_route(
         .existing_txnid(sender_user, sender_device, &body.txn_id)?
         .is_some()
     {
-        return Ok(send_event_to_device::Response.into());
+        return Ok(send_event_to_device::v3::Response.into());
     }
     */
 
@@ -93,5 +93,5 @@ pub async fn send_event_to_device_route(
 
     db.flush()?;
 
-    Ok(send_event_to_device::Response {})
+    Ok(send_event_to_device::v3::Response {})
 }
diff --git a/src/client_server/typing.rs b/src/client_server/typing.rs
index bbc852d2..9d4ba6f8 100644
--- a/src/client_server/typing.rs
+++ b/src/client_server/typing.rs
@@ -1,14 +1,15 @@
 use crate::{database::DatabaseGuard, utils, Result, Ruma};
-use create_typing_event::Typing;
-use ruma::api::client::r0::typing::create_typing_event;
+use ruma::api::client::typing::create_typing_event;
 
 /// # `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}`
 ///
 /// Sets the typing state of the sender user.
 pub async fn create_typing_event_route(
     db: DatabaseGuard,
-    body: Ruma<create_typing_event::Request<'_>>,
-) -> Result<create_typing_event::Response> {
+    body: Ruma<create_typing_event::v3::Request<'_>>,
+) -> Result<create_typing_event::v3::Response> {
+    use create_typing_event::v3::Typing;
+
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     if let Typing::Yes(duration) = body.state {
@@ -24,5 +25,5 @@ pub async fn create_typing_event_route(
             .typing_remove(sender_user, &body.room_id, &db.globals)?;
     }
 
-    Ok(create_typing_event::Response {})
+    Ok(create_typing_event::v3::Response {})
 }
diff --git a/src/client_server/unversioned.rs b/src/client_server/unversioned.rs
index 168f172a..84ac355e 100644
--- a/src/client_server/unversioned.rs
+++ b/src/client_server/unversioned.rs
@@ -1,7 +1,7 @@
 use std::{collections::BTreeMap, iter::FromIterator};
 
 use crate::{Result, Ruma};
-use ruma::api::client::unversioned::get_supported_versions;
+use ruma::api::client::discover::get_supported_versions;
 
 /// # `GET /_matrix/client/versions`
 ///
diff --git a/src/client_server/user_directory.rs b/src/client_server/user_directory.rs
index cecba7f2..d641848f 100644
--- a/src/client_server/user_directory.rs
+++ b/src/client_server/user_directory.rs
@@ -1,5 +1,5 @@
 use crate::{database::DatabaseGuard, Result, Ruma};
-use ruma::api::client::r0::user_directory::search_users;
+use ruma::api::client::user_directory::search_users;
 
 /// # `POST /_matrix/client/r0/user_directory/search`
 ///
@@ -8,15 +8,15 @@ use ruma::api::client::r0::user_directory::search_users;
 /// - TODO: Hide users that are not in any public rooms?
 pub async fn search_users_route(
     db: DatabaseGuard,
-    body: Ruma<search_users::Request<'_>>,
-) -> Result<search_users::Response> {
+    body: Ruma<search_users::v3::Request<'_>>,
+) -> Result<search_users::v3::Response> {
     let limit = u64::from(body.limit) as usize;
 
     let mut users = db.users.iter().filter_map(|user_id| {
         // Filter out buggy users (they should not exist, but you never know...)
         let user_id = user_id.ok()?;
 
-        let user = search_users::User {
+        let user = search_users::v3::User {
             user_id: user_id.clone(),
             display_name: db.users.displayname(&user_id).ok()?,
             avatar_url: db.users.avatar_url(&user_id).ok()?,
@@ -47,5 +47,5 @@ pub async fn search_users_route(
     let results = users.by_ref().take(limit).collect();
     let limited = users.next().is_some();
 
-    Ok(search_users::Response { results, limited })
+    Ok(search_users::v3::Response { results, limited })
 }
diff --git a/src/client_server/voip.rs b/src/client_server/voip.rs
index e9a553a9..6281744b 100644
--- a/src/client_server/voip.rs
+++ b/src/client_server/voip.rs
@@ -1,6 +1,6 @@
 use crate::{database::DatabaseGuard, Result, Ruma};
 use hmac::{Hmac, Mac, NewMac};
-use ruma::{api::client::r0::voip::get_turn_server_info, SecondsSinceUnixEpoch};
+use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch};
 use sha1::Sha1;
 use std::time::{Duration, SystemTime};
 
@@ -11,8 +11,8 @@ type HmacSha1 = Hmac<Sha1>;
 /// TODO: Returns information about the recommended turn server.
 pub async fn turn_server_route(
     db: DatabaseGuard,
-    body: Ruma<get_turn_server_info::Request>,
-) -> Result<get_turn_server_info::Response> {
+    body: Ruma<get_turn_server_info::v3::Request>,
+) -> Result<get_turn_server_info::v3::Response> {
     let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
     let turn_secret = db.globals.turn_secret();
@@ -39,7 +39,7 @@ pub async fn turn_server_route(
         )
     };
 
-    Ok(get_turn_server_info::Response {
+    Ok(get_turn_server_info::v3::Response {
         username,
         password,
         uris: db.globals.turn_uris().to_vec(),
diff --git a/src/database/globals.rs b/src/database/globals.rs
index c5b2b779..7bc300d2 100644
--- a/src/database/globals.rs
+++ b/src/database/globals.rs
@@ -1,7 +1,7 @@
 use crate::{database::Config, server_server::FedDest, utils, Error, Result};
 use ruma::{
     api::{
-        client::r0::sync::sync_events,
+        client::sync::sync_events,
         federation::discovery::{ServerSigningKeys, VerifyKey},
     },
     DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, ServerName, ServerSigningKeyId, UserId,
@@ -27,8 +27,8 @@ type WellKnownMap = HashMap<Box<ServerName>, (FedDest, String)>;
 type TlsNameMap = HashMap<String, (Vec<IpAddr>, u16)>;
 type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
 type SyncHandle = (
-    Option<String>,                                  // since
-    Receiver<Option<Result<sync_events::Response>>>, // rx
+    Option<String>,                                      // since
+    Receiver<Option<Result<sync_events::v3::Response>>>, // rx
 );
 
 pub struct Globals {
diff --git a/src/database/key_backups.rs b/src/database/key_backups.rs
index 2eefe481..10443f6b 100644
--- a/src/database/key_backups.rs
+++ b/src/database/key_backups.rs
@@ -1,8 +1,8 @@
 use crate::{utils, Error, Result};
 use ruma::{
     api::client::{
+        backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
         error::ErrorKind,
-        r0::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
     },
     serde::Raw,
     RoomId, UserId,
diff --git a/src/database/pusher.rs b/src/database/pusher.rs
index bc7017b0..36f8454e 100644
--- a/src/database/pusher.rs
+++ b/src/database/pusher.rs
@@ -2,7 +2,7 @@ use crate::{Database, Error, PduEvent, Result};
 use bytes::BytesMut;
 use ruma::{
     api::{
-        client::r0::push::{get_pushers, set_pusher, PusherKind},
+        client::push::{get_pushers, set_pusher, PusherKind},
         push_gateway::send_event_notification::{
             self,
             v1::{Device, Notification, NotificationCounts, NotificationPriority},
@@ -30,7 +30,7 @@ pub struct PushData {
 
 impl PushData {
     #[tracing::instrument(skip(self, sender, pusher))]
-    pub fn set_pusher(&self, sender: &UserId, pusher: set_pusher::Pusher) -> Result<()> {
+    pub fn set_pusher(&self, sender: &UserId, pusher: set_pusher::v3::Pusher) -> Result<()> {
         let mut key = sender.as_bytes().to_vec();
         key.push(0xff);
         key.extend_from_slice(pusher.pushkey.as_bytes());
@@ -53,7 +53,7 @@ impl PushData {
     }
 
     #[tracing::instrument(skip(self, senderkey))]
-    pub fn get_pusher(&self, senderkey: &[u8]) -> Result<Option<get_pushers::Pusher>> {
+    pub fn get_pusher(&self, senderkey: &[u8]) -> Result<Option<get_pushers::v3::Pusher>> {
         self.senderkey_pusher
             .get(senderkey)?
             .map(|push| {
@@ -64,7 +64,7 @@ impl PushData {
     }
 
     #[tracing::instrument(skip(self, sender))]
-    pub fn get_pushers(&self, sender: &UserId) -> Result<Vec<get_pushers::Pusher>> {
+    pub fn get_pushers(&self, sender: &UserId) -> Result<Vec<get_pushers::v3::Pusher>> {
         let mut prefix = sender.as_bytes().to_vec();
         prefix.push(0xff);
 
@@ -171,7 +171,7 @@ where
 pub async fn send_push_notice(
     user: &UserId,
     unread: UInt,
-    pusher: &get_pushers::Pusher,
+    pusher: &get_pushers::v3::Pusher,
     ruleset: Ruleset,
     pdu: &PduEvent,
     db: &Database,
@@ -251,7 +251,7 @@ pub fn get_actions<'a>(
 #[tracing::instrument(skip(unread, pusher, tweaks, event, db))]
 async fn send_notice(
     unread: UInt,
-    pusher: &get_pushers::Pusher,
+    pusher: &get_pushers::v3::Pusher,
     tweaks: Vec<Tweak>,
     event: &PduEvent,
     db: &Database,
diff --git a/src/database/rooms.rs b/src/database/rooms.rs
index c751167a..3a71a3b5 100644
--- a/src/database/rooms.rs
+++ b/src/database/rooms.rs
@@ -2606,7 +2606,7 @@ impl Rooms {
                 .send_federation_request(
                     &db.globals,
                     &remote_server,
-                    federation::membership::get_leave_event::v1::Request { room_id, user_id },
+                    federation::membership::prepare_leave_event::v1::Request { room_id, user_id },
                 )
                 .await;
 
diff --git a/src/database/uiaa.rs b/src/database/uiaa.rs
index b2244b5d..6b15d721 100644
--- a/src/database/uiaa.rs
+++ b/src/database/uiaa.rs
@@ -7,7 +7,7 @@ use crate::{client_server::SESSION_ID_LENGTH, utils, Error, Result};
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::uiaa::{
+        uiaa::{
             AuthType, IncomingAuthData, IncomingPassword, IncomingUserIdentifier::MatrixId,
             UiaaInfo,
         },
diff --git a/src/database/users.rs b/src/database/users.rs
index 681ee284..a66fa93a 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -1,9 +1,6 @@
 use crate::{utils, Error, Result};
 use ruma::{
-    api::client::{
-        error::ErrorKind,
-        r0::{device::Device, filter::IncomingFilterDefinition},
-    },
+    api::client::{device::Device, error::ErrorKind, filter::IncomingFilterDefinition},
     encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
     events::{AnyToDeviceEvent, EventType},
     identifiers::MxcUri,
diff --git a/src/error.rs b/src/error.rs
index a16a3abd..206a055f 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -4,7 +4,7 @@ use http::StatusCode;
 use ruma::{
     api::client::{
         error::{Error as RumaError, ErrorKind},
-        r0::uiaa::{UiaaInfo, UiaaResponse},
+        uiaa::{UiaaInfo, UiaaResponse},
     },
     ServerName,
 };
diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs
index ee89cc28..119c3ea8 100644
--- a/src/ruma_wrapper.rs
+++ b/src/ruma_wrapper.rs
@@ -1,6 +1,6 @@
 use crate::Error;
 use ruma::{
-    api::client::r0::uiaa::UiaaResponse,
+    api::client::uiaa::UiaaResponse,
     identifiers::{DeviceId, UserId},
     signatures::CanonicalJsonValue,
     Outgoing, ServerName,
diff --git a/src/server_server.rs b/src/server_server.rs
index a4442f05..9dc26170 100644
--- a/src/server_server.rs
+++ b/src/server_server.rs
@@ -26,7 +26,7 @@ use ruma::{
             membership::{
                 create_invite,
                 create_join_event::{self, RoomState},
-                create_join_event_template,
+                prepare_join_event,
             },
             query::{get_profile_information, get_room_information},
             transactions::{
@@ -49,7 +49,7 @@ use ruma::{
     },
     int,
     receipt::ReceiptType,
-    serde::{Base64, JsonObject},
+    serde::{Base64, JsonObject, Raw},
     signatures::{CanonicalJsonObject, CanonicalJsonValue},
     state_res::{self, RoomVersion, StateMap},
     to_device::DeviceIdOrAllDevices,
@@ -532,7 +532,7 @@ pub async fn get_server_keys_route(db: DatabaseGuard) -> Result<impl IntoRespons
     );
     let mut response = serde_json::from_slice(
         get_server_keys::v2::Response {
-            server_key: ServerSigningKeys {
+            server_key: Raw::new(&ServerSigningKeys {
                 server_name: db.globals.server_name().to_owned(),
                 verify_keys,
                 old_verify_keys: BTreeMap::new(),
@@ -541,7 +541,8 @@ pub async fn get_server_keys_route(db: DatabaseGuard) -> Result<impl IntoRespons
                     SystemTime::now() + Duration::from_secs(86400 * 7),
                 )
                 .expect("time is valid"),
-            },
+            })
+            .expect("static conversion, no errors"),
         }
         .try_into_http_response::<Vec<u8>>()
         .unwrap()
@@ -1981,24 +1982,23 @@ pub(crate) async fn fetch_signing_keys(
 
     debug!("Fetching signing keys for {} over federation", origin);
 
-    if let Ok(get_keys_response) = db
+    if let Some(server_key) = db
         .sending
         .send_federation_request(&db.globals, origin, get_server_keys::v2::Request::new())
         .await
+        .ok()
+        .and_then(|resp| resp.server_key.deserialize().ok())
     {
-        db.globals
-            .add_signing_key(origin, get_keys_response.server_key.clone())?;
+        db.globals.add_signing_key(origin, server_key.clone())?;
 
         result.extend(
-            get_keys_response
-                .server_key
+            server_key
                 .verify_keys
                 .into_iter()
                 .map(|(k, v)| (k.to_string(), v.key)),
         );
         result.extend(
-            get_keys_response
-                .server_key
+            server_key
                 .old_verify_keys
                 .into_iter()
                 .map(|(k, v)| (k.to_string(), v.key)),
@@ -2011,7 +2011,7 @@ pub(crate) async fn fetch_signing_keys(
 
     for server in db.globals.trusted_servers() {
         debug!("Asking {} for {}'s signing key", server, origin);
-        if let Ok(keys) = db
+        if let Some(server_keys) = db
             .sending
             .send_federation_request(
                 &db.globals,
@@ -2027,9 +2027,16 @@ pub(crate) async fn fetch_signing_keys(
                 ),
             )
             .await
+            .ok()
+            .map(|resp| {
+                resp.server_keys
+                    .into_iter()
+                    .filter_map(|e| e.deserialize().ok())
+                    .collect::<Vec<_>>()
+            })
         {
-            trace!("Got signing keys: {:?}", keys);
-            for k in keys.server_keys {
+            trace!("Got signing keys: {:?}", server_keys);
+            for k in server_keys {
                 db.globals.add_signing_key(origin, k.clone())?;
                 result.extend(
                     k.verify_keys
@@ -2538,8 +2545,8 @@ pub async fn get_room_state_ids_route(
 /// Creates a join template.
 pub async fn create_join_event_template_route(
     db: DatabaseGuard,
-    body: Ruma<create_join_event_template::v1::Request<'_>>,
-) -> Result<create_join_event_template::v1::Response> {
+    body: Ruma<prepare_join_event::v1::Request<'_>>,
+) -> Result<prepare_join_event::v1::Response> {
     if !db.globals.allow_federation() {
         return Err(Error::bad_config("Federation is disabled."));
     }
@@ -2701,7 +2708,7 @@ pub async fn create_join_event_template_route(
         CanonicalJsonValue::String(db.globals.server_name().as_str().to_owned()),
     );
 
-    Ok(create_join_event_template::v1::Response {
+    Ok(prepare_join_event::v1::Response {
         room_version: Some(room_version_id),
         event: to_raw_value(&pdu_json).expect("CanonicalJson can be serialized to JSON"),
     })
@@ -3293,6 +3300,8 @@ pub(crate) async fn fetch_join_signing_keys(
                 .write()
                 .map_err(|_| Error::bad_database("RwLock is poisoned."))?;
             for k in keys.server_keys {
+                let k = k.deserialize().unwrap();
+
                 // TODO: Check signature from trusted server?
                 servers.remove(&k.server_name);
 
@@ -3332,7 +3341,7 @@ pub(crate) async fn fetch_join_signing_keys(
         if let (Ok(get_keys_response), origin) = result {
             let result: BTreeMap<_, _> = db
                 .globals
-                .add_signing_key(&origin, get_keys_response.server_key.clone())?
+                .add_signing_key(&origin, get_keys_response.server_key.deserialize().unwrap())?
                 .into_iter()
                 .map(|(k, v)| (k.to_string(), v.key))
                 .collect();