From 66d7b69377ac4ec82fbc1f84cee3a1a00e4dae66 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 23 Oct 2022 13:34:37 +0200
Subject: [PATCH 1/5] server: refactor API handler and returning errors

This refactors the API handler to not use default exports, be async
instead of constructing a promise and modify how errors are returned.
---
 .../backend/src/server/api/api-handler.ts     | 61 +++++++++----------
 packages/backend/src/server/api/index.ts      |  2 +-
 2 files changed, 30 insertions(+), 33 deletions(-)

diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts
index 956096367..f389ab8da 100644
--- a/packages/backend/src/server/api/api-handler.ts
+++ b/packages/backend/src/server/api/api-handler.ts
@@ -5,59 +5,56 @@ import authenticate, { AuthenticationError } from './authenticate.js';
 import call from './call.js';
 import { ApiError } from './error.js';
 
-export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => {
+export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<void> {
 	const body = ctx.is('multipart/form-data')
 		? (ctx.request as any).body
 		: ctx.method === 'GET'
 			? ctx.query
 			: ctx.request.body;
 
-	const reply = (x?: any, y?: ApiError) => {
-		if (x == null) {
-			ctx.status = 204;
-		} else if (typeof x === 'number' && y) {
-			ctx.status = x;
-			ctx.body = {
-				error: {
-					message: y!.message,
-					code: y!.code,
-					id: y!.id,
-					kind: y!.kind,
-					...(y!.info ? { info: y!.info } : {}),
-				},
-			};
-		} else {
-			// 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない
-			ctx.body = typeof x === 'string' ? JSON.stringify(x) : x;
+	const error = (e: ApiError): void => {
+		ctx.status = e.httpStatusCode ?? 500;
+		if (e.httpStatusCode === 401) {
+			ctx.response.set('WWW-Authenticate', 'Bearer');
 		}
-		res();
-	};
+		ctx.body = {
+			error: {
+				message: e!.message,
+				code: e!.code,
+				...(e!.info ? { info: e!.info } : {}),
+				endpoint: endpoint.name,
+			},
+		};
+	}
 
 	// Authentication
 	// for GET requests, do not even pass on the body parameter as it is considered unsafe
-	authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(([user, app]) => {
+	await authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(async ([user, app]) => {
 		// API invoking
-		call(endpoint.name, user, app, body, ctx).then((res: any) => {
+		await call(endpoint.name, user, app, body, ctx).then((res: any) => {
 			if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
 				ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
 			}
-			reply(res);
+			if (res == null) {
+				ctx.status = 204;
+			} else {
+				ctx.status = 200;
+				// If a string is returned, it must be passed through JSON.stringify to be recognized as JSON.
+				ctx.body = typeof res === 'string' ? JSON.stringify(res) : res;
+			}
 		}).catch((e: ApiError) => {
-			reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e);
+			error(e);
 		});
 	}).catch(e => {
 		if (e instanceof AuthenticationError) {
-			ctx.response.status = 403;
-			ctx.response.set('WWW-Authenticate', 'Bearer');
-			ctx.response.body = {
+			error({
 				message: 'Authentication failed: ' + e.message,
 				code: 'AUTHENTICATION_FAILED',
 				id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
-				kind: 'client',
-			};
-			res();
+				httpStatusCode: 401,
+			});
 		} else {
-			reply(500, new ApiError());
+			error(new ApiError());
 		}
 	});
-});
+};
diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts
index 83ece51f5..3f5708022 100644
--- a/packages/backend/src/server/api/index.ts
+++ b/packages/backend/src/server/api/index.ts
@@ -11,7 +11,7 @@ import cors from '@koa/cors';
 import { Instances, AccessTokens, Users } from '@/models/index.js';
 import config from '@/config/index.js';
 import endpoints from './endpoints.js';
-import handler from './api-handler.js';
+import { handler } from './api-handler.js';
 import signup from './private/signup.js';
 import signin from './private/signin.js';
 import signupPending from './private/signup-pending.js';

From 934ee82b8f48d71b2d4f675f1bd2106dee6c5f38 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 20 Oct 2022 23:04:24 +0200
Subject: [PATCH 2/5] server: refactor ApiError to store error descriptions
 centrally

The UUIDs are no longer used for errors and all errors should now have
a descriptive message attached to them. Also, all errors should now have
the proper HTTP status code for a reply instead of the generic 400 and 500
response codes. Because the errors all have more specific error codes, the
"kind" of client or server is also abolished.
---
 .../backend/src/server/api/api-handler.ts     |   9 +-
 packages/backend/src/server/api/call.ts       |  56 +--
 .../backend/src/server/api/common/signup.ts   |  30 +-
 packages/backend/src/server/api/define.ts     |  14 +-
 .../endpoints/admin/announcements/delete.ts   |  10 +-
 .../endpoints/admin/announcements/update.ts   |  10 +-
 .../api/endpoints/admin/drive/show-file.ts    |  12 +-
 .../server/api/endpoints/admin/emoji/add.ts   |  10 +-
 .../server/api/endpoints/admin/emoji/copy.ts  |  14 +-
 .../api/endpoints/admin/emoji/delete.ts       |  10 +-
 .../api/endpoints/admin/emoji/update.ts       |  10 +-
 .../server/api/endpoints/admin/relays/add.ts  |  12 +-
 .../server/api/endpoints/antennas/create.ts   |  22 +-
 .../server/api/endpoints/antennas/delete.ts   |  12 +-
 .../server/api/endpoints/antennas/notes.ts    |  12 +-
 .../src/server/api/endpoints/antennas/show.ts |  12 +-
 .../server/api/endpoints/antennas/update.ts   |  32 +-
 .../src/server/api/endpoints/ap/get.ts        |   3 -
 .../src/server/api/endpoints/ap/show.ts       |  10 +-
 .../src/server/api/endpoints/app/show.ts      |  18 +-
 .../src/server/api/endpoints/auth/accept.ts   |  12 +-
 .../api/endpoints/auth/session/generate.ts    |  10 +-
 .../server/api/endpoints/auth/session/show.ts |  12 +-
 .../api/endpoints/auth/session/userkey.ts     |  32 +-
 .../server/api/endpoints/blocking/create.ts   |  30 +-
 .../server/api/endpoints/blocking/delete.ts   |  34 +-
 .../server/api/endpoints/channels/create.ts   |  12 +-
 .../server/api/endpoints/channels/follow.ts   |  12 +-
 .../src/server/api/endpoints/channels/show.ts |  12 +-
 .../server/api/endpoints/channels/timeline.ts |  12 +-
 .../server/api/endpoints/channels/unfollow.ts |  12 +-
 .../server/api/endpoints/channels/update.ts   |  32 +-
 .../server/api/endpoints/clips/add-note.ts    |  30 +-
 .../src/server/api/endpoints/clips/delete.ts  |  12 +-
 .../src/server/api/endpoints/clips/notes.ts   |  14 +-
 .../server/api/endpoints/clips/remove-note.ts |  31 +-
 .../src/server/api/endpoints/clips/show.ts    |  14 +-
 .../src/server/api/endpoints/clips/update.ts  |  12 +-
 .../endpoints/drive/files/attached-notes.ts   |  12 +-
 .../api/endpoints/drive/files/create.ts       |  12 +-
 .../api/endpoints/drive/files/delete.ts       |  20 +-
 .../server/api/endpoints/drive/files/show.ts  |  20 +-
 .../api/endpoints/drive/files/update.ts       |  38 +-
 .../api/endpoints/drive/folders/create.ts     |  12 +-
 .../api/endpoints/drive/folders/delete.ts     |  20 +-
 .../api/endpoints/drive/folders/show.ts       |  12 +-
 .../api/endpoints/drive/folders/update.ts     |  32 +-
 .../server/api/endpoints/following/create.ts  |  46 +--
 .../server/api/endpoints/following/delete.ts  |  30 +-
 .../api/endpoints/following/invalidate.ts     |  30 +-
 .../endpoints/following/requests/accept.ts    |  17 +-
 .../endpoints/following/requests/cancel.ts    |  18 +-
 .../endpoints/following/requests/reject.ts    |  10 +-
 .../api/endpoints/gallery/posts/create.ts     |   4 -
 .../api/endpoints/gallery/posts/delete.ts     |  12 +-
 .../api/endpoints/gallery/posts/like.ts       |  22 +-
 .../api/endpoints/gallery/posts/show.ts       |  12 +-
 .../api/endpoints/gallery/posts/unlike.ts     |  22 +-
 .../api/endpoints/gallery/posts/update.ts     |   4 -
 .../src/server/api/endpoints/hashtags/show.ts |  12 +-
 .../server/api/endpoints/i/import-blocking.ts |  33 +-
 .../api/endpoints/i/import-following.ts       |  33 +-
 .../server/api/endpoints/i/import-muting.ts   |  33 +-
 .../api/endpoints/i/import-user-lists.ts      |  33 +-
 .../backend/src/server/api/endpoints/i/pin.ts |  26 +-
 .../api/endpoints/i/read-announcement.ts      |  16 +-
 .../api/endpoints/i/registry/get-detail.ts    |  12 +-
 .../server/api/endpoints/i/registry/get.ts    |  12 +-
 .../server/api/endpoints/i/registry/remove.ts |  12 +-
 .../src/server/api/endpoints/i/unpin.ts       |  10 +-
 .../server/api/endpoints/i/update-email.ts    |  24 +-
 .../src/server/api/endpoints/i/update.ts      |  52 +--
 .../server/api/endpoints/i/webhooks/delete.ts |  12 +-
 .../server/api/endpoints/i/webhooks/show.ts   |  12 +-
 .../server/api/endpoints/i/webhooks/update.ts |  13 +-
 .../api/endpoints/messaging/messages.ts       |  30 +-
 .../endpoints/messaging/messages/create.ts    |  64 +--
 .../endpoints/messaging/messages/delete.ts    |  12 +-
 .../api/endpoints/messaging/messages/read.ts  |  16 +-
 .../src/server/api/endpoints/mute/create.ts   |  30 +-
 .../src/server/api/endpoints/mute/delete.ts   |  30 +-
 .../src/server/api/endpoints/notes/clips.ts   |  10 +-
 .../api/endpoints/notes/conversation.ts       |  10 +-
 .../src/server/api/endpoints/notes/create.ts  |  80 +---
 .../src/server/api/endpoints/notes/delete.ts  |  18 +-
 .../api/endpoints/notes/favorites/create.ts   |  20 +-
 .../api/endpoints/notes/favorites/delete.ts   |  20 +-
 .../api/endpoints/notes/global-timeline.ts    |  10 +-
 .../api/endpoints/notes/hybrid-timeline.ts    |  10 +-
 .../api/endpoints/notes/local-timeline.ts     |  10 +-
 .../server/api/endpoints/notes/polls/vote.ts  |  56 +--
 .../server/api/endpoints/notes/reactions.ts   |  10 +-
 .../api/endpoints/notes/reactions/create.ts   |  26 +-
 .../api/endpoints/notes/reactions/delete.ts   |  18 +-
 .../src/server/api/endpoints/notes/renotes.ts |  10 +-
 .../src/server/api/endpoints/notes/search.ts  |   3 -
 .../src/server/api/endpoints/notes/show.ts    |  12 +-
 .../endpoints/notes/thread-muting/create.ts   |  10 +-
 .../endpoints/notes/thread-muting/delete.ts   |  10 +-
 .../server/api/endpoints/notes/translate.ts   |  10 +-
 .../server/api/endpoints/notes/unrenote.ts    |  10 +-
 .../api/endpoints/notes/user-list-timeline.ts |  12 +-
 .../api/endpoints/notes/watching/create.ts    |  10 +-
 .../api/endpoints/notes/watching/delete.ts    |  10 +-
 .../api/endpoints/notifications/create.ts     |   3 -
 .../api/endpoints/notifications/read.ts       |   9 +-
 .../src/server/api/endpoints/page-push.ts     |  12 +-
 .../src/server/api/endpoints/pages/create.ts  |  19 +-
 .../src/server/api/endpoints/pages/delete.ts  |  26 +-
 .../src/server/api/endpoints/pages/like.ts    |  22 +-
 .../src/server/api/endpoints/pages/show.ts    |  12 +-
 .../src/server/api/endpoints/pages/unlike.ts  |  22 +-
 .../src/server/api/endpoints/pages/update.ts  |  45 +--
 .../api/endpoints/renote-mute/create.ts       |  30 +-
 .../api/endpoints/renote-mute/delete.ts       |  30 +-
 .../src/server/api/endpoints/reset-db.ts      |   4 -
 .../server/api/endpoints/reset-password.ts    |  12 +-
 .../server/api/endpoints/users/followers.ts   |  24 +-
 .../server/api/endpoints/users/following.ts   |  24 +-
 .../api/endpoints/users/groups/delete.ts      |  12 +-
 .../users/groups/invitations/accept.ts        |  17 +-
 .../users/groups/invitations/reject.ts        |  17 +-
 .../api/endpoints/users/groups/invite.ts      |  40 +-
 .../api/endpoints/users/groups/leave.ts       |  22 +-
 .../server/api/endpoints/users/groups/pull.ts |  30 +-
 .../server/api/endpoints/users/groups/show.ts |  14 +-
 .../api/endpoints/users/groups/transfer.ts    |  30 +-
 .../api/endpoints/users/groups/update.ts      |  12 +-
 .../api/endpoints/users/lists/delete.ts       |  12 +-
 .../server/api/endpoints/users/lists/pull.ts  |  20 +-
 .../server/api/endpoints/users/lists/push.ts  |  40 +-
 .../server/api/endpoints/users/lists/show.ts  |  12 +-
 .../api/endpoints/users/lists/update.ts       |  12 +-
 .../src/server/api/endpoints/users/notes.ts   |  10 +-
 .../server/api/endpoints/users/reactions.ts   |  10 +-
 .../api/endpoints/users/report-abuse.ts       |  30 +-
 .../src/server/api/endpoints/users/show.ts    |  19 +-
 .../src/server/api/endpoints/users/stats.ts   |  10 +-
 packages/backend/src/server/api/error.ts      | 366 +++++++++++++++++-
 139 files changed, 768 insertions(+), 2241 deletions(-)

diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts
index f389ab8da..e21585158 100644
--- a/packages/backend/src/server/api/api-handler.ts
+++ b/packages/backend/src/server/api/api-handler.ts
@@ -13,7 +13,7 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<vo
 			: ctx.request.body;
 
 	const error = (e: ApiError): void => {
-		ctx.status = e.httpStatusCode ?? 500;
+		ctx.status = e.httpStatusCode;
 		if (e.httpStatusCode === 401) {
 			ctx.response.set('WWW-Authenticate', 'Bearer');
 		}
@@ -47,12 +47,7 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<vo
 		});
 	}).catch(e => {
 		if (e instanceof AuthenticationError) {
-			error({
-				message: 'Authentication failed: ' + e.message,
-				code: 'AUTHENTICATION_FAILED',
-				id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
-				httpStatusCode: 401,
-			});
+			error(new ApiError('AUTHENTICATION_FAILED', e.message));
 		} else {
 			error(new ApiError());
 		}
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index fe1698dbc..0bdbfe633 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -8,29 +8,16 @@ import endpoints, { IEndpointMeta } from './endpoints.js';
 import { ApiError } from './error.js';
 import { apiLogger } from './logger.js';
 
-const accessDenied = {
-	message: 'Access denied.',
-	code: 'ACCESS_DENIED',
-	id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e',
-};
-
 export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
 	const isSecure = user != null && token == null;
 	const isModerator = user != null && (user.isModerator || user.isAdmin);
 
 	const ep = endpoints.find(e => e.name === endpoint);
 
-	if (ep == null) {
-		throw new ApiError({
-			message: 'No such endpoint.',
-			code: 'NO_SUCH_ENDPOINT',
-			id: 'f8080b67-5f9c-4eb7-8c18-7f1eeae8f709',
-			httpStatusCode: 404,
-		});
-	}
+	if (ep == null) throw new ApiError('NO_SUCH_ENDPOINT');
 
 	if (ep.meta.secure && !isSecure) {
-		throw new ApiError(accessDenied);
+		throw new ApiError('ACCESS_DENIED', 'This operation can only be performed with a native token.');
 	}
 
 	if (ep.meta.limit && !isModerator) {
@@ -50,47 +37,28 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 
 		// Rate limit
 		await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => {
-			throw new ApiError({
-				message: 'Rate limit exceeded. Please try again later.',
-				code: 'RATE_LIMIT_EXCEEDED',
-				id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
-				httpStatusCode: 429,
-			});
+			throw new ApiError('RATE_LIMIT_EXCEEDED');
 		});
 	}
 
 	if (ep.meta.requireCredential && user == null) {
-		throw new ApiError({
-			message: 'Credential required.',
-			code: 'CREDENTIAL_REQUIRED',
-			id: '1384574d-a912-4b81-8601-c7b1c4085df1',
-			httpStatusCode: 401,
-		});
+		throw new ApiError('AUTHENTICATION_REQUIRED');
 	}
 
 	if (ep.meta.requireCredential && user!.isSuspended) {
-		throw new ApiError({
-			message: 'Your account has been suspended.',
-			code: 'YOUR_ACCOUNT_SUSPENDED',
-			id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370',
-			httpStatusCode: 403,
-		});
+		throw new ApiError('SUSPENDED');
 	}
 
 	if (ep.meta.requireAdmin && !user!.isAdmin) {
-		throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
+		throw new ApiError('ACCESS_DENIED', 'This operation requires administrator privileges.');
 	}
 
 	if (ep.meta.requireModerator && !isModerator) {
-		throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
+		throw new ApiError('ACCESS_DENIED', 'This operation requires moderator privileges.');
 	}
 
 	if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) {
-		throw new ApiError({
-			message: 'Your app does not have the necessary permissions to use this endpoint.',
-			code: 'PERMISSION_DENIED',
-			id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
-		});
+		throw new ApiError('ACCESS_DENIED', 'This operation requires privileges which this token does not grant.');
 	}
 
 	// Cast non JSON input
@@ -101,11 +69,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 				try {
 					data[k] = JSON.parse(data[k]);
 				} catch (e) {
-					throw	new ApiError({
-						message: 'Invalid param.',
-						code: 'INVALID_PARAM',
-						id: '0b5f1631-7c1a-41a6-b399-cce335f34d85',
-					}, {
+					throw new ApiError('INVALID_PARAM', {
 						param: k,
 						reason: `cannot cast to ${param.type}`,
 					});
@@ -129,7 +93,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 					stack: e.stack,
 				},
 			});
-			throw new ApiError(null, {
+			throw new ApiError('INTERNAL_ERROR', {
 				e: {
 					message: e.message,
 					code: e.name,
diff --git a/packages/backend/src/server/api/common/signup.ts b/packages/backend/src/server/api/common/signup.ts
index 3ac4ac0ba..150d27218 100644
--- a/packages/backend/src/server/api/common/signup.ts
+++ b/packages/backend/src/server/api/common/signup.ts
@@ -24,25 +24,13 @@ export async function signup(opts: {
 
 	// Validate username
 	if (!Users.validateLocalUsername(username)) {
-		throw new ApiError({
-			message: 'This username is invalid.',
-			code: 'INVALID_USERNAME',
-			id: 'ece89f3c-d845-4d9a-850b-1735285e8cd4',
-			kind: 'client',
-			httpStatusCode: 400,
-		});
+		throw new ApiError('INVALID_USERNAME');
 	}
 
 	if (password != null && passwordHash == null) {
 		// Validate password
 		if (!Users.validatePassword(password)) {
-			throw new ApiError({
-				message: 'This password is invalid.',
-				code: 'INVALID_PASSWORD',
-				id: 'a941905b-fe7b-43e2-8ecd-50ad3a2287ab',
-				kind: 'client',
-				httpStatusCode: 400,
-			});
+			throw new ApiError('INVALID_PASSWORD');
 		}
 
 		// Generate hash of password
@@ -53,22 +41,14 @@ export async function signup(opts: {
 	// Generate secret
 	const secret = generateUserToken();
 
-	const duplicateUsernameError = {
-		message: 'This username is not available.',
-		code: 'USED_USERNAME',
-		id: '7ddd595e-6860-4593-93c5-9fdbcb80cd81',
-		kind: 'client',
-		httpStatusCode: 409,
-	};
-
 	// Check username duplication
 	if (await Users.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) {
-		throw new ApiError(duplicateUsernameError);
+		throw new ApiError('USED_USERNAME');
 	}
 
 	// Check deleted username duplication
 	if (await UsedUsernames.findOneBy({ username: username.toLowerCase() })) {
-		throw new ApiError(duplicateUsernameError);
+		throw new ApiError('USED_USERNAME');
 	}
 
 	const keyPair = await new Promise<string[]>((res, rej) =>
@@ -97,7 +77,7 @@ export async function signup(opts: {
 			host: IsNull(),
 		});
 
-		if (exist) throw new ApiError(duplicateUsernameError);
+		if (exist) throw new ApiError('USED_USERNAME');
 
 		account = await transactionalEntityManager.save(new User({
 			id: genId(),
diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts
index 6000357bd..840283957 100644
--- a/packages/backend/src/server/api/define.ts
+++ b/packages/backend/src/server/api/define.ts
@@ -28,22 +28,16 @@ export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, pa
 			fs.unlink(file.path, () => {});
 		}
 
-		if (meta.requireFile && file == null) return Promise.reject(new ApiError({
-			message: 'File required.',
-			code: 'FILE_REQUIRED',
-			id: '4267801e-70d1-416a-b011-4ee502885d8b',
-		}));
+		if (meta.requireFile && file == null) {
+			return Promise.reject(new ApiError('FILE_REQUIRED'));
+		}
 
 		const valid = validate(params);
 		if (!valid) {
 			if (file) cleanup();
 
 			const errors = validate.errors!;
-			const err = new ApiError({
-				message: 'Invalid param.',
-				code: 'INVALID_PARAM',
-				id: '3d81ceae-475f-4600-b2a8-2bc116157532',
-			}, {
+			const err = new ApiError('INVALID_PARAM', {
 				param: errors[0].schemaPath,
 				reason: errors[0].message,
 			});
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
index 0debf2579..656d4c553 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
@@ -8,13 +8,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchAnnouncement: {
-			message: 'No such announcement.',
-			code: 'NO_SUCH_ANNOUNCEMENT',
-			id: 'ecad8040-a276-4e85-bda9-015a708d291e',
-		},
-	},
+	errors: ['NO_SUCH_ANNOUNCEMENT'],
 } as const;
 
 export const paramDef = {
@@ -29,7 +23,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	const announcement = await Announcements.findOneBy({ id: ps.id });
 
-	if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
+	if (announcement == null) throw new ApiError('NO_SUCH_ANNOUNCEMENT');
 
 	await Announcements.delete(announcement.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
index af1fa4568..6cc94ce3b 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
@@ -8,13 +8,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchAnnouncement: {
-			message: 'No such announcement.',
-			code: 'NO_SUCH_ANNOUNCEMENT',
-			id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc',
-		},
-	},
+	errors: ['NO_SUCH_ANNOUNCEMENT'],
 } as const;
 
 export const paramDef = {
@@ -32,7 +26,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	const announcement = await Announcements.findOneBy({ id: ps.id });
 
-	if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
+	if (announcement == null) throw new ApiError('NO_SUCH_ANNOUNCEMENT');
 
 	await Announcements.update(announcement.id, {
 		updatedAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index ebe378c4f..9f36e6f30 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -8,13 +8,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'caf3ca38-c6e5-472e-a30c-b05377dcc240',
-		},
-	},
+	errors: ['NO_SUCH_FILE'],
 
 	res: {
 		type: 'object',
@@ -180,9 +174,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		}],
 	});
 
-	if (file == null) {
-		throw new ApiError(meta.errors.noSuchFile);
-	}
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
 
 	return file;
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 2f612fa7c..ad5b9896c 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -13,13 +13,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
-		},
-	},
+	errors: ['NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -34,7 +28,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) throw new ApiError(meta.errors.noSuchFile);
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
 
 	const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`;
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index e5bbaefe9..a13a47376 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -13,13 +13,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchEmoji: {
-			message: 'No such emoji.',
-			code: 'NO_SUCH_EMOJI',
-			id: 'e2785b66-dca3-4087-9cac-b93c541cc425',
-		},
-	},
+	errors: ['NO_SUCH_EMOJI', 'INTERNAL_ERROR'],
 
 	res: {
 		type: 'object',
@@ -46,9 +40,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	const emoji = await Emojis.findOneBy({ id: ps.emojiId });
 
-	if (emoji == null) {
-		throw new ApiError(meta.errors.noSuchEmoji);
-	}
+	if (emoji == null) throw new ApiError('NO_SUCH_EMOJI');
 
 	let driveFile: DriveFile;
 
@@ -56,7 +48,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		// Create file
 		driveFile = await uploadFromUrl({ url: emoji.originalUrl, user: null, force: true });
 	} catch (e) {
-		throw new ApiError();
+		throw new ApiError('INTERNAL_ERROR', e);
 	}
 
 	const copied = await Emojis.insert({
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index 3cb1402e4..ab80e81bb 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -10,13 +10,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchEmoji: {
-			message: 'No such emoji.',
-			code: 'NO_SUCH_EMOJI',
-			id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2',
-		},
-	},
+	errors: ['NO_SUCH_EMOJI'],
 } as const;
 
 export const paramDef = {
@@ -31,7 +25,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	const emoji = await Emojis.findOneBy({ id: ps.id });
 
-	if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
+	if (emoji == null) throw new ApiError('NO_SUCH_EMOJI');
 
 	await Emojis.delete(emoji.id);
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index 70820e8df..e451374d5 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -9,13 +9,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		noSuchEmoji: {
-			message: 'No such emoji.',
-			code: 'NO_SUCH_EMOJI',
-			id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
-		},
-	},
+	errors: ['NO_SUCH_EMOJI'],
 } as const;
 
 export const paramDef = {
@@ -39,7 +33,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps) => {
 	const emoji = await Emojis.findOneBy({ id: ps.id });
 
-	if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
+	if (emoji == null) throw new ApiError('NO_SUCH_EMOJI');
 
 	await Emojis.update(emoji.id, {
 		updatedAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
index 94f757bdc..147b7298c 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
@@ -9,13 +9,7 @@ export const meta = {
 	requireCredential: true,
 	requireModerator: true,
 
-	errors: {
-		invalidUrl: {
-			message: 'Invalid URL',
-			code: 'INVALID_URL',
-			id: 'fb8c92d3-d4e5-44e7-b3d4-800d5cef8b2c',
-		},
-	},
+	errors: ['INVALID_URL'],
 
 	res: {
 		type: 'object',
@@ -58,8 +52,8 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	try {
 		if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only');
-	} catch {
-		throw new ApiError(meta.errors.invalidUrl);
+	} catch (e) {
+		throw new ApiError('INVALID_URL', e);
 	}
 
 	return await addRelay(ps.inbox);
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index dca31edb1..5fae03808 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -11,19 +11,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchUserList: {
-			message: 'No such user list.',
-			code: 'NO_SUCH_USER_LIST',
-			id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f',
-		},
-
-		noSuchUserGroup: {
-			message: 'No such user group.',
-			code: 'NO_SUCH_USER_GROUP',
-			id: 'aa3c0b9a-8cae-47c0-92ac-202ce5906682',
-		},
-	},
+	errors: ['NO_SUCH_USER_LIST', 'NO_SUCH_GROUP'],
 
 	res: {
 		type: 'object',
@@ -71,18 +59,14 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (userList == null) {
-			throw new ApiError(meta.errors.noSuchUserList);
-		}
+		if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 	} else if (ps.src === 'group' && ps.userGroupId) {
 		userGroupJoining = await UserGroupJoinings.findOneBy({
 			userGroupId: ps.userGroupId,
 			userId: user.id,
 		});
 
-		if (userGroupJoining == null) {
-			throw new ApiError(meta.errors.noSuchUserGroup);
-		}
+		if (userGroupJoining == null) throw new ApiError('NO_SUCH_GROUP');
 	}
 
 	const antenna = await Antennas.insert({
diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts
index d87f9ba00..c3ad26dda 100644
--- a/packages/backend/src/server/api/endpoints/antennas/delete.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchAntenna: {
-			message: 'No such antenna.',
-			code: 'NO_SUCH_ANTENNA',
-			id: 'b34dcf9d-348f-44bb-99d0-6c9314cfe2df',
-		},
-	},
+	errors: ['NO_SUCH_ANTENNA'],
 } as const;
 
 export const paramDef = {
@@ -34,9 +28,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (antenna == null) {
-		throw new ApiError(meta.errors.noSuchAntenna);
-	}
+	if (antenna == null) throw new ApiError('NO_SUCH_ANTENNA');
 
 	await Antennas.delete(antenna.id);
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 0231fcfd6..4dbc824a8 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -14,13 +14,7 @@ export const meta = {
 
 	kind: 'read:account',
 
-	errors: {
-		noSuchAntenna: {
-			message: 'No such antenna.',
-			code: 'NO_SUCH_ANTENNA',
-			id: '850926e0-fd3b-49b6-b69a-b28a5dbd82fe',
-		},
-	},
+	errors: ['NO_SUCH_ANTENNA'],
 
 	res: {
 		type: 'array',
@@ -53,9 +47,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (antenna == null) {
-		throw new ApiError(meta.errors.noSuchAntenna);
-	}
+	if (antenna == null) throw new ApiError('NO_SUCH_ANTENNA');
 
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'),
 		ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts
index 11e6e95c9..76278cd2c 100644
--- a/packages/backend/src/server/api/endpoints/antennas/show.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/show.ts
@@ -9,13 +9,7 @@ export const meta = {
 
 	kind: 'read:account',
 
-	errors: {
-		noSuchAntenna: {
-			message: 'No such antenna.',
-			code: 'NO_SUCH_ANTENNA',
-			id: 'c06569fb-b025-4f23-b22d-1fcd20d2816b',
-		},
-	},
+	errors: ['NO_SUCH_ANTENNA'],
 
 	res: {
 		type: 'object',
@@ -40,9 +34,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (antenna == null) {
-		throw new ApiError(meta.errors.noSuchAntenna);
-	}
+	if (antenna == null) throw new ApiError('NO_SUCH_ANTENNA');
 
 	return await Antennas.pack(antenna);
 });
diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts
index e36696486..1bdafec4c 100644
--- a/packages/backend/src/server/api/endpoints/antennas/update.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/update.ts
@@ -10,25 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchAntenna: {
-			message: 'No such antenna.',
-			code: 'NO_SUCH_ANTENNA',
-			id: '10c673ac-8852-48eb-aa1f-f5b67f069290',
-		},
-
-		noSuchUserList: {
-			message: 'No such user list.',
-			code: 'NO_SUCH_USER_LIST',
-			id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
-		},
-
-		noSuchUserGroup: {
-			message: 'No such user group.',
-			code: 'NO_SUCH_USER_GROUP',
-			id: '109ed789-b6eb-456e-b8a9-6059d567d385',
-		},
-	},
+	errors: ['NO_SUCH_ANTENNA', 'NO_SUCH_USER_LIST', 'NO_SUCH_GROUP'],
 
 	res: {
 		type: 'object',
@@ -74,9 +56,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (antenna == null) {
-		throw new ApiError(meta.errors.noSuchAntenna);
-	}
+	if (antenna == null) throw new ApiError('NO_SUCH_ANTENNA');
 
 	let userList;
 	let userGroupJoining;
@@ -87,18 +67,14 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (userList == null) {
-			throw new ApiError(meta.errors.noSuchUserList);
-		}
+		if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 	} else if (ps.src === 'group' && ps.userGroupId) {
 		userGroupJoining = await UserGroupJoinings.findOneBy({
 			userGroupId: ps.userGroupId,
 			userId: user.id,
 		});
 
-		if (userGroupJoining == null) {
-			throw new ApiError(meta.errors.noSuchUserGroup);
-		}
+		if (userGroupJoining == null) throw new ApiError('NO_SUCH_GROUP');
 	}
 
 	await Antennas.update(antenna.id, {
diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts
index 6e7804953..d20c36e7c 100644
--- a/packages/backend/src/server/api/endpoints/ap/get.ts
+++ b/packages/backend/src/server/api/endpoints/ap/get.ts
@@ -12,9 +12,6 @@ export const meta = {
 		max: 30,
 	},
 
-	errors: {
-	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index cbebf0044..a1b18cab1 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -24,13 +24,7 @@ export const meta = {
 		max: 30,
 	},
 
-	errors: {
-		noSuchObject: {
-			message: 'No such object.',
-			code: 'NO_SUCH_OBJECT',
-			id: 'dc94d745-1262-4e63-a17d-fecaa57efc82',
-		},
-	},
+	errors: ['NO_SUCH_OBJECT'],
 
 	res: {
 		optional: false, nullable: false,
@@ -83,7 +77,7 @@ export default define(meta, paramDef, async (ps, me) => {
 	if (object) {
 		return object;
 	} else {
-		throw new ApiError(meta.errors.noSuchObject);
+		throw new ApiError('NO_SUCH_OBJECT');
 	}
 });
 
diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts
index 3b84fa07b..048848437 100644
--- a/packages/backend/src/server/api/endpoints/app/show.ts
+++ b/packages/backend/src/server/api/endpoints/app/show.ts
@@ -5,13 +5,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	tags: ['app'],
 
-	errors: {
-		noSuchApp: {
-			message: 'No such app.',
-			code: 'NO_SUCH_APP',
-			id: 'dce83913-2dc6-4093-8a7b-71dbb11718a3',
-		},
-	},
+	errors: ['NO_SUCH_APP'],
 
 	res: {
 		type: 'object',
@@ -33,14 +27,12 @@ export default define(meta, paramDef, async (ps, user, token) => {
 	const isSecure = user != null && token == null;
 
 	// Lookup app
-	const ap = await Apps.findOneBy({ id: ps.appId });
+	const app = await Apps.findOneBy({ id: ps.appId });
 
-	if (ap == null) {
-		throw new ApiError(meta.errors.noSuchApp);
-	}
+	if (app == null) throw new ApiError('NO_SUCH_APP');
 
-	return await Apps.pack(ap, user, {
+	return await Apps.pack(app, user, {
 		detail: true,
-		includeSecret: isSecure && (ap.userId === user!.id),
+		includeSecret: isSecure && (app.userId === user!.id),
 	});
 });
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index dac74afcc..691b4a867 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -12,13 +12,7 @@ export const meta = {
 
 	secure: true,
 
-	errors: {
-		noSuchSession: {
-			message: 'No such session.',
-			code: 'NO_SUCH_SESSION',
-			id: '9c72d8de-391a-43c1-9d06-08d29efde8df',
-		},
-	},
+	errors: ['NO_SUCH_SESSION'],
 } as const;
 
 export const paramDef = {
@@ -35,9 +29,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	const session = await AuthSessions
 		.findOneBy({ token: ps.token });
 
-	if (session == null) {
-		throw new ApiError(meta.errors.noSuchSession);
-	}
+	if (session == null) throw new ApiError('NO_SUCH_SESSION');
 
 	// Generate access token
 	const accessToken = secureRndstr(32, true);
diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
index f40d70c2b..eeb51abc6 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
@@ -26,13 +26,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchApp: {
-			message: 'No such app.',
-			code: 'NO_SUCH_APP',
-			id: '92f93e63-428e-4f2f-a5a4-39e1407fe998',
-		},
-	},
+	errors: ['NO_SUCH_APP'],
 } as const;
 
 export const paramDef = {
@@ -51,7 +45,7 @@ export default define(meta, paramDef, async (ps) => {
 	});
 
 	if (app == null) {
-		throw new ApiError(meta.errors.noSuchApp);
+		throw new ApiError('NO_SUCH_APP');
 	}
 
 	// Generate token
diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts
index 91ff9c12e..cd30bfcca 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/show.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts
@@ -7,13 +7,7 @@ export const meta = {
 
 	requireCredential: false,
 
-	errors: {
-		noSuchSession: {
-			message: 'No such session.',
-			code: 'NO_SUCH_SESSION',
-			id: 'bd72c97d-eba7-4adb-a467-f171b8847250',
-		},
-	},
+	errors: ['NO_SUCH_SESSION'],
 
 	res: {
 		type: 'object',
@@ -52,9 +46,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		token: ps.token,
 	});
 
-	if (session == null) {
-		throw new ApiError(meta.errors.noSuchSession);
-	}
+	if (session == null) throw new ApiError('NO_SUCH_SESSION');
 
 	return await AuthSessions.pack(session, user);
 });
diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
index 36c915ea4..3a741db44 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
@@ -24,25 +24,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchApp: {
-			message: 'No such app.',
-			code: 'NO_SUCH_APP',
-			id: 'fcab192a-2c5a-43b7-8ad8-9b7054d8d40d',
-		},
-
-		noSuchSession: {
-			message: 'No such session.',
-			code: 'NO_SUCH_SESSION',
-			id: '5b5a1503-8bc8-4bd0-8054-dc189e8cdcb3',
-		},
-
-		pendingSession: {
-			message: 'This session is not completed yet.',
-			code: 'PENDING_SESSION',
-			id: '8c8a4145-02cc-4cca-8e66-29ba60445a8e',
-		},
-	},
+	errors: ['NO_SUCH_APP', 'NO_SUCH_SESSION', 'PENDING_SESSION'],
 } as const;
 
 export const paramDef = {
@@ -61,9 +43,7 @@ export default define(meta, paramDef, async (ps) => {
 		secret: ps.appSecret,
 	});
 
-	if (app == null) {
-		throw new ApiError(meta.errors.noSuchApp);
-	}
+	if (app == null) throw new ApiError('NO_SUCH_APP');
 
 	// Fetch token
 	const session = await AuthSessions.findOneBy({
@@ -71,13 +51,9 @@ export default define(meta, paramDef, async (ps) => {
 		appId: app.id,
 	});
 
-	if (session == null) {
-		throw new ApiError(meta.errors.noSuchSession);
-	}
+	if (session == null) throw new ApiError('NO_SUCH_SESSION');
 
-	if (session.userId == null) {
-		throw new ApiError(meta.errors.pendingSession);
-	}
+	if (session.userId == null) throw new ApiError('PENDING_SESSION');
 
 	// Lookup access token
 	const accessToken = await AccessTokens.findOneByOrFail({
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index 14d6e121d..7f45ad522 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -17,25 +17,7 @@ export const meta = {
 
 	kind: 'write:blocks',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '7cc4f851-e2f1-4621-9633-ec9e1d00c01e',
-		},
-
-		blockeeIsYourself: {
-			message: 'Blockee is yourself.',
-			code: 'BLOCKEE_IS_YOURSELF',
-			id: '88b19138-f28d-42c0-8499-6a31bbd0fdc6',
-		},
-
-		alreadyBlocking: {
-			message: 'You are already blocking that user.',
-			code: 'ALREADY_BLOCKING',
-			id: '787fed64-acb9-464a-82eb-afbd745b9614',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'BLOCKEE_IS_YOURSELF', 'ALREADY_BLOCKING'],
 
 	res: {
 		type: 'object',
@@ -57,13 +39,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const blocker = await Users.findOneByOrFail({ id: user.id });
 
 	// 自分自身
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.blockeeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('BLOCKEE_IS_YOURSELF');
 
 	// Get blockee
 	const blockee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -73,9 +53,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		blockeeId: blockee.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyBlocking);
-	}
+	if (exist != null) throw new ApiError('ALREADY_BLOCKING');
 
 	await create(blocker, blockee);
 
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index 53efc5cca..5e9ca9368 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -17,25 +17,7 @@ export const meta = {
 
 	kind: 'write:blocks',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '8621d8bf-c358-4303-a066-5ea78610eb3f',
-		},
-
-		blockeeIsYourself: {
-			message: 'Blockee is yourself.',
-			code: 'BLOCKEE_IS_YOURSELF',
-			id: '06f6fac6-524b-473c-a354-e97a40ae6eac',
-		},
-
-		notBlocking: {
-			message: 'You are not blocking that user.',
-			code: 'NOT_BLOCKING',
-			id: '291b2efa-60c6-45c0-9f6a-045c8f9b02cd',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'BLOCKEE_IS_YOURSELF', 'NOT_BLOCKING'],
 
 	res: {
 		type: 'object',
@@ -54,16 +36,14 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
-	const blocker = await Users.findOneByOrFail({ id: user.id });
-
 	// Check if the blockee is yourself
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.blockeeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('BLOCKEE_IS_YOURSELF');
+
+	const blocker = await Users.findOneByOrFail({ id: user.id });
 
 	// Get blockee
 	const blockee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -73,9 +53,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		blockeeId: blockee.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notBlocking);
-	}
+	if (exist == null) throw new ApiError('NOT_BLOCKING');
 
 	// Delete blocking
 	await deleteBlocking(blocker, blockee);
diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts
index ac8d63dd8..4962bd3da 100644
--- a/packages/backend/src/server/api/endpoints/channels/create.ts
+++ b/packages/backend/src/server/api/endpoints/channels/create.ts
@@ -17,13 +17,7 @@ export const meta = {
 		ref: 'Channel',
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050',
-		},
-	},
+	errors: ['NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -45,9 +39,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (banner == null) {
-			throw new ApiError(meta.errors.noSuchFile);
-		}
+		if (banner == null) throw new ApiError('NO_SUCH_FILE');
 	}
 
 	const channel = await Channels.insert({
diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts
index 9107ec4e8..99bf508c8 100644
--- a/packages/backend/src/server/api/endpoints/channels/follow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/follow.ts
@@ -11,13 +11,7 @@ export const meta = {
 
 	kind: 'write:channels',
 
-	errors: {
-		noSuchChannel: {
-			message: 'No such channel.',
-			code: 'NO_SUCH_CHANNEL',
-			id: 'c0031718-d573-4e85-928e-10039f1fbb68',
-		},
-	},
+	errors: ['NO_SUCH_CHANNEL'],
 } as const;
 
 export const paramDef = {
@@ -34,9 +28,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		id: ps.channelId,
 	});
 
-	if (channel == null) {
-		throw new ApiError(meta.errors.noSuchChannel);
-	}
+	if (channel == null) throw new ApiError('NO_SUCH_CHANNEL');
 
 	await ChannelFollowings.insert({
 		id: genId(),
diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts
index 0e55e73a0..c6a063746 100644
--- a/packages/backend/src/server/api/endpoints/channels/show.ts
+++ b/packages/backend/src/server/api/endpoints/channels/show.ts
@@ -13,13 +13,7 @@ export const meta = {
 		ref: 'Channel',
 	},
 
-	errors: {
-		noSuchChannel: {
-			message: 'No such channel.',
-			code: 'NO_SUCH_CHANNEL',
-			id: '6f6c314b-7486-4897-8966-c04a66a02923',
-		},
-	},
+	errors: ['NO_SUCH_CHANNEL'],
 } as const;
 
 export const paramDef = {
@@ -36,9 +30,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		id: ps.channelId,
 	});
 
-	if (channel == null) {
-		throw new ApiError(meta.errors.noSuchChannel);
-	}
+	if (channel == null) throw new ApiError('NO_SUCH_CHANNEL');
 
 	return await Channels.pack(channel, me);
 });
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 90db2db37..99e13d5ab 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -19,13 +19,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchChannel: {
-			message: 'No such channel.',
-			code: 'NO_SUCH_CHANNEL',
-			id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f',
-		},
-	},
+	errors: ['NO_SUCH_CHANNEL'],
 } as const;
 
 export const paramDef = {
@@ -47,9 +41,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		id: ps.channelId,
 	});
 
-	if (channel == null) {
-		throw new ApiError(meta.errors.noSuchChannel);
-	}
+	if (channel == null) throw new ApiError('NO_SUCH_CHANNEL');
 
 	//#region Construct query
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
index 262af1720..47e5d6d9b 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:channels',
 
-	errors: {
-		noSuchChannel: {
-			message: 'No such channel.',
-			code: 'NO_SUCH_CHANNEL',
-			id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6',
-		},
-	},
+	errors: ['NO_SUCH_CHANNEL'],
 } as const;
 
 export const paramDef = {
@@ -33,9 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		id: ps.channelId,
 	});
 
-	if (channel == null) {
-		throw new ApiError(meta.errors.noSuchChannel);
-	}
+	if (channel == null) throw new ApiError('NO_SUCH_CHANNEL');
 
 	await ChannelFollowings.delete({
 		followerId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index 4ca9369d1..33557df12 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -15,25 +15,7 @@ export const meta = {
 		ref: 'Channel',
 	},
 
-	errors: {
-		noSuchChannel: {
-			message: 'No such channel.',
-			code: 'NO_SUCH_CHANNEL',
-			id: 'f9c5467f-d492-4c3c-9a8d-a70dacc86512',
-		},
-
-		accessDenied: {
-			message: 'You do not have edit privilege of the channel.',
-			code: 'ACCESS_DENIED',
-			id: '1fb7cb09-d46a-4fdf-b8df-057788cce513',
-		},
-
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'NO_SUCH_CHANNEL', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -53,13 +35,9 @@ export default define(meta, paramDef, async (ps, me) => {
 		id: ps.channelId,
 	});
 
-	if (channel == null) {
-		throw new ApiError(meta.errors.noSuchChannel);
-	}
+	if (channel == null) throw new ApiError('NO_SUCH_CHANNEL');
 
-	if (channel.userId !== me.id) {
-		throw new ApiError(meta.errors.accessDenied);
-	}
+	if (channel.userId !== me.id) throw new ApiError('ACCESS_DENIED', 'You are not the owner of this channel.');
 
 	// eslint:disable-next-line:no-unnecessary-initializer
 	let banner = undefined;
@@ -69,9 +47,7 @@ export default define(meta, paramDef, async (ps, me) => {
 			userId: me.id,
 		});
 
-		if (banner == null) {
-			throw new ApiError(meta.errors.noSuchFile);
-		}
+		if (banner == null) throw new ApiError('NO_SUCH_FILE');
 	} else if (ps.bannerId === null) {
 		banner = null;
 	}
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index 15828303b..13b432d13 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -11,25 +11,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchClip: {
-			message: 'No such clip.',
-			code: 'NO_SUCH_CLIP',
-			id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf',
-		},
-
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b',
-		},
-
-		alreadyClipped: {
-			message: 'The note has already been clipped.',
-			code: 'ALREADY_CLIPPED',
-			id: '734806c4-542c-463a-9311-15c512803965',
-		},
-	},
+	errors: ['ALREADY_CLIPPED', 'NO_SUCH_CLIP', 'NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -48,12 +30,10 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (clip == null) {
-		throw new ApiError(meta.errors.noSuchClip);
-	}
+	if (clip == null) throw new ApiError('NO_SUCH_CLIP');
 
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
@@ -62,9 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		clipId: clip.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyClipped);
-	}
+	if (exist != null) throw new ApiError('ALREADY_CLIPPED');
 
 	await ClipNotes.insert({
 		id: genId(),
diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts
index a6c10f546..3fd9ee0b1 100644
--- a/packages/backend/src/server/api/endpoints/clips/delete.ts
+++ b/packages/backend/src/server/api/endpoints/clips/delete.ts
@@ -9,13 +9,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchClip: {
-			message: 'No such clip.',
-			code: 'NO_SUCH_CLIP',
-			id: '70ca08ba-6865-4630-b6fb-8494759aa754',
-		},
-	},
+	errors: ['NO_SUCH_CLIP'],
 } as const;
 
 export const paramDef = {
@@ -33,9 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (clip == null) {
-		throw new ApiError(meta.errors.noSuchClip);
-	}
+	if (clip == null) throw new ApiError('NO_SUCH_CLIP');
 
 	await Clips.delete(clip.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index b8614d6e1..8af046d03 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -13,13 +13,7 @@ export const meta = {
 
 	kind: 'read:account',
 
-	errors: {
-		noSuchClip: {
-			message: 'No such clip.',
-			code: 'NO_SUCH_CLIP',
-			id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00',
-		},
-	},
+	errors: ['NO_SUCH_CLIP'],
 
 	res: {
 		type: 'array',
@@ -49,12 +43,10 @@ export default define(meta, paramDef, async (ps, user) => {
 		id: ps.clipId,
 	});
 
-	if (clip == null) {
-		throw new ApiError(meta.errors.noSuchClip);
-	}
+	if (clip == null) throw new ApiError('NO_SUCH_CLIP');
 
 	if (!clip.isPublic && (user == null || (clip.userId !== user.id))) {
-		throw new ApiError(meta.errors.noSuchClip);
+		throw new ApiError('NO_SUCH_CLIP');
 	}
 
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
index 7987553f6..e6b60178e 100644
--- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
@@ -10,26 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchClip: {
-			message: 'No such clip.',
-			code: 'NO_SUCH_CLIP',
-			id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52',
-		},
-
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'aff017de-190e-434b-893e-33a9ff5049d8',
-		},
-
-		notClipped: {
-			message: 'That note is not added to this clip.',
-			code: 'NOT_CLIPPED',
-			id: '6b20c697-6e51-4120-b340-0e47899c7a20',
-			httpStatusCode: 409,
-		},
-	},
+	errors: ['NO_SUCH_CLIP', 'NO_SUCH_NOTE', 'NOT_CLIPPED'],
 } as const;
 
 export const paramDef = {
@@ -48,12 +29,10 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (clip == null) {
-		throw new ApiError(meta.errors.noSuchClip);
-	}
+	if (clip == null) throw new ApiError('NO_SUCH_CLIP');
 
 	const note = await getNote(ps.noteId).catch(e => {
-		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw e;
 	});
 
@@ -62,7 +41,5 @@ export default define(meta, paramDef, async (ps, user) => {
 		clipId: clip.id,
 	});
 
-	if (affected === 0) {
-		throw new ApiError(meta.errors.notClipped);
-	}
+	if (affected === 0) throw new ApiError('NOT_CLIPPED');
 });
diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts
index 88a5c2eeb..942890dfe 100644
--- a/packages/backend/src/server/api/endpoints/clips/show.ts
+++ b/packages/backend/src/server/api/endpoints/clips/show.ts
@@ -9,13 +9,7 @@ export const meta = {
 
 	kind: 'read:account',
 
-	errors: {
-		noSuchClip: {
-			message: 'No such clip.',
-			code: 'NO_SUCH_CLIP',
-			id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20',
-		},
-	},
+	errors: ['NO_SUCH_CLIP'],
 
 	res: {
 		type: 'object',
@@ -39,12 +33,10 @@ export default define(meta, paramDef, async (ps, me) => {
 		id: ps.clipId,
 	});
 
-	if (clip == null) {
-		throw new ApiError(meta.errors.noSuchClip);
-	}
+	if (clip == null) throw new ApiError('NO_SUCH_CLIP');
 
 	if (!clip.isPublic && (me == null || (clip.userId !== me.id))) {
-		throw new ApiError(meta.errors.noSuchClip);
+		throw new ApiError('NO_SUCH_CLIP');
 	}
 
 	return await Clips.pack(clip);
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index 2878042ba..6b26e2b20 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -9,13 +9,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchClip: {
-			message: 'No such clip.',
-			code: 'NO_SUCH_CLIP',
-			id: 'b4d92d70-b216-46fa-9a3f-a8c811699257',
-		},
-	},
+	errors: ['NO_SUCH_CLIP'],
 
 	res: {
 		type: 'object',
@@ -43,9 +37,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (clip == null) {
-		throw new ApiError(meta.errors.noSuchClip);
-	}
+	if (clip == null) throw new ApiError('NO_SUCH_CLIP');
 
 	await Clips.update(clip.id, {
 		name: ps.name,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
index 69718b926..bad04f188 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
@@ -21,13 +21,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'c118ece3-2e4b-4296-99d1-51756e32d232',
-		},
-	},
+	errors: ['NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -46,9 +40,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (file == null) {
-		throw new ApiError(meta.errors.noSuchFile);
-	}
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
 
 	const notes = await Notes.createQueryBuilder('note')
 		.where(':file = ANY(note.fileIds)', { file: file.id })
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index 123eadfce..8cbac7907 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -28,13 +28,7 @@ export const meta = {
 		ref: 'DriveFile',
 	},
 
-	errors: {
-		invalidFileName: {
-			message: 'Invalid file name.',
-			code: 'INVALID_FILE_NAME',
-			id: 'f449b209-0c60-4e51-84d5-29486263bfd4',
-		},
-	},
+	errors: ['INTERNAL_ERROR', 'INVALID_FILE_NAME'],
 } as const;
 
 export const paramDef = {
@@ -60,7 +54,7 @@ export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
 		} else if (name === 'blob') {
 			name = null;
 		} else if (!DriveFiles.validateFileName(name)) {
-			throw new ApiError(meta.errors.invalidFileName);
+			throw new ApiError('INVALID_FILE_NAME');
 		}
 	} else {
 		name = null;
@@ -74,7 +68,7 @@ export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
 		if (e instanceof Error || typeof e === 'string') {
 			apiLogger.error(e);
 		}
-		throw new ApiError();
+		throw new ApiError('INTERNAL_ERROR');
 	} finally {
 		cleanup!();
 	}
diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
index a3f608c9d..c5a8f94b6 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
@@ -13,19 +13,7 @@ export const meta = {
 
 	description: 'Delete an existing drive file.',
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: '908939ec-e52b-4458-b395-1025195cea58',
-		},
-
-		accessDenied: {
-			message: 'Access denied.',
-			code: 'ACCESS_DENIED',
-			id: '5eb8d909-2540-4970-90b8-dd6f86088121',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -40,12 +28,10 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) {
-		throw new ApiError(meta.errors.noSuchFile);
-	}
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
 
 	if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
-		throw new ApiError(meta.errors.accessDenied);
+		throw new ApiError('ACCESS_DENIED');
 	}
 
 	// Delete
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index 92796d552..08ac5ae2d 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -18,19 +18,7 @@ export const meta = {
 		ref: 'DriveFile',
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: '067bc436-2718-4795-b0fb-ecbe43949e31',
-		},
-
-		accessDenied: {
-			message: 'Access denied.',
-			code: 'ACCESS_DENIED',
-			id: '25b73c73-68b1-41d0-bad1-381cfdf6579f',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -69,12 +57,10 @@ export default define(meta, paramDef, async (ps, user) => {
 		});
 	}
 
-	if (file == null) {
-		throw new ApiError(meta.errors.noSuchFile);
-	}
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
 
 	if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
-		throw new ApiError(meta.errors.accessDenied);
+		throw new ApiError('ACCESS_DENIED');
 	}
 
 	return await DriveFiles.pack(file, {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index dedec03e2..3b5e57461 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -12,31 +12,7 @@ export const meta = {
 
 	description: 'Update the properties of a drive file.',
 
-	errors: {
-		invalidFileName: {
-			message: 'Invalid file name.',
-			code: 'INVALID_FILE_NAME',
-			id: '395e7156-f9f0-475e-af89-53c3c23080c2',
-		},
-
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'e7778c7e-3af9-49cd-9690-6dbc3e6c972d',
-		},
-
-		accessDenied: {
-			message: 'Access denied.',
-			code: 'ACCESS_DENIED',
-			id: '01a53b27-82fc-445b-a0c1-b558465a8ed2',
-		},
-
-		noSuchFolder: {
-			message: 'No such folder.',
-			code: 'NO_SUCH_FOLDER',
-			id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'INVALID_FILE_NAME', 'NO_SUCH_FILE', 'NO_SUCH_FOLDER'],
 
 	res: {
 		type: 'object',
@@ -61,17 +37,15 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) {
-		throw new ApiError(meta.errors.noSuchFile);
-	}
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
 
 	if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
-		throw new ApiError(meta.errors.accessDenied);
+		throw new ApiError('ACCESS_DENIED');
 	}
 
 	if (ps.name) file.name = ps.name;
 	if (!DriveFiles.validateFileName(file.name)) {
-		throw new ApiError(meta.errors.invalidFileName);
+		throw new ApiError('INVALID_FILE_NAME');
 	}
 
 	if (ps.comment !== undefined) file.comment = ps.comment;
@@ -87,9 +61,7 @@ export default define(meta, paramDef, async (ps, user) => {
 				userId: user.id,
 			});
 
-			if (folder == null) {
-				throw new ApiError(meta.errors.noSuchFolder);
-			}
+			if (folder == null) throw new ApiError('NO_SUCH_FOLDER');
 
 			file.folderId = folder.id;
 		}
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
index 6cb161a7f..ea4e201ff 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
@@ -11,13 +11,7 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	errors: {
-		noSuchFolder: {
-			message: 'No such folder.',
-			code: 'NO_SUCH_FOLDER',
-			id: '53326628-a00d-40a6-a3cd-8975105c0f95',
-		},
-	},
+	errors: ['NO_SUCH_FOLDER'],
 
 	res: {
 		type: 'object' as const,
@@ -46,9 +40,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (parent == null) {
-			throw new ApiError(meta.errors.noSuchFolder);
-		}
+		if (parent == null) throw new ApiError('NO_SUCH_FOLDER');
 	}
 
 	// Create folder
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
index a4176312e..76aeba323 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
@@ -10,19 +10,7 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	errors: {
-		noSuchFolder: {
-			message: 'No such folder.',
-			code: 'NO_SUCH_FOLDER',
-			id: '1069098f-c281-440f-b085-f9932edbe091',
-		},
-
-		hasChildFilesOrFolders: {
-			message: 'This folder has child files or folders.',
-			code: 'HAS_CHILD_FILES_OR_FOLDERS',
-			id: 'b0fc8a17-963c-405d-bfbc-859a487295e1',
-		},
-	},
+	errors: ['HAS_CHILD_FILES_OR_FOLDERS', 'NO_SUCH_FOLDER'],
 } as const;
 
 export const paramDef = {
@@ -41,9 +29,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (folder == null) {
-		throw new ApiError(meta.errors.noSuchFolder);
-	}
+	if (folder == null) throw new ApiError('NO_SUCH_FOLDER');
 
 	const [childFoldersCount, childFilesCount] = await Promise.all([
 		DriveFolders.countBy({ parentId: folder.id }),
@@ -51,7 +37,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	]);
 
 	if (childFoldersCount !== 0 || childFilesCount !== 0) {
-		throw new ApiError(meta.errors.hasChildFilesOrFolders);
+		throw new ApiError('HAS_CHILD_FILES_OR_FOLDERS');
 	}
 
 	await DriveFolders.delete(folder.id);
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts
index afb448214..9c8734227 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts
@@ -15,13 +15,7 @@ export const meta = {
 		ref: 'DriveFolder',
 	},
 
-	errors: {
-		noSuchFolder: {
-			message: 'No such folder.',
-			code: 'NO_SUCH_FOLDER',
-			id: 'd74ab9eb-bb09-4bba-bf24-fb58f761e1e9',
-		},
-	},
+	errors: ['NO_SUCH_FOLDER'],
 } as const;
 
 export const paramDef = {
@@ -40,9 +34,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (folder == null) {
-		throw new ApiError(meta.errors.noSuchFolder);
-	}
+	if (folder == null) throw new ApiError('NO_SUCH_FOLDER');
 
 	return await DriveFolders.pack(folder, {
 		detail: true,
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
index a87420042..8813456bb 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
@@ -10,25 +10,7 @@ export const meta = {
 
 	kind: 'write:drive',
 
-	errors: {
-		noSuchFolder: {
-			message: 'No such folder.',
-			code: 'NO_SUCH_FOLDER',
-			id: 'f7974dac-2c0d-4a27-926e-23583b28e98e',
-		},
-
-		noSuchParentFolder: {
-			message: 'No such parent folder.',
-			code: 'NO_SUCH_PARENT_FOLDER',
-			id: 'ce104e3a-faaf-49d5-b459-10ff0cbbcaa1',
-		},
-
-		recursiveNesting: {
-			message: 'It can not be structured like nesting folders recursively.',
-			code: 'NO_SUCH_PARENT_FOLDER',
-			id: 'ce104e3a-faaf-49d5-b459-10ff0cbbcaa1',
-		},
-	},
+	errors: ['NO_SUCH_FOLDER', 'NO_SUCH_PARENT_FOLDER', 'RECURSIVE_FOLDER'],
 
 	res: {
 		type: 'object',
@@ -55,15 +37,13 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (folder == null) {
-		throw new ApiError(meta.errors.noSuchFolder);
-	}
+	if (folder == null) throw new ApiError('NO_SUCH_FOLDER');
 
 	if (ps.name) folder.name = ps.name;
 
 	if (ps.parentId !== undefined) {
 		if (ps.parentId === folder.id) {
-			throw new ApiError(meta.errors.recursiveNesting);
+			throw new ApiError('RECURSIVE_FOLDER');
 		} else if (ps.parentId === null) {
 			folder.parentId = null;
 		} else {
@@ -73,9 +53,7 @@ export default define(meta, paramDef, async (ps, user) => {
 				userId: user.id,
 			});
 
-			if (parent == null) {
-				throw new ApiError(meta.errors.noSuchParentFolder);
-			}
+			if (parent == null) throw new ApiError('NO_SUCH_PARENT_FOLDER');
 
 			// Check if the circular reference will occur
 			async function checkCircle(folderId: string): Promise<boolean> {
@@ -95,7 +73,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 			if (parent.parentId !== null) {
 				if (await checkCircle(parent.parentId)) {
-					throw new ApiError(meta.errors.recursiveNesting);
+					throw new ApiError('RECURSIVE_FOLDER');
 				}
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 1bca40f16..90ec95172 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -18,37 +18,7 @@ export const meta = {
 
 	kind: 'write:following',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
-		},
-
-		followeeIsYourself: {
-			message: 'Followee is yourself.',
-			code: 'FOLLOWEE_IS_YOURSELF',
-			id: '26fbe7bb-a331-4857-af17-205b426669a9',
-		},
-
-		alreadyFollowing: {
-			message: 'You are already following that user.',
-			code: 'ALREADY_FOLLOWING',
-			id: '35387507-38c7-4cb9-9197-300b93783fa0',
-		},
-
-		blocking: {
-			message: 'You are blocking that user.',
-			code: 'BLOCKING',
-			id: '4e2206ec-aa4f-4960-b865-6c23ac38e2d9',
-		},
-
-		blocked: {
-			message: 'You are blocked by that user.',
-			code: 'BLOCKED',
-			id: 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0',
-		},
-	},
+	errors: ['ALREADY_FOLLOWING', 'BLOCKING', 'BLOCKED', 'FOLLOWEE_IS_YOURSELF', 'NO_SUCH_USER'],
 
 	res: {
 		type: 'object',
@@ -70,13 +40,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const follower = user;
 
 	// 自分自身
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.followeeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('FOLLOWEE_IS_YOURSELF');
 
 	// Get followee
 	const followee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -86,16 +54,14 @@ export default define(meta, paramDef, async (ps, user) => {
 		followeeId: followee.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyFollowing);
-	}
+	if (exist != null) throw new ApiError('ALREADY_FOLLOWING');
 
 	try {
 		await create(follower, followee);
 	} catch (e) {
 		if (e instanceof IdentifiableError) {
-			if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking);
-			if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked);
+			if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError('BLOCKING');
+			if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError('BLOCKED');
 		}
 		throw e;
 	}
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index 3f9c217de..72c1ce6c6 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -17,25 +17,7 @@ export const meta = {
 
 	kind: 'write:following',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8',
-		},
-
-		followeeIsYourself: {
-			message: 'Followee is yourself.',
-			code: 'FOLLOWEE_IS_YOURSELF',
-			id: 'd9e400b9-36b0-4808-b1d8-79e707f1296c',
-		},
-
-		notFollowing: {
-			message: 'You are not following that user.',
-			code: 'NOT_FOLLOWING',
-			id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09',
-		},
-	},
+	errors: ['FOLLOWEE_IS_YOURSELF', 'NO_SUCH_USER', 'NOT_FOLLOWING'],
 
 	res: {
 		type: 'object',
@@ -57,13 +39,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const follower = user;
 
 	// Check if the followee is yourself
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.followeeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('FOLLOWEE_IS_YOURSELF');
 
 	// Get followee
 	const followee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -73,9 +53,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		followeeId: followee.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notFollowing);
-	}
+	if (exist == null) throw new ApiError('NOT_FOLLOWING');
 
 	await deleteFollowing(follower, followee);
 
diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts
index b9e84d81a..437d20ae7 100644
--- a/packages/backend/src/server/api/endpoints/following/invalidate.ts
+++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts
@@ -17,25 +17,7 @@ export const meta = {
 
 	kind: 'write:following',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8',
-		},
-
-		followerIsYourself: {
-			message: 'Follower is yourself.',
-			code: 'FOLLOWER_IS_YOURSELF',
-			id: '07dc03b9-03da-422d-885b-438313707662',
-		},
-
-		notFollowing: {
-			message: 'The other use is not following you.',
-			code: 'NOT_FOLLOWING',
-			id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09',
-		},
-	},
+	errors: ['FOLLOWER_IS_YOURSELF', 'NO_SUCH_USER', 'NOT_FOLLOWING'],
 
 	res: {
 		type: 'object',
@@ -57,13 +39,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const followee = user;
 
 	// Check if the follower is yourself
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.followerIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('FOLLOWER_IS_YOURSELF');
 
 	// Get follower
 	const follower = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -73,9 +53,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		followeeId: followee.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notFollowing);
-	}
+	if (exist == null) throw new ApiError('NOT_FOLLOWING');
 
 	await deleteFollowing(follower, followee);
 
diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts
index e5df55375..cd30eab98 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts
@@ -10,18 +10,7 @@ export const meta = {
 
 	kind: 'write:following',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '66ce1645-d66c-46bb-8b79-96739af885bd',
-		},
-		noFollowRequest: {
-			message: 'No follow request.',
-			code: 'NO_FOLLOW_REQUEST',
-			id: 'bcde4f8b-0913-4614-8881-614e522fb041',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'NO_SUCH_FOLLOW_REQUEST'],
 } as const;
 
 export const paramDef = {
@@ -36,12 +25,12 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	// Fetch follower
 	const follower = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
 	await acceptFollowRequest(user, follower).catch(e => {
-		if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError(meta.errors.noFollowRequest);
+		if (e.id === '8884c2dd-5795-4ac9-b27e-6a01d38190f9') throw new ApiError('NO_SUCH_FOLLOW_REQUEST');
 		throw e;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
index 5f3a9c691..3827007f1 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
@@ -12,19 +12,7 @@ export const meta = {
 
 	kind: 'write:following',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '4e68c551-fc4c-4e46-bb41-7d4a37bf9dab',
-		},
-
-		followRequestNotFound: {
-			message: 'Follow request not found.',
-			code: 'FOLLOW_REQUEST_NOT_FOUND',
-			id: '089b125b-d338-482a-9a09-e2622ac9f8d4',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'NO_SUCH_FOLLOW_REQUEST'],
 
 	res: {
 		type: 'object',
@@ -45,7 +33,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	// Fetch followee
 	const followee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -53,7 +41,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		await cancelFollowRequest(followee, user);
 	} catch (e) {
 		if (e instanceof IdentifiableError) {
-			if (e.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError(meta.errors.followRequestNotFound);
+			if (e.id === '17447091-ce07-46dd-b331-c1fd4f15b1e7') throw new ApiError('NO_SUCH_FOLLOW_REQUEST');
 		}
 		throw e;
 	}
diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts
index cebe60428..8de83d508 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:following',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'abc2ffa6-25b2-4380-ba99-321ff3a94555',
-		},
-	},
+	errors: ['NO_SUCH_USER'],
 } as const;
 
 export const paramDef = {
@@ -31,7 +25,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	// Fetch follower
 	const follower = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index 6757dba1f..a22b603a5 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -22,10 +22,6 @@ export const meta = {
 		optional: false, nullable: false,
 		ref: 'GalleryPost',
 	},
-
-	errors: {
-
-	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
index 83bae3af8..65c0e62d0 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
@@ -9,13 +9,7 @@ export const meta = {
 
 	kind: 'write:gallery',
 
-	errors: {
-		noSuchPost: {
-			message: 'No such post.',
-			code: 'NO_SUCH_POST',
-			id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5',
-		},
-	},
+	errors: ['NO_SUCH_POST'],
 } as const;
 
 export const paramDef = {
@@ -33,9 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (post == null) {
-		throw new ApiError(meta.errors.noSuchPost);
-	}
+	if (post == null) throw new ApiError('NO_SUCH_POST');
 
 	await GalleryPosts.delete(post.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index 60ae30710..3e0eda503 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -10,19 +10,7 @@ export const meta = {
 
 	kind: 'write:gallery-likes',
 
-	errors: {
-		noSuchPost: {
-			message: 'No such post.',
-			code: 'NO_SUCH_POST',
-			id: '56c06af3-1287-442f-9701-c93f7c4a62ff',
-		},
-
-		alreadyLiked: {
-			message: 'The post has already been liked.',
-			code: 'ALREADY_LIKED',
-			id: '40e9ed56-a59c-473a-bf3f-f289c54fb5a7',
-		},
-	},
+	errors: ['NO_SUCH_POST', 'ALREADY_LIKED'],
 } as const;
 
 export const paramDef = {
@@ -36,9 +24,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const post = await GalleryPosts.findOneBy({ id: ps.postId });
-	if (post == null) {
-		throw new ApiError(meta.errors.noSuchPost);
-	}
+	if (post == null) throw new ApiError('NO_SUCH_POST');
 
 	// if already liked
 	const exist = await GalleryLikes.findOneBy({
@@ -46,9 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyLiked);
-	}
+	if (exist != null) throw new ApiError('ALREADY_LIKED');
 
 	// Create like
 	await GalleryLikes.insert({
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
index 5fa28b48b..640048028 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
@@ -7,13 +7,7 @@ export const meta = {
 
 	requireCredential: false,
 
-	errors: {
-		noSuchPost: {
-			message: 'No such post.',
-			code: 'NO_SUCH_POST',
-			id: '1137bf14-c5b0-4604-85bb-5b5371b1cd45',
-		},
-	},
+	errors: ['NO_SUCH_POST'],
 
 	res: {
 		type: 'object',
@@ -36,9 +30,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		id: ps.postId,
 	});
 
-	if (post == null) {
-		throw new ApiError(meta.errors.noSuchPost);
-	}
+	if (post == null) throw new ApiError('NO_SUCH_POST');
 
 	return await GalleryPosts.pack(post, me);
 });
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
index fd1a10f2b..61d71905e 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
@@ -9,19 +9,7 @@ export const meta = {
 
 	kind: 'write:gallery-likes',
 
-	errors: {
-		noSuchPost: {
-			message: 'No such post.',
-			code: 'NO_SUCH_POST',
-			id: 'c32e6dd0-b555-4413-925e-b3757d19ed84',
-		},
-
-		notLiked: {
-			message: 'You have not liked that post.',
-			code: 'NOT_LIKED',
-			id: 'e3e8e06e-be37-41f7-a5b4-87a8250288f0',
-		},
-	},
+	errors: ['NO_SUCH_POST', 'NOT_LIKED'],
 } as const;
 
 export const paramDef = {
@@ -35,18 +23,14 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const post = await GalleryPosts.findOneBy({ id: ps.postId });
-	if (post == null) {
-		throw new ApiError(meta.errors.noSuchPost);
-	}
+	if (post == null) throw new ApiError('NO_SUCH_POST');
 
 	const exist = await GalleryLikes.findOneBy({
 		postId: post.id,
 		userId: user.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notLiked);
-	}
+	if (exist == null) throw new ApiError('NOT_LIKED');
 
 	// Delete like
 	await GalleryLikes.delete(exist.id);
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index 94be32b02..20cab9243 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -20,10 +20,6 @@ export const meta = {
 		optional: false, nullable: false,
 		ref: 'GalleryPost',
 	},
-
-	errors: {
-
-	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts
index ae3d1449f..e66661a68 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/show.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts
@@ -14,13 +14,7 @@ export const meta = {
 		ref: 'Hashtag',
 	},
 
-	errors: {
-		noSuchHashtag: {
-			message: 'No such hashtag.',
-			code: 'NO_SUCH_HASHTAG',
-			id: '110ee688-193e-4a3a-9ecf-c167b2e6981e',
-		},
-	},
+	errors: ['NO_SUCH_HASHTAG'],
 } as const;
 
 export const paramDef = {
@@ -34,9 +28,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const hashtag = await Hashtags.findOneBy({ name: normalizeForSearch(ps.tag) });
-	if (hashtag == null) {
-		throw new ApiError(meta.errors.noSuchHashtag);
-	}
+	if (hashtag == null) throw new ApiError('NO_SUCH_HASHTAG');
 
 	return await Hashtags.pack(hashtag);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index f3ea6a42f..ed5ae4259 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -13,31 +13,7 @@ export const meta = {
 		max: 1,
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'ebb53e5f-6574-9c0c-0b92-7ca6def56d7e',
-		},
-
-		unexpectedFileType: {
-			message: 'We need csv file.',
-			code: 'UNEXPECTED_FILE_TYPE',
-			id: 'b6fab7d6-d945-d67c-dfdb-32da1cd12cfe',
-		},
-
-		tooBigFile: {
-			message: 'That file is too big.',
-			code: 'TOO_BIG_FILE',
-			id: 'b7fbf0b1-aeef-3b21-29ef-fadd4cb72ccf',
-		},
-
-		emptyFile: {
-			message: 'That file is empty.',
-			code: 'EMPTY_FILE',
-			id: '6f3a4dcc-f060-a707-4950-806fbdbe60d6',
-		},
-	},
+	errors: ['EMPTY_FILE', 'FILE_TOO_BIG', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -52,10 +28,9 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) throw new ApiError(meta.errors.noSuchFile);
-	//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-	if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
-	if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+	if (file == null) throw new ApiError('EMPTY_FILE');
+	if (file.size > 50000) throw new ApiError('FILE_TOO_BIG');
+	if (file.size === 0) throw new ApiError('EMPTY_FILE');
 
 	createImportBlockingJob(user, file.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index e5a4f1adf..b0d1a2dba 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -12,31 +12,7 @@ export const meta = {
 		max: 1,
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'b98644cf-a5ac-4277-a502-0b8054a709a3',
-		},
-
-		unexpectedFileType: {
-			message: 'We need csv file.',
-			code: 'UNEXPECTED_FILE_TYPE',
-			id: '660f3599-bce0-4f95-9dde-311fd841c183',
-		},
-
-		tooBigFile: {
-			message: 'That file is too big.',
-			code: 'TOO_BIG_FILE',
-			id: 'dee9d4ed-ad07-43ed-8b34-b2856398bc60',
-		},
-
-		emptyFile: {
-			message: 'That file is empty.',
-			code: 'EMPTY_FILE',
-			id: '31a1b42c-06f7-42ae-8a38-a661c5c9f691',
-		},
-	},
+	errors: ['EMPTY_FILE', 'FILE_TOO_BIG', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -51,10 +27,9 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) throw new ApiError(meta.errors.noSuchFile);
-	//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-	if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
-	if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
+	if (file.size > 50000) throw new ApiError('FILE_TOO_BIG');
+	if (file.size === 0) throw new ApiError('EMPTY_FILE');
 
 	createImportFollowingJob(user, file.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 29acb5638..37149408b 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -13,31 +13,7 @@ export const meta = {
 		max: 1,
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'e674141e-bd2a-ba85-e616-aefb187c9c2a',
-		},
-
-		unexpectedFileType: {
-			message: 'We need csv file.',
-			code: 'UNEXPECTED_FILE_TYPE',
-			id: '568c6e42-c86c-ba09-c004-517f83f9f1a8',
-		},
-
-		tooBigFile: {
-			message: 'That file is too big.',
-			code: 'TOO_BIG_FILE',
-			id: '9b4ada6d-d7f7-0472-0713-4f558bd1ec9c',
-		},
-
-		emptyFile: {
-			message: 'That file is empty.',
-			code: 'EMPTY_FILE',
-			id: 'd2f12af1-e7b4-feac-86a3-519548f2728e',
-		},
-	},
+	errors: ['EMPTY_FILE', 'FILE_TOO_BIG', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -52,10 +28,9 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) throw new ApiError(meta.errors.noSuchFile);
-	//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-	if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
-	if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
+	if (file.size > 50000) throw new ApiError('FILE_TOO_BIG');
+	if (file.size === 0) throw new ApiError('EMPTY_FILE');
 
 	createImportMutingJob(user, file.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index 1158af3a1..67d6afd0c 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -12,31 +12,7 @@ export const meta = {
 		max: 1,
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'ea9cc34f-c415-4bc6-a6fe-28ac40357049',
-		},
-
-		unexpectedFileType: {
-			message: 'We need csv file.',
-			code: 'UNEXPECTED_FILE_TYPE',
-			id: 'a3c9edda-dd9b-4596-be6a-150ef813745c',
-		},
-
-		tooBigFile: {
-			message: 'That file is too big.',
-			code: 'TOO_BIG_FILE',
-			id: 'ae6e7a22-971b-4b52-b2be-fc0b9b121fe9',
-		},
-
-		emptyFile: {
-			message: 'That file is empty.',
-			code: 'EMPTY_FILE',
-			id: '99efe367-ce6e-4d44-93f8-5fae7b040356',
-		},
-	},
+	errors: ['EMPTY_FILE', 'FILE_TOO_BIG', 'NO_SUCH_FILE'],
 } as const;
 
 export const paramDef = {
@@ -51,10 +27,9 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const file = await DriveFiles.findOneBy({ id: ps.fileId });
 
-	if (file == null) throw new ApiError(meta.errors.noSuchFile);
-	//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-	if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile);
-	if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+	if (file == null) throw new ApiError('NO_SUCH_FILE');
+	if (file.size > 30000) throw new ApiError('FILE_TOO_BIG');
+	if (file.size === 0) throw new ApiError('EMPTY_FILE');
 
 	createImportUserListsJob(user, file.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts
index 64f6a719d..e3a58bc70 100644
--- a/packages/backend/src/server/api/endpoints/i/pin.ts
+++ b/packages/backend/src/server/api/endpoints/i/pin.ts
@@ -10,25 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '56734f8b-3928-431e-bf80-6ff87df40cb3',
-		},
-
-		pinLimitExceeded: {
-			message: 'You can not pin notes any more.',
-			code: 'PIN_LIMIT_EXCEEDED',
-			id: '72dab508-c64d-498f-8740-a8eec1ba385a',
-		},
-
-		alreadyPinned: {
-			message: 'That note has already been pinned.',
-			code: 'ALREADY_PINNED',
-			id: '8b18c2b7-68fe-4edb-9892-c0cbaeb6c913',
-		},
-	},
+	errors: ['ALREADY_PINNED', 'NO_SUCH_NOTE', 'PIN_LIMIT_EXCEEDED'],
 
 	res: {
 		type: 'object',
@@ -48,9 +30,9 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	await addPinned(user, ps.noteId).catch(e => {
-		if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote);
-		if (e.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded);
-		if (e.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned);
+		if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError('NO_SUCH_NOTE');
+		if (e.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError('PIN_LIMIT_EXCEEDED');
+		if (e.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError('ALREADY_PINNED');
 		throw e;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
index 44d533667..6d7e8954c 100644
--- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
@@ -11,13 +11,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchAnnouncement: {
-			message: 'No such announcement.',
-			code: 'NO_SUCH_ANNOUNCEMENT',
-			id: '184663db-df88-4bc2-8b52-fb85f0681939',
-		},
-	},
+	errors: ['NO_SUCH_ANNOUNCEMENT'],
 } as const;
 
 export const paramDef = {
@@ -33,9 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	// Check if announcement exists
 	const announcement = await Announcements.findOneBy({ id: ps.announcementId });
 
-	if (announcement == null) {
-		throw new ApiError(meta.errors.noSuchAnnouncement);
-	}
+	if (announcement == null) throw new ApiError('NO_SUCH_ANNOUNCEMENT');
 
 	// Check if already read
 	const read = await AnnouncementReads.findOneBy({
@@ -43,9 +35,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (read != null) {
-		return;
-	}
+	if (read != null) return;
 
 	// Create read
 	await AnnouncementReads.insert({
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
index 744b1d05b..7addd0fab 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
@@ -7,13 +7,7 @@ export const meta = {
 
 	secure: true,
 
-	errors: {
-		noSuchKey: {
-			message: 'No such key.',
-			code: 'NO_SUCH_KEY',
-			id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a',
-		},
-	},
+	errors: ['NO_SUCH_KEY'],
 } as const;
 
 export const paramDef = {
@@ -37,9 +31,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	const item = await query.getOne();
 
-	if (item == null) {
-		throw new ApiError(meta.errors.noSuchKey);
-	}
+	if (item == null) throw new ApiError('NO_SUCH_KEY');
 
 	return {
 		updatedAt: item.updatedAt,
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts
index 1bae3c886..a9ca60485 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts
@@ -7,13 +7,7 @@ export const meta = {
 
 	secure: true,
 
-	errors: {
-		noSuchKey: {
-			message: 'No such key.',
-			code: 'NO_SUCH_KEY',
-			id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a',
-		},
-	},
+	errors: ['NO_SUCH_KEY'],
 } as const;
 
 export const paramDef = {
@@ -37,9 +31,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	const item = await query.getOne();
 
-	if (item == null) {
-		throw new ApiError(meta.errors.noSuchKey);
-	}
+	if (item == null) throw new ApiError('NO_SUCH_KEY');
 
 	return item.value;
 });
diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
index 8467b2513..f656f3d83 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
@@ -7,13 +7,7 @@ export const meta = {
 
 	secure: true,
 
-	errors: {
-		noSuchKey: {
-			message: 'No such key.',
-			code: 'NO_SUCH_KEY',
-			id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019',
-		},
-	},
+	errors: ['NO_SUCH_KEY'],
 } as const;
 
 export const paramDef = {
@@ -37,9 +31,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	const item = await query.getOne();
 
-	if (item == null) {
-		throw new ApiError(meta.errors.noSuchKey);
-	}
+	if (item == null) throw new ApiError('NO_SUCH_KEY');
 
 	await RegistryItems.remove(item);
 });
diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts
index 2c63f8a88..88cb751b4 100644
--- a/packages/backend/src/server/api/endpoints/i/unpin.ts
+++ b/packages/backend/src/server/api/endpoints/i/unpin.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '454170ce-9d63-4a43-9da1-ea10afe81e21',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 
 	res: {
 		type: 'object',
@@ -36,7 +30,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	await removePinned(user, ps.noteId).catch(e => {
-		if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote);
+		if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError('NO_SUCH_NOTE');
 		throw e;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index 3f9411de4..cb3d6356a 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -19,19 +19,9 @@ export const meta = {
 		max: 3,
 	},
 
-	errors: {
-		incorrectPassword: {
-			message: 'Incorrect password.',
-			code: 'INCORRECT_PASSWORD',
-			id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3',
-		},
-
-		unavailable: {
-			message: 'Unavailable email address.',
-			code: 'UNAVAILABLE',
-			id: 'a2defefb-f220-8849-0af6-17f816099323',
-		},
-	},
+	// FIXME: refactor to remove both of these errors?
+	// the password should not be passed as it is not compatible with using OAuth
+	errors: ['ACCESS_DENIED', 'INTERNAL_ERROR'],
 } as const;
 
 export const paramDef = {
@@ -50,15 +40,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	// Compare password
 	const same = await bcrypt.compare(ps.password, profile.password!);
 
-	if (!same) {
-		throw new ApiError(meta.errors.incorrectPassword);
-	}
+	if (!same) throw new ApiError('ACCESS_DENIED');
 
 	if (ps.email != null) {
 		const available = await validateEmailForAccount(ps.email);
-		if (!available) {
-			throw new ApiError(meta.errors.unavailable);
-		}
+		if (!available) throw new ApiError('INTERNAL_ERROR');
 	}
 
 	await UserProfiles.update(user.id, {
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index c9557eadd..ce613b190 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -22,43 +22,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchAvatar: {
-			message: 'No such avatar file.',
-			code: 'NO_SUCH_AVATAR',
-			id: '539f3a45-f215-4f81-a9a8-31293640207f',
-		},
-
-		noSuchBanner: {
-			message: 'No such banner file.',
-			code: 'NO_SUCH_BANNER',
-			id: '0d8f5629-f210-41c2-9433-735831a58595',
-		},
-
-		avatarNotAnImage: {
-			message: 'The file specified as an avatar is not an image.',
-			code: 'AVATAR_NOT_AN_IMAGE',
-			id: 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191',
-		},
-
-		bannerNotAnImage: {
-			message: 'The file specified as a banner is not an image.',
-			code: 'BANNER_NOT_AN_IMAGE',
-			id: '75aedb19-2afd-4e6d-87fc-67941256fa60',
-		},
-
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: '8e01b590-7eb9-431b-a239-860e086c408e',
-		},
-
-		invalidRegexp: {
-			message: 'Invalid Regular Expression.',
-			code: 'INVALID_REGEXP',
-			id: '0d786918-10df-41cd-8f33-8dec7d9a89a5',
-		},
-	},
+	errors: ['INVALID_REGEXP', 'NO_SUCH_FILE', 'NO_SUCH_PAGE', 'NOT_AN_IMAGE'],
 
 	res: {
 		type: 'object',
@@ -142,12 +106,12 @@ export default define(meta, paramDef, async (ps, _user, token) => {
 		// validate regular expression syntax
 		ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => {
 			const regexp = x.match(/^\/(.+)\/(.*)$/);
-			if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
+			if (!regexp) throw new ApiError('INVALID_REGEXP');
 
 			try {
 				new RE2(regexp[1], regexp[2]);
 			} catch (err) {
-				throw new ApiError(meta.errors.invalidRegexp);
+				throw new ApiError('INVALID_REGEXP');
 			}
 		});
 
@@ -174,21 +138,21 @@ export default define(meta, paramDef, async (ps, _user, token) => {
 	if (ps.avatarId) {
 		const avatar = await DriveFiles.findOneBy({ id: ps.avatarId });
 
-		if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
-		if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage);
+		if (avatar == null || avatar.userId !== user.id) throw new ApiError('NO_SUCH_FILE', 'Avatar file not found.');
+		if (!avatar.type.startsWith('image/')) throw new ApiError('NOT_AN_IMAGE', 'Avatar file is not an image.');
 	}
 
 	if (ps.bannerId) {
 		const banner = await DriveFiles.findOneBy({ id: ps.bannerId });
 
-		if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
-		if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
+		if (banner == null || banner.userId !== user.id) throw new ApiError('NO_SUCH_FILE', 'Banner file not found.');
+		if (!banner.type.startsWith('image/')) throw new ApiError('BANNER_NOT_AN_IMAGE', 'Banner file is not an image.');
 	}
 
 	if (ps.pinnedPageId) {
 		const page = await Pages.findOneBy({ id: ps.pinnedPageId });
 
-		if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
+		if (page == null || page.userId !== user.id) throw new ApiError('NO_SUCH_PAGE');
 
 		profileUpdates.pinnedPageId = page.id;
 	} else if (ps.pinnedPageId === null) {
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
index 81ac30541..61778aa22 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchWebhook: {
-			message: 'No such webhook.',
-			code: 'NO_SUCH_WEBHOOK',
-			id: 'bae73e5a-5522-4965-ae19-3a8688e71d82',
-		},
-	},
+	errors: ['NO_SUCH_WEBHOOK'],
 } as const;
 
 export const paramDef = {
@@ -34,9 +28,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (webhook == null) {
-		throw new ApiError(meta.errors.noSuchWebhook);
-	}
+	if (webhook == null) throw new ApiError('NO_SUCH_WEBHOOK');
 
 	await Webhooks.delete(webhook.id);
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
index d45a39813..21baed811 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -9,13 +9,7 @@ export const meta = {
 
 	kind: 'read:account',
 
-	errors: {
-		noSuchWebhook: {
-			message: 'No such webhook.',
-			code: 'NO_SUCH_WEBHOOK',
-			id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098',
-		},
-	},
+	errors: ['NO_SUCH_WEBHOOK'],
 } as const;
 
 export const paramDef = {
@@ -33,9 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (webhook == null) {
-		throw new ApiError(meta.errors.noSuchWebhook);
-	}
+	if (webhook == null) throw new ApiError('NO_SUCH_WEBHOOK');
 
 	return webhook;
 });
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
index 0ffa8a318..d3076ba16 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
@@ -11,14 +11,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchWebhook: {
-			message: 'No such webhook.',
-			code: 'NO_SUCH_WEBHOOK',
-			id: 'fb0fea69-da18-45b1-828d-bd4fd1612518',
-		},
-	},
-
+	errors: ['NO_SUCH_WEBHOOK'],
 } as const;
 
 export const paramDef = {
@@ -43,9 +36,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (webhook == null) {
-		throw new ApiError(meta.errors.noSuchWebhook);
-	}
+	if (webhook == null) throw new ApiError('NO_SUCH_WEBHOOK');
 
 	await Webhooks.update(webhook.id, {
 		name: ps.name,
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts
index c8006f490..88e17a054 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts
@@ -23,25 +23,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '11795c64-40ea-4198-b06e-3c873ed9039d',
-		},
-
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f',
-		},
-
-		groupAccessDenied: {
-			message: 'You can not read messages of groups that you have not joined.',
-			code: 'GROUP_ACCESS_DENIED',
-			id: 'a053a8dd-a491-4718-8f87-50775aad9284',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'NO_SUCH_USER', 'NO_SUCH_GROUP'],
 } as const;
 
 export const paramDef = {
@@ -73,7 +55,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	if (ps.userId != null) {
 		// Fetch recipient (user)
 		const recipient = await getUser(ps.userId).catch(e => {
-			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 			throw e;
 		});
 
@@ -110,9 +92,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		// Fetch recipient (group)
 		const recipientGroup = await UserGroups.findOneBy({ id: ps.groupId });
 
-		if (recipientGroup == null) {
-			throw new ApiError(meta.errors.noSuchGroup);
-		}
+		if (recipientGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 		// check joined
 		const joining = await UserGroupJoinings.findOneBy({
@@ -120,9 +100,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userGroupId: recipientGroup.id,
 		});
 
-		if (joining == null) {
-			throw new ApiError(meta.errors.groupAccessDenied);
-		}
+		if (joining == null) throw new ApiError('ACCESS_DENIED', 'You have to join a group to read messages in it.');
 
 		const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
 			.andWhere('message.groupId = :groupId', { groupId: recipientGroup.id });
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
index ad69ba1e0..020d596fc 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts
@@ -19,51 +19,11 @@ export const meta = {
 		ref: 'MessagingMessage',
 	},
 
-	errors: {
-		recipientIsYourself: {
-			message: 'You can not send a message to yourself.',
-			code: 'RECIPIENT_IS_YOURSELF',
-			id: '17e2ba79-e22a-4cbc-bf91-d327643f4a7e',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '11795c64-40ea-4198-b06e-3c873ed9039d',
-		},
-
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537',
-		},
-
-		groupAccessDenied: {
-			message: 'You can not send messages to groups that you have not joined.',
-			code: 'GROUP_ACCESS_DENIED',
-			id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd',
-		},
-
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: '4372b8e2-185d-4146-8749-2f68864a3e5f',
-		},
-
-		youHaveBeenBlocked: {
-			message: 'You cannot send a message because you have been blocked by this user.',
-			code: 'YOU_HAVE_BEEN_BLOCKED',
-			id: 'c15a5199-7422-4968-941a-2a462c478f7d',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'BLOCKED', 'NO_SUCH_FILE', 'NO_SUCH_USER', 'NO_SUCH_GROUP', 'RECIPIENT_IS_YOURSELF'],
 } as const;
 
 export const paramDef = {
 	type: 'object',
-	properties: {
-		text: { type: 'string', nullable: true, maxLength: 3000 },
-		fileId: { type: 'string', format: 'misskey:id' },
-	},
 	anyOf: [
 		{
 			properties: {
@@ -137,13 +97,11 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	if (ps.userId != null) {
 		// Myself
-		if (ps.userId === user.id) {
-			throw new ApiError(meta.errors.recipientIsYourself);
-		}
+		if (ps.userId === user.id) throw new ApiError('RECIPIENT_IS_YOURSELF');
 
 		// Fetch recipient (user)
 		recipientUser = await getUser(ps.userId).catch(e => {
-			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 			throw e;
 		});
 
@@ -152,16 +110,12 @@ export default define(meta, paramDef, async (ps, user) => {
 			blockerId: recipientUser.id,
 			blockeeId: user.id,
 		});
-		if (block) {
-			throw new ApiError(meta.errors.youHaveBeenBlocked);
-		}
+		if (block) throw new ApiError('BLOCKED');
 	} else if (ps.groupId != null) {
 		// Fetch recipient (group)
 		recipientGroup = await UserGroups.findOneBy({ id: ps.groupId! });
 
-		if (recipientGroup == null) {
-			throw new ApiError(meta.errors.noSuchGroup);
-		}
+		if (recipientGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 		// check joined
 		const joining = await UserGroupJoinings.findOneBy({
@@ -169,9 +123,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userGroupId: recipientGroup.id,
 		});
 
-		if (joining == null) {
-			throw new ApiError(meta.errors.groupAccessDenied);
-		}
+		if (joining == null) throw new ApiError('ACCESS_DENIED', 'You have to join a group to send a message in it.');
 	}
 
 	let file = null;
@@ -181,9 +133,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (file == null) {
-			throw new ApiError(meta.errors.noSuchFile);
-		}
+		if (file == null) throw new ApiError('NO_SUCH_FILE');
 	}
 
 	return await createMessage(user, recipientUser, recipientGroup, ps.text, file);
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
index 1997a8eda..4e86fa530 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
@@ -17,13 +17,7 @@ export const meta = {
 		minInterval: SECOND,
 	},
 
-	errors: {
-		noSuchMessage: {
-			message: 'No such message.',
-			code: 'NO_SUCH_MESSAGE',
-			id: '54b5b326-7925-42cf-8019-130fda8b56af',
-		},
-	},
+	errors: ['NO_SUCH_MESSAGE'],
 } as const;
 
 export const paramDef = {
@@ -41,9 +35,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (message == null) {
-		throw new ApiError(meta.errors.noSuchMessage);
-	}
+	if (message == null) throw new ApiError('NO_SUCH_MESSAGE');
 
 	await deleteMessage(message);
 });
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts
index c02b53469..3a3372c83 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:messaging',
 
-	errors: {
-		noSuchMessage: {
-			message: 'No such message.',
-			code: 'NO_SUCH_MESSAGE',
-			id: '86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14',
-		},
-	},
+	errors: ['NO_SUCH_MESSAGE'],
 } as const;
 
 export const paramDef = {
@@ -31,18 +25,16 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const message = await MessagingMessages.findOneBy({ id: ps.messageId });
 
-	if (message == null) {
-		throw new ApiError(meta.errors.noSuchMessage);
-	}
+	if (message == null) throw new ApiError('NO_SUCH_MESSAGE');
 
 	if (message.recipientId) {
 		await readUserMessagingMessage(user.id, message.userId, [message.id]).catch(e => {
-			if (e.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage);
+			if (e.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError('NO_SUCH_MESSAGE');
 			throw e;
 		});
 	} else if (message.groupId) {
 		await readGroupMessagingMessage(user.id, message.groupId, [message.id]).catch(e => {
-			if (e.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage);
+			if (e.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError('NO_SUCH_MESSAGE');
 			throw e;
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index f10fe0e2f..705fe9deb 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -13,25 +13,7 @@ export const meta = {
 
 	kind: 'write:mutes',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '6fef56f3-e765-4957-88e5-c6f65329b8a5',
-		},
-
-		muteeIsYourself: {
-			message: 'Mutee is yourself.',
-			code: 'MUTEE_IS_YOURSELF',
-			id: 'a4619cb2-5f23-484b-9301-94c903074e10',
-		},
-
-		alreadyMuting: {
-			message: 'You are already muting that user.',
-			code: 'ALREADY_MUTING',
-			id: '7e7359cb-160c-4956-b08f-4d1c653cd007',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'MUTEE_IS_YOURSELF', 'ALREADY_MUTING'],
 } as const;
 
 export const paramDef = {
@@ -52,13 +34,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const muter = user;
 
 	// 自分自身
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.muteeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
 
 	// Get mutee
 	const mutee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -68,9 +48,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		muteeId: mutee.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyMuting);
-	}
+	if (exist != null) throw new ApiError('ALREADY_MUTING');
 
 	if (ps.expiresAt && ps.expiresAt <= Date.now()) {
 		return;
diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts
index eed38528a..7585aeacb 100644
--- a/packages/backend/src/server/api/endpoints/mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/mute/delete.ts
@@ -11,25 +11,7 @@ export const meta = {
 
 	kind: 'write:mutes',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'b851d00b-8ab1-4a56-8b1b-e24187cb48ef',
-		},
-
-		muteeIsYourself: {
-			message: 'Mutee is yourself.',
-			code: 'MUTEE_IS_YOURSELF',
-			id: 'f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9',
-		},
-
-		notMuting: {
-			message: 'You are not muting that user.',
-			code: 'NOT_MUTING',
-			id: '5467d020-daa9-4553-81e1-135c0c35a96d',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'MUTEE_IS_YOURSELF', 'NOT_MUTING'],
 } as const;
 
 export const paramDef = {
@@ -45,13 +27,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const muter = user;
 
 	// Check if the mutee is yourself
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.muteeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
 
 	// Get mutee
 	const mutee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -61,9 +41,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		muteeId: mutee.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notMuting);
-	}
+	if (exist == null) throw new ApiError('NOT_MUTING');
 
 	// Delete mute
 	await Mutings.delete({
diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts
index 976c11260..e20b744a1 100644
--- a/packages/backend/src/server/api/endpoints/notes/clips.ts
+++ b/packages/backend/src/server/api/endpoints/notes/clips.ts
@@ -19,13 +19,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -39,7 +33,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, me) => {
 	const note = await getNote(ps.noteId, me).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index 7ee052001..b4cbc55f0 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -19,13 +19,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'e1035875-9551-45ec-afa8-1ded1fcb53c8',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -41,7 +35,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 97cbe6677..a52f38df0 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -37,55 +37,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchRenoteTarget: {
-			message: 'No such renote target.',
-			code: 'NO_SUCH_RENOTE_TARGET',
-			id: 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4',
-		},
-
-		cannotReRenote: {
-			message: 'You can not Renote a pure Renote.',
-			code: 'CANNOT_RENOTE_TO_A_PURE_RENOTE',
-			id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a',
-		},
-
-		noSuchReplyTarget: {
-			message: 'No such reply target.',
-			code: 'NO_SUCH_REPLY_TARGET',
-			id: '749ee0f6-d3da-459a-bf02-282e2da4292c',
-		},
-
-		cannotReplyToPureRenote: {
-			message: 'You can not reply to a pure Renote.',
-			code: 'CANNOT_REPLY_TO_A_PURE_RENOTE',
-			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
-		},
-
-		cannotCreateAlreadyExpiredPoll: {
-			message: 'Poll is already expired.',
-			code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
-			id: '04da457d-b083-4055-9082-955525eda5a5',
-		},
-
-		noSuchChannel: {
-			message: 'No such channel.',
-			code: 'NO_SUCH_CHANNEL',
-			id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb',
-		},
-
-		youHaveBeenBlocked: {
-			message: 'You have been blocked by this user.',
-			code: 'YOU_HAVE_BEEN_BLOCKED',
-			id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3',
-		},
-
-		lessRestrictiveVisibility: {
-			message: 'The visibility cannot be less restrictive than the parent note.',
-			code: 'LESS_RESTRICTIVE_VISIBILITY',
-			id: 'c8ab7a7a-8852-41e2-8b24-079bbaceb585',
-		},
-	},
+	errors: ['NO_SUCH_NOTE', 'PURE_RENOTE', 'EXPIRED_POLL', 'NO_SUCH_CHANNEL', 'BLOCKED', 'LESS_RESTRICTIVE_VISIBILITY'],
 } as const;
 
 export const paramDef = {
@@ -199,17 +151,15 @@ export default define(meta, paramDef, async (ps, user) => {
 	if (ps.renoteId != null) {
 		// Fetch renote to note
 		renote = await getNote(ps.renoteId, user).catch(e => {
-			if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchRenoteTarget);
+			if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE', 'Note to be renoted not found.');
 			throw e;
 		});
 
-		if (isPureRenote(renote)) {
-			throw new ApiError(meta.errors.cannotReRenote);
-		}
+		if (isPureRenote(renote)) throw new ApiError('PURE_RENOTE', 'Cannot renote a pure renote.');
 
 		// check that the visibility is not less restrictive
 		if (noteVisibilities.indexOf(renote.visibility) > noteVisibilities.indexOf(ps.visibility)) {
-			throw new ApiError(meta.errors.lessRestrictiveVisibility);
+			throw new ApiError('LESS_RESTRICTIVE_VISIBILITY', `The renote has visibility ${renote.visibility}.`);
 		}
 
 		// Check blocking
@@ -218,9 +168,7 @@ export default define(meta, paramDef, async (ps, user) => {
 				blockerId: renote.userId,
 				blockeeId: user.id,
 			});
-			if (block) {
-				throw new ApiError(meta.errors.youHaveBeenBlocked);
-			}
+			if (block) throw new ApiError('BLOCKED', 'Blocked by author of note to be renoted.');
 		}
 	}
 
@@ -228,17 +176,15 @@ export default define(meta, paramDef, async (ps, user) => {
 	if (ps.replyId != null) {
 		// Fetch reply
 		reply = await getNote(ps.replyId, user).catch(e => {
-			if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchReplyTarget);
+			if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE', 'Replied to note not found.');
 			throw e;
 		});
 
-		if (isPureRenote(reply)) {
-			throw new ApiError(meta.errors.cannotReplyToPureRenote);
-		}
+		if (isPureRenote(reply)) throw new ApiError('PURE_RENOTE', 'Cannot reply to a pure renote.');
 
 		// check that the visibility is not less restrictive
 		if (noteVisibilities.indexOf(reply.visibility) > noteVisibilities.indexOf(ps.visibility)) {
-			throw new ApiError(meta.errors.lessRestrictiveVisibility);
+			throw new ApiError('LESS_RESTRICTIVE_VISIBILITY', `The replied to note has visibility ${reply.visibility}.`);
 		}
 
 		// Check blocking
@@ -247,16 +193,14 @@ export default define(meta, paramDef, async (ps, user) => {
 				blockerId: reply.userId,
 				blockeeId: user.id,
 			});
-			if (block) {
-				throw new ApiError(meta.errors.youHaveBeenBlocked);
-			}
+			if (block) throw new ApiError('BLOCKED', 'Blocked by author of replied to note.');
 		}
 	}
 
 	if (ps.poll) {
 		if (typeof ps.poll.expiresAt === 'number') {
 			if (ps.poll.expiresAt < Date.now()) {
-				throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll);
+				throw new ApiError('EXPIRED_POLL');
 			}
 		} else if (typeof ps.poll.expiredAfter === 'number') {
 			ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter;
@@ -267,9 +211,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	if (ps.channelId != null) {
 		channel = await Channels.findOneBy({ id: ps.channelId });
 
-		if (channel == null) {
-			throw new ApiError(meta.errors.noSuchChannel);
-		}
+		if (channel == null) throw new ApiError('NO_SUCH_CHANNEL');
 	}
 
 	// 投稿を作成
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index 8aa1af85e..f33d04782 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -18,19 +18,7 @@ export const meta = {
 		minInterval: SECOND,
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '490be23f-8c1f-4796-819f-94cb4f9d1630',
-		},
-
-		accessDenied: {
-			message: 'Access denied.',
-			code: 'ACCESS_DENIED',
-			id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -44,12 +32,12 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
 	if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) {
-		throw new ApiError(meta.errors.accessDenied);
+		throw new ApiError('ACCESS_DENIED');
 	}
 
 	// この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index b5dd88a4e..ffcb9b9e2 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -11,19 +11,7 @@ export const meta = {
 
 	kind: 'write:favorites',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '6dd26674-e060-4816-909a-45ba3f4da458',
-		},
-
-		alreadyFavorited: {
-			message: 'The note has already been marked as a favorite.',
-			code: 'ALREADY_FAVORITED',
-			id: 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6',
-		},
-	},
+	errors: ['NO_SUCH_NOTE', 'ALREADY_FAVORITED'],
 } as const;
 
 export const paramDef = {
@@ -38,7 +26,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	// Get favoritee
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
@@ -48,9 +36,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyFavorited);
-	}
+	if (exist != null) throw new ApiError('ALREADY_FAVORITED');
 
 	// Create favorite
 	await NoteFavorites.insert({
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
index 3f4d39254..7c590c403 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
@@ -10,19 +10,7 @@ export const meta = {
 
 	kind: 'write:favorites',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '80848a2c-398f-4343-baa9-df1d57696c56',
-		},
-
-		notFavorited: {
-			message: 'You have not marked that note a favorite.',
-			code: 'NOT_FAVORITED',
-			id: 'b625fc69-635e-45e9-86f4-dbefbef35af5',
-		},
-	},
+	errors: ['NO_SUCH_NOTE', 'NOT_FAVORITED'],
 } as const;
 
 export const paramDef = {
@@ -37,7 +25,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	// Get favoritee
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
@@ -47,9 +35,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notFavorited);
-	}
+	if (exist == null) throw new ApiError('NOT_FAVORITED');
 
 	// Delete favorite
 	await NoteFavorites.delete(exist.id);
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 3d32e7c7a..3cc8d292a 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -23,13 +23,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		gtlDisabled: {
-			message: 'Global timeline has been disabled.',
-			code: 'GTL_DISABLED',
-			id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b',
-		},
-	},
+	errors: ['TIMELINE_DISABLED'],
 } as const;
 
 export const paramDef = {
@@ -54,7 +48,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
 	if (m.disableGlobalTimeline) {
 		if (user == null || (!user.isAdmin && !user.isModerator)) {
-			throw new ApiError(meta.errors.gtlDisabled);
+			throw new ApiError('TIMELINE_DISABLED');
 		}
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index f2e86915f..18724c981 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -28,13 +28,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		stlDisabled: {
-			message: 'Hybrid timeline has been disabled.',
-			code: 'STL_DISABLED',
-			id: '620763f4-f621-4533-ab33-0577a1a3c342',
-		},
-	},
+	errors: ['TIMELINE_DISABLED'],
 } as const;
 
 export const paramDef = {
@@ -61,7 +55,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
 	if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) {
-		throw new ApiError(meta.errors.stlDisabled);
+		throw new ApiError('TIMELINE_DISABLED');
 	}
 
 	//#region Construct query
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 6a65c028a..2e13fb432 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -26,13 +26,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		ltlDisabled: {
-			message: 'Local timeline has been disabled.',
-			code: 'LTL_DISABLED',
-			id: '45a6eb02-7695-4393-b023-dd3be9aaaefd',
-		},
-	},
+	errors: ['TIMELINE_DISABLED'],
 } as const;
 
 export const paramDef = {
@@ -61,7 +55,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
 	if (m.disableLocalTimeline) {
 		if (user == null || (!user.isAdmin && !user.isModerator)) {
-			throw new ApiError(meta.errors.ltlDisabled);
+			throw new ApiError('TIMELINE_DISABLED');
 		}
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 331b03971..6648e996b 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -19,50 +19,14 @@ export const meta = {
 
 	kind: 'write:votes',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'ecafbd2e-c283-4d6d-aecb-1a0a33b75396',
-		},
-
-		noPoll: {
-			message: 'The note does not attach a poll.',
-			code: 'NO_POLL',
-			id: '5f979967-52d9-4314-a911-1c673727f92f',
-		},
-
-		invalidChoice: {
-			message: 'Choice ID is invalid.',
-			code: 'INVALID_CHOICE',
-			id: 'e0cc9a04-f2e8-41e4-a5f1-4127293260cc',
-		},
-
-		alreadyVoted: {
-			message: 'You have already voted.',
-			code: 'ALREADY_VOTED',
-			id: '0963fc77-efac-419b-9424-b391608dc6d8',
-		},
-
-		alreadyExpired: {
-			message: 'The poll is already expired.',
-			code: 'ALREADY_EXPIRED',
-			id: '1022a357-b085-4054-9083-8f8de358337e',
-		},
-
-		youHaveBeenBlocked: {
-			message: 'You cannot vote this poll because you have been blocked by this user.',
-			code: 'YOU_HAVE_BEEN_BLOCKED',
-			id: '85a5377e-b1e9-4617-b0b9-5bea73331e49',
-		},
-	},
+	errors: ['NO_SUCH_NOTE', 'INVALID_CHOICE', 'ALREADY_VOTED', 'EXPIRED_POLL', 'BLOCKED'],
 } as const;
 
 export const paramDef = {
 	type: 'object',
 	properties: {
 		noteId: { type: 'string', format: 'misskey:id' },
-		choice: { type: 'integer' },
+		choice: { type: 'integer', minimum: 0 },
 	},
 	required: ['noteId', 'choice'],
 } as const;
@@ -73,12 +37,12 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	// Get votee
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
 	if (!note.hasPoll) {
-		throw new ApiError(meta.errors.noPoll);
+		throw new ApiError('NO_SUCH_NOTE', 'The note exists but does not have a poll attached.');
 	}
 
 	// Check blocking
@@ -87,19 +51,17 @@ export default define(meta, paramDef, async (ps, user) => {
 			blockerId: note.userId,
 			blockeeId: user.id,
 		});
-		if (block) {
-			throw new ApiError(meta.errors.youHaveBeenBlocked);
-		}
+		if (block) throw new ApiError('BLOCKED');
 	}
 
 	const poll = await Polls.findOneByOrFail({ noteId: note.id });
 
 	if (poll.expiresAt && poll.expiresAt < createdAt) {
-		throw new ApiError(meta.errors.alreadyExpired);
+		throw new ApiError('EXPIRED_POLL');
 	}
 
 	if (poll.choices[ps.choice] == null) {
-		throw new ApiError(meta.errors.invalidChoice);
+		throw new ApiError('INVALID_CHOICE', `There are only ${poll.choices.length} choices.`);
 	}
 
 	// if already voted
@@ -111,10 +73,10 @@ export default define(meta, paramDef, async (ps, user) => {
 	if (exist.length) {
 		if (poll.multiple) {
 			if (exist.some(x => x.choice === ps.choice)) {
-				throw new ApiError(meta.errors.alreadyVoted);
+				throw new ApiError('ALREADY_VOTED', 'This is a multiple choice poll, but you already voted for that option.');
 			}
 		} else {
-			throw new ApiError(meta.errors.alreadyVoted);
+			throw new ApiError('ALREADY_VOTED', 'This is a single choice poll.');
 		}
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index b3b8e01c5..d7a63c056 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -23,13 +23,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '263fff3d-d0e1-4af4-bea7-8408059b451a',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -53,7 +47,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	// check note visibility
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index 5d27ab8fb..d99dc314e 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -10,25 +10,7 @@ export const meta = {
 
 	kind: 'write:reactions',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '033d0620-5bfe-4027-965d-980b0c85a3ea',
-		},
-
-		alreadyReacted: {
-			message: 'You are already reacting to that note.',
-			code: 'ALREADY_REACTED',
-			id: '71efcf98-86d6-4e2b-b2ad-9d032369366b',
-		},
-
-		youHaveBeenBlocked: {
-			message: 'You cannot react this note because you have been blocked by this user.',
-			code: 'YOU_HAVE_BEEN_BLOCKED',
-			id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
-		},
-	},
+	errors: ['NO_SUCH_NOTE', 'ALREADY_REACTED', 'BLOCKED'],
 } as const;
 
 export const paramDef = {
@@ -43,12 +25,12 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 	await createReaction(user, note, ps.reaction).catch(e => {
-		if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
-		if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
+		if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError('ALREADY_REACTED');
+		if (e.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError('BLOCKED');
 		throw e;
 	});
 	return;
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
index a5c50e4c2..b974fc3ef 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
@@ -17,19 +17,7 @@ export const meta = {
 		minInterval: 3 * SECOND,
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '764d9fce-f9f2-4a0e-92b1-6ceac9a7ad37',
-		},
-
-		notReacted: {
-			message: 'You are not reacting to that note.',
-			code: 'NOT_REACTED',
-			id: '92f4426d-4196-4125-aa5b-02943e2ec8fc',
-		},
-	},
+	errors: ['NO_SUCH_NOTE', 'NOT_REACTED'],
 } as const;
 
 export const paramDef = {
@@ -43,11 +31,11 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 	await deleteReaction(user, note).catch(e => {
-		if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted);
+		if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError('NOT_REACTED');
 		throw e;
 	});
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index 1fa9c5230..a0402cf2f 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -22,13 +22,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '12908022-2e21-46cd-ba6a-3edaf6093f46',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -45,7 +39,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 1d393f796..8d8b21238 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -22,9 +22,6 @@ export const meta = {
 			ref: 'Note',
 		},
 	},
-
-	errors: {
-	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index c9c148747..b2dde8647 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -14,13 +14,7 @@ export const meta = {
 		ref: 'Note',
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -34,7 +28,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
@@ -42,7 +36,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		// FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
 		detail: true,
 	}).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
index efcafe306..dddb55fcb 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
@@ -13,13 +13,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -41,7 +35,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
index cbc0e5ce5..c1ea564eb 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -30,7 +24,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index c29623aa8..f62e07b4e 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -55,13 +55,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'bea9b03f-36e0-49c5-a4db-627a029f8971',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 // List of permitted languages from https://www.deepl.com/docs-api/translate-text/translate-text/
@@ -116,7 +110,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 68fab7889..b7bb3009a 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -18,13 +18,7 @@ export const meta = {
 		minInterval: SECOND,
 	},
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'efd4a259-2442-496b-8dd7-b255aa1a160f',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -38,7 +32,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index e603a8f62..c95efd6b6 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -21,13 +21,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff',
-		},
-	},
+	errors: ['NO_SUCH_USER_LIST'],
 } as const;
 
 export const paramDef = {
@@ -58,9 +52,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (list == null) {
-		throw new ApiError(meta.errors.noSuchList);
-	}
+	if (list == null) throw new ApiError('NO_SUCH_USER_LIST');
 
 	//#region Construct query
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
index 07c0517a1..002e9b007 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -30,7 +24,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
index 5949c9cd1..54f7163b9 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	kind: 'write:account',
 
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: '09b3695c-f72c-4731-a428-7cff825fc82e',
-		},
-	},
+	errors: ['NO_SUCH_NOTE'],
 } as const;
 
 export const paramDef = {
@@ -30,7 +24,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const note = await getNote(ps.noteId, user).catch(err => {
-		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
 		throw err;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts
index 80d513d8d..e45ebdd1d 100644
--- a/packages/backend/src/server/api/endpoints/notifications/create.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/create.ts
@@ -7,9 +7,6 @@ export const meta = {
 	requireCredential: true,
 
 	kind: 'write:notifications',
-
-	errors: {
-	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
index 7bce525a5..f7b64fddb 100644
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/read.ts
@@ -10,13 +10,8 @@ export const meta = {
 
 	description: 'Mark a notification as read.',
 
-	errors: {
-		noSuchNotification: {
-			message: 'No such notification.',
-			code: 'NO_SUCH_NOTIFICATION',
-			id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e',
-		},
-	},
+	// FIXME: This error makes sense here but will never be thrown here.
+	// errors: ['NO_SUCH_NOTIFICATION'],
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts
index 6dd3ede85..e69b279ea 100644
--- a/packages/backend/src/server/api/endpoints/page-push.ts
+++ b/packages/backend/src/server/api/endpoints/page-push.ts
@@ -7,13 +7,7 @@ export const meta = {
 	requireCredential: true,
 	secure: true,
 
-	errors: {
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: '4a13ad31-6729-46b4-b9af-e86b265c2e74',
-		},
-	},
+	errors: ['NO_SUCH_PAGE'],
 } as const;
 
 export const paramDef = {
@@ -29,9 +23,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOneBy({ id: ps.pageId });
-	if (page == null) {
-		throw new ApiError(meta.errors.noSuchPage);
-	}
+	if (page == null) throw new ApiError('NO_SUCH_PAGE');
 
 	publishMainStream(page.userId, 'pageEvent', {
 		pageId: ps.pageId,
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 8eafe556c..7649792cd 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -23,18 +23,7 @@ export const meta = {
 		ref: 'Page',
 	},
 
-	errors: {
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'b7b97489-0f66-4b12-a5ff-b21bd63f6e1c',
-		},
-		nameAlreadyExists: {
-			message: 'Specified name already exists.',
-			code: 'NAME_ALREADY_EXISTS',
-			id: '4650348e-301c-499a-83c9-6aa988c66bc1',
-		},
-	},
+	errors: ['NO_SUCH_FILE', 'NAME_ALREADY_EXISTS'],
 } as const;
 
 export const paramDef = {
@@ -61,9 +50,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (eyeCatchingImage == null) {
-			throw new ApiError(meta.errors.noSuchFile);
-		}
+		if (eyeCatchingImage == null) throw new ApiError('NO_SUCH_FILE');
 	}
 
 	await Pages.findBy({
@@ -71,7 +58,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		name: ps.name,
 	}).then(result => {
 		if (result.length > 0) {
-			throw new ApiError(meta.errors.nameAlreadyExists);
+			throw new ApiError('NAME_ALREADY_EXISTS');
 		}
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts
index a7708e658..b8f8d43c7 100644
--- a/packages/backend/src/server/api/endpoints/pages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/pages/delete.ts
@@ -9,19 +9,7 @@ export const meta = {
 
 	kind: 'write:pages',
 
-	errors: {
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: 'eb0c6e1d-d519-4764-9486-52a7e1c6392a',
-		},
-
-		accessDenied: {
-			message: 'Access denied.',
-			code: 'ACCESS_DENIED',
-			id: '8b741b3e-2c22-44b3-a15f-29949aa1601e',
-		},
-	},
+	errors: ['NO_SUCH_PAGE'],
 } as const;
 
 export const paramDef = {
@@ -34,13 +22,11 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
-	const page = await Pages.findOneBy({ id: ps.pageId });
-	if (page == null) {
-		throw new ApiError(meta.errors.noSuchPage);
-	}
-	if (page.userId !== user.id) {
-		throw new ApiError(meta.errors.accessDenied);
-	}
+	const page = await Pages.findOneBy({
+		id: ps.pageId,
+		userId: user.id,
+	});
+	if (page == null) throw new ApiError('NO_SUCH_PAGE');
 
 	await Pages.delete(page.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index b4aab40d3..19b0d99ed 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -10,19 +10,7 @@ export const meta = {
 
 	kind: 'write:page-likes',
 
-	errors: {
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3',
-		},
-
-		alreadyLiked: {
-			message: 'The page has already been liked.',
-			code: 'ALREADY_LIKED',
-			id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3',
-		},
-	},
+	errors: ['ALREADY_LIKED', 'NO_SUCH_PAGE'],
 } as const;
 
 export const paramDef = {
@@ -36,9 +24,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOneBy({ id: ps.pageId });
-	if (page == null) {
-		throw new ApiError(meta.errors.noSuchPage);
-	}
+	if (page == null) throw new ApiError('NO_SUCH_PAGE');
 
 	// if already liked
 	const exist = await PageLikes.findOneBy({
@@ -46,9 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyLiked);
-	}
+	if (exist != null) throw new ApiError('ALREADY_LIKED');
 
 	// Create like
 	await PageLikes.insert({
diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts
index 5d37e86b9..9861efdaa 100644
--- a/packages/backend/src/server/api/endpoints/pages/show.ts
+++ b/packages/backend/src/server/api/endpoints/pages/show.ts
@@ -15,13 +15,7 @@ export const meta = {
 		ref: 'Page',
 	},
 
-	errors: {
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: '222120c0-3ead-4528-811b-b96f233388d7',
-		},
-	},
+	errors: ['NO_SUCH_PAGE'],
 } as const;
 
 export const paramDef = {
@@ -62,9 +56,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		}
 	}
 
-	if (page == null) {
-		throw new ApiError(meta.errors.noSuchPage);
-	}
+	if (page == null) throw new ApiError('NO_SUCH_PAGE');
 
 	return await Pages.pack(page, user);
 });
diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts
index 6b3a2bec1..5c18312d3 100644
--- a/packages/backend/src/server/api/endpoints/pages/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts
@@ -9,19 +9,7 @@ export const meta = {
 
 	kind: 'write:page-likes',
 
-	errors: {
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: 'a0d41e20-1993-40bd-890e-f6e560ae648e',
-		},
-
-		notLiked: {
-			message: 'You have not liked that page.',
-			code: 'NOT_LIKED',
-			id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee',
-		},
-	},
+	errors: ['NO_SUCH_PAGE', 'NOT_LIKED'],
 } as const;
 
 export const paramDef = {
@@ -35,18 +23,14 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const page = await Pages.findOneBy({ id: ps.pageId });
-	if (page == null) {
-		throw new ApiError(meta.errors.noSuchPage);
-	}
+	if (page == null) throw new ApiError('NO_SUCH_PAGE');
 
 	const exist = await PageLikes.findOneBy({
 		pageId: page.id,
 		userId: user.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notLiked);
-	}
+	if (exist == null) throw new ApiError('NOT_LIKED');
 
 	// Delete like
 	await PageLikes.delete(exist.id);
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index 319af3b88..f82f754d1 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -16,30 +16,7 @@ export const meta = {
 		max: 300,
 	},
 
-	errors: {
-		noSuchPage: {
-			message: 'No such page.',
-			code: 'NO_SUCH_PAGE',
-			id: '21149b9e-3616-4778-9592-c4ce89f5a864',
-		},
-
-		accessDenied: {
-			message: 'Access denied.',
-			code: 'ACCESS_DENIED',
-			id: '3c15cd52-3b4b-4274-967d-6456fc4f792b',
-		},
-
-		noSuchFile: {
-			message: 'No such file.',
-			code: 'NO_SUCH_FILE',
-			id: 'cfc23c7c-3887-490e-af30-0ed576703c82',
-		},
-		nameAlreadyExists: {
-			message: 'Specified name already exists.',
-			code: 'NAME_ALREADY_EXISTS',
-			id: '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab',
-		},
-	},
+	errors: ['NAME_ALREADY_EXISTS', 'NO_SUCH_FILE', 'NO_SUCH_PAGE'],
 } as const;
 
 export const paramDef = {
@@ -60,13 +37,11 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
-	const page = await Pages.findOneBy({ id: ps.pageId });
-	if (page == null) {
-		throw new ApiError(meta.errors.noSuchPage);
-	}
-	if (page.userId !== user.id) {
-		throw new ApiError(meta.errors.accessDenied);
-	}
+	const page = await Pages.findOneBy({
+		id: ps.pageId,
+		userId: user.id,
+	});
+	if (page == null) throw new ApiError('NO_SUCH_PAGE');
 
 	let eyeCatchingImage = null;
 	if (ps.eyeCatchingImageId != null) {
@@ -75,9 +50,7 @@ export default define(meta, paramDef, async (ps, user) => {
 			userId: user.id,
 		});
 
-		if (eyeCatchingImage == null) {
-			throw new ApiError(meta.errors.noSuchFile);
-		}
+		if (eyeCatchingImage == null) throw new ApiError('NO_SUCH_FILE');
 	}
 
 	await Pages.findBy({
@@ -85,9 +58,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 		name: ps.name,
 	}).then(result => {
-		if (result.length > 0) {
-			throw new ApiError(meta.errors.nameAlreadyExists);
-		}
+		if (result.length > 0) throw new ApiError('NAME_ALREADY_EXISTS');
 	});
 
 	await Pages.update(page.id, {
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index e1db204b7..9320c4e2c 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -13,25 +13,7 @@ export const meta = {
 
 	kind: 'write:mutes',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '6fef56f3-e765-4957-88e5-c6f65329b8a5',
-		},
-
-		muteeIsYourself: {
-			message: 'Mutee is yourself.',
-			code: 'MUTEE_IS_YOURSELF',
-			id: 'a4619cb2-5f23-484b-9301-94c903074e10',
-		},
-
-		alreadyMuting: {
-			message: 'You are already muting that user.',
-			code: 'ALREADY_MUTING',
-			id: '7e7359cb-160c-4956-b08f-4d1c653cd007',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'MUTEE_IS_YOURSELF', 'ALREADY_MUTING'],
 } as const;
 
 export const paramDef = {
@@ -47,13 +29,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const muter = user;
 
 	// Check if the mutee is yourself
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.muteeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
 
 	// Get mutee
 	const mutee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -63,9 +43,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		muteeId: mutee.id,
 	});
 
-	if (exist != null) {
-		throw new ApiError(meta.errors.alreadyMuting);
-	}
+	if (exist != null) throw new ApiError('ALREADY_MUTING');
 
 	// Create mute
 	await RenoteMutings.insert({
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
index bdfaae263..0f7c4cde7 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
@@ -11,25 +11,7 @@ export const meta = {
 
 	kind: 'write:mutes',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'b851d00b-8ab1-4a56-8b1b-e24187cb48ef',
-		},
-
-		muteeIsYourself: {
-			message: 'Mutee is yourself.',
-			code: 'MUTEE_IS_YOURSELF',
-			id: 'f428b029-6b39-4d48-a1d2-cc1ae6dd5cf9',
-		},
-
-		notMuting: {
-			message: 'You are not muting that user.',
-			code: 'NOT_MUTING',
-			id: '5467d020-daa9-4553-81e1-135c0c35a96d',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'MUTEE_IS_YOURSELF', 'NOT_MUTING'],
 } as const;
 
 export const paramDef = {
@@ -45,13 +27,11 @@ export default define(meta, paramDef, async (ps, user) => {
 	const muter = user;
 
 	// Check if the mutee is yourself
-	if (user.id === ps.userId) {
-		throw new ApiError(meta.errors.muteeIsYourself);
-	}
+	if (user.id === ps.userId) throw new ApiError('MUTEE_IS_YOURSELF');
 
 	// Get mutee
 	const mutee = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -61,9 +41,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		muteeId: mutee.id,
 	});
 
-	if (exist == null) {
-		throw new ApiError(meta.errors.notMuting);
-	}
+	if (exist == null) throw new ApiError('NOT_MUTING');
 
 	// Delete mute
 	await RenoteMutings.delete({
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index 7f17339e9..1d145c31d 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -7,10 +7,6 @@ export const meta = {
 	requireCredential: false,
 
 	description: 'Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis.',
-
-	errors: {
-
-	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts
index 5ed4600ec..51b26a2b0 100644
--- a/packages/backend/src/server/api/endpoints/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/reset-password.ts
@@ -15,13 +15,7 @@ export const meta = {
 		max: 1,
 	},
 
-	errors: {
-		noSuchResetRequest: {
-			message: 'No such reset request.',
-			code: 'NO_SUCH_RESET_REQUEST',
-			id: '6382759d-294c-43de-89b3-4e825006ca43',
-		},
-	},
+	errors: ['NO_SUCH_RESET_REQUEST'],
 } as const;
 
 export const paramDef = {
@@ -38,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	const req = await PasswordResetRequests.findOneBy({
 		token: ps.token,
 	});
-	if (req == null) throw new ApiError(meta.errors.noSuchResetRequest);
+	if (req == null) throw new ApiError('NO_SUCH_RESET_REQUEST');
 
 	// expires after 30 minutes
 	// This is a secondary check just in case the expiry task is broken,
@@ -46,7 +40,7 @@ export default define(meta, paramDef, async (ps, user) => {
 	// else strange is going on.
 	if (Date.now() - req.createdAt.getTime() > 30 * MINUTE) {
 		await PasswordResetRequests.delete(req.id);
-		throw new ApiError(meta.errors.noSuchResetRequest);
+		throw new ApiError('NO_SUCH_RESET_REQUEST');
 	}
 
 	// Generate hash of password
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 7f9f98076..e395c3f79 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -22,19 +22,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '27fa5435-88ab-43de-9360-387de88727cd',
-		},
-
-		forbidden: {
-			message: 'Forbidden.',
-			code: 'FORBIDDEN',
-			id: '3c6a84db-d619-26af-ca14-06232a21df8a',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'ACCESS_DENIED'],
 } as const;
 
 export const paramDef = {
@@ -71,26 +59,24 @@ export default define(meta, paramDef, async (ps, me) => {
 		? { id: ps.userId }
 		: { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() });
 
-	if (user == null) {
-		throw new ApiError(meta.errors.noSuchUser);
-	}
+	if (user == null) throw new ApiError('NO_SUCH_USER');
 
 	const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
 
 	if (profile.ffVisibility === 'private') {
 		if (me == null || (me.id !== user.id)) {
-			throw new ApiError(meta.errors.forbidden);
+			throw new ApiError('ACCESS_DENIED');
 		}
 	} else if (profile.ffVisibility === 'followers') {
 		if (me == null) {
-			throw new ApiError(meta.errors.forbidden);
+			throw new ApiError('ACCESS_DENIED');
 		} else if (me.id !== user.id) {
 			const following = await Followings.findOneBy({
 				followeeId: user.id,
 				followerId: me.id,
 			});
 			if (following == null) {
-				throw new ApiError(meta.errors.forbidden);
+				throw new ApiError('ACCESS_DENIED');
 			}
 		}
 	}
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 0aaa810f7..51ad75a77 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -22,19 +22,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '63e4aba4-4156-4e53-be25-c9559e42d71b',
-		},
-
-		forbidden: {
-			message: 'Forbidden.',
-			code: 'FORBIDDEN',
-			id: 'f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba',
-		},
-	},
+	errors: ['ACCESS_DENIED', 'NO_SUCH_USER'],
 } as const;
 
 export const paramDef = {
@@ -71,26 +59,24 @@ export default define(meta, paramDef, async (ps, me) => {
 		? { id: ps.userId }
 		: { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) ?? IsNull() });
 
-	if (user == null) {
-		throw new ApiError(meta.errors.noSuchUser);
-	}
+	if (user == null) throw new ApiError('NO_SUCH_USER');
 
 	const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
 
 	if (profile.ffVisibility === 'private') {
 		if (me == null || (me.id !== user.id)) {
-			throw new ApiError(meta.errors.forbidden);
+			throw new ApiError('ACCESS_DENIED');
 		}
 	} else if (profile.ffVisibility === 'followers') {
 		if (me == null) {
-			throw new ApiError(meta.errors.forbidden);
+			throw new ApiError('ACCESS_DENIED');
 		} else if (me.id !== user.id) {
 			const following = await Followings.findOneBy({
 				followeeId: user.id,
 				followerId: me.id,
 			});
 			if (following == null) {
-				throw new ApiError(meta.errors.forbidden);
+				throw new ApiError('ACCESS_DENIED');
 			}
 		}
 	}
diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
index 2ff1f9aec..8c9ff7134 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
@@ -11,13 +11,7 @@ export const meta = {
 
 	description: 'Delete an existing group.',
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: '63dbd64c-cd77-413f-8e08-61781e210b38',
-		},
-	},
+	errors: ['NO_SUCH_GROUP'],
 } as const;
 
 export const paramDef = {
@@ -35,9 +29,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 	await UserGroups.delete(userGroup.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
index 220fff5f3..88086a084 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
@@ -13,13 +13,7 @@ export const meta = {
 
 	description: 'Join a group the authenticated user has been invited to.',
 
-	errors: {
-		noSuchInvitation: {
-			message: 'No such invitation.',
-			code: 'NO_SUCH_INVITATION',
-			id: '98c11eca-c890-4f42-9806-c8c8303ebb5e',
-		},
-	},
+	errors: ['NO_SUCH_INVITATION'],
 } as const;
 
 export const paramDef = {
@@ -35,15 +29,10 @@ export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the invitation
 	const invitation = await UserGroupInvitations.findOneBy({
 		id: ps.invitationId,
+		userId: user.id,
 	});
 
-	if (invitation == null) {
-		throw new ApiError(meta.errors.noSuchInvitation);
-	}
-
-	if (invitation.userId !== user.id) {
-		throw new ApiError(meta.errors.noSuchInvitation);
-	}
+	if (invitation == null) throw new ApiError('NO_SUCH_INVITATION');
 
 	// Push the user
 	await UserGroupJoinings.insert({
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
index 8d1d3db73..0f3712b02 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
@@ -11,13 +11,7 @@ export const meta = {
 
 	description: 'Delete an existing group invitation for the authenticated user without joining the group.',
 
-	errors: {
-		noSuchInvitation: {
-			message: 'No such invitation.',
-			code: 'NO_SUCH_INVITATION',
-			id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656',
-		},
-	},
+	errors: ['NO_SUCH_INVITATION'],
 } as const;
 
 export const paramDef = {
@@ -33,15 +27,10 @@ export default define(meta, paramDef, async (ps, user) => {
 	// Fetch the invitation
 	const invitation = await UserGroupInvitations.findOneBy({
 		id: ps.invitationId,
+		userId: user.id,
 	});
 
-	if (invitation == null) {
-		throw new ApiError(meta.errors.noSuchInvitation);
-	}
-
-	if (invitation.userId !== user.id) {
-		throw new ApiError(meta.errors.noSuchInvitation);
-	}
+	if (invitation == null) throw new ApiError('NO_SUCH_INVITATION');
 
 	await UserGroupInvitations.delete(invitation.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
index 1a8d320f3..7a792a49b 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
@@ -15,31 +15,7 @@ export const meta = {
 
 	description: 'Invite a user to an existing group.',
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: '583f8bc0-8eee-4b78-9299-1e14fc91e409',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'da52de61-002c-475b-90e1-ba64f9cf13a8',
-		},
-
-		alreadyAdded: {
-			message: 'That user has already been added to that group.',
-			code: 'ALREADY_ADDED',
-			id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c',
-		},
-
-		alreadyInvited: {
-			message: 'That user has already been invited to that group.',
-			code: 'ALREADY_INVITED',
-			id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'NO_SUCH_GROUP', 'ALREADY_ADDED', 'ALREADY_INVITED'],
 } as const;
 
 export const paramDef = {
@@ -59,13 +35,11 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 	// Fetch the user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -74,18 +48,14 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: user.id,
 	});
 
-	if (joining) {
-		throw new ApiError(meta.errors.alreadyAdded);
-	}
+	if (joining) throw new ApiError('ALREADY_ADDED');
 
 	const existInvitation = await UserGroupInvitations.findOneBy({
 		userGroupId: userGroup.id,
 		userId: user.id,
 	});
 
-	if (existInvitation) {
-		throw new ApiError(meta.errors.alreadyInvited);
-	}
+	if (existInvitation) throw new ApiError('ALREADY_INVITED');
 
 	const invitation = await UserGroupInvitations.insert({
 		id: genId(),
diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
index 83dc757db..19a4982bb 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
@@ -11,19 +11,7 @@ export const meta = {
 
 	description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.',
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: '62780270-1f67-5dc0-daca-3eb510612e31',
-		},
-
-		youAreOwner: {
-			message: 'Your are the owner.',
-			code: 'YOU_ARE_OWNER',
-			id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69',
-		},
-	},
+	errors: ['NO_SUCH_GROUP', 'GROUP_OWNER'],
 } as const;
 
 export const paramDef = {
@@ -41,13 +29,9 @@ export default define(meta, paramDef, async (ps, me) => {
 		id: ps.groupId,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
-	if (me.id === userGroup.userId) {
-		throw new ApiError(meta.errors.youAreOwner);
-	}
+	if (me.id === userGroup.userId) throw new ApiError('GROUP_OWNER');
 
 	await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: me.id });
 });
diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
index ba67a1e5c..30caceb27 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
@@ -12,25 +12,7 @@ export const meta = {
 
 	description: 'Removes a specified user from a group. The owner can not be removed.',
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: '4662487c-05b1-4b78-86e5-fd46998aba74',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '0b5cc374-3681-41da-861e-8bc1146f7a55',
-		},
-
-		isOwner: {
-			message: 'The user is the owner.',
-			code: 'IS_OWNER',
-			id: '1546eed5-4414-4dea-81c1-b0aec4f6d2af',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'NO_SUCH_GROUP', 'GROUP_OWNER'],
 } as const;
 
 export const paramDef = {
@@ -50,19 +32,15 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 	// Fetch the user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
-	if (user.id === userGroup.userId) {
-		throw new ApiError(meta.errors.isOwner);
-	}
+	if (user.id === userGroup.userId) throw new ApiError('GROUP_OWNER');
 
 	// Pull the user
 	await UserGroupJoinings.delete({ userGroupId: userGroup.id, userId: user.id });
diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts
index 21e3d9da2..eddd933c2 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts
@@ -17,13 +17,7 @@ export const meta = {
 		ref: 'UserGroup',
 	},
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b',
-		},
-	},
+	errors: ['NO_SUCH_GROUP'],
 } as const;
 
 export const paramDef = {
@@ -41,9 +35,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		id: ps.groupId,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 	const joining = await UserGroupJoinings.findOneBy({
 		userId: me.id,
@@ -51,7 +43,7 @@ export default define(meta, paramDef, async (ps, me) => {
 	});
 
 	if (joining == null && userGroup.userId !== me.id) {
-		throw new ApiError(meta.errors.noSuchGroup);
+		throw new ApiError('NO_SUCH_GROUP');
 	}
 
 	return await UserGroups.pack(userGroup);
diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
index 6456e70dd..b6607e4f8 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
@@ -18,25 +18,7 @@ export const meta = {
 		ref: 'UserGroup',
 	},
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: '8e31d36b-2f88-4ccd-a438-e2d78a9162db',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '711f7ebb-bbb9-4dfa-b540-b27809fed5e9',
-		},
-
-		noSuchGroupMember: {
-			message: 'No such group member.',
-			code: 'NO_SUCH_GROUP_MEMBER',
-			id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4',
-		},
-	},
+	errors: ['NO_SUCH_GROUP', 'NO_SUCH_USER'],
 } as const;
 
 export const paramDef = {
@@ -56,13 +38,11 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 	// Fetch the user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -71,9 +51,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: user.id,
 	});
 
-	if (joining == null) {
-		throw new ApiError(meta.errors.noSuchGroupMember);
-	}
+	if (joining == null) throw new ApiError('NO_SUCH_USER', 'The user exists but is not a member of the group.');
 
 	await UserGroups.update(userGroup.id, {
 		userId: ps.userId,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts
index 0a96165fc..81d5f26ec 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts
@@ -17,13 +17,7 @@ export const meta = {
 		ref: 'UserGroup',
 	},
 
-	errors: {
-		noSuchGroup: {
-			message: 'No such group.',
-			code: 'NO_SUCH_GROUP',
-			id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6',
-		},
-	},
+	errors: ['NO_SUCH_GROUP'],
 } as const;
 
 export const paramDef = {
@@ -43,9 +37,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userGroup == null) {
-		throw new ApiError(meta.errors.noSuchGroup);
-	}
+	if (userGroup == null) throw new ApiError('NO_SUCH_GROUP');
 
 	await UserGroups.update(userGroup.id, {
 		name: ps.name,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
index 5a7613c98..e8f476794 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
@@ -11,13 +11,7 @@ export const meta = {
 
 	description: 'Delete an existing list of users.',
 
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '78436795-db79-42f5-b1e2-55ea2cf19166',
-		},
-	},
+	errors: ['NO_SUCH_USER_LIST'],
 } as const;
 
 export const paramDef = {
@@ -35,9 +29,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (userList == null) {
-		throw new ApiError(meta.errors.noSuchList);
-	}
+	if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 
 	await UserLists.delete(userList.id);
 });
diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
index d3d1d6555..0860b97ef 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
@@ -13,19 +13,7 @@ export const meta = {
 
 	description: 'Remove a user from a list.',
 
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '7f44670e-ab16-43b8-b4c1-ccd2ee89cc02',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '588e7f72-c744-4a61-b180-d354e912bda2',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'NO_SUCH_USER_LIST'],
 } as const;
 
 export const paramDef = {
@@ -45,13 +33,11 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userList == null) {
-		throw new ApiError(meta.errors.noSuchList);
-	}
+	if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 
 	// Fetch the user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index 12b7b8634..7552f3331 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -13,31 +13,7 @@ export const meta = {
 
 	description: 'Add a user to an existing list.',
 
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '2214501d-ac96-4049-b717-91e42272a711',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'a89abd3d-f0bc-4cce-beb1-2f446f4f1e6a',
-		},
-
-		alreadyAdded: {
-			message: 'That user has already been added to that list.',
-			code: 'ALREADY_ADDED',
-			id: '1de7c884-1595-49e9-857e-61f12f4d4fc5',
-		},
-
-		youHaveBeenBlocked: {
-			message: 'You cannot push this user because you have been blocked by this user.',
-			code: 'YOU_HAVE_BEEN_BLOCKED',
-			id: '990232c5-3f9d-4d83-9f3f-ef27b6332a4b',
-		},
-	},
+	errors: ['ALREADY_ADDED', 'BLOCKED', 'NO_SUCH_USER', 'NO_SUCH_USER_LIST'],
 } as const;
 
 export const paramDef = {
@@ -57,13 +33,11 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userList == null) {
-		throw new ApiError(meta.errors.noSuchList);
-	}
+	if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 
 	// Fetch the user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
@@ -73,9 +47,7 @@ export default define(meta, paramDef, async (ps, me) => {
 			blockerId: user.id,
 			blockeeId: me.id,
 		});
-		if (block) {
-			throw new ApiError(meta.errors.youHaveBeenBlocked);
-		}
+		if (block) throw new ApiError('BLOCKED');
 	}
 
 	const exist = await UserListJoinings.findOneBy({
@@ -83,9 +55,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: user.id,
 	});
 
-	if (exist) {
-		throw new ApiError(meta.errors.alreadyAdded);
-	}
+	if (exist) throw new ApiError('ALREADY_ADDED');
 
 	// Push the user
 	await pushUserToUserList(user, userList);
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index fd0612f73..64f280a61 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -17,13 +17,7 @@ export const meta = {
 		ref: 'UserList',
 	},
 
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686',
-		},
-	},
+	errors: ['NO_SUCH_USER_LIST'],
 } as const;
 
 export const paramDef = {
@@ -42,9 +36,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		userId: me.id,
 	});
 
-	if (userList == null) {
-		throw new ApiError(meta.errors.noSuchList);
-	}
+	if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 
 	return await UserLists.pack(userList);
 });
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts
index 65e708b95..f0df2d282 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts
@@ -17,13 +17,7 @@ export const meta = {
 		ref: 'UserList',
 	},
 
-	errors: {
-		noSuchList: {
-			message: 'No such list.',
-			code: 'NO_SUCH_LIST',
-			id: '796666fe-3dff-4d39-becb-8a5932c1d5b7',
-		},
-	},
+	errors: ['NO_SUCH_USER_LIST'],
 } as const;
 
 export const paramDef = {
@@ -43,9 +37,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		userId: user.id,
 	});
 
-	if (userList == null) {
-		throw new ApiError(meta.errors.noSuchList);
-	}
+	if (userList == null) throw new ApiError('NO_SUCH_USER_LIST');
 
 	await UserLists.update(userList.id, {
 		name: ps.name,
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 9fa56fe83..02520036f 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -23,13 +23,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '27e494ba-2ac2-48e8-893b-10d4d8c2387b',
-		},
-	},
+	errors: ['NO_SUCH_USER'],
 } as const;
 
 export const paramDef = {
@@ -56,7 +50,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	// Lookup user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index 4750dc4f9..67ef1271f 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -21,13 +21,7 @@ export const meta = {
 		},
 	},
 
-	errors: {
-		reactionsNotPublic: {
-			message: 'Reactions of the user is not public.',
-			code: 'REACTIONS_NOT_PUBLIC',
-			id: '673a7dd2-6924-1093-e0c0-e68456ceae5c',
-		},
-	},
+	errors: ['ACCESS_DENIED'],
 } as const;
 
 export const paramDef = {
@@ -48,7 +42,7 @@ export default define(meta, paramDef, async (ps, me) => {
 	const profile = await UserProfiles.findOneByOrFail({ userId: ps.userId });
 
 	if (me == null || (me.id !== ps.userId && !profile.publicReactions)) {
-		throw new ApiError(meta.errors.reactionsNotPublic);
+		throw new ApiError('ACCESS_DENIED');
 	}
 
 	const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'),
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index eacfe72f4..7ad92293f 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -16,25 +16,7 @@ export const meta = {
 
 	description: 'File a report.',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '1acefcb5-0959-43fd-9685-b48305736cb5',
-		},
-
-		cannotReportYourself: {
-			message: 'Cannot report yourself.',
-			code: 'CANNOT_REPORT_YOURSELF',
-			id: '1e13149e-b1e8-43cf-902e-c01dbfcb202f',
-		},
-
-		cannotReportAdmin: {
-			message: 'Cannot report the admin.',
-			code: 'CANNOT_REPORT_THE_ADMIN',
-			id: '35e166f5-05fb-4f87-a2d5-adb42676d48f',
-		},
-	},
+	errors: ['NO_SUCH_USER', 'CANNOT_REPORT_ADMIN', 'CANNOT_REPORT_YOURSELF'],
 } as const;
 
 export const paramDef = {
@@ -51,17 +33,13 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	// Lookup user
 	const user = await getUser(ps.userId).catch(e => {
-		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError('NO_SUCH_USER');
 		throw e;
 	});
 
-	if (user.id === me.id) {
-		throw new ApiError(meta.errors.cannotReportYourself);
-	}
+	if (user.id === me.id) throw new ApiError('CANNOT_REPORT_YOURSELF');
 
-	if (user.isAdmin) {
-		throw new ApiError(meta.errors.cannotReportAdmin);
-	}
+	if (user.isAdmin) throw new ApiError('CANNOT_REPORT_ADMIN');
 
 	const uri = user.host == null ? `${config.url}/users/${user.id}` : user.uri;
 	if (!ps.urls.includes(uri)) {
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 846d83b49..33c0cfca3 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -30,20 +30,7 @@ export const meta = {
 		],
 	},
 
-	errors: {
-		failedToResolveRemoteUser: {
-			message: 'Failed to resolve remote user.',
-			code: 'FAILED_TO_RESOLVE_REMOTE_USER',
-			id: 'ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c',
-			kind: 'server',
-		},
-
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '4362f8dc-731f-4ad8-a694-be5a88922a24',
-		},
-	},
+	errors: ['FAILED_TO_RESOLVE_REMOTE_USER', 'NO_SUCH_USER'],
 } as const;
 
 export const paramDef = {
@@ -109,7 +96,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		if (typeof ps.host === 'string' && typeof ps.username === 'string') {
 			user = await resolveUser(ps.username, ps.host).catch(e => {
 				apiLogger.warn(`failed to resolve remote user: ${e}`);
-				throw new ApiError(meta.errors.failedToResolveRemoteUser);
+				throw new ApiError('FAILED_TO_RESOLVE_REMOTE_USER');
 			});
 		} else {
 			const q: FindOptionsWhere<User> = ps.userId != null
@@ -120,7 +107,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		}
 
 		if (user == null || (!isAdminOrModerator && user.isSuspended)) {
-			throw new ApiError(meta.errors.noSuchUser);
+			throw new ApiError('NO_SUCH_USER');
 		}
 
 		return await Users.pack(user, me, {
diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts
index 47f322ee9..55d4f4314 100644
--- a/packages/backend/src/server/api/endpoints/users/stats.ts
+++ b/packages/backend/src/server/api/endpoints/users/stats.ts
@@ -10,13 +10,7 @@ export const meta = {
 
 	description: 'Show statistics about a user.',
 
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: '9e638e45-3b25-4ef7-8f95-07e8498f1819',
-		},
-	},
+	errors: ['NO_SUCH_USER'],
 
 	res: {
 		type: 'object',
@@ -119,7 +113,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, me) => {
 	const user = await Users.findOneBy({ id: ps.userId });
 	if (user == null) {
-		throw new ApiError(meta.errors.noSuchUser);
+		throw new ApiError('NO_SUCH_USER');
 	}
 
 	const result = await awaitAll({
diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts
index 6fc5ea38b..2cdabce48 100644
--- a/packages/backend/src/server/api/error.ts
+++ b/packages/backend/src/server/api/error.ts
@@ -1,29 +1,357 @@
-type E = { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number };
-
 export class ApiError extends Error {
 	public message: string;
 	public code: string;
-	public id: string;
-	public kind: string;
-	public httpStatusCode?: number;
+	public httpStatusCode: number;
 	public info?: any;
 
 	constructor(
-		e: E = {
-			message: 'Internal error occurred. Please contact us if the error persists.',
-			code: 'INTERNAL_ERROR',
-			id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
-			kind: 'server',
-			httpStatusCode: 500,
-		},
+		code: keyof errors = 'INTERNAL_ERROR',
 		info?: any | null,
 	) {
-		super(e.message);
-		this.message = e.message;
-		this.code = e.code;
-		this.id = e.id;
-		this.kind = e.kind ?? 'client';
-		this.httpStatusCode = e.httpStatusCode ?? 500;
-		this.info = info;
+		if(!code in errors) {
+			info = `Unknown error "${code}" occurred.`;
+			code = 'INTERNAL_ERROR';
+		}
+
+		const { message, httpStatusCode } = errors[code];
+		super(message);
+		this.code = code;
+		this.message = message;
+		this.httpStatusCode = httpStatusCode;
 	}
 }
+
+export const errors: Record<string, { message: string, httpStatusCode: number }> = {
+	ACCESS_DENIED: {
+		message: 'Access denied.',
+		httpStatusCode: 403,
+	},
+	ALREADY_ADDED: {
+		message: 'That user has already been added to that list or group.',
+		httpStatusCode: 409,
+	},
+	ALREADY_BLOCKING: {
+		message: 'You are already blocking that user.',
+		httpStatusCode: 409,
+	},
+	ALREADY_CLIPPED: {
+		message: 'That note is already added to that clip.',
+		httpStatusCode: 409,
+	},
+	ALREADY_FAVORITED: {
+		message: 'That note is already favorited.',
+		httpStatusCode: 409,
+	},
+	ALREADY_FOLLOWING: {
+		message: 'You are already following that user.',
+		httpStatusCode: 409,
+	},
+	ALREADY_INVITED: {
+		message: 'That user has already been invited to that group.',
+		httpStatusCode: 409,
+	},
+	ALREADY_LIKED: {
+		message: 'You already liked that page or gallery post.',
+		httpStatusCode: 409,
+	},
+	ALREADY_MUTING: {
+		message: 'You are already muting that user.',
+		httpStatusCode: 409,
+	},
+	ALREADY_PINNED: {
+		message: 'You already pinned that note.',
+		httpStatusCode: 409,
+	},
+	ALREADY_REACTED: {
+		message: 'You already reacted to that note.',
+		httpStatusCode: 409,
+	},
+	ALREADY_VOTED: {
+		message: 'You have already voted in that poll.',
+		httpStatusCode: 409,
+	},
+	AUTHENTICATION_FAILED: {
+		message: 'Authentication failed.',
+		httpStatusCode: 401,
+	},
+	AUTHENTICATION_REQUIRED: {
+		message: 'Authentication is required, but authenticating information was not or not appropriately provided.',
+		httpStatusCode: 401,
+	},
+	BLOCKED: {
+		message: 'You are blocked by that user.',
+		httpStatusCode: 400,
+	},
+	BLOCKEE_IS_YOURSELF: {
+		message: 'You cannot block yourself.',
+		httpStatusCode: 400,
+	},
+	BLOCKING: {
+		message: 'You are blocking that user.',
+		httpStatusCode: 400,
+	},
+	CANNOT_REPORT_ADMIN: {
+		message: 'You cannot report an administrator.',
+		httpStatusCode: 400,
+	},
+	CANNOT_REPORT_YOURSELF: {
+		message: 'You cannot report yourself.',
+		httpStatusCode: 400,
+	},
+	EMPTY_FILE: {
+		message: 'The provided file is empty.',
+		httpStatusCode: 400,
+	},
+	EXPIRED_POLL: {
+		message: 'Poll is already expired.',
+		httpStatusCode: 400,
+	},
+	FAILED_TO_RESOLVE_REMOTE_USER: {
+		message: 'Failed to resolve remote user.',
+		httpStatusCode: 502,
+	},
+	FILE_TOO_BIG: {
+		message: 'The provided file is too big.',
+		httpStatusCode: 400,
+	},
+	FILE_REQUIRED: {
+		message: 'This operation requires a file to be provided.',
+		httpStatusCode: 400,
+	},
+	FOLLOWEE_IS_YOURSELF: {
+		message: 'You cannot follow yourself.',
+		httpStatusCode: 400,
+	},
+	FOLLOWER_IS_YOURSELF: {
+		message: 'You cannot unfollow yourself.',
+		httpStatusCode: 400,
+	},
+	GROUP_OWNER: {
+		message: 'The owner of a group may not leave. Instead, ownership can be transferred or the group deleted.',
+		httpStatusCode: 400,
+	},
+	HAS_CHILD_FILES_OR_FOLDERS: {
+		message: 'That folder is not empty.',
+		httpStatusCode: 400,
+	},
+	INTERNAL_ERROR: {
+		message: 'Internal error occurred. Please contact us if the error persists.',
+		httpStatusCode: 500,
+	},
+	INVALID_CHOICE: {
+		message: 'Choice index is invalid.',
+		httpStatusCode: 400,
+	},
+	INVALID_FILE_NAME: {
+		message: 'Invalid file name.',
+		httpStatusCode: 400,
+	},
+	INVALID_PARAM: {
+		message: 'One or more parameters do not match the API definition.',
+		httpStatusCode: 400,
+	},
+	INVALID_PASSWORD: {
+		message: 'The provided password is not suitable.',
+		httpStatusCode: 400,
+	},
+	INVALID_REGEXP: {
+		message: 'Invalid Regular Expression',
+		httpStatusCode: 400,
+	},
+	INVALID_URL: {
+		message: 'Invalid URL.',
+		httpStatusCode: 400,
+	},
+	INVALID_USERNAME: {
+		message: 'Invalid username.',
+		httpStatusCode: 400,
+	},
+	LESS_RESTRICTIVE_VISIBILITY: {
+		message: 'The visibility cannot be less restrictive than the parent note.',
+		httpStatusCode: 400,
+	},
+	MUTEE_IS_YOURSELF: {
+		message: 'You cannot mute yourself.',
+		httpStatusCode: 400,
+	},
+	NAME_ALREADY_EXISTS: {
+		message: 'The specified name already exists.',
+		httpStatusCode: 409,
+	},
+	NO_POLL: {
+		message: 'The note does not have an attached poll.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_ANNOUNCEMENT: {
+		message: 'No such announcement.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_ANTENNA: {
+		message: 'No such antenna.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_APP: {
+		message: 'No such app.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_CLIP: {
+		message: 'No such clip.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_CHANNEL: {
+		message: 'No such channel.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_EMOJI: {
+		message: 'No such emoji.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_ENDPOINT: {
+		message: 'No such endpoint.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_FILE: {
+		message: 'No such file.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_FOLDER: {
+		message: 'No such folder.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_FOLLOW_REQUEST: {
+		message: 'No such follow request.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_GROUP: {
+		message: 'No such user group.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_HASHTAG: {
+		message: 'No such hashtag.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_INVITATION: {
+		message: 'No such group invitation.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_KEY: {
+		message: 'No such key.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_NOTE: {
+		message: 'No such note.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_NOTIFICATION: {
+		message: 'No such notification.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_MESSAGE: {
+		message: 'No such message.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_OBJECT: {
+		message: 'No such object.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_PAGE: {
+		message: 'No such page.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_PARENT_FOLDER: {
+		message: 'No such parent folder.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_POST: {
+		message: 'No such gallery post.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_RESET_REQUEST: {
+		message: 'No such password reset request.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_SESSION: {
+		message: 'No such session',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_USER: {
+		message: 'No such user.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_USER_LIST: {
+		message: 'No such user list.',
+		httpStatusCode: 404,
+	},
+	NO_SUCH_WEBHOOK: {
+		message: 'No such webhook.',
+		httpStatusCode: 404,
+	},
+	NOT_AN_IMAGE: {
+		message: 'The file specified was expected to be an image, but it is not.',
+		httpStatusCode: 400,
+	},
+	NOT_BLOCKING: {
+		message: 'You are not blocking that user.',
+		httpStatusCode: 409,
+	},
+	NOT_CLIPPED: {
+		message: 'That note is not added to that clip.',
+		httpStatusCode: 409,
+	},
+	NOT_FAVORITED: {
+		message: 'You have not favorited that note.',
+		httpStatusCode: 409,
+	},
+	NOT_FOLLOWING: {
+		message: 'You are not following that user.',
+		httpStatusCode: 409,
+	},
+	NOT_LIKED: {
+		message: 'You have not liked that page or gallery post.',
+		httpStatusCode: 409,
+	},
+	NOT_MUTING: {
+		message: 'You are not muting that user.',
+		httpStatusCode: 409,
+	},
+	NOT_REACTED: {
+		message: 'You have not reacted to that note.',
+		httpStatusCode: 409,
+	},
+	PENDING_SESSION: {
+		message: 'That authorization process has not been completed yet.',
+		httpStatusCode: 400,
+	},
+	PIN_LIMIT_EXCEEDED: {
+		message: 'You can not pin any more notes.',
+		httpStatusCode: 400,
+	},
+	PURE_RENOTE: {
+		message: 'You cannot renote or reply to a pure renote.',
+		httpStatusCode: 400,
+	},
+	RATE_LIMIT_EXCEEDED: {
+		message: 'Rate limit exceeded. Please try again later.',
+		httpStatusCode: 429,
+	},
+	RECIPIENT_IS_YOURSELF: {
+		message: 'You cannot send a message to yourself.',
+		httpStatusCode: 400,
+	},
+	RECURSIVE_FOLDER: {
+		message: 'Folder cannot be its own parent.',
+		httpStatusCode: 400,
+	},
+	SUSPENDED: {
+		message: 'Your account has been suspended.',
+		httpStatusCode: 403,
+	},
+	TIMELINE_DISABLED: {
+		message: 'This timeline is disabled by an administrator.',
+		httpStatusCode: 503,
+	},
+	USED_USERNAME: {
+		message: 'That username is not available because it is being used or has been used before. Usernames cannot be reassigned.',
+		httpStatusCode: 409,
+	},
+};

From 1dd935dc0ca8d140deae5a34effadfae8af83758 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 27 Oct 2022 19:50:00 +0200
Subject: [PATCH 3/5] fix endpoint type definition for errors

---
 packages/backend/src/server/api/endpoints.ts | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index ff35d848d..1268f2670 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -1,4 +1,5 @@
 import { Schema } from '@/misc/schema.js';
+import { errors } from './error.js';
 
 import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
@@ -621,13 +622,7 @@ export interface IEndpointMeta {
 
 	readonly tags?: ReadonlyArray<string>;
 
-	readonly errors?: {
-		readonly [key: string]: {
-			readonly message: string;
-			readonly code: string;
-			readonly id: string;
-		};
-	};
+	readonly errors?: Array<keyof errors>;
 
 	readonly res?: Schema;
 

From fb76843c190ebc6c6185cfa35629ceda1cfbdf5f Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 27 Oct 2022 19:59:11 +0200
Subject: [PATCH 4/5] adapt OpenAPI documentation generation to new error
 definitions

---
 .../backend/src/server/api/openapi/errors.ts  |  69 --------
 .../src/server/api/openapi/gen-spec.ts        | 161 ++++++++----------
 .../src/server/api/openapi/http-codes.ts      |  67 ++++++++
 .../backend/src/server/api/openapi/schemas.ts |  18 +-
 4 files changed, 145 insertions(+), 170 deletions(-)
 delete mode 100644 packages/backend/src/server/api/openapi/errors.ts
 create mode 100644 packages/backend/src/server/api/openapi/http-codes.ts

diff --git a/packages/backend/src/server/api/openapi/errors.ts b/packages/backend/src/server/api/openapi/errors.ts
deleted file mode 100644
index 3f733b4ea..000000000
--- a/packages/backend/src/server/api/openapi/errors.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-
-export const errors = {
-	'400': {
-		'INVALID_PARAM': {
-			value: {
-				error: {
-					message: 'Invalid param.',
-					code: 'INVALID_PARAM',
-					id: '3d81ceae-475f-4600-b2a8-2bc116157532',
-				},
-			},
-		},
-	},
-	'401': {
-		'CREDENTIAL_REQUIRED': {
-			value: {
-				error: {
-					message: 'Credential required.',
-					code: 'CREDENTIAL_REQUIRED',
-					id: '1384574d-a912-4b81-8601-c7b1c4085df1',
-				},
-			},
-		},
-	},
-	'403': {
-		'AUTHENTICATION_FAILED': {
-			value: {
-				error: {
-					message: 'Authentication failed. Please ensure your token is correct.',
-					code: 'AUTHENTICATION_FAILED',
-					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
-				},
-			},
-		},
-	},
-	'418': {
-		'I_AM_A_TEAPOT': {
-			value: {
-				error: {
-					message: 'I am a teapot.',
-					code: 'I_AM_A_TEAPOT',
-					id: '60c46cd1-f23a-46b1-bebe-5d2b73951a84',
-				},
-			},
-		},
-	},
-	'429': {
-		'RATE_LIMIT_EXCEEDED': {
-			value: {
-				error: {
-					message: 'Rate limit exceeded. Please try again later.',
-					code: 'RATE_LIMIT_EXCEEDED',
-					id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
-				},
-			},
-		},
-	},
-	'500': {
-		'INTERNAL_ERROR': {
-			value: {
-				error: {
-					message: 'Internal error occurred. Please contact us if the error persists.',
-					code: 'INTERNAL_ERROR',
-					id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
-				},
-			},
-		},
-	},
-};
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index 48b57a03c..497230596 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -1,7 +1,8 @@
 import config from '@/config/index.js';
+import { errors as errorDefinitions } from '../error.js';
 import endpoints from '../endpoints.js';
-import { errors as basicErrors } from './errors.js';
 import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
+import { httpCodes } from './http-codes.js';
 
 export function genOpenapiSpec() {
 	const spec = {
@@ -43,19 +44,75 @@ export function genOpenapiSpec() {
 	};
 
 	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
-		const errors = {} as any;
+		// generate possible responses, first starting with errors
+		const responses = [
+			// general error codes that can always happen
+			'INVALID_PARAM',
+			'INTERNAL_ERROR',
+			// error codes that happen only if authentication is required
+			...(!endpoint.meta.requireCredential ? [] : [
+				'ACCESS_DENIED',
+				'AUTHENTICATION_REQUIRED',
+				'AUTHENTICATION_FAILED',
+				'SUSPENDED',
+			]),
+			// error codes that happen only if a rate limit is defined
+			...(!endpoint.meta.limit ? [] : [
+				'RATE_LIMIT_EXCEEDED',
+			]),
+			// error codes that happen only if a file is required
+			...(!endpoint.meta.requireFile ? [] : [
+				'FILE_REQUIRED',
+			]),
+			// endpoint specific error codes
+			...(endpoint.meta.errors ?? []),
+		]
+		.reduce((acc, code) => {
+			const { message, httpStatusCode } = errorDefinitions[code];
+			const httpCode = httpStatusCode.toString();
 
-		if (endpoint.meta.errors) {
-			for (const e of Object.values(endpoint.meta.errors)) {
-				errors[e.code] = {
-					value: {
-						error: e,
+			if(!(httpCode in acc)) {
+				acc[httpCode] = {
+					description: httpCodes[httpCode],
+					content: {
+						'application/json': {
+							schema: {
+								'$ref': '#/components/schemas/Error',
+							},
+							examples: {},
+						},
 					},
 				};
 			}
-		}
 
-		const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
+			acc[httpCode].content['application/json'].examples[code] = {
+				value: {
+					error: {
+						code,
+						message,
+						endpoint: endpoint.name,
+					},
+				},
+			};
+
+			return acc;
+		}, {});
+
+		// add successful response
+		if (endpoint.meta.res) {
+			responses['200'] = {
+				description: 'OK',
+				content: {
+					'application/json': {
+						schema: convertSchemaToOpenApiSchema(endpoint.meta.res),
+					},
+				},
+			};
+		} else {
+			responses['204'] = {
+				description: 'No Content',
+			};
+		}
 
 		let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
 		desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
@@ -107,90 +164,7 @@ export function genOpenapiSpec() {
 					},
 				},
 			},
-			responses: {
-				...(endpoint.meta.res ? {
-					'200': {
-						description: 'OK (with results)',
-						content: {
-							'application/json': {
-								schema: resSchema,
-							},
-						},
-					},
-				} : {
-					'204': {
-						description: 'OK (without any results)',
-					},
-				}),
-				'400': {
-					description: 'Client error',
-					content: {
-						'application/json': {
-							schema: {
-								$ref: '#/components/schemas/Error',
-							},
-							examples: { ...errors, ...basicErrors['400'] },
-						},
-					},
-				},
-				'401': {
-					description: 'Authentication error',
-					content: {
-						'application/json': {
-							schema: {
-								$ref: '#/components/schemas/Error',
-							},
-							examples: basicErrors['401'],
-						},
-					},
-				},
-				'403': {
-					description: 'Forbidden error',
-					content: {
-						'application/json': {
-							schema: {
-								$ref: '#/components/schemas/Error',
-							},
-							examples: basicErrors['403'],
-						},
-					},
-				},
-				'418': {
-					description: 'I\'m Ai',
-					content: {
-						'application/json': {
-							schema: {
-								$ref: '#/components/schemas/Error',
-							},
-							examples: basicErrors['418'],
-						},
-					},
-				},
-				...(endpoint.meta.limit ? {
-					'429': {
-						description: 'Too many requests',
-						content: {
-							'application/json': {
-								schema: {
-									$ref: '#/components/schemas/Error',
-								},
-								examples: basicErrors['429'],
-							},
-						},
-					},
-				} : {}),
-				'500': {
-					description: 'Internal server error',
-					content: {
-						'application/json': {
-							schema: {
-								$ref: '#/components/schemas/Error',
-							},
-							examples: basicErrors['500'],
-						},
-					},
-				},
-			},
+			responses,
 		};
 
 		const path = {
@@ -200,6 +174,7 @@ export function genOpenapiSpec() {
 			path.get = { ...info };
 			// API Key authentication is not permitted for GET requests
 			path.get.security = path.get.security.filter(elem => !Object.prototype.hasOwnProperty.call(elem, 'ApiKeyAuth'));
+
 			// fix the way parameters are passed
 			delete path.get.requestBody;
 			path.get.parameters = [];
diff --git a/packages/backend/src/server/api/openapi/http-codes.ts b/packages/backend/src/server/api/openapi/http-codes.ts
new file mode 100644
index 000000000..4727ef485
--- /dev/null
+++ b/packages/backend/src/server/api/openapi/http-codes.ts
@@ -0,0 +1,67 @@
+export const httpCodes: Record<string, string> = {
+	"100": "Continue",
+	"101": "Switching Protocols",
+	"102": "Processing",
+	"103": "Early Hints",
+	"200": "OK",
+	"201": "Created",
+	"202": "Accepted",
+	"203": "Non-Authoritative Information",
+	"204": "No Content",
+	"205": "Reset Content",
+	"206": "Partial Content",
+	"207": "Multi-Status",
+	"208": "Already Reported",
+	"226": "IM Used",
+	"300": "Multiple Choices",
+	"301": "Moved Permanently",
+	"302": "Found",
+	"303": "See Other",
+	"304": "Not Modified",
+	"305": "Use Proxy",
+	"307": "Temporary Redirect",
+	"308": "Permanent Redirect",
+	"400": "Bad Request",
+	"401": "Unauthorized",
+	"402": "Payment Required",
+	"403": "Forbidden",
+	"404": "Not Found",
+	"405": "Method Not Allowed",
+	"406": "Not Acceptable",
+	"407": "Proxy Authentication Required",
+	"408": "Request Timeout",
+	"409": "Conflict",
+	"410": "Gone",
+	"411": "Length Required",
+	"412": "Precondition Failed",
+	"413": "Content Too Large",
+	"414": "URI Too Long",
+	"415": "Unsupported Media Type",
+	"416": "Range Not Satisfiable",
+	"417": "Expectation Failed",
+	"418": "I'm a Teapot",
+	"421": "Misdirected Request",
+	"422": "Unprocessable Content",
+	"423": "Locked",
+	"424": "Failed Dependency",
+	"425": "Too Early",
+	"426": "Upgrade Required",
+	"427": "Unassigned",
+	"428": "Precondition Required",
+	"429": "Too Many Requests",
+	"430": "Unassigned",
+	"431": "Request Header Fields Too Large",
+	"451": "Unavailable For Legal Reasons",
+	"500": "Internal Server Error",
+	"501": "Not Implemented",
+	"502": "Bad Gateway",
+	"503": "Service Unavailable",
+	"504": "Gateway Timeout",
+	"505": "HTTP Version Not Supported",
+	"506": "Variant Also Negotiates",
+	"507": "Insufficient Storage",
+	"508": "Loop Detected",
+	"509": "Unassigned",
+	"510": "Not Extended",
+	"511": "Network Authentication Required"
+}
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index 4a0844b42..4bb72cd8a 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -36,19 +36,21 @@ export const schemas = {
 				properties: {
 					code: {
 						type: 'string',
-						description: 'An error code. Unique within the endpoint.',
+						description: 'A machine and human readable error code.',
+					},
+					endpoint: {
+						type: 'string',
+						description: 'Name of the API endpoint the error happened in.',
 					},
 					message: {
 						type: 'string',
-						description: 'An error message.',
-					},
-					id: {
-						type: 'string',
-						format: 'uuid',
-						description: 'An error ID. This ID is static.',
+						description: 'A human readable error description in English.',
 					},
+					info: {
+						description: 'Potentially more information, primarily intended for developers.',
+					}
 				},
-				required: ['code', 'id', 'message'],
+				required: ['code', 'endpoint', 'message'],
 			},
 		},
 		required: ['error'],

From 735b9ab5029f301328c9f9747358038c7d47dadd Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 28 Oct 2022 16:57:56 +0200
Subject: [PATCH 5/5] fix some lints

---
 .../backend/src/server/api/api-handler.ts     |   4 +-
 packages/backend/src/server/api/call.ts       |   2 +-
 packages/backend/src/server/api/error.ts      |  12 +-
 .../src/server/api/openapi/gen-spec.ts        |   2 +-
 .../src/server/api/openapi/http-codes.ts      | 132 +++++++++---------
 .../backend/src/server/api/openapi/schemas.ts |   2 +-
 6 files changed, 78 insertions(+), 76 deletions(-)

diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts
index e21585158..30a8dbcf5 100644
--- a/packages/backend/src/server/api/api-handler.ts
+++ b/packages/backend/src/server/api/api-handler.ts
@@ -25,7 +25,7 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<vo
 				endpoint: endpoint.name,
 			},
 		};
-	}
+	};
 
 	// Authentication
 	// for GET requests, do not even pass on the body parameter as it is considered unsafe
@@ -52,4 +52,4 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<vo
 			error(new ApiError());
 		}
 	});
-};
+}
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 0bdbfe633..89493ce96 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -36,7 +36,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 		}
 
 		// Rate limit
-		await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => {
+		await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(() => {
 			throw new ApiError('RATE_LIMIT_EXCEEDED');
 		});
 	}
diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts
index 2cdabce48..b93f5a2d2 100644
--- a/packages/backend/src/server/api/error.ts
+++ b/packages/backend/src/server/api/error.ts
@@ -8,14 +8,16 @@ export class ApiError extends Error {
 		code: keyof errors = 'INTERNAL_ERROR',
 		info?: any | null,
 	) {
-		if(!code in errors) {
-			info = `Unknown error "${code}" occurred.`;
-			code = 'INTERNAL_ERROR';
+		if (!(code in errors)) {
+			this.info = `Unknown error "${code}" occurred.`;
+			this.code = 'INTERNAL_ERROR';
+		} else {
+			this.info = info;
+			this.code = code;
 		}
 
-		const { message, httpStatusCode } = errors[code];
+		const { message, httpStatusCode } = errors[this.code];
 		super(message);
-		this.code = code;
 		this.message = message;
 		this.httpStatusCode = httpStatusCode;
 	}
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index 497230596..f9795884d 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -71,7 +71,7 @@ export function genOpenapiSpec() {
 			const { message, httpStatusCode } = errorDefinitions[code];
 			const httpCode = httpStatusCode.toString();
 
-			if(!(httpCode in acc)) {
+			if (!(httpCode in acc)) {
 				acc[httpCode] = {
 					description: httpCodes[httpCode],
 					content: {
diff --git a/packages/backend/src/server/api/openapi/http-codes.ts b/packages/backend/src/server/api/openapi/http-codes.ts
index 4727ef485..b3b74f867 100644
--- a/packages/backend/src/server/api/openapi/http-codes.ts
+++ b/packages/backend/src/server/api/openapi/http-codes.ts
@@ -1,67 +1,67 @@
 export const httpCodes: Record<string, string> = {
-	"100": "Continue",
-	"101": "Switching Protocols",
-	"102": "Processing",
-	"103": "Early Hints",
-	"200": "OK",
-	"201": "Created",
-	"202": "Accepted",
-	"203": "Non-Authoritative Information",
-	"204": "No Content",
-	"205": "Reset Content",
-	"206": "Partial Content",
-	"207": "Multi-Status",
-	"208": "Already Reported",
-	"226": "IM Used",
-	"300": "Multiple Choices",
-	"301": "Moved Permanently",
-	"302": "Found",
-	"303": "See Other",
-	"304": "Not Modified",
-	"305": "Use Proxy",
-	"307": "Temporary Redirect",
-	"308": "Permanent Redirect",
-	"400": "Bad Request",
-	"401": "Unauthorized",
-	"402": "Payment Required",
-	"403": "Forbidden",
-	"404": "Not Found",
-	"405": "Method Not Allowed",
-	"406": "Not Acceptable",
-	"407": "Proxy Authentication Required",
-	"408": "Request Timeout",
-	"409": "Conflict",
-	"410": "Gone",
-	"411": "Length Required",
-	"412": "Precondition Failed",
-	"413": "Content Too Large",
-	"414": "URI Too Long",
-	"415": "Unsupported Media Type",
-	"416": "Range Not Satisfiable",
-	"417": "Expectation Failed",
-	"418": "I'm a Teapot",
-	"421": "Misdirected Request",
-	"422": "Unprocessable Content",
-	"423": "Locked",
-	"424": "Failed Dependency",
-	"425": "Too Early",
-	"426": "Upgrade Required",
-	"427": "Unassigned",
-	"428": "Precondition Required",
-	"429": "Too Many Requests",
-	"430": "Unassigned",
-	"431": "Request Header Fields Too Large",
-	"451": "Unavailable For Legal Reasons",
-	"500": "Internal Server Error",
-	"501": "Not Implemented",
-	"502": "Bad Gateway",
-	"503": "Service Unavailable",
-	"504": "Gateway Timeout",
-	"505": "HTTP Version Not Supported",
-	"506": "Variant Also Negotiates",
-	"507": "Insufficient Storage",
-	"508": "Loop Detected",
-	"509": "Unassigned",
-	"510": "Not Extended",
-	"511": "Network Authentication Required"
-}
+	'100': 'Continue',
+	'101': 'Switching Protocols',
+	'102': 'Processing',
+	'103': 'Early Hints',
+	'200': 'OK',
+	'201': 'Created',
+	'202': 'Accepted',
+	'203': 'Non-Authoritative Information',
+	'204': 'No Content',
+	'205': 'Reset Content',
+	'206': 'Partial Content',
+	'207': 'Multi-Status',
+	'208': 'Already Reported',
+	'226': 'IM Used',
+	'300': 'Multiple Choices',
+	'301': 'Moved Permanently',
+	'302': 'Found',
+	'303': 'See Other',
+	'304': 'Not Modified',
+	'305': 'Use Proxy',
+	'307': 'Temporary Redirect',
+	'308': 'Permanent Redirect',
+	'400': 'Bad Request',
+	'401': 'Unauthorized',
+	'402': 'Payment Required',
+	'403': 'Forbidden',
+	'404': 'Not Found',
+	'405': 'Method Not Allowed',
+	'406': 'Not Acceptable',
+	'407': 'Proxy Authentication Required',
+	'408': 'Request Timeout',
+	'409': 'Conflict',
+	'410': 'Gone',
+	'411': 'Length Required',
+	'412': 'Precondition Failed',
+	'413': 'Content Too Large',
+	'414': 'URI Too Long',
+	'415': 'Unsupported Media Type',
+	'416': 'Range Not Satisfiable',
+	'417': 'Expectation Failed',
+	'418': 'I'm a Teapot',
+	'421': 'Misdirected Request',
+	'422': 'Unprocessable Content',
+	'423': 'Locked',
+	'424': 'Failed Dependency',
+	'425': 'Too Early',
+	'426': 'Upgrade Required',
+	'427': 'Unassigned',
+	'428': 'Precondition Required',
+	'429': 'Too Many Requests',
+	'430': 'Unassigned',
+	'431': 'Request Header Fields Too Large',
+	'451': 'Unavailable For Legal Reasons',
+	'500': 'Internal Server Error',
+	'501': 'Not Implemented',
+	'502': 'Bad Gateway',
+	'503': 'Service Unavailable',
+	'504': 'Gateway Timeout',
+	'505': 'HTTP Version Not Supported',
+	'506': 'Variant Also Negotiates',
+	'507': 'Insufficient Storage',
+	'508': 'Loop Detected',
+	'509': 'Unassigned',
+	'510': 'Not Extended',
+	'511': 'Network Authentication Required',
+};
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index 4bb72cd8a..04346c53b 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -48,7 +48,7 @@ export const schemas = {
 					},
 					info: {
 						description: 'Potentially more information, primarily intended for developers.',
-					}
+					},
 				},
 				required: ['code', 'endpoint', 'message'],
 			},