diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts
index e716dcdc01..77a445e186 100644
--- a/src/server/api/api-handler.ts
+++ b/src/server/api/api-handler.ts
@@ -1,6 +1,6 @@
 import * as Koa from 'koa';
 
-import { Endpoint } from './endpoints';
+import Endpoint from './endpoint';
 import authenticate from './authenticate';
 import call from './call';
 import { IUser } from '../../models/user';
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index 96c218b371..eb3e292dc1 100644
--- a/src/server/api/call.ts
+++ b/src/server/api/call.ts
@@ -1,44 +1,66 @@
-import endpoints, { Endpoint } from './endpoints';
+import * as path from 'path';
+import * as glob from 'glob';
+
+import Endpoint from './endpoint';
 import limitter from './limitter';
 import { IUser } from '../../models/user';
 import { IApp } from '../../models/app';
 
+const files = glob.sync('**/*.js', {
+	cwd: path.resolve(__dirname + '/endpoints/')
+});
+
+const endpoints: Array<{
+	exec: any,
+	meta: Endpoint
+}> = files.map(f => {
+	const ep = require('./endpoints/' + f);
+
+	ep.meta = ep.meta || {};
+	ep.meta.name = f.replace('.js', '');
+
+	return {
+		exec: ep.default,
+		meta: ep.meta
+	};
+});
+
 export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any, file?: any) => new Promise<any>(async (ok, rej) => {
 	const isSecure = user != null && app == null;
 
 	const epName = typeof endpoint === 'string' ? endpoint : endpoint.name;
-	const ep = endpoints.find(e => e.name === epName);
+	const ep = endpoints.find(e => e.meta.name === epName);
 
-	if (ep.secure && !isSecure) {
+	if (ep.meta.secure && !isSecure) {
 		return rej('ACCESS_DENIED');
 	}
 
-	if (ep.withCredential && user == null) {
+	if (ep.meta.requireCredential && user == null) {
 		return rej('SIGNIN_REQUIRED');
 	}
 
-	if (ep.withCredential && user.isSuspended) {
+	if (ep.meta.requireCredential && user.isSuspended) {
 		return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
 	}
 
-	if (app && ep.kind) {
-		if (!app.permission.some(p => p === ep.kind)) {
+	if (app && ep.meta.kind) {
+		if (!app.permission.some(p => p === ep.meta.kind)) {
 			return rej('PERMISSION_DENIED');
 		}
 	}
 
-	if (ep.withCredential && ep.limit) {
+	if (ep.meta.requireCredential && ep.meta.limit) {
 		try {
-			await limitter(ep, user); // Rate limit
+			await limitter(ep.meta, user); // Rate limit
 		} catch (e) {
 			// drop request if limit exceeded
 			return rej('RATE_LIMIT_EXCEEDED');
 		}
 	}
 
-	let exec = require(`${__dirname}/endpoints/${ep.name}`).default;
+	let exec = ep.exec;
 
-	if (ep.withFile && file) {
+	if (ep.meta.withFile && file) {
 		exec = exec.bind(null, file);
 	}
 
diff --git a/src/server/api/endpoint.ts b/src/server/api/endpoint.ts
new file mode 100644
index 0000000000..5936a85033
--- /dev/null
+++ b/src/server/api/endpoint.ts
@@ -0,0 +1,60 @@
+export default interface IEndpoint {
+	/**
+	 * エンドポイント名
+	 */
+	name: string;
+
+	/**
+	 * このエンドポイントにリクエストするのにユーザー情報が必須か否か
+	 * 省略した場合は false として解釈されます。
+	 */
+	requireCredential?: boolean;
+
+	/**
+	 * エンドポイントのリミテーションに関するやつ
+	 * 省略した場合はリミテーションは無いものとして解釈されます。
+	 * また、withCredential が false の場合はリミテーションを行うことはできません。
+	 */
+	limit?: {
+
+		/**
+		 * 複数のエンドポイントでリミットを共有したい場合に指定するキー
+		 */
+		key?: string;
+
+		/**
+		 * リミットを適用する期間(ms)
+		 * このプロパティを設定する場合、max プロパティも設定する必要があります。
+		 */
+		duration?: number;
+
+		/**
+		 * durationで指定した期間内にいくつまでリクエストできるのか
+		 * このプロパティを設定する場合、duration プロパティも設定する必要があります。
+		 */
+		max?: number;
+
+		/**
+		 * 最低でもどれくらいの間隔を開けてリクエストしなければならないか(ms)
+		 */
+		minInterval?: number;
+	};
+
+	/**
+	 * ファイルの添付を必要とするか否か
+	 * 省略した場合は false として解釈されます。
+	 */
+	withFile?: boolean;
+
+	/**
+	 * サードパーティアプリからはリクエストすることができないか否か
+	 * 省略した場合は false として解釈されます。
+	 */
+	secure?: boolean;
+
+	/**
+	 * エンドポイントの種類
+	 * パーミッションの実現に利用されます。
+	 */
+	kind?: string;
+}
diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts
deleted file mode 100644
index 58b251c924..0000000000
--- a/src/server/api/endpoints.ts
+++ /dev/null
@@ -1,659 +0,0 @@
-const ms = require('ms');
-
-/**
- * エンドポイントを表します。
- */
-export type Endpoint = {
-
-	/**
-	 * エンドポイント名
-	 */
-	name: string;
-
-	/**
-	 * このエンドポイントにリクエストするのにユーザー情報が必須か否か
-	 * 省略した場合は false として解釈されます。
-	 */
-	withCredential?: boolean;
-
-	/**
-	 * エンドポイントのリミテーションに関するやつ
-	 * 省略した場合はリミテーションは無いものとして解釈されます。
-	 * また、withCredential が false の場合はリミテーションを行うことはできません。
-	 */
-	limit?: {
-
-		/**
-		 * 複数のエンドポイントでリミットを共有したい場合に指定するキー
-		 */
-		key?: string;
-
-		/**
-		 * リミットを適用する期間(ms)
-		 * このプロパティを設定する場合、max プロパティも設定する必要があります。
-		 */
-		duration?: number;
-
-		/**
-		 * durationで指定した期間内にいくつまでリクエストできるのか
-		 * このプロパティを設定する場合、duration プロパティも設定する必要があります。
-		 */
-		max?: number;
-
-		/**
-		 * 最低でもどれくらいの間隔を開けてリクエストしなければならないか(ms)
-		 */
-		minInterval?: number;
-	};
-
-	/**
-	 * ファイルの添付を必要とするか否か
-	 * 省略した場合は false として解釈されます。
-	 */
-	withFile?: boolean;
-
-	/**
-	 * サードパーティアプリからはリクエストすることができないか否か
-	 * 省略した場合は false として解釈されます。
-	 */
-	secure?: boolean;
-
-	/**
-	 * エンドポイントの種類
-	 * パーミッションの実現に利用されます。
-	 */
-	kind?: string;
-};
-
-const endpoints: Endpoint[] = [
-	{
-		name: 'meta'
-	},
-	{
-		name: 'stats'
-	},
-	{
-		name: 'username/available'
-	},
-	{
-		name: 'my/apps',
-		withCredential: true
-	},
-	{
-		name: 'app/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1day'),
-			max: 3
-		}
-	},
-	{
-		name: 'app/show'
-	},
-	{
-		name: 'app/name_id/available'
-	},
-	{
-		name: 'auth/session/generate'
-	},
-	{
-		name: 'auth/session/show'
-	},
-	{
-		name: 'auth/session/userkey'
-	},
-	{
-		name: 'auth/accept',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'auth/deny',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'aggregation/notes',
-	},
-	{
-		name: 'aggregation/users',
-	},
-	{
-		name: 'aggregation/users/activity',
-	},
-	{
-		name: 'aggregation/users/note',
-	},
-	{
-		name: 'aggregation/users/followers'
-	},
-	{
-		name: 'aggregation/users/following'
-	},
-	{
-		name: 'aggregation/users/reaction'
-	},
-	{
-		name: 'aggregation/notes/renote'
-	},
-	{
-		name: 'aggregation/notes/reply'
-	},
-	{
-		name: 'aggregation/notes/reaction'
-	},
-	{
-		name: 'aggregation/notes/reactions'
-	},
-
-	{
-		name: 'sw/register',
-		withCredential: true
-	},
-
-	{
-		name: 'i',
-		withCredential: true
-	},
-	{
-		name: 'i/2fa/register',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/2fa/unregister',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/2fa/done',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/update',
-		withCredential: true,
-		limit: {
-			duration: ms('1day'),
-			max: 50
-		},
-		kind: 'account-write'
-	},
-	{
-		name: 'i/update_home',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/update_mobile_home',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/update_widget',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/change_password',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/regenerate_token',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/update_client_setting',
-		withCredential: true,
-		secure: true
-	},
-	{
-		name: 'i/pin',
-		kind: 'account-write'
-	},
-	{
-		name: 'i/appdata/get',
-		withCredential: true
-	},
-	{
-		name: 'i/appdata/set',
-		withCredential: true
-	},
-	{
-		name: 'i/signin_history',
-		withCredential: true,
-		kind: 'account-read'
-	},
-	{
-		name: 'i/authorized_apps',
-		withCredential: true,
-		secure: true
-	},
-
-	{
-		name: 'i/notifications',
-		withCredential: true,
-		kind: 'notification-read'
-	},
-
-	{
-		name: 'i/favorites',
-		withCredential: true,
-		kind: 'favorites-read'
-	},
-
-	{
-		name: 'games/reversi/match',
-		withCredential: true
-	},
-
-	{
-		name: 'games/reversi/match/cancel',
-		withCredential: true
-	},
-
-	{
-		name: 'games/reversi/invitations',
-		withCredential: true
-	},
-
-	{
-		name: 'games/reversi/games',
-		withCredential: true
-	},
-
-	{
-		name: 'games/reversi/games/show'
-	},
-
-	{
-		name: 'mute/create',
-		withCredential: true,
-		kind: 'account/write'
-	},
-	{
-		name: 'mute/delete',
-		withCredential: true,
-		kind: 'account/write'
-	},
-	{
-		name: 'mute/list',
-		withCredential: true,
-		kind: 'account/read'
-	},
-
-	{
-		name: 'notifications/delete',
-		withCredential: true,
-		kind: 'notification-write'
-	},
-	{
-		name: 'notifications/delete_all',
-		withCredential: true,
-		kind: 'notification-write'
-	},
-	{
-		name: 'notifications/mark_as_read_all',
-		withCredential: true,
-		kind: 'notification-write'
-	},
-
-	{
-		name: 'drive',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/stream',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/files',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/files/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		withFile: true,
-		kind: 'drive-write'
-	},
-	{
-		name: 'drive/files/upload_from_url',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 10
-		},
-		kind: 'drive-write'
-	},
-	{
-		name: 'drive/files/show',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/files/find',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/files/delete',
-		withCredential: true,
-		kind: 'drive-write'
-	},
-	{
-		name: 'drive/files/update',
-		withCredential: true,
-		kind: 'drive-write'
-	},
-	{
-		name: 'drive/folders',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/folders/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 50
-		},
-		kind: 'drive-write'
-	},
-	{
-		name: 'drive/folders/show',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/folders/find',
-		withCredential: true,
-		kind: 'drive-read'
-	},
-	{
-		name: 'drive/folders/update',
-		withCredential: true,
-		kind: 'drive-write'
-	},
-
-	{
-		name: 'users'
-	},
-	{
-		name: 'users/show'
-	},
-	{
-		name: 'users/search'
-	},
-	{
-		name: 'users/search_by_username'
-	},
-	{
-		name: 'users/notes'
-	},
-	{
-		name: 'users/following'
-	},
-	{
-		name: 'users/followers'
-	},
-	{
-		name: 'users/recommendation',
-		withCredential: true,
-		kind: 'account-read'
-	},
-	{
-		name: 'users/get_frequently_replied_users'
-	},
-
-	{
-		name: 'users/lists/show',
-		withCredential: true,
-		kind: 'account-read'
-	},
-	{
-		name: 'users/lists/create',
-		withCredential: true,
-		kind: 'account-write'
-	},
-	{
-		name: 'users/lists/push',
-		withCredential: true,
-		kind: 'account-write'
-	},
-	{
-		name: 'users/lists/list',
-		withCredential: true,
-		kind: 'account-read'
-	},
-
-	{
-		name: 'following/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'following-write'
-	},
-	{
-		name: 'following/delete',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'following-write'
-	},
-	{
-		name: 'following/requests/accept',
-		withCredential: true,
-		kind: 'following-write'
-	},
-	{
-		name: 'following/requests/reject',
-		withCredential: true,
-		kind: 'following-write'
-	},
-	{
-		name: 'following/requests/cancel',
-		withCredential: true,
-		kind: 'following-write'
-	},
-	{
-		name: 'following/requests/list',
-		withCredential: true,
-		kind: 'following-read'
-	},
-	{
-		name: 'following/stalk',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'following-write'
-	},
-	{
-		name: 'following/unstalk',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'following-write'
-	},
-
-	{
-		name: 'notes'
-	},
-	{
-		name: 'notes/show'
-	},
-	{
-		name: 'notes/replies'
-	},
-	{
-		name: 'notes/conversation'
-	},
-	{
-		name: 'notes/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 300,
-			minInterval: ms('1second')
-		},
-		kind: 'note-write'
-	},
-	{
-		name: 'notes/delete',
-		withCredential: true,
-		kind: 'note-write'
-	},
-	{
-		name: 'notes/renotes'
-	},
-	{
-		name: 'notes/search'
-	},
-	{
-		name: 'notes/search_by_tag'
-	},
-	{
-		name: 'notes/timeline',
-		withCredential: true,
-		limit: {
-			duration: ms('10minutes'),
-			max: 100
-		}
-	},
-	{
-		name: 'notes/local-timeline',
-		limit: {
-			duration: ms('10minutes'),
-			max: 100
-		}
-	},
-	{
-		name: 'notes/hybrid-timeline',
-		limit: {
-			duration: ms('10minutes'),
-			max: 100
-		}
-	},
-	{
-		name: 'notes/global-timeline',
-		limit: {
-			duration: ms('10minutes'),
-			max: 100
-		}
-	},
-	{
-		name: 'notes/user-list-timeline',
-		withCredential: true,
-		limit: {
-			duration: ms('10minutes'),
-			max: 100
-		}
-	},
-	{
-		name: 'notes/mentions',
-		withCredential: true,
-		limit: {
-			duration: ms('10minutes'),
-			max: 100
-		}
-	},
-	{
-		name: 'notes/trend',
-		withCredential: true
-	},
-	{
-		name: 'notes/categorize',
-		withCredential: true
-	},
-	{
-		name: 'notes/reactions',
-		withCredential: true
-	},
-	{
-		name: 'notes/reactions/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 300
-		},
-		kind: 'reaction-write'
-	},
-	{
-		name: 'notes/reactions/delete',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'reaction-write'
-	},
-	{
-		name: 'notes/favorites/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'favorite-write'
-	},
-	{
-		name: 'notes/favorites/delete',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'favorite-write'
-	},
-	{
-		name: 'notes/polls/vote',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 100
-		},
-		kind: 'vote-write'
-	},
-	{
-		name: 'notes/polls/recommendation',
-		withCredential: true
-	},
-
-	{
-		name: 'hashtags/trend'
-	},
-
-	{
-		name: 'messaging/history',
-		withCredential: true,
-		kind: 'messaging-read'
-	},
-	{
-		name: 'messaging/messages',
-		withCredential: true,
-		kind: 'messaging-read'
-	},
-	{
-		name: 'messaging/messages/create',
-		withCredential: true,
-		kind: 'messaging-write'
-	}
-];
-
-export default endpoints;
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 2d331642c9..7125d02de9 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -1,4 +1,5 @@
 import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
+const ms = require('ms');
 import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
 import User, { ILocalUser, IUser } from '../../../../models/user';
 import DriveFile, { IDriveFile } from '../../../../models/drive-file';
@@ -13,6 +14,16 @@ export const meta = {
 		ja: '投稿します。'
 	},
 
+	requireCredential: true,
+
+	limit: {
+		duration: ms('1hour'),
+		max: 300,
+		minInterval: ms('1second')
+	},
+
+	kind: 'note-write',
+
 	params: {
 		visibility: $.str.optional.or(['public', 'home', 'followers', 'specified', 'private']).note({
 			default: 'public',
diff --git a/src/server/api/limitter.ts b/src/server/api/limitter.ts
index 55cb660700..bd30685ade 100644
--- a/src/server/api/limitter.ts
+++ b/src/server/api/limitter.ts
@@ -1,7 +1,7 @@
 import * as Limiter from 'ratelimiter';
 import * as debug from 'debug';
 import limiterDB from '../../db/redis';
-import { Endpoint } from './endpoints';
+import Endpoint from './endpoint';
 import getAcct from '../../misc/acct/render';
 import { IUser } from '../../models/user';