diff --git a/src/database/guild.rs b/src/database/guild.rs
index b2ff3b5653b3d40a4f74a750208010dc4c08f770..9368007ed081b52926c23035ec948687728e95fd 100644
--- a/src/database/guild.rs
+++ b/src/database/guild.rs
@@ -1,10 +1,17 @@
 use bson::doc;
 use serde::{Deserialize, Serialize};
 
+#[derive(Serialize, Deserialize, Debug)]
+pub struct MemberRef {
+    pub guild: String,
+    pub user: String,
+}
+
 #[derive(Serialize, Deserialize, Debug)]
 pub struct Member {
-    pub id: String,
-    pub nickname: String,
+    #[serde(rename = "_id")]
+    pub id: MemberRef,
+    pub nickname: Option<String>,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -14,6 +21,12 @@ pub struct Invite {
     pub channel: String,
 }
 
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Ban {
+    pub id: String,
+    pub reason: String,
+}
+
 #[derive(Serialize, Deserialize, Debug)]
 pub struct Guild {
     #[serde(rename = "_id")]
@@ -25,6 +38,7 @@ pub struct Guild {
 
     pub channels: Vec<String>,
     pub invites: Vec<Invite>,
+    pub bans: Vec<Ban>,
 
     pub default_permissions: u32,
 }
diff --git a/src/database/permissions.rs b/src/database/permissions.rs
index 02e6397b204700d31715fb00dae25fb7556f2454..3b34c020d31cec60d3141328425acbc6db1afe6f 100644
--- a/src/database/permissions.rs
+++ b/src/database/permissions.rs
@@ -1,11 +1,10 @@
 use super::mutual::has_mutual_connection;
-use crate::database::get_collection;
+use crate::database::guild::Member;
 use crate::database::user::UserRelationship;
 use crate::guards::auth::UserRef;
 use crate::guards::channel::ChannelRef;
-use crate::guards::guild::GuildRef;
+use crate::guards::guild::{get_member, GuildRef};
 
-use bson::doc;
 use num_enum::TryFromPrimitive;
 
 #[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
@@ -91,6 +90,7 @@ pub struct PermissionCalculator {
     pub user: UserRef,
     pub channel: Option<ChannelRef>,
     pub guild: Option<GuildRef>,
+    pub member: Option<Member>,
 }
 
 impl PermissionCalculator {
@@ -99,6 +99,7 @@ impl PermissionCalculator {
             user,
             channel: None,
             guild: None,
+            member: None,
         }
     }
 
@@ -116,7 +117,7 @@ impl PermissionCalculator {
         }
     }
 
-    pub fn calculate(self) -> u32 {
+    pub fn fetch_data(mut self) -> PermissionCalculator {
         let guild = if let Some(value) = self.guild {
             Some(value)
         } else if let Some(channel) = &self.channel {
@@ -135,22 +136,23 @@ impl PermissionCalculator {
             None
         };
 
-        let mut permissions: u32 = 0;
-        if let Some(guild) = guild {
-            if let Ok(result) = get_collection("members").find_one(
-                doc! {
-                    "_id.user": &self.user.id,
-                    "_id.guild": &guild.id,
-                },
-                None,
-            ) {
-                if result.is_some() {
-                    if guild.owner == self.user.id {
-                        return u32::MAX;
-                    }
+        if let Some(guild) = &guild {
+            self.member = get_member(guild, &self.user.id);
+        }
 
-                    permissions = guild.default_permissions as u32;
+        self.guild = guild;
+        self
+    }
+
+    pub fn calculate(&self) -> u32 {
+        let mut permissions: u32 = 0;
+        if let Some(guild) = &self.guild {
+            if let Some(_member) = &self.member {
+                if guild.owner == self.user.id {
+                    return u32::MAX;
                 }
+
+                permissions = guild.default_permissions as u32;
             }
         }
 
@@ -206,7 +208,7 @@ impl PermissionCalculator {
         permissions
     }
 
-    pub fn as_permission(self) -> MemberPermissions<[u32; 1]> {
+    pub fn as_permission(&self) -> MemberPermissions<[u32; 1]> {
         MemberPermissions([self.calculate()])
     }
 }
diff --git a/src/guards/guild.rs b/src/guards/guild.rs
index 1ca1f09fcb2b5f48b1577b8dddc68cc4aea5206f..b7da66905ba13a208b931e6dda168791e299abd5 100644
--- a/src/guards/guild.rs
+++ b/src/guards/guild.rs
@@ -1,10 +1,11 @@
-use bson::{doc, from_bson, Document};
+use bson::{doc, from_bson, Bson, Document};
 use mongodb::options::FindOneOptions;
 use rocket::http::RawStr;
 use rocket::request::FromParam;
 use serde::{Deserialize, Serialize};
 
 use crate::database;
+use crate::database::guild::{Ban, Member};
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct GuildRef {
@@ -15,6 +16,8 @@ pub struct GuildRef {
     pub owner: String,
 
     pub channels: Vec<String>,
+    pub bans: Vec<Ban>,
+
     pub default_permissions: i32,
 }
 
@@ -28,6 +31,7 @@ impl GuildRef {
                     "description": 1,
                     "owner": 1,
                     "channels": 1,
+                    "bans": 1,
                     "default_permissions": 1
                 })
                 .build(),
@@ -73,3 +77,21 @@ impl<'r> FromParam<'r> for GuildRef {
         }
     }
 }
+
+pub fn get_member(guild: &GuildRef, member: &String) -> Option<Member> {
+    if let Ok(result) = database::get_collection("members").find_one(
+        doc! {
+            "_id.guild": &guild.id,
+            "_id.user": &member,
+        },
+        None,
+    ) {
+        if let Some(doc) = result {
+            Some(from_bson(Bson::Document(doc)).expect("Failed to unwrap member."))
+        } else {
+            None
+        }
+    } else {
+        None
+    }
+}
diff --git a/src/routes/channel.rs b/src/routes/channel.rs
index 290639964001b975b4625761c6af12c8a8075e5b..8dbf4986df096e398c8060b40935c3c4eb8604eb 100644
--- a/src/routes/channel.rs
+++ b/src/routes/channel.rs
@@ -29,13 +29,14 @@ macro_rules! with_permissions {
     ($user: expr, $target: expr) => {{
         let permissions = PermissionCalculator::new($user.clone())
             .channel($target.clone())
-            .as_permission();
+            .fetch_data();
 
-        if !permissions.get_access() {
+        let value = permissions.as_permission();
+        if !value.get_access() {
             return None;
         }
 
-        permissions
+        value
     }};
 }
 
diff --git a/src/routes/guild.rs b/src/routes/guild.rs
index 142df3bb4495ec983e3bdc0f47e1d8a0a5d55f32..7a11d51c789308f60963960162236f7242ef94b3 100644
--- a/src/routes/guild.rs
+++ b/src/routes/guild.rs
@@ -1,11 +1,12 @@
 use super::channel::ChannelType;
 use super::Response;
-use crate::database::{self, channel::Channel, PermissionCalculator, Permission};
+use crate::database::{self, channel::Channel, Permission, PermissionCalculator};
 use crate::guards::auth::UserRef;
-use crate::guards::guild::GuildRef;
+use crate::guards::guild::{get_member, GuildRef};
 
 use bson::{doc, from_bson, Bson};
 use mongodb::options::FindOptions;
+use rocket::request::Form;
 use rocket_contrib::json::Json;
 use serde::{Deserialize, Serialize};
 use ulid::Ulid;
@@ -14,13 +15,14 @@ macro_rules! with_permissions {
     ($user: expr, $target: expr) => {{
         let permissions = PermissionCalculator::new($user.clone())
             .guild($target.clone())
-            .as_permission();
+            .fetch_data();
 
-        if !permissions.get_access() {
+        let value = permissions.as_permission();
+        if !value.get_access() {
             return None;
         }
 
-        permissions
+        (value, permissions.member.unwrap())
     }};
 }
 
@@ -214,6 +216,7 @@ pub fn create_guild(user: UserRef, info: Json<CreateGuild>) -> Response {
                     &channel_id
                 ],
                 "invites": [],
+                "bans": [],
                 "default_permissions": 51,
             },
             None,
@@ -262,26 +265,14 @@ pub fn fetch_members(user: UserRef, target: GuildRef) -> Option<Response> {
 pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> {
     with_permissions!(user, target);
 
-    if let Ok(result) = database::get_collection("members").find_one(
-        doc! {
-            "_id.guild": &target.id,
-            "_id.user": &other,
-        },
-        None,
-    ) {
-        if let Some(doc) = result {
-            Some(Response::Success(json!({
-                "id": doc.get_document("_id").unwrap().get_str("user").unwrap(),
-                "nickname": doc.get_str("nickname").ok(),
-            })))
-        } else {
-            Some(Response::NotFound(
-                json!({ "error": "User not part of guild." }),
-            ))
-        }
+    if let Some(member) = get_member(&target, &other) {
+        Some(Response::Success(json!({
+            "id": member.id.user,
+            "nickname": member.nickname,
+        })))
     } else {
         Some(Response::InternalServerError(
-            json!({ "error": "Failed to fetch member." }),
+            json!({ "error": "Failed to fetch member or user does not exist." }),
         ))
     }
 }
@@ -289,23 +280,34 @@ pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> Option<Re
 /// kick a guild member
 #[delete("/<target>/members/<other>")]
 pub fn kick_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> {
-    let permissions = with_permissions!(user, target);
+    let (permissions, _) = with_permissions!(user, target);
+
+    if user.id == other {
+        return Some(Response::BadRequest(
+            json!({ "error": "Cannot kick yourself." }),
+        ));
+    }
 
     if !permissions.get_kick_members() {
         return Some(Response::LackingPermission(Permission::KickMembers));
     }
 
-    if user.id == other {
-        return Some(Response::BadRequest(json!({ "error": "Cannot kick yourself." })))
+    if get_member(&target, &other).is_none() {
+        return Some(Response::BadRequest(
+            json!({ "error": "User not part of guild." }),
+        ));
     }
 
-    if database::get_collection("members").delete_one(
-        doc! {
-            "_id.guild": &target.id,
-            "_id.user": &other,
-        },
-        None,
-    ).is_ok() {
+    if database::get_collection("members")
+        .delete_one(
+            doc! {
+                "_id.guild": &target.id,
+                "_id.user": &other,
+            },
+            None,
+        )
+        .is_ok()
+    {
         Some(Response::Result(super::Status::Ok))
     } else {
         Some(Response::InternalServerError(
@@ -313,3 +315,136 @@ pub fn kick_member(user: UserRef, target: GuildRef, other: String) -> Option<Res
         ))
     }
 }
+
+#[derive(Serialize, Deserialize, FromForm)]
+pub struct BanOptions {
+    reason: Option<String>,
+}
+
+/// ban a guild member
+#[put("/<target>/members/<other>/ban?<options..>")]
+pub fn ban_member(
+    user: UserRef,
+    target: GuildRef,
+    other: String,
+    options: Form<BanOptions>,
+) -> Option<Response> {
+    let (permissions, _) = with_permissions!(user, target);
+    let reason: String = options
+        .reason
+        .clone()
+        .unwrap_or("No reason specified.".to_string())
+        .chars()
+        .take(64)
+        .collect();
+
+    if user.id == other {
+        return Some(Response::BadRequest(
+            json!({ "error": "Cannot ban yourself." }),
+        ));
+    }
+
+    if !permissions.get_ban_members() {
+        return Some(Response::LackingPermission(Permission::BanMembers));
+    }
+
+    if get_member(&target, &other).is_none() {
+        return Some(Response::BadRequest(
+            json!({ "error": "User not part of guild." }),
+        ));
+    }
+
+    if database::get_collection("guilds")
+        .update_one(
+            doc! { "_id": &target.id },
+            doc! {
+                "$push": {
+                    "bans": {
+                        "id": &other,
+                        "reason": reason,
+                    }
+                }
+            },
+            None,
+        )
+        .is_err()
+    {
+        return Some(Response::BadRequest(
+            json!({ "error": "Failed to add ban to guild." }),
+        ));
+    }
+
+    if database::get_collection("members")
+        .delete_one(
+            doc! {
+                "_id.guild": &target.id,
+                "_id.user": &other,
+            },
+            None,
+        )
+        .is_ok()
+    {
+        Some(Response::Result(super::Status::Ok))
+    } else {
+        Some(Response::InternalServerError(
+            json!({ "error": "Failed to kick member after adding to ban list." }),
+        ))
+    }
+}
+
+/// unban a guild member
+#[delete("/<target>/members/<other>/ban")]
+pub fn unban_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> {
+    let (permissions, _) = with_permissions!(user, target);
+
+    if user.id == other {
+        return Some(Response::BadRequest(
+            json!({ "error": "Cannot unban yourself (not checking if you're banned)." }),
+        ));
+    }
+
+    if !permissions.get_ban_members() {
+        return Some(Response::LackingPermission(Permission::BanMembers));
+    }
+
+    if target
+        .fetch_data_given(
+            doc! {
+                "bans": {
+                    "$elemMatch": {
+                        "id": &other
+                    }
+                }
+            },
+            doc! { }
+        )
+        .is_none()
+    {
+        return Some(Response::BadRequest(json!({ "error": "User not banned." })));
+    }
+
+    if database::get_collection("guilds")
+        .update_one(
+            doc! {
+                "_id": &target.id
+            },
+            doc! {
+                "$pull": {
+                    "bans": {
+                        "$elemMatch": {
+                            "id": &other
+                        }
+                    }
+                }
+            },
+            None,
+        )
+        .is_ok()
+    {
+        Some(Response::Result(super::Status::Ok))
+    } else {
+        Some(Response::BadRequest(
+            json!({ "error": "Failed to remove ban." }),
+        ))
+    }
+}
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index fe95c2ddfe286dd209ce50055576320451593013..830cce982e464b67fa5157eeada09ecc070a932a 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -109,7 +109,9 @@ pub fn mount(rocket: Rocket) -> Rocket {
                 guild::create_guild,
                 guild::fetch_members,
                 guild::fetch_member,
-                guild::kick_member
+                guild::kick_member,
+                guild::ban_member,
+                guild::unban_member
             ],
         )
 }
diff --git a/src/routes/user.rs b/src/routes/user.rs
index 6c4543a7337f08a0faa0df43dddd566d0be0eed1..f2dee0fcbdc33ca696bd4b3d1174ee5c94256c4a 100644
--- a/src/routes/user.rs
+++ b/src/routes/user.rs
@@ -42,14 +42,14 @@ pub fn user(user: UserRef, target: UserRef) -> Response {
 }
 
 #[derive(Serialize, Deserialize)]
-pub struct Query {
+pub struct LookupQuery {
     username: String,
 }
 
 /// lookup a user on Revolt
 /// currently only supports exact username searches
 #[post("/lookup", data = "<query>")]
-pub fn lookup(user: UserRef, query: Json<Query>) -> Response {
+pub fn lookup(user: UserRef, query: Json<LookupQuery>) -> Response {
     let relationships = user.fetch_relationships();
     let col = database::get_collection("users");