summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPuyodead1 <puyodead@proton.me>2023-03-24 21:21:21 -0400
committerPuyodead1 <puyodead@proton.me>2023-04-13 15:28:41 -0400
commitc2ce88dee726bb6c1993bf4615aa866d089ca494 (patch)
treef5b3fe7d7d1e23b5f2ce6540c7e78d34b4c1e43f /src
parentUpdate openapi.json (diff)
downloadserver-c2ce88dee726bb6c1993bf4615aa866d089ca494.tar.xz
guilds
Diffstat (limited to 'src')
-rw-r--r--src/api/routes/guilds/#guild_id/bans.ts71
-rw-r--r--src/api/routes/guilds/#guild_id/channels.ts40
-rw-r--r--src/api/routes/guilds/#guild_id/delete.ts60
-rw-r--r--src/api/routes/guilds/#guild_id/discovery-requirements.ts70
-rw-r--r--src/api/routes/guilds/#guild_id/emojis.ts94
-rw-r--r--src/api/routes/guilds/#guild_id/index.ts71
-rw-r--r--src/api/routes/guilds/#guild_id/invites.ts11
-rw-r--r--src/api/routes/guilds/#guild_id/member-verification.ts26
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/index.ts175
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/nick.ts13
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts20
-rw-r--r--src/api/routes/guilds/#guild_id/members/index.ts67
-rw-r--r--src/api/routes/guilds/#guild_id/messages/search.ts253
-rw-r--r--src/api/routes/guilds/#guild_id/profile/index.ts15
-rw-r--r--src/api/routes/guilds/#guild_id/prune.ts53
-rw-r--r--src/api/routes/guilds/#guild_id/regions.ts37
-rw-r--r--src/api/routes/guilds/#guild_id/roles/#role_id/index.ts65
-rw-r--r--src/api/routes/guilds/#guild_id/roles/index.ts36
-rw-r--r--src/api/routes/guilds/#guild_id/stickers.ts84
-rw-r--r--src/api/routes/guilds/#guild_id/templates.ts68
-rw-r--r--src/api/routes/guilds/#guild_id/vanity-url.ts31
-rw-r--r--src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts16
-rw-r--r--src/api/routes/guilds/#guild_id/welcome-screen.ts34
-rw-r--r--src/api/routes/guilds/#guild_id/widget.json.ts143
-rw-r--r--src/api/routes/guilds/#guild_id/widget.png.ts294
-rw-r--r--src/api/routes/guilds/#guild_id/widget.ts45
-rw-r--r--src/api/routes/guilds/index.ts16
-rw-r--r--src/api/routes/guilds/templates/index.ts105
-rw-r--r--src/util/entities/Guild.ts13
-rw-r--r--src/util/interfaces/GuildWelcomeScreen.ts10
-rw-r--r--src/util/interfaces/index.ts1
-rw-r--r--src/util/schemas/responses/GuildBansResponse.ts10
-rw-r--r--src/util/schemas/responses/GuildChannelsResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildCreateResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildDiscoveryRequirements.ts23
-rw-r--r--src/util/schemas/responses/GuildEmojisResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildInvitesResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildMembersResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildMessagesSearchResponse.ts32
-rw-r--r--src/util/schemas/responses/GuildPruneResponse.ts7
-rw-r--r--src/util/schemas/responses/GuildResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildRolesResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildStickersResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildTemplatesResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildVanityUrl.ts17
-rw-r--r--src/util/schemas/responses/GuildVoiceRegionsResponse.ts9
-rw-r--r--src/util/schemas/responses/GuildWidgetJsonResponse.ts21
-rw-r--r--src/util/schemas/responses/GuildWidgetSettingsResponse.ts6
-rw-r--r--src/util/schemas/responses/MemberJoinGuildResponse.ts8
-rw-r--r--src/util/schemas/responses/index.ts18
50 files changed, 1575 insertions, 640 deletions
diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index b5fd301a..0776ab62 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -37,7 +37,17 @@ const router: Router = Router();
 
 router.get(
 	"/",
-	route({ permission: "BAN_MEMBERS" }),
+	route({
+		permission: "BAN_MEMBERS",
+		responses: {
+			200: {
+				body: "GuildBansResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 
@@ -73,7 +83,20 @@ router.get(
 
 router.get(
 	"/:user",
-	route({ permission: "BAN_MEMBERS" }),
+	route({
+		permission: "BAN_MEMBERS",
+		responses: {
+			200: {
+				body: "BanModeratorSchema",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const user_id = req.params.ban;
@@ -97,7 +120,21 @@ router.get(
 
 router.put(
 	"/:user_id",
-	route({ requestBody: "BanCreateSchema", permission: "BAN_MEMBERS" }),
+	route({
+		requestBody: "BanCreateSchema",
+		permission: "BAN_MEMBERS",
+		responses: {
+			200: {
+				body: "Ban",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const banned_user_id = req.params.user_id;
@@ -143,7 +180,20 @@ router.put(
 
 router.put(
 	"/@me",
-	route({ requestBody: "BanCreateSchema" }),
+	route({
+		requestBody: "BanCreateSchema",
+		responses: {
+			200: {
+				body: "Ban",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 
@@ -182,7 +232,18 @@ router.put(
 
 router.delete(
 	"/:user_id",
-	route({ permission: "BAN_MEMBERS" }),
+	route({
+		permission: "BAN_MEMBERS",
+		responses: {
+			204: {},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id, user_id } = req.params;
 
diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index ff167b02..0cbfca00 100644
--- a/src/api/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -28,18 +28,39 @@ import { Request, Response, Router } from "express";
 import { HTTPError } from "lambert-server";
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const channels = await Channel.find({ where: { guild_id } });
+router.get(
+	"/",
+	route({
+		responses: {
+			201: {
+				body: "GuildChannelsResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+		const channels = await Channel.find({ where: { guild_id } });
 
-	res.json(channels);
-});
+		res.json(channels);
+	},
+);
 
 router.post(
 	"/",
 	route({
 		requestBody: "ChannelModifySchema",
 		permission: "MANAGE_CHANNELS",
+		responses: {
+			201: {
+				body: "Channel",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
@@ -60,6 +81,15 @@ router.patch(
 	route({
 		requestBody: "ChannelReorderSchema",
 		permission: "MANAGE_CHANNELS",
+		responses: {
+			204: {},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		// changes guild channel position
diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index ec72a4ae..dee52c81 100644
--- a/src/api/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -16,37 +16,51 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { emitEvent, GuildDeleteEvent, Guild } from "@spacebar/util";
-import { Router, Request, Response } from "express";
-import { HTTPError } from "lambert-server";
 import { route } from "@spacebar/api";
+import { Guild, GuildDeleteEvent, emitEvent } from "@spacebar/util";
+import { Request, Response, Router } from "express";
+import { HTTPError } from "lambert-server";
 
 const router = Router();
 
 // discord prefixes this route with /delete instead of using the delete method
 // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
-router.post("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
+router.post(
+	"/",
+	route({
+		responses: {
+			204: {},
+			401: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
 
-	const guild = await Guild.findOneOrFail({
-		where: { id: guild_id },
-		select: ["owner_id"],
-	});
-	if (guild.owner_id !== req.user_id)
-		throw new HTTPError("You are not the owner of this guild", 401);
+		const guild = await Guild.findOneOrFail({
+			where: { id: guild_id },
+			select: ["owner_id"],
+		});
+		if (guild.owner_id !== req.user_id)
+			throw new HTTPError("You are not the owner of this guild", 401);
 
-	await Promise.all([
-		Guild.delete({ id: guild_id }), // this will also delete all guild related data
-		emitEvent({
-			event: "GUILD_DELETE",
-			data: {
-				id: guild_id,
-			},
-			guild_id: guild_id,
-		} as GuildDeleteEvent),
-	]);
+		await Promise.all([
+			Guild.delete({ id: guild_id }), // this will also delete all guild related data
+			emitEvent({
+				event: "GUILD_DELETE",
+				data: {
+					id: guild_id,
+				},
+				guild_id: guild_id,
+			} as GuildDeleteEvent),
+		]);
 
-	return res.sendStatus(204);
-});
+		return res.sendStatus(204);
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index 5e15676a..76b6d895 100644
--- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -16,40 +16,50 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Router, Request, Response } from "express";
 import { route } from "@spacebar/api";
+import { Request, Response, Router } from "express";
 
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	// TODO:
-	// Load from database
-	// Admin control, but for now it allows anyone to be discoverable
-
-	res.send({
-		guild_id: guild_id,
-		safe_environment: true,
-		healthy: true,
-		health_score_pending: false,
-		size: true,
-		nsfw_properties: {},
-		protected: true,
-		sufficient: true,
-		sufficient_without_grace_period: true,
-		valid_rules_channel: true,
-		retention_healthy: true,
-		engagement_healthy: true,
-		age: true,
-		minimum_age: 0,
-		health_score: {
-			avg_nonnew_participators: 0,
-			avg_nonnew_communicators: 0,
-			num_intentful_joiners: 0,
-			perc_ret_w1_intentful: 0,
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildDiscoveryRequirements",
+			},
 		},
-		minimum_size: 0,
-	});
-});
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+		// TODO:
+		// Load from database
+		// Admin control, but for now it allows anyone to be discoverable
+
+		res.send({
+			guild_id: guild_id,
+			safe_environment: true,
+			healthy: true,
+			health_score_pending: false,
+			size: true,
+			nsfw_properties: {},
+			protected: true,
+			sufficient: true,
+			sufficient_without_grace_period: true,
+			valid_rules_channel: true,
+			retention_healthy: true,
+			engagement_healthy: true,
+			age: true,
+			minimum_age: 0,
+			health_score: {
+				avg_nonnew_participators: 0,
+				avg_nonnew_communicators: 0,
+				num_intentful_joiners: 0,
+				perc_ret_w1_intentful: 0,
+			},
+			minimum_size: 0,
+		});
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index f8707b24..b1f3c7bf 100644
--- a/src/api/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -34,37 +34,77 @@ import { Request, Response, Router } from "express";
 
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildEmojisResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
 
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	const emojis = await Emoji.find({
-		where: { guild_id: guild_id },
-		relations: ["user"],
-	});
+		const emojis = await Emoji.find({
+			where: { guild_id: guild_id },
+			relations: ["user"],
+		});
 
-	return res.json(emojis);
-});
+		return res.json(emojis);
+	},
+);
 
-router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
-	const { guild_id, emoji_id } = req.params;
+router.get(
+	"/:emoji_id",
+	route({
+		responses: {
+			200: {
+				body: "Emoji",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id, emoji_id } = req.params;
 
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	const emoji = await Emoji.findOneOrFail({
-		where: { guild_id: guild_id, id: emoji_id },
-		relations: ["user"],
-	});
+		const emoji = await Emoji.findOneOrFail({
+			where: { guild_id: guild_id, id: emoji_id },
+			relations: ["user"],
+		});
 
-	return res.json(emoji);
-});
+		return res.json(emoji);
+	},
+);
 
 router.post(
 	"/",
 	route({
 		requestBody: "EmojiCreateSchema",
 		permission: "MANAGE_EMOJIS_AND_STICKERS",
+		responses: {
+			201: {
+				body: "Emoji",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
@@ -115,6 +155,14 @@ router.patch(
 	route({
 		requestBody: "EmojiModifySchema",
 		permission: "MANAGE_EMOJIS_AND_STICKERS",
+		responses: {
+			200: {
+				body: "Emoji",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		const { emoji_id, guild_id } = req.params;
@@ -141,7 +189,15 @@ router.patch(
 
 router.delete(
 	"/:emoji_id",
-	route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+	route({
+		permission: "MANAGE_EMOJIS_AND_STICKERS",
+		responses: {
+			204: {},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { emoji_id, guild_id } = req.params;
 
diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index 46346008..7c8f583e 100644
--- a/src/api/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -34,28 +34,61 @@ import { HTTPError } from "lambert-server";
 
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	const [guild, member] = await Promise.all([
-		Guild.findOneOrFail({ where: { id: guild_id } }),
-		Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
-	]);
-	if (!member)
-		throw new HTTPError(
-			"You are not a member of the guild you are trying to access",
-			401,
-		);
-
-	return res.send({
-		...guild,
-		joined_at: member?.joined_at,
-	});
-});
+router.get(
+	"/",
+	route({
+		responses: {
+			"200": {
+				body: "GuildResponse",
+			},
+			401: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+
+		const [guild, member] = await Promise.all([
+			Guild.findOneOrFail({ where: { id: guild_id } }),
+			Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
+		]);
+		if (!member)
+			throw new HTTPError(
+				"You are not a member of the guild you are trying to access",
+				401,
+			);
+
+		return res.send({
+			...guild,
+			joined_at: member?.joined_at,
+		});
+	},
+);
 
 router.patch(
 	"/",
-	route({ requestBody: "GuildUpdateSchema", permission: "MANAGE_GUILD" }),
+	route({
+		requestBody: "GuildUpdateSchema",
+		permission: "MANAGE_GUILD",
+		responses: {
+			"200": {
+				body: "GuildUpdateSchema",
+			},
+			401: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const body = req.body as GuildUpdateSchema;
 		const { guild_id } = req.params;
diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index 9c446928..219b6a4f 100644
--- a/src/api/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -16,15 +16,22 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Invite, PublicInviteRelation } from "@spacebar/util";
 import { route } from "@spacebar/api";
+import { Invite, PublicInviteRelation } from "@spacebar/util";
 import { Request, Response, Router } from "express";
 
 const router = Router();
 
 router.get(
 	"/",
-	route({ permission: "MANAGE_GUILD" }),
+	route({
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: {
+				body: "GuildInvitesResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 
diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 242f3684..2c39093e 100644
--- a/src/api/routes/guilds/#guild_id/member-verification.ts
+++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -16,17 +16,27 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Router, Request, Response } from "express";
 import { route } from "@spacebar/api";
+import { Request, Response, Router } from "express";
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	// TODO: member verification
+router.get(
+	"/",
+	route({
+		responses: {
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		// TODO: member verification
 
-	res.status(404).json({
-		message: "Unknown Guild Member Verification Form",
-		code: 10068,
-	});
-});
+		res.status(404).json({
+			message: "Unknown Guild Member Verification Form",
+			code: 10068,
+		});
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 814c8f8b..5f1f6fa7 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -34,20 +34,52 @@ import { Request, Response, Router } from "express";
 
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id, member_id } = req.params;
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "Member",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id, member_id } = req.params;
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	const member = await Member.findOneOrFail({
-		where: { id: member_id, guild_id },
-	});
+		const member = await Member.findOneOrFail({
+			where: { id: member_id, guild_id },
+		});
 
-	return res.json(member);
-});
+		return res.json(member);
+	},
+);
 
 router.patch(
 	"/",
-	route({ requestBody: "MemberChangeSchema" }),
+	route({
+		requestBody: "MemberChangeSchema",
+		responses: {
+			200: {
+				body: "Member",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const member_id =
@@ -119,54 +151,81 @@ router.patch(
 	},
 );
 
-router.put("/", route({}), async (req: Request, res: Response) => {
-	// TODO: Lurker mode
-
-	const rights = await getRights(req.user_id);
-
-	const { guild_id } = req.params;
-	let { member_id } = req.params;
-	if (member_id === "@me") {
-		member_id = req.user_id;
-		rights.hasThrow("JOIN_GUILDS");
-	} else {
-		// TODO: join others by controller
-	}
-
-	const guild = await Guild.findOneOrFail({
-		where: { id: guild_id },
-	});
-
-	const emoji = await Emoji.find({
-		where: { guild_id: guild_id },
-	});
-
-	const roles = await Role.find({
-		where: { guild_id: guild_id },
-	});
-
-	const stickers = await Sticker.find({
-		where: { guild_id: guild_id },
-	});
-
-	await Member.addToGuild(member_id, guild_id);
-	res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
-});
-
-router.delete("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id, member_id } = req.params;
-	const permission = await getPermission(req.user_id, guild_id);
-	const rights = await getRights(req.user_id);
-	if (member_id === "@me" || member_id === req.user_id) {
-		// TODO: unless force-joined
-		rights.hasThrow("SELF_LEAVE_GROUPS");
-	} else {
-		rights.hasThrow("KICK_BAN_MEMBERS");
-		permission.hasThrow("KICK_MEMBERS");
-	}
-
-	await Member.removeFromGuild(member_id, guild_id);
-	res.sendStatus(204);
-});
+router.put(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "MemberJoinGuildResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		// TODO: Lurker mode
+
+		const rights = await getRights(req.user_id);
+
+		const { guild_id } = req.params;
+		let { member_id } = req.params;
+		if (member_id === "@me") {
+			member_id = req.user_id;
+			rights.hasThrow("JOIN_GUILDS");
+		} else {
+			// TODO: join others by controller
+		}
+
+		const guild = await Guild.findOneOrFail({
+			where: { id: guild_id },
+		});
+
+		const emoji = await Emoji.find({
+			where: { guild_id: guild_id },
+		});
+
+		const roles = await Role.find({
+			where: { guild_id: guild_id },
+		});
+
+		const stickers = await Sticker.find({
+			where: { guild_id: guild_id },
+		});
+
+		await Member.addToGuild(member_id, guild_id);
+		res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
+	},
+);
+
+router.delete(
+	"/",
+	route({
+		responses: {
+			204: {},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id, member_id } = req.params;
+		const permission = await getPermission(req.user_id, guild_id);
+		const rights = await getRights(req.user_id);
+		if (member_id === "@me" || member_id === req.user_id) {
+			// TODO: unless force-joined
+			rights.hasThrow("SELF_LEAVE_GROUPS");
+		} else {
+			rights.hasThrow("KICK_BAN_MEMBERS");
+			permission.hasThrow("KICK_MEMBERS");
+		}
+
+		await Member.removeFromGuild(member_id, guild_id);
+		res.sendStatus(204);
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 41aaa84b..7b8e44d3 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -24,7 +24,18 @@ const router = Router();
 
 router.patch(
 	"/",
-	route({ requestBody: "MemberNickChangeSchema" }),
+	route({
+		requestBody: "MemberNickChangeSchema",
+		responses: {
+			200: {},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 698df88f..46dd70bb 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -16,15 +16,23 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Member } from "@spacebar/util";
 import { route } from "@spacebar/api";
+import { Member } from "@spacebar/util";
 import { Request, Response, Router } from "express";
 
 const router = Router();
 
 router.delete(
 	"/",
-	route({ permission: "MANAGE_ROLES" }),
+	route({
+		permission: "MANAGE_ROLES",
+		responses: {
+			204: {},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id, role_id, member_id } = req.params;
 
@@ -35,7 +43,13 @@ router.delete(
 
 router.put(
 	"/",
-	route({ permission: "MANAGE_ROLES" }),
+	route({
+		permission: "MANAGE_ROLES",
+		responses: {
+			204: {},
+			403: {},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id, role_id, member_id } = req.params;
 
diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index f7a55cf1..1ab9dd28 100644
--- a/src/api/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -16,35 +16,58 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Request, Response, Router } from "express";
-import { Member, PublicMemberProjection } from "@spacebar/util";
 import { route } from "@spacebar/api";
-import { MoreThan } from "typeorm";
+import { Member, PublicMemberProjection } from "@spacebar/util";
+import { Request, Response, Router } from "express";
 import { HTTPError } from "lambert-server";
+import { MoreThan } from "typeorm";
 
 const router = Router();
 
 // TODO: send over websocket
 // TODO: check for GUILD_MEMBERS intent
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const limit = Number(req.query.limit) || 1;
-	if (limit > 1000 || limit < 1)
-		throw new HTTPError("Limit must be between 1 and 1000");
-	const after = `${req.query.after}`;
-	const query = after ? { id: MoreThan(after) } : {};
-
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-
-	const members = await Member.find({
-		where: { guild_id, ...query },
-		select: PublicMemberProjection,
-		take: limit,
-		order: { id: "ASC" },
-	});
-
-	return res.json(members);
-});
+router.get(
+	"/",
+	route({
+		query: {
+			limit: {
+				type: "number",
+				description:
+					"max number of members to return (1-1000). default 1",
+			},
+			after: {
+				type: "string",
+			},
+		},
+		responses: {
+			200: {
+				body: "GuildMembersResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+		const limit = Number(req.query.limit) || 1;
+		if (limit > 1000 || limit < 1)
+			throw new HTTPError("Limit must be between 1 and 1000");
+		const after = `${req.query.after}`;
+		const query = after ? { id: MoreThan(after) } : {};
+
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
+
+		const members = await Member.find({
+			where: { guild_id, ...query },
+			select: PublicMemberProjection,
+			take: limit,
+			order: { id: "ASC" },
+		});
+
+		return res.json(members);
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index bc5f1b6e..637d1e43 100644
--- a/src/api/routes/guilds/#guild_id/messages/search.ts
+++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -18,140 +18,159 @@
 
 /* eslint-disable @typescript-eslint/ban-ts-comment */
 
-import { Request, Response, Router } from "express";
 import { route } from "@spacebar/api";
-import { getPermission, FieldErrors, Message, Channel } from "@spacebar/util";
+import { Channel, FieldErrors, Message, getPermission } from "@spacebar/util";
+import { Request, Response, Router } from "express";
 import { HTTPError } from "lambert-server";
 import { FindManyOptions, In, Like } from "typeorm";
 
 const router: Router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const {
-		channel_id,
-		content,
-		// include_nsfw, // TODO
-		offset,
-		sort_order,
-		// sort_by, // TODO: Handle 'relevance'
-		limit,
-		author_id,
-	} = req.query;
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildMessagesSearchResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			422: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const {
+			channel_id,
+			content,
+			// include_nsfw, // TODO
+			offset,
+			sort_order,
+			// sort_by, // TODO: Handle 'relevance'
+			limit,
+			author_id,
+		} = req.query;
 
-	const parsedLimit = Number(limit) || 50;
-	if (parsedLimit < 1 || parsedLimit > 100)
-		throw new HTTPError("limit must be between 1 and 100", 422);
+		const parsedLimit = Number(limit) || 50;
+		if (parsedLimit < 1 || parsedLimit > 100)
+			throw new HTTPError("limit must be between 1 and 100", 422);
 
-	if (sort_order) {
-		if (
-			typeof sort_order != "string" ||
-			["desc", "asc"].indexOf(sort_order) == -1
-		)
-			throw FieldErrors({
-				sort_order: {
-					message: "Value must be one of ('desc', 'asc').",
-					code: "BASE_TYPE_CHOICES",
-				},
-			}); // todo this is wrong
-	}
+		if (sort_order) {
+			if (
+				typeof sort_order != "string" ||
+				["desc", "asc"].indexOf(sort_order) == -1
+			)
+				throw FieldErrors({
+					sort_order: {
+						message: "Value must be one of ('desc', 'asc').",
+						code: "BASE_TYPE_CHOICES",
+					},
+				}); // todo this is wrong
+		}
 
-	const permissions = await getPermission(
-		req.user_id,
-		req.params.guild_id,
-		channel_id as string | undefined,
-	);
-	permissions.hasThrow("VIEW_CHANNEL");
-	if (!permissions.has("READ_MESSAGE_HISTORY"))
-		return res.json({ messages: [], total_results: 0 });
+		const permissions = await getPermission(
+			req.user_id,
+			req.params.guild_id,
+			channel_id as string | undefined,
+		);
+		permissions.hasThrow("VIEW_CHANNEL");
+		if (!permissions.has("READ_MESSAGE_HISTORY"))
+			return res.json({ messages: [], total_results: 0 });
 
-	const query: FindManyOptions<Message> = {
-		order: {
-			timestamp: sort_order
-				? (sort_order.toUpperCase() as "ASC" | "DESC")
-				: "DESC",
-		},
-		take: parsedLimit || 0,
-		where: {
-			guild: {
-				id: req.params.guild_id,
+		const query: FindManyOptions<Message> = {
+			order: {
+				timestamp: sort_order
+					? (sort_order.toUpperCase() as "ASC" | "DESC")
+					: "DESC",
 			},
-		},
-		relations: [
-			"author",
-			"webhook",
-			"application",
-			"mentions",
-			"mention_roles",
-			"mention_channels",
-			"sticker_items",
-			"attachments",
-		],
-		skip: offset ? Number(offset) : 0,
-	};
-	//@ts-ignore
-	if (channel_id) query.where.channel = { id: channel_id };
-	else {
-		// get all channel IDs that this user can access
-		const channels = await Channel.find({
-			where: { guild_id: req.params.guild_id },
-			select: ["id"],
-		});
-		const ids = [];
+			take: parsedLimit || 0,
+			where: {
+				guild: {
+					id: req.params.guild_id,
+				},
+			},
+			relations: [
+				"author",
+				"webhook",
+				"application",
+				"mentions",
+				"mention_roles",
+				"mention_channels",
+				"sticker_items",
+				"attachments",
+			],
+			skip: offset ? Number(offset) : 0,
+		};
+		//@ts-ignore
+		if (channel_id) query.where.channel = { id: channel_id };
+		else {
+			// get all channel IDs that this user can access
+			const channels = await Channel.find({
+				where: { guild_id: req.params.guild_id },
+				select: ["id"],
+			});
+			const ids = [];
 
-		for (const channel of channels) {
-			const perm = await getPermission(
-				req.user_id,
-				req.params.guild_id,
-				channel.id,
-			);
-			if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
-				continue;
-			ids.push(channel.id);
-		}
+			for (const channel of channels) {
+				const perm = await getPermission(
+					req.user_id,
+					req.params.guild_id,
+					channel.id,
+				);
+				if (
+					!perm.has("VIEW_CHANNEL") ||
+					!perm.has("READ_MESSAGE_HISTORY")
+				)
+					continue;
+				ids.push(channel.id);
+			}
 
+			//@ts-ignore
+			query.where.channel = { id: In(ids) };
+		}
+		//@ts-ignore
+		if (author_id) query.where.author = { id: author_id };
 		//@ts-ignore
-		query.where.channel = { id: In(ids) };
-	}
-	//@ts-ignore
-	if (author_id) query.where.author = { id: author_id };
-	//@ts-ignore
-	if (content) query.where.content = Like(`%${content}%`);
+		if (content) query.where.content = Like(`%${content}%`);
 
-	const messages: Message[] = await Message.find(query);
+		const messages: Message[] = await Message.find(query);
 
-	const messagesDto = messages.map((x) => [
-		{
-			id: x.id,
-			type: x.type,
-			content: x.content,
-			channel_id: x.channel_id,
-			author: {
-				id: x.author?.id,
-				username: x.author?.username,
-				avatar: x.author?.avatar,
-				avatar_decoration: null,
-				discriminator: x.author?.discriminator,
-				public_flags: x.author?.public_flags,
+		const messagesDto = messages.map((x) => [
+			{
+				id: x.id,
+				type: x.type,
+				content: x.content,
+				channel_id: x.channel_id,
+				author: {
+					id: x.author?.id,
+					username: x.author?.username,
+					avatar: x.author?.avatar,
+					avatar_decoration: null,
+					discriminator: x.author?.discriminator,
+					public_flags: x.author?.public_flags,
+				},
+				attachments: x.attachments,
+				embeds: x.embeds,
+				mentions: x.mentions,
+				mention_roles: x.mention_roles,
+				pinned: x.pinned,
+				mention_everyone: x.mention_everyone,
+				tts: x.tts,
+				timestamp: x.timestamp,
+				edited_timestamp: x.edited_timestamp,
+				flags: x.flags,
+				components: x.components,
+				hit: true,
 			},
-			attachments: x.attachments,
-			embeds: x.embeds,
-			mentions: x.mentions,
-			mention_roles: x.mention_roles,
-			pinned: x.pinned,
-			mention_everyone: x.mention_everyone,
-			tts: x.tts,
-			timestamp: x.timestamp,
-			edited_timestamp: x.edited_timestamp,
-			flags: x.flags,
-			components: x.components,
-			hit: true,
-		},
-	]);
+		]);
 
-	return res.json({
-		messages: messagesDto,
-		total_results: messages.length,
-	});
-});
+		return res.json({
+			messages: messagesDto,
+			total_results: messages.length,
+		});
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts
index 32de8653..60526259 100644
--- a/src/api/routes/guilds/#guild_id/profile/index.ts
+++ b/src/api/routes/guilds/#guild_id/profile/index.ts
@@ -31,7 +31,20 @@ const router = Router();
 
 router.patch(
 	"/:member_id",
-	route({ requestBody: "MemberChangeProfileSchema" }),
+	route({
+		requestBody: "MemberChangeProfileSchema",
+		responses: {
+			200: {
+				body: "Member",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		// const member_id =
diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index dbed546b..92ea91fc 100644
--- a/src/api/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -16,10 +16,10 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Router, Request, Response } from "express";
-import { Guild, Member, Snowflake } from "@spacebar/util";
-import { LessThan, IsNull } from "typeorm";
 import { route } from "@spacebar/api";
+import { Guild, Member, Snowflake } from "@spacebar/util";
+import { Request, Response, Router } from "express";
+import { IsNull, LessThan } from "typeorm";
 const router = Router();
 
 //Returns all inactive members, respecting role hierarchy
@@ -80,25 +80,46 @@ export const inactiveMembers = async (
 	return members;
 };
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const days = parseInt(req.query.days as string);
+router.get(
+	"/",
+	route({
+		responses: {
+			"200": {
+				body: "GuildPruneResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const days = parseInt(req.query.days as string);
 
-	let roles = req.query.include_roles;
-	if (typeof roles === "string") roles = [roles]; //express will return array otherwise
+		let roles = req.query.include_roles;
+		if (typeof roles === "string") roles = [roles]; //express will return array otherwise
 
-	const members = await inactiveMembers(
-		req.params.guild_id,
-		req.user_id,
-		days,
-		roles as string[],
-	);
+		const members = await inactiveMembers(
+			req.params.guild_id,
+			req.user_id,
+			days,
+			roles as string[],
+		);
 
-	res.send({ pruned: members.length });
-});
+		res.send({ pruned: members.length });
+	},
+);
 
 router.post(
 	"/",
-	route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }),
+	route({
+		permission: "KICK_MEMBERS",
+		right: "KICK_BAN_MEMBERS",
+		responses: {
+			200: {
+				body: "GuildPurgeResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const days = parseInt(req.body.days);
 
diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index de1e8769..166c9625 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -16,22 +16,35 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
+import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
 import { Guild } from "@spacebar/util";
 import { Request, Response, Router } from "express";
-import { getVoiceRegions, route, getIpAdress } from "@spacebar/api";
 
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-	//TODO we should use an enum for guild's features and not hardcoded strings
-	return res.json(
-		await getVoiceRegions(
-			getIpAdress(req),
-			guild.features.includes("VIP_REGIONS"),
-		),
-	);
-});
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildVoiceRegionsResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+		const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+		//TODO we should use an enum for guild's features and not hardcoded strings
+		return res.json(
+			await getVoiceRegions(
+				getIpAdress(req),
+				guild.features.includes("VIP_REGIONS"),
+			),
+		);
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index c7f1a8e8..ea1a782a 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -31,16 +31,48 @@ import { HTTPError } from "lambert-server";
 
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id, role_id } = req.params;
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
-	const role = await Role.findOneOrFail({ where: { guild_id, id: role_id } });
-	return res.json(role);
-});
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "Role",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id, role_id } = req.params;
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
+		const role = await Role.findOneOrFail({
+			where: { guild_id, id: role_id },
+		});
+		return res.json(role);
+	},
+);
 
 router.delete(
 	"/",
-	route({ permission: "MANAGE_ROLES" }),
+	route({
+		permission: "MANAGE_ROLES",
+		responses: {
+			204: {},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id, role_id } = req.params;
 		if (role_id === guild_id)
@@ -69,7 +101,24 @@ router.delete(
 
 router.patch(
 	"/",
-	route({ requestBody: "RoleModifySchema", permission: "MANAGE_ROLES" }),
+	route({
+		requestBody: "RoleModifySchema",
+		permission: "MANAGE_ROLES",
+		responses: {
+			200: {
+				body: "Role",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { role_id, guild_id } = req.params;
 		const body = req.body as RoleModifySchema;
diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index 0efafab7..77d84347 100644
--- a/src/api/routes/guilds/#guild_id/roles/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -21,7 +21,6 @@ import {
 	Config,
 	DiscordApiErrors,
 	emitEvent,
-	getPermission,
 	GuildRoleCreateEvent,
 	GuildRoleUpdateEvent,
 	Member,
@@ -47,7 +46,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
 
 router.post(
 	"/",
-	route({ requestBody: "RoleModifySchema", permission: "MANAGE_ROLES" }),
+	route({
+		requestBody: "RoleModifySchema",
+		permission: "MANAGE_ROLES",
+		responses: {
+			200: {
+				body: "Role",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const guild_id = req.params.guild_id;
 		const body = req.body as RoleModifySchema;
@@ -104,14 +117,25 @@ router.post(
 
 router.patch(
 	"/",
-	route({ requestBody: "RolePositionUpdateSchema" }),
+	route({
+		requestBody: "RolePositionUpdateSchema",
+		permission: "MANAGE_ROLES",
+		responses: {
+			200: {
+				body: "GuildRolesResponse",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const body = req.body as RolePositionUpdateSchema;
 
-		const perms = await getPermission(req.user_id, guild_id);
-		perms.hasThrow("MANAGE_ROLES");
-
 		await Promise.all(
 			body.map(async (x) =>
 				Role.update({ guild_id, id: x.id }, { position: x.position }),
diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 2e9470ec..38b10e7d 100644
--- a/src/api/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -33,12 +33,25 @@ import { HTTPError } from "lambert-server";
 import multer from "multer";
 const router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildStickersResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	res.json(await Sticker.find({ where: { guild_id } }));
-});
+		res.json(await Sticker.find({ where: { guild_id } }));
+	},
+);
 
 const bodyParser = multer({
 	limits: {
@@ -55,6 +68,17 @@ router.post(
 	route({
 		permission: "MANAGE_EMOJIS_AND_STICKERS",
 		requestBody: "ModifyGuildStickerSchema",
+		responses: {
+			200: {
+				body: "Sticker",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		if (!req.file) throw new HTTPError("missing file");
@@ -98,20 +122,46 @@ export function getStickerFormat(mime_type: string) {
 	}
 }
 
-router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
-	const { guild_id, sticker_id } = req.params;
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
+router.get(
+	"/:sticker_id",
+	route({
+		responses: {
+			200: {
+				body: "Sticker",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id, sticker_id } = req.params;
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	res.json(
-		await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }),
-	);
-});
+		res.json(
+			await Sticker.findOneOrFail({
+				where: { guild_id, id: sticker_id },
+			}),
+		);
+	},
+);
 
 router.patch(
 	"/:sticker_id",
 	route({
 		requestBody: "ModifyGuildStickerSchema",
 		permission: "MANAGE_EMOJIS_AND_STICKERS",
+		responses: {
+			200: {
+				body: "Sticker",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		const { guild_id, sticker_id } = req.params;
@@ -141,7 +191,15 @@ async function sendStickerUpdateEvent(guild_id: string) {
 
 router.delete(
 	"/:sticker_id",
-	route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+	route({
+		permission: "MANAGE_EMOJIS_AND_STICKERS",
+		responses: {
+			204: {},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id, sticker_id } = req.params;
 
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index cb517083..12b235c7 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -40,19 +40,46 @@ const TemplateGuildProjection: (keyof Guild)[] = [
 	"icon",
 ];
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildTemplatesResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
 
-	const templates = await Template.find({
-		where: { source_guild_id: guild_id },
-	});
+		const templates = await Template.find({
+			where: { source_guild_id: guild_id },
+		});
 
-	return res.json(templates);
-});
+		return res.json(templates);
+	},
+);
 
 router.post(
 	"/",
-	route({ requestBody: "TemplateCreateSchema", permission: "MANAGE_GUILD" }),
+	route({
+		requestBody: "TemplateCreateSchema",
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: {
+				body: "Template",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const guild = await Guild.findOneOrFail({
@@ -80,7 +107,13 @@ router.post(
 
 router.delete(
 	"/:code",
-	route({ permission: "MANAGE_GUILD" }),
+	route({
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: { body: "Template" },
+			403: { body: "APIErrorResponse" },
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { code, guild_id } = req.params;
 
@@ -95,7 +128,13 @@ router.delete(
 
 router.put(
 	"/:code",
-	route({ permission: "MANAGE_GUILD" }),
+	route({
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: { body: "Template" },
+			403: { body: "APIErrorResponse" },
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { code, guild_id } = req.params;
 		const guild = await Guild.findOneOrFail({
@@ -114,7 +153,14 @@ router.put(
 
 router.patch(
 	"/:code",
-	route({ requestBody: "TemplateModifySchema", permission: "MANAGE_GUILD" }),
+	route({
+		requestBody: "TemplateModifySchema",
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: { body: "Template" },
+			403: { body: "APIErrorResponse" },
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { code, guild_id } = req.params;
 		const { name, description } = req.body;
diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index 73620f8b..d271c976 100644
--- a/src/api/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -33,7 +33,20 @@ const InviteRegex = /\W/g;
 
 router.get(
 	"/",
-	route({ permission: "MANAGE_GUILD" }),
+	route({
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: {
+				body: "GuildVanityUrlResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@@ -60,7 +73,21 @@ router.get(
 
 router.patch(
 	"/",
-	route({ requestBody: "VanityUrlSchema", permission: "MANAGE_GUILD" }),
+	route({
+		requestBody: "VanityUrlSchema",
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: {
+				body: "GuildVanityUrlCreateResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const { guild_id } = req.params;
 		const body = req.body as VanityUrlSchema;
diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index ff1bc487..60c69075 100644
--- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -34,7 +34,21 @@ const router = Router();
 
 router.patch(
 	"/",
-	route({ requestBody: "VoiceStateUpdateSchema" }),
+	route({
+		requestBody: "VoiceStateUpdateSchema",
+		responses: {
+			204: {},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const body = req.body as VoiceStateUpdateSchema;
 		const { guild_id } = req.params;
diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index 35320e0c..2a739683 100644
--- a/src/api/routes/guilds/#guild_id/welcome-screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -23,20 +23,42 @@ import { HTTPError } from "lambert-server";
 
 const router: Router = Router();
 
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const guild_id = req.params.guild_id;
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildWelcomeScreen",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const guild_id = req.params.guild_id;
 
-	const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-	await Member.IsInGuildOrFail(req.user_id, guild_id);
+		const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+		await Member.IsInGuildOrFail(req.user_id, guild_id);
 
-	res.json(guild.welcome_screen);
-});
+		res.json(guild.welcome_screen);
+	},
+);
 
 router.patch(
 	"/",
 	route({
 		requestBody: "GuildUpdateWelcomeScreenSchema",
 		permission: "MANAGE_GUILD",
+		responses: {
+			204: {},
+			400: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		const guild_id = req.params.guild_id;
diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index 1799f0be..69b5d48c 100644
--- a/src/api/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -16,10 +16,10 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
+import { random, route } from "@spacebar/api";
+import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util";
 import { Request, Response, Router } from "express";
-import { Permissions, Guild, Invite, Channel, Member } from "@spacebar/util";
 import { HTTPError } from "lambert-server";
-import { random, route } from "@spacebar/api";
 
 const router: Router = Router();
 
@@ -32,77 +32,90 @@ const router: Router = Router();
 
 // https://discord.com/developers/docs/resources/guild#get-guild-widget
 // TODO: Cache the response for a guild for 5 minutes regardless of response
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildWidgetJsonResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
 
-	const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-	if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
+		const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+		if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
 
-	// Fetch existing widget invite for widget channel
-	let invite = await Invite.findOne({
-		where: { channel_id: guild.widget_channel_id },
-	});
+		// Fetch existing widget invite for widget channel
+		let invite = await Invite.findOne({
+			where: { channel_id: guild.widget_channel_id },
+		});
 
-	if (guild.widget_channel_id && !invite) {
-		// Create invite for channel if none exists
-		// TODO: Refactor invite create code to a shared function
-		const max_age = 86400; // 24 hours
-		const expires_at = new Date(max_age * 1000 + Date.now());
+		if (guild.widget_channel_id && !invite) {
+			// Create invite for channel if none exists
+			// TODO: Refactor invite create code to a shared function
+			const max_age = 86400; // 24 hours
+			const expires_at = new Date(max_age * 1000 + Date.now());
 
-		invite = await Invite.create({
-			code: random(),
-			temporary: false,
-			uses: 0,
-			max_uses: 0,
-			max_age: max_age,
-			expires_at,
-			created_at: new Date(),
-			guild_id,
-			channel_id: guild.widget_channel_id,
-		}).save();
-	}
+			invite = await Invite.create({
+				code: random(),
+				temporary: false,
+				uses: 0,
+				max_uses: 0,
+				max_age: max_age,
+				expires_at,
+				created_at: new Date(),
+				guild_id,
+				channel_id: guild.widget_channel_id,
+			}).save();
+		}
 
-	// Fetch voice channels, and the @everyone permissions object
-	const channels: { id: string; name: string; position: number }[] = [];
+		// Fetch voice channels, and the @everyone permissions object
+		const channels: { id: string; name: string; position: number }[] = [];
 
-	(
-		await Channel.find({
-			where: { guild_id: guild_id, type: 2 },
-			order: { position: "ASC" },
-		})
-	).filter((doc) => {
-		// Only return channels where @everyone has the CONNECT permission
-		if (
-			doc.permission_overwrites === undefined ||
-			Permissions.channelPermission(
-				doc.permission_overwrites,
-				Permissions.FLAGS.CONNECT,
-			) === Permissions.FLAGS.CONNECT
-		) {
-			channels.push({
-				id: doc.id,
-				name: doc.name ?? "Unknown channel",
-				position: doc.position ?? 0,
-			});
-		}
-	});
+		(
+			await Channel.find({
+				where: { guild_id: guild_id, type: 2 },
+				order: { position: "ASC" },
+			})
+		).filter((doc) => {
+			// Only return channels where @everyone has the CONNECT permission
+			if (
+				doc.permission_overwrites === undefined ||
+				Permissions.channelPermission(
+					doc.permission_overwrites,
+					Permissions.FLAGS.CONNECT,
+				) === Permissions.FLAGS.CONNECT
+			) {
+				channels.push({
+					id: doc.id,
+					name: doc.name ?? "Unknown channel",
+					position: doc.position ?? 0,
+				});
+			}
+		});
 
-	// Fetch members
-	// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
-	const members = await Member.find({ where: { guild_id: guild_id } });
+		// Fetch members
+		// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
+		const members = await Member.find({ where: { guild_id: guild_id } });
 
-	// Construct object to respond with
-	const data = {
-		id: guild_id,
-		name: guild.name,
-		instant_invite: invite?.code,
-		channels: channels,
-		members: members,
-		presence_count: guild.presence_count,
-	};
+		// Construct object to respond with
+		const data = {
+			id: guild_id,
+			name: guild.name,
+			instant_invite: invite?.code,
+			channels: channels,
+			members: members,
+			presence_count: guild.presence_count,
+		};
 
-	res.set("Cache-Control", "public, max-age=300");
-	return res.json(data);
-});
+		res.set("Cache-Control", "public, max-age=300");
+		return res.json(data);
+	},
+);
 
 export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index 4e975603..c9ba8afc 100644
--- a/src/api/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -18,11 +18,11 @@
 
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
-import { Request, Response, Router } from "express";
-import { Guild } from "@spacebar/util";
-import { HTTPError } from "lambert-server";
 import { route } from "@spacebar/api";
+import { Guild } from "@spacebar/util";
+import { Request, Response, Router } from "express";
 import fs from "fs";
+import { HTTPError } from "lambert-server";
 import path from "path";
 
 const router: Router = Router();
@@ -31,130 +31,178 @@ const router: Router = Router();
 
 // https://discord.com/developers/docs/resources/guild#get-guild-widget-image
 // TODO: Cache the response
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
-
-	const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-	if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
-
-	// Fetch guild information
-	const icon = guild.icon;
-	const name = guild.name;
-	const presence = guild.presence_count + " ONLINE";
-
-	// Fetch parameter
-	const style = req.query.style?.toString() || "shield";
-	if (
-		!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)
-	) {
-		throw new HTTPError(
-			"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
-			400,
-		);
-	}
-
-	// Setup canvas
-	const { createCanvas } = require("canvas");
-	const { loadImage } = require("canvas");
-	const sizeOf = require("image-size");
-
-	// TODO: Widget style templates need Spacebar branding
-	const source = path.join(
-		__dirname,
-		"..",
-		"..",
-		"..",
-		"..",
-		"..",
-		"assets",
-		"widget",
-		`${style}.png`,
-	);
-	if (!fs.existsSync(source)) {
-		throw new HTTPError("Widget template does not exist.", 400);
-	}
-
-	// Create base template image for parameter
-	const { width, height } = await sizeOf(source);
-	const canvas = createCanvas(width, height);
-	const ctx = canvas.getContext("2d");
-	const template = await loadImage(source);
-	ctx.drawImage(template, 0, 0);
-
-	// Add the guild specific information to the template asset image
-	switch (style) {
-		case "shield":
-			ctx.textAlign = "center";
-			await drawText(
-				ctx,
-				73,
-				13,
-				"#FFFFFF",
-				"thin 10px Verdana",
-				presence,
-			);
-			break;
-		case "banner1":
-			if (icon) await drawIcon(ctx, 20, 27, 50, icon);
-			await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
-			await drawText(
-				ctx,
-				83,
-				66,
-				"#C9D2F0FF",
-				"thin 11px Verdana",
-				presence,
-			);
-			break;
-		case "banner2":
-			if (icon) await drawIcon(ctx, 13, 19, 36, icon);
-			await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
-			await drawText(
-				ctx,
-				62,
-				49,
-				"#C9D2F0FF",
-				"thin 11px Verdana",
-				presence,
-			);
-			break;
-		case "banner3":
-			if (icon) await drawIcon(ctx, 20, 20, 50, icon);
-			await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
-			await drawText(
-				ctx,
-				83,
-				58,
-				"#C9D2F0FF",
-				"thin 11px Verdana",
-				presence,
-			);
-			break;
-		case "banner4":
-			if (icon) await drawIcon(ctx, 21, 136, 50, icon);
-			await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
-			await drawText(
-				ctx,
-				84,
-				171,
-				"#C9D2F0FF",
-				"thin 12px Verdana",
-				presence,
-			);
-			break;
-		default:
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {},
+			400: {
+				body: "APIErrorResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
+
+		const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+		if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
+
+		// Fetch guild information
+		const icon = guild.icon;
+		const name = guild.name;
+		const presence = guild.presence_count + " ONLINE";
+
+		// Fetch parameter
+		const style = req.query.style?.toString() || "shield";
+		if (
+			!["shield", "banner1", "banner2", "banner3", "banner4"].includes(
+				style,
+			)
+		) {
 			throw new HTTPError(
 				"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
 				400,
 			);
-	}
-
-	// Return final image
-	const buffer = canvas.toBuffer("image/png");
-	res.set("Content-Type", "image/png");
-	res.set("Cache-Control", "public, max-age=3600");
-	return res.send(buffer);
-});
+		}
+
+		// Setup canvas
+		const { createCanvas } = require("canvas");
+		const { loadImage } = require("canvas");
+		const sizeOf = require("image-size");
+
+		// TODO: Widget style templates need Spacebar branding
+		const source = path.join(
+			__dirname,
+			"..",
+			"..",
+			"..",
+			"..",
+			"..",
+			"assets",
+			"widget",
+			`${style}.png`,
+		);
+		if (!fs.existsSync(source)) {
+			throw new HTTPError("Widget template does not exist.", 400);
+		}
+
+		// Create base template image for parameter
+		const { width, height } = await sizeOf(source);
+		const canvas = createCanvas(width, height);
+		const ctx = canvas.getContext("2d");
+		const template = await loadImage(source);
+		ctx.drawImage(template, 0, 0);
+
+		// Add the guild specific information to the template asset image
+		switch (style) {
+			case "shield":
+				ctx.textAlign = "center";
+				await drawText(
+					ctx,
+					73,
+					13,
+					"#FFFFFF",
+					"thin 10px Verdana",
+					presence,
+				);
+				break;
+			case "banner1":
+				if (icon) await drawIcon(ctx, 20, 27, 50, icon);
+				await drawText(
+					ctx,
+					83,
+					51,
+					"#FFFFFF",
+					"12px Verdana",
+					name,
+					22,
+				);
+				await drawText(
+					ctx,
+					83,
+					66,
+					"#C9D2F0FF",
+					"thin 11px Verdana",
+					presence,
+				);
+				break;
+			case "banner2":
+				if (icon) await drawIcon(ctx, 13, 19, 36, icon);
+				await drawText(
+					ctx,
+					62,
+					34,
+					"#FFFFFF",
+					"12px Verdana",
+					name,
+					15,
+				);
+				await drawText(
+					ctx,
+					62,
+					49,
+					"#C9D2F0FF",
+					"thin 11px Verdana",
+					presence,
+				);
+				break;
+			case "banner3":
+				if (icon) await drawIcon(ctx, 20, 20, 50, icon);
+				await drawText(
+					ctx,
+					83,
+					44,
+					"#FFFFFF",
+					"12px Verdana",
+					name,
+					27,
+				);
+				await drawText(
+					ctx,
+					83,
+					58,
+					"#C9D2F0FF",
+					"thin 11px Verdana",
+					presence,
+				);
+				break;
+			case "banner4":
+				if (icon) await drawIcon(ctx, 21, 136, 50, icon);
+				await drawText(
+					ctx,
+					84,
+					156,
+					"#FFFFFF",
+					"13px Verdana",
+					name,
+					27,
+				);
+				await drawText(
+					ctx,
+					84,
+					171,
+					"#C9D2F0FF",
+					"thin 12px Verdana",
+					presence,
+				);
+				break;
+			default:
+				throw new HTTPError(
+					"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
+					400,
+				);
+		}
+
+		// Return final image
+		const buffer = canvas.toBuffer("image/png");
+		res.set("Content-Type", "image/png");
+		res.set("Cache-Control", "public, max-age=3600");
+		return res.send(buffer);
+	},
+);
 
 async function drawIcon(
 	canvas: any,
diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index 2cacd8d3..cae0d6be 100644
--- a/src/api/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -23,21 +23,48 @@ import { Request, Response, Router } from "express";
 const router: Router = Router();
 
 // https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const { guild_id } = req.params;
+router.get(
+	"/",
+	route({
+		responses: {
+			200: {
+				body: "GuildWidgetSettingsResponse",
+			},
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { guild_id } = req.params;
 
-	const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+		const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
 
-	return res.json({
-		enabled: guild.widget_enabled || false,
-		channel_id: guild.widget_channel_id || null,
-	});
-});
+		return res.json({
+			enabled: guild.widget_enabled || false,
+			channel_id: guild.widget_channel_id || null,
+		});
+	},
+);
 
 // https://discord.com/developers/docs/resources/guild#modify-guild-widget
 router.patch(
 	"/",
-	route({ requestBody: "WidgetModifySchema", permission: "MANAGE_GUILD" }),
+	route({
+		requestBody: "WidgetModifySchema",
+		permission: "MANAGE_GUILD",
+		responses: {
+			200: {
+				body: "WidgetModifySchema",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const body = req.body as WidgetModifySchema;
 		const { guild_id } = req.params;
diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 55fe088e..26173ed5 100644
--- a/src/api/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -33,7 +33,21 @@ const router: Router = Router();
 
 router.post(
 	"/",
-	route({ requestBody: "GuildCreateSchema", right: "CREATE_GUILDS" }),
+	route({
+		requestBody: "GuildCreateSchema",
+		right: "CREATE_GUILDS",
+		responses: {
+			201: {
+				body: "GuildCreateResponse",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
 	async (req: Request, res: Response) => {
 		const body = req.body as GuildCreateSchema;
 
diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 8eff5563..32129cca 100644
--- a/src/api/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -31,53 +31,72 @@ import { Request, Response, Router } from "express";
 import fetch from "node-fetch";
 const router: Router = Router();
 
-router.get("/:code", route({}), async (req: Request, res: Response) => {
-	const { allowDiscordTemplates, allowRaws, enabled } =
-		Config.get().templates;
-	if (!enabled)
-		res.json({
-			code: 403,
-			message: "Template creation & usage is disabled on this instance.",
-		}).sendStatus(403);
-
-	const { code } = req.params;
-
-	if (code.startsWith("discord:")) {
-		if (!allowDiscordTemplates)
-			return res
-				.json({
-					code: 403,
-					message:
-						"Discord templates cannot be used on this instance.",
-				})
-				.sendStatus(403);
-		const discordTemplateID = code.split("discord:", 2)[1];
-
-		const discordTemplateData = await fetch(
-			`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
-			{
-				method: "get",
-				headers: { "Content-Type": "application/json" },
+router.get(
+	"/:code",
+	route({
+		responses: {
+			200: {
+				body: "GuildTemplate",
+			},
+			403: {
+				body: "APIErrorResponse",
 			},
-		);
-		return res.json(await discordTemplateData.json());
-	}
+			404: {
+				body: "APIErrorResponse",
+			},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const { allowDiscordTemplates, allowRaws, enabled } =
+			Config.get().templates;
+		if (!enabled)
+			res.json({
+				code: 403,
+				message:
+					"Template creation & usage is disabled on this instance.",
+			}).sendStatus(403);
 
-	if (code.startsWith("external:")) {
-		if (!allowRaws)
-			return res
-				.json({
-					code: 403,
-					message: "Importing raws is disabled on this instance.",
-				})
-				.sendStatus(403);
+		const { code } = req.params;
 
-		return res.json(code.split("external:", 2)[1]);
-	}
+		if (code.startsWith("discord:")) {
+			if (!allowDiscordTemplates)
+				return res
+					.json({
+						code: 403,
+						message:
+							"Discord templates cannot be used on this instance.",
+					})
+					.sendStatus(403);
+			const discordTemplateID = code.split("discord:", 2)[1];
+
+			const discordTemplateData = await fetch(
+				`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
+				{
+					method: "get",
+					headers: { "Content-Type": "application/json" },
+				},
+			);
+			return res.json(await discordTemplateData.json());
+		}
+
+		if (code.startsWith("external:")) {
+			if (!allowRaws)
+				return res
+					.json({
+						code: 403,
+						message: "Importing raws is disabled on this instance.",
+					})
+					.sendStatus(403);
 
-	const template = await Template.findOneOrFail({ where: { code: code } });
-	res.json(template);
-});
+			return res.json(code.split("external:", 2)[1]);
+		}
+
+		const template = await Template.findOneOrFail({
+			where: { code: code },
+		});
+		res.json(template);
+	},
+);
 
 router.post(
 	"/:code",
diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts
index e8454986..7d7ae1ea 100644
--- a/src/util/entities/Guild.ts
+++ b/src/util/entities/Guild.ts
@@ -24,7 +24,7 @@ import {
 	OneToMany,
 	RelationId,
 } from "typeorm";
-import { Config, handleFile, Snowflake } from "..";
+import { Config, GuildWelcomeScreen, handleFile, Snowflake } from "..";
 import { Ban } from "./Ban";
 import { BaseClass } from "./BaseClass";
 import { Channel } from "./Channel";
@@ -270,16 +270,7 @@ export class Guild extends BaseClass {
 	verification_level?: number;
 
 	@Column({ type: "simple-json" })
-	welcome_screen: {
-		enabled: boolean;
-		description: string;
-		welcome_channels: {
-			description: string;
-			emoji_id?: string;
-			emoji_name?: string;
-			channel_id: string;
-		}[];
-	};
+	welcome_screen: GuildWelcomeScreen;
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.widget_channel)
diff --git a/src/util/interfaces/GuildWelcomeScreen.ts b/src/util/interfaces/GuildWelcomeScreen.ts
new file mode 100644
index 00000000..38b6061b
--- /dev/null
+++ b/src/util/interfaces/GuildWelcomeScreen.ts
@@ -0,0 +1,10 @@
+export interface GuildWelcomeScreen {
+	enabled: boolean;
+	description: string;
+	welcome_channels: {
+		description: string;
+		emoji_id?: string;
+		emoji_name?: string;
+		channel_id: string;
+	}[];
+}
diff --git a/src/util/interfaces/index.ts b/src/util/interfaces/index.ts
index c6a00458..6620ba32 100644
--- a/src/util/interfaces/index.ts
+++ b/src/util/interfaces/index.ts
@@ -19,6 +19,7 @@
 export * from "./Activity";
 export * from "./ConnectedAccount";
 export * from "./Event";
+export * from "./GuildWelcomeScreen";
 export * from "./Interaction";
 export * from "./Presence";
 export * from "./Status";
diff --git a/src/util/schemas/responses/GuildBansResponse.ts b/src/util/schemas/responses/GuildBansResponse.ts
new file mode 100644
index 00000000..876a4bc4
--- /dev/null
+++ b/src/util/schemas/responses/GuildBansResponse.ts
@@ -0,0 +1,10 @@
+export interface GuildBansResponse {
+	reason: string;
+	user: {
+		username: string;
+		discriminator: string;
+		id: string;
+		avatar: string | null;
+		public_flags: number;
+	};
+}
diff --git a/src/util/schemas/responses/GuildChannelsResponse.ts b/src/util/schemas/responses/GuildChannelsResponse.ts
new file mode 100644
index 00000000..3321455d
--- /dev/null
+++ b/src/util/schemas/responses/GuildChannelsResponse.ts
@@ -0,0 +1,3 @@
+import { Channel } from "../../entities";
+
+export type GuildChannelsResponse = Channel[];
diff --git a/src/util/schemas/responses/GuildCreateResponse.ts b/src/util/schemas/responses/GuildCreateResponse.ts
new file mode 100644
index 00000000..8185cb86
--- /dev/null
+++ b/src/util/schemas/responses/GuildCreateResponse.ts
@@ -0,0 +1,3 @@
+export interface GuildCreateResponse {
+	id: string;
+}
diff --git a/src/util/schemas/responses/GuildDiscoveryRequirements.ts b/src/util/schemas/responses/GuildDiscoveryRequirements.ts
new file mode 100644
index 00000000..2d303133
--- /dev/null
+++ b/src/util/schemas/responses/GuildDiscoveryRequirements.ts
@@ -0,0 +1,23 @@
+export interface GuildDiscoveryRequirements {
+	uild_id: string;
+	safe_environment: boolean;
+	healthy: boolean;
+	health_score_pending: boolean;
+	size: boolean;
+	nsfw_properties: unknown;
+	protected: boolean;
+	sufficient: boolean;
+	sufficient_without_grace_period: boolean;
+	valid_rules_channel: boolean;
+	retention_healthy: boolean;
+	engagement_healthy: boolean;
+	age: boolean;
+	minimum_age: number;
+	health_score: {
+		avg_nonnew_participators: number;
+		avg_nonnew_communicators: number;
+		num_intentful_joiners: number;
+		perc_ret_w1_intentful: number;
+	};
+	minimum_size: number;
+}
diff --git a/src/util/schemas/responses/GuildEmojisResponse.ts b/src/util/schemas/responses/GuildEmojisResponse.ts
new file mode 100644
index 00000000..cea6fd55
--- /dev/null
+++ b/src/util/schemas/responses/GuildEmojisResponse.ts
@@ -0,0 +1,3 @@
+import { Emoji } from "../../entities";
+
+export type GuildEmojisResponse = Emoji[];
diff --git a/src/util/schemas/responses/GuildInvitesResponse.ts b/src/util/schemas/responses/GuildInvitesResponse.ts
new file mode 100644
index 00000000..cf9ed9cc
--- /dev/null
+++ b/src/util/schemas/responses/GuildInvitesResponse.ts
@@ -0,0 +1,3 @@
+import { Invite } from "../../entities";
+
+export type GuildInvitesResponse = Invite[];
diff --git a/src/util/schemas/responses/GuildMembersResponse.ts b/src/util/schemas/responses/GuildMembersResponse.ts
new file mode 100644
index 00000000..8d14fd9e
--- /dev/null
+++ b/src/util/schemas/responses/GuildMembersResponse.ts
@@ -0,0 +1,3 @@
+import { Member } from "../../entities";
+
+export type GuildMembersResponse = Member[];
diff --git a/src/util/schemas/responses/GuildMessagesSearchResponse.ts b/src/util/schemas/responses/GuildMessagesSearchResponse.ts
new file mode 100644
index 00000000..0b6248b7
--- /dev/null
+++ b/src/util/schemas/responses/GuildMessagesSearchResponse.ts
@@ -0,0 +1,32 @@
+import {
+	Attachment,
+	Embed,
+	MessageType,
+	PublicUser,
+	Role,
+} from "../../entities";
+
+export interface GuildMessagesSearchMessage {
+	id: string;
+	type: MessageType;
+	content?: string;
+	channel_id: string;
+	author: PublicUser;
+	attachments: Attachment[];
+	embeds: Embed[];
+	mentions: PublicUser[];
+	mention_roles: Role[];
+	pinned: boolean;
+	mention_everyone?: boolean;
+	tts: boolean;
+	timestamp: string;
+	edited_timestamp: string | null;
+	flags: number;
+	components: unknown[];
+	hit: true;
+}
+
+export interface GuildMessagesSearchResponse {
+	messages: GuildMessagesSearchMessage[];
+	total_results: number;
+}
diff --git a/src/util/schemas/responses/GuildPruneResponse.ts b/src/util/schemas/responses/GuildPruneResponse.ts
new file mode 100644
index 00000000..fb1abb89
--- /dev/null
+++ b/src/util/schemas/responses/GuildPruneResponse.ts
@@ -0,0 +1,7 @@
+export interface GuildPruneResponse {
+	pruned: number;
+}
+
+export interface GuildPurgeResponse {
+	purged: number;
+}
diff --git a/src/util/schemas/responses/GuildResponse.ts b/src/util/schemas/responses/GuildResponse.ts
new file mode 100644
index 00000000..00035243
--- /dev/null
+++ b/src/util/schemas/responses/GuildResponse.ts
@@ -0,0 +1,3 @@
+import { Guild } from "../../entities";
+
+export type GuildResponse = Guild & { joined_at: string };
diff --git a/src/util/schemas/responses/GuildRolesResponse.ts b/src/util/schemas/responses/GuildRolesResponse.ts
new file mode 100644
index 00000000..a064cddb
--- /dev/null
+++ b/src/util/schemas/responses/GuildRolesResponse.ts
@@ -0,0 +1,3 @@
+import { Role } from "../../entities";
+
+export type GuildRolesResponse = Role[];
diff --git a/src/util/schemas/responses/GuildStickersResponse.ts b/src/util/schemas/responses/GuildStickersResponse.ts
new file mode 100644
index 00000000..a02f3e55
--- /dev/null
+++ b/src/util/schemas/responses/GuildStickersResponse.ts
@@ -0,0 +1,3 @@
+import { Sticker } from "../../entities";
+
+export type GuildStickersResponse = Sticker[];
diff --git a/src/util/schemas/responses/GuildTemplatesResponse.ts b/src/util/schemas/responses/GuildTemplatesResponse.ts
new file mode 100644
index 00000000..e975fe43
--- /dev/null
+++ b/src/util/schemas/responses/GuildTemplatesResponse.ts
@@ -0,0 +1,3 @@
+import { Template } from "../../entities";
+
+export type GuildTemplatesResponse = Template[];
diff --git a/src/util/schemas/responses/GuildVanityUrl.ts b/src/util/schemas/responses/GuildVanityUrl.ts
new file mode 100644
index 00000000..ff37bf4e
--- /dev/null
+++ b/src/util/schemas/responses/GuildVanityUrl.ts
@@ -0,0 +1,17 @@
+export interface GuildVanityUrl {
+	code: string;
+	uses: number;
+}
+
+export interface GuildVanityUrlNoInvite {
+	code: null;
+}
+
+export type GuildVanityUrlResponse =
+	| GuildVanityUrl
+	| GuildVanityUrl[]
+	| GuildVanityUrlNoInvite;
+
+export interface GuildVanityUrlCreateResponse {
+	code: string;
+}
diff --git a/src/util/schemas/responses/GuildVoiceRegionsResponse.ts b/src/util/schemas/responses/GuildVoiceRegionsResponse.ts
new file mode 100644
index 00000000..c17e2f5d
--- /dev/null
+++ b/src/util/schemas/responses/GuildVoiceRegionsResponse.ts
@@ -0,0 +1,9 @@
+export interface GuildVoiceRegion {
+	id: string;
+	name: string;
+	custom: boolean;
+	deprecated: boolean;
+	optimal: boolean;
+}
+
+export type GuildVoiceRegionsResponse = GuildVoiceRegion[];
diff --git a/src/util/schemas/responses/GuildWidgetJsonResponse.ts b/src/util/schemas/responses/GuildWidgetJsonResponse.ts
new file mode 100644
index 00000000..ef85dd08
--- /dev/null
+++ b/src/util/schemas/responses/GuildWidgetJsonResponse.ts
@@ -0,0 +1,21 @@
+import { ClientStatus } from "../../interfaces";
+
+export interface GuildWidgetJsonResponse {
+	id: string;
+	name: string;
+	instant_invite: string;
+	channels: {
+		id: string;
+		name: string;
+		position: number;
+	}[];
+	members: {
+		id: string;
+		username: string;
+		discriminator: string;
+		avatar: string | null;
+		status: ClientStatus;
+		avatar_url: string;
+	}[];
+	presence_count: number;
+}
diff --git a/src/util/schemas/responses/GuildWidgetSettingsResponse.ts b/src/util/schemas/responses/GuildWidgetSettingsResponse.ts
new file mode 100644
index 00000000..3c6b45ce
--- /dev/null
+++ b/src/util/schemas/responses/GuildWidgetSettingsResponse.ts
@@ -0,0 +1,6 @@
+import { Snowflake } from "../../util";
+
+export interface GuildWidgetSettingsResponse {
+	enabled: boolean;
+	channel_id: Snowflake | null;
+}
diff --git a/src/util/schemas/responses/MemberJoinGuildResponse.ts b/src/util/schemas/responses/MemberJoinGuildResponse.ts
new file mode 100644
index 00000000..d7b39d10
--- /dev/null
+++ b/src/util/schemas/responses/MemberJoinGuildResponse.ts
@@ -0,0 +1,8 @@
+import { Emoji, Guild, Role, Sticker } from "../../entities";
+
+export interface MemberJoinGuildResponse {
+	guild: Guild;
+	emojis: Emoji[];
+	roles: Role[];
+	stickers: Sticker[];
+}
diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts
index 30949f7f..91c889db 100644
--- a/src/util/schemas/responses/index.ts
+++ b/src/util/schemas/responses/index.ts
@@ -12,7 +12,25 @@ export * from "./ChannelWebhooksResponse";
 export * from "./GatewayBotResponse";
 export * from "./GatewayResponse";
 export * from "./GenerateRegistrationTokensResponse";
+export * from "./GuildBansResponse";
+export * from "./GuildChannelsResponse";
+export * from "./GuildCreateResponse";
+export * from "./GuildDiscoveryRequirements";
+export * from "./GuildEmojisResponse";
+export * from "./GuildInvitesResponse";
+export * from "./GuildMembersResponse";
+export * from "./GuildMessagesSearchResponse";
+export * from "./GuildPruneResponse";
+export * from "./GuildResponse";
+export * from "./GuildRolesResponse";
+export * from "./GuildStickersResponse";
+export * from "./GuildTemplatesResponse";
+export * from "./GuildVanityUrl";
+export * from "./GuildVoiceRegionsResponse";
+export * from "./GuildWidgetJsonResponse";
+export * from "./GuildWidgetSettingsResponse";
 export * from "./LocationMetadataResponse";
+export * from "./MemberJoinGuildResponse";
 export * from "./Tenor";
 export * from "./TokenResponse";
 export * from "./UserProfileResponse";