diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs
index fefdc0e9ddf53f82e2d0fcd84c89d1c64c932263..ff1d48e2ca9541be82406d4d931109937ea36d2d 100644
--- a/src/database/entities/server.rs
+++ b/src/database/entities/server.rs
@@ -42,9 +42,6 @@ pub struct Server {
     pub owner: String,
 
     pub name: String,
-    // pub default_permissions: u32,
-    // pub invites: Vec<Invite>,
-    // pub bans: Vec<Ban>,
     pub channels: Vec<String>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -90,6 +87,8 @@ impl Server {
         let messages = get_collection("messages");
 
         // Check if there are any attachments we need to delete.
+        // ! FIXME: make this generic and merge with channel delete
+        // ! e.g. delete_channel(filter: doc!)
         let message_ids = messages
             .find(
                 doc! {
@@ -206,6 +205,32 @@ impl Server {
         Ok(())
     }
 
+    pub async fn fetch_members(id: &str) -> Result<Vec<String>> {
+        Ok(get_collection("server_members")
+            .find(
+                doc! {
+                    "_id.server": id
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find",
+                with: "server_members",
+            })?
+            .filter_map(async move |s| s.ok())
+            .collect::<Vec<Document>>()
+            .await
+            .into_iter()
+            .filter_map(|x| {
+                x.get_document("_id")
+                    .ok()
+                    .map(|i| i.get_str("user").ok().map(|x| x.to_string()))
+            })
+            .flatten()
+            .collect::<Vec<String>>())
+    }
+
     pub async fn join_member(&self, id: &str) -> Result<()> {
         get_collection("server_members")
             .insert_one(
diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs
index 7833ccdd6c8ae708c53a8aeb1cb84567870bf5b7..21c427dc2d212a09954728343cdf765727ea3d1b 100644
--- a/src/database/entities/user.rs
+++ b/src/database/entities/user.rs
@@ -184,6 +184,7 @@ impl User {
     }
 
     /// Utility function for fetching multiple users from the perspective of one.
+    /// Assumes user has a mutual connection with others.
     pub async fn fetch_multiple_users(&self, user_ids: Vec<String>) -> Result<Vec<User>> {
         let mut users = vec![];
         let mut cursor = get_collection("users")
diff --git a/src/notifications/events.rs b/src/notifications/events.rs
index f51c329dcfb776a3be68dfb71ecaec71de3682b1..a78a312d0f6bbc8752ba105540fbbf694aff4e35 100644
--- a/src/notifications/events.rs
+++ b/src/notifications/events.rs
@@ -166,30 +166,7 @@ pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()
                 }
                 Channel::TextChannel { server, .. } => {
                     // ! FIXME: write a better algorithm?
-                    let members = get_collection("server_members")
-                        .find(
-                            doc! {
-                                "_id.server": server
-                            },
-                            None,
-                        )
-                        .await
-                        .map_err(|_| Error::DatabaseError {
-                            operation: "find",
-                            with: "server_members",
-                        })?
-                        .filter_map(async move |s| s.ok())
-                        .collect::<Vec<Document>>()
-                        .await
-                        .into_iter()
-                        .filter_map(|x| {
-                            x.get_document("_id")
-                                .ok()
-                                .map(|i| i.get_str("user").ok().map(|x| x.to_string()))
-                        })
-                        .flatten()
-                        .collect::<Vec<String>>();
-
+                    let members = Server::fetch_members(server).await?;
                     for member in members {
                         subscribe_if_exists(member.clone(), channel_id.to_string()).ok();
                     }
diff --git a/src/routes/servers/members_fetch.rs b/src/routes/servers/members_fetch.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dfe9a375e5d2b3470eafe0021edd587a2f1f334d
--- /dev/null
+++ b/src/routes/servers/members_fetch.rs
@@ -0,0 +1,23 @@
+use crate::database::*;
+use crate::util::result::{Error, Result};
+
+use rocket_contrib::json::JsonValue;
+
+// ! FIXME: this is a temporary route while permissions are being worked on.
+
+#[get("/<target>/members")]
+pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
+    let target = target.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+    
+    if !perm.get_view() {
+        Err(Error::MissingPermission)?
+    }
+
+    let members = Server::fetch_members(&target.id).await?;
+    Ok(json!(user.fetch_multiple_users(members).await?))
+}
diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs
index 20ef08b0aa43dd86a99ef77d2dd1fd55139dae0e..b668399dfa0c141d1efd79238119976eea6b7c5a 100644
--- a/src/routes/servers/mod.rs
+++ b/src/routes/servers/mod.rs
@@ -7,12 +7,15 @@ mod server_edit;
 
 mod channel_create;
 
+mod members_fetch;
+
 pub fn routes() -> Vec<Route> {
     routes![
         server_create::req,
         server_delete::req,
         server_fetch::req,
         server_edit::req,
-        channel_create::req
+        channel_create::req,
+        members_fetch::req
     ]
 }