diff --git a/gulpfile.ts b/gulpfile.ts
index 49a80879d2..607f77fc31 100644
--- a/gulpfile.ts
+++ b/gulpfile.ts
@@ -20,7 +20,7 @@ import * as replace from 'gulp-replace';
 import * as htmlmin from 'gulp-htmlmin';
 const uglifyes = require('uglify-es');
 
-import locales from './locales';
+const locales = require('./locales');
 import { fa } from './src/build/fa';
 const client = require('./built/client/meta.json');
 import config from './src/config';
diff --git a/locales/index.js b/locales/index.js
new file mode 100644
index 0000000000..95cc339169
--- /dev/null
+++ b/locales/index.js
@@ -0,0 +1,27 @@
+/**
+ * Languages Loader
+ */
+
+const fs = require('fs');
+const yaml = require('js-yaml');
+
+const loadLang = lang => yaml.safeLoad(
+	fs.readFileSync(`./locales/${lang}.yml`, 'utf-8'));
+
+const native = loadLang('ja');
+
+const langs = {
+	'de': loadLang('de'),
+	'en': loadLang('en'),
+	'fr': loadLang('fr'),
+	'ja': native,
+	'pl': loadLang('pl'),
+	'es': loadLang('es')
+};
+
+Object.entries(langs).map(([, locale]) => {
+	// Extend native language (Japanese)
+	locale = Object.assign({}, native, locale);
+});
+
+module.exports = langs;
diff --git a/locales/index.ts b/locales/index.ts
deleted file mode 100644
index 45b5df0957..0000000000
--- a/locales/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Languages Loader
- */
-
-import * as fs from 'fs';
-import * as yaml from 'js-yaml';
-
-export type LangKey = 'de' | 'en' | 'fr' | 'ja' | 'pl' | 'es';
-export type LocaleObject = { [key: string]: any };
-
-const loadLang = (lang: LangKey) => yaml.safeLoad(
-	fs.readFileSync(`./locales/${lang}.yml`, 'utf-8')) as LocaleObject;
-
-const native = loadLang('ja');
-
-const langs: { [key: string]: LocaleObject } = {
-	'de': loadLang('de'),
-	'en': loadLang('en'),
-	'fr': loadLang('fr'),
-	'ja': native,
-	'pl': loadLang('pl'),
-	'es': loadLang('es')
-};
-
-Object.entries(langs).map(([, locale]) => {
-	// Extend native language (Japanese)
-	locale = Object.assign({}, native, locale);
-});
-
-export function isAvailableLanguage(lang: string): lang is LangKey {
-	return lang in langs;
-}
-
-export default langs;
diff --git a/locales/ja.yml b/locales/ja.yml
index fc790d8c53..9c51743526 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -1092,7 +1092,4 @@ docs:
     props:
       name: "名前"
       type: "型"
-      optional: "オプション"
       description: "説明"
-      yes: "はい"
-      no: "いいえ"
diff --git a/package.json b/package.json
index b5cc2457c7..2bd8dc6f44 100644
--- a/package.json
+++ b/package.json
@@ -89,7 +89,7 @@
 		"autwh": "0.1.0",
 		"bcryptjs": "2.4.3",
 		"bootstrap-vue": "2.0.0-rc.11",
-		"cafy": "8.0.0",
+		"cafy": "11.2.0",
 		"chalk": "2.4.1",
 		"crc-32": "1.2.0",
 		"css-loader": "0.28.11",
diff --git a/src/build/i18n.ts b/src/build/i18n.ts
index dc48251c95..4ed3c7a2df 100644
--- a/src/build/i18n.ts
+++ b/src/build/i18n.ts
@@ -2,7 +2,7 @@
  * Replace i18n texts
  */
 
-import locale, { isAvailableLanguage, LocaleObject } from '../../locales';
+const locale = require('../../locales');
 
 export default class Replacer {
 	private lang: string;
@@ -16,8 +16,8 @@ export default class Replacer {
 		this.replacement = this.replacement.bind(this);
 	}
 
-	private get(path: string, key: string): string {
-		if (!isAvailableLanguage(this.lang)) {
+	public get(path: string, key: string): string {
+		if (!(this.lang in locale)) {
 			console.warn(`lang '${this.lang}' is not supported`);
 			return key; // Fallback
 		}
@@ -28,7 +28,7 @@ export default class Replacer {
 
 		if (path) {
 			if (text.hasOwnProperty(path)) {
-				text = text[path] as LocaleObject;
+				text = text[path];
 			} else {
 				console.warn(`path '${path}' not found in '${this.lang}'`);
 				return key; // Fallback
@@ -38,7 +38,7 @@ export default class Replacer {
 		// Check the key existance
 		const error = key.split('.').some(k => {
 			if (text.hasOwnProperty(k)) {
-				text = (text as LocaleObject)[k];
+				text = text[k];
 				return false;
 			} else {
 				return true;
diff --git a/src/cafy-id.ts b/src/cafy-id.ts
index dac0f97bd2..f3e1f5251b 100644
--- a/src/cafy-id.ts
+++ b/src/cafy-id.ts
@@ -1,5 +1,5 @@
 import * as mongo from 'mongodb';
-import { Query } from 'cafy';
+import { Context } from 'cafy';
 
 export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
 export const isNotAnId = (x: any) => !isAnId(x);
@@ -7,7 +7,7 @@ export const isNotAnId = (x: any) => !isAnId(x);
 /**
  * ID
  */
-export default class ID extends Query<mongo.ObjectID> {
+export default class ID extends Context<mongo.ObjectID> {
 	constructor() {
 		super();
 
@@ -26,4 +26,8 @@ export default class ID extends Query<mongo.ObjectID> {
 			return true;
 		});
 	}
+
+	public getType() {
+		return super.getType('string');
+	}
 }
diff --git a/src/client/docs/api/endpoints/notes/create.yaml b/src/client/docs/api/endpoints/notes/create.yaml
deleted file mode 100644
index 04ada2ecd5..0000000000
--- a/src/client/docs/api/endpoints/notes/create.yaml
+++ /dev/null
@@ -1,59 +0,0 @@
-endpoint: "notes/create"
-
-desc:
-  ja: "投稿します。"
-  en: "Compose new note."
-
-params:
-  - name: "text"
-    type: "string"
-    optional: true
-    desc:
-      ja: "投稿の本文"
-      en: "The text of your note"
-  - name: "cw"
-    type: "string"
-    optional: true
-    desc:
-      ja: "コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。"
-      en: "Content Warning"
-  - name: "mediaIds"
-    type: "id(DriveFile)[]"
-    optional: true
-    desc:
-      ja: "添付するメディア(1~4つ)"
-      en: "Media you want to attach (1~4)"
-  - name: "replyId"
-    type: "id(Note)"
-    optional: true
-    desc:
-      ja: "返信する投稿"
-      en: "The note you want to reply"
-  - name: "renoteId"
-    type: "id(Note)"
-    optional: true
-    desc:
-      ja: "引用する投稿"
-      en: "The note you want to quote"
-  - name: "poll"
-    type: "object"
-    optional: true
-    desc:
-      ja: "投票"
-      en: "The poll"
-    defName: "poll"
-    def:
-      - name: "choices"
-        type: "string[]"
-        optional: false
-        desc:
-          ja: "投票の選択肢"
-          en: "Choices of a poll"
-
-res:
-  - name: "createdNote"
-    type: "entity(Note)"
-    optional: false
-    desc:
-      ja: "作成した投稿"
-      en: "A note that created"
diff --git a/src/client/docs/api/endpoints/notes/timeline.yaml b/src/client/docs/api/endpoints/notes/timeline.yaml
deleted file mode 100644
index 71c346f355..0000000000
--- a/src/client/docs/api/endpoints/notes/timeline.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-endpoint: "notes/timeline"
-
-desc:
-  ja: "タイムラインを取得します。"
-  en: "Get your timeline."
-
-params:
-  - name: "limit"
-    type: "number"
-    optional: true
-    desc:
-      ja: "取得する最大の数"
-  - name: "sinceId"
-    type: "id(Note)"
-    optional: true
-    desc:
-      ja: "指定すると、この投稿を基点としてより新しい投稿を取得します"
-  - name: "untilId"
-    type: "id(Note)"
-    optional: true
-    desc:
-      ja: "指定すると、この投稿を基点としてより古い投稿を取得します"
-  - name: "sinceDate"
-    type: "number"
-    optional: true
-    desc:
-      ja: "指定した時間を基点としてより新しい投稿を取得します。数値は、1970 年 1 月 1 日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。"
-  - name: "untilDate"
-    type: "number"
-    optional: true
-    desc:
-      ja: "指定した時間を基点としてより古い投稿を取得します。数値は、1970 年 1 月 1 日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。"
diff --git a/src/client/docs/api/endpoints/view.pug b/src/client/docs/api/endpoints/view.pug
index f8795c8442..24fff1b798 100644
--- a/src/client/docs/api/endpoints/view.pug
+++ b/src/client/docs/api/endpoints/view.pug
@@ -17,7 +17,7 @@ block main
 	p#desc= desc[lang] || desc['ja']
 
 	section
-		h2 %i18n:docs.api.endpoints.params%
+		h2= i18n('docs.api.endpoints.params')
 		+propTable(params)
 
 		if paramDefs
@@ -28,5 +28,5 @@ block main
 
 	if res
 		section
-			h2 %i18n:docs.api.endpoints.res%
+			h2= i18n('docs.api.endpoints.res')
 			+propTable(res)
diff --git a/src/client/docs/api/entities/note.yaml b/src/client/docs/api/entities/note.yaml
index 6fd26543bb..c508dab3db 100644
--- a/src/client/docs/api/entities/note.yaml
+++ b/src/client/docs/api/entities/note.yaml
@@ -27,8 +27,8 @@ props:
     type: "string"
     optional: true
     desc:
-      ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)"
-      en: "The text of this note (in Markdown like format if local)"
+      ja: "投稿の本文"
+      en: "The text of this note"
   - name: "mediaIds"
     type: "id(DriveFile)[]"
     optional: true
diff --git a/src/client/docs/api/entities/view.pug b/src/client/docs/api/entities/view.pug
index ac938151a7..a930f71eb6 100644
--- a/src/client/docs/api/entities/view.pug
+++ b/src/client/docs/api/entities/view.pug
@@ -10,7 +10,7 @@ block main
 	p#desc= desc[lang] || desc['ja']
 
 	section
-		h2 %i18n:docs.api.entities.properties%
+		h2= i18n('docs.api.entities.properties')
 		+propTable(props)
 
 		if propDefs
diff --git a/src/client/docs/api/gulpfile.ts b/src/client/docs/api/gulpfile.ts
deleted file mode 100644
index 0eb8b88287..0000000000
--- a/src/client/docs/api/gulpfile.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
- * Gulp tasks
- */
-
-import * as fs from 'fs';
-import * as path from 'path';
-import * as glob from 'glob';
-import * as gulp from 'gulp';
-import * as pug from 'pug';
-import * as yaml from 'js-yaml';
-import * as mkdirp from 'mkdirp';
-
-import locales from '../../../../locales';
-import I18nReplacer from '../../../build/i18n';
-import fa from '../../../build/fa';
-import config from './../../../config';
-
-import generateVars from '../vars';
-
-const langs = Object.keys(locales);
-
-const kebab = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
-
-// WIP type
-const parseParam = (param: any) => {
-	const id = param.type.match(/^id\((.+?)\)|^id/);
-	const entity = param.type.match(/^entity\((.+?)\)/);
-	const isObject = /^object/.test(param.type);
-	const isDate = /^date/.test(param.type);
-	const isArray = /\[\]$/.test(param.type);
-	if (id) {
-		param.kind = 'id';
-		param.type = 'string';
-		param.entity = id[1];
-		if (isArray) {
-			param.type += '[]';
-		}
-	}
-	if (entity) {
-		param.kind = 'entity';
-		param.type = 'object';
-		param.entity = entity[1];
-		if (isArray) {
-			param.type += '[]';
-		}
-	}
-	if (isObject) {
-		param.kind = 'object';
-	}
-	if (isDate) {
-		param.kind = 'date';
-		param.type = 'string';
-		if (isArray) {
-			param.type += '[]';
-		}
-	}
-
-	return param;
-};
-
-const sortParams = (params: Array<{name: string}>) => {
-	params.sort((a, b) => {
-		if (a.name < b.name)
-			return -1;
-		if (a.name > b.name)
-			return 1;
-		return 0;
-	});
-	return params;
-};
-
-// WIP type
-const extractDefs = (params: any[]) => {
-	let defs: any[] = [];
-
-	params.forEach(param => {
-		if (param.def) {
-			defs.push({
-				name: param.defName,
-				params: sortParams(param.def.map((p: any) => parseParam(p)))
-			});
-
-			const childDefs = extractDefs(param.def);
-
-			defs = defs.concat(childDefs);
-		}
-	});
-
-	return sortParams(defs);
-};
-
-gulp.task('doc:api', [
-	'doc:api:endpoints',
-	'doc:api:entities'
-]);
-
-gulp.task('doc:api:endpoints', async () => {
-	const commonVars = await generateVars();
-	glob('./src/client/docs/api/endpoints/**/*.yaml', (globErr, files) => {
-		if (globErr) {
-			console.error(globErr);
-			return;
-		}
-		//console.log(files);
-		files.forEach(file => {
-			const ep: any = yaml.safeLoad(fs.readFileSync(file, 'utf-8'));
-			const vars = {
-				endpoint: ep.endpoint,
-				url: {
-					host: config.api_url,
-					path: ep.endpoint
-				},
-				desc: ep.desc,
-				// @ts-ignore
-				params: sortParams(ep.params.map(p => parseParam(p))),
-				paramDefs: extractDefs(ep.params),
-				// @ts-ignore
-				res: ep.res ? sortParams(ep.res.map(p => parseParam(p))) : null,
-				resDefs: ep.res ? extractDefs(ep.res) : null,
-			};
-			langs.forEach(lang => {
-				pug.renderFile('./src/client/docs/api/endpoints/view.pug', Object.assign({}, vars, {
-					lang,
-					title: ep.endpoint,
-					src: `https://github.com/syuilo/misskey/tree/master/src/client/docs/api/endpoints/${ep.endpoint}.yaml`,
-					kebab,
-					common: commonVars
-				}), (renderErr, html) => {
-					if (renderErr) {
-						console.error(renderErr);
-						return;
-					}
-					const i18n = new I18nReplacer(lang);
-					html = html.replace(i18n.pattern, i18n.replacement);
-					html = fa(html);
-					const htmlPath = `./built/client/docs/${lang}/api/endpoints/${ep.endpoint}.html`;
-					mkdirp(path.dirname(htmlPath), (mkdirErr) => {
-						if (mkdirErr) {
-							console.error(mkdirErr);
-							return;
-						}
-						fs.writeFileSync(htmlPath, html, 'utf-8');
-					});
-				});
-			});
-		});
-	});
-});
-
-gulp.task('doc:api:entities', async () => {
-	const commonVars = await generateVars();
-	glob('./src/client/docs/api/entities/**/*.yaml', (globErr, files) => {
-		if (globErr) {
-			console.error(globErr);
-			return;
-		}
-		files.forEach(file => {
-			const entity = yaml.safeLoad(fs.readFileSync(file, 'utf-8')) as any;
-			const vars = {
-				name: entity.name,
-				desc: entity.desc,
-				// WIP type
-				props: sortParams(entity.props.map((p: any) => parseParam(p))),
-				propDefs: extractDefs(entity.props),
-			};
-			langs.forEach(lang => {
-				pug.renderFile('./src/client/docs/api/entities/view.pug', Object.assign({}, vars, {
-					lang,
-					title: entity.name,
-					src: `https://github.com/syuilo/misskey/tree/master/src/client/docs/api/entities/${kebab(entity.name)}.yaml`,
-					kebab,
-					common: commonVars
-				}), (renderErr, html) => {
-					if (renderErr) {
-						console.error(renderErr);
-						return;
-					}
-					const i18n = new I18nReplacer(lang);
-					html = html.replace(i18n.pattern, i18n.replacement);
-					html = fa(html);
-					const htmlPath = `./built/client/docs/${lang}/api/entities/${kebab(entity.name)}.html`;
-					mkdirp(path.dirname(htmlPath), (mkdirErr) => {
-						if (mkdirErr) {
-							console.error(mkdirErr);
-							return;
-						}
-						fs.writeFileSync(htmlPath, html, 'utf-8');
-					});
-				});
-			});
-		});
-	});
-});
diff --git a/src/client/docs/api/mixins.pug b/src/client/docs/api/mixins.pug
index 913135a85f..79665a61e3 100644
--- a/src/client/docs/api/mixins.pug
+++ b/src/client/docs/api/mixins.pug
@@ -1,10 +1,9 @@
 mixin propTable(props)
 	table.props
 		thead: tr
-			th %i18n:docs.api.props.name%
-			th %i18n:docs.api.props.type%
-			th %i18n:docs.api.props.optional%
-			th %i18n:docs.api.props.description%
+			th= i18n('docs.api.props.name')
+			th= i18n('docs.api.props.type')
+			th= i18n('docs.api.props.description')
 		tbody
 			each prop in props
 				tr
@@ -29,9 +28,4 @@ mixin propTable(props)
 								| )
 						else if prop.kind == 'date'
 							|  (Date)
-					td.optional
-						if prop.optional
-							| %i18n:docs.api.props.yes%
-						else
-							| %i18n:docs.api.props.no%
-					td.desc!= prop.desc[lang] || prop.desc['ja']
+					td.desc!= prop.desc ? prop.desc[lang] || prop.desc['ja'] : null
diff --git a/src/client/docs/gulpfile.ts b/src/client/docs/gulpfile.ts
index 4683a04659..2a95dfbfee 100644
--- a/src/client/docs/gulpfile.ts
+++ b/src/client/docs/gulpfile.ts
@@ -2,73 +2,14 @@
  * Gulp tasks
  */
 
-import * as fs from 'fs';
-import * as path from 'path';
-import * as glob from 'glob';
 import * as gulp from 'gulp';
-import * as pug from 'pug';
-import * as mkdirp from 'mkdirp';
 const stylus = require('gulp-stylus');
 const cssnano = require('gulp-cssnano');
 
-import I18nReplacer from '../../build/i18n';
-import fa from '../../build/fa';
-import generateVars from './vars';
-
-require('./api/gulpfile.ts');
-
 gulp.task('doc', [
-	'doc:docs',
-	'doc:api',
 	'doc:styles'
 ]);
 
-gulp.task('doc:docs', async () => {
-	const commonVars = await generateVars();
-
-	glob('./src/client/docs/**/*.*.pug', (globErr, files) => {
-		if (globErr) {
-			console.error(globErr);
-			return;
-		}
-		files.forEach(file => {
-			const [, name, lang] = file.match(/docs\/(.+?)\.(.+?)\.pug$/);
-			const vars = {
-				common: commonVars,
-				lang: lang,
-				title: fs.readFileSync(file, 'utf-8').match(/^h1 (.+?)\r?\n/)[1],
-				src: `https://github.com/syuilo/misskey/tree/master/src/client/docs/${name}.${lang}.pug`,
-			};
-			pug.renderFile(file, vars, (renderErr, content) => {
-				if (renderErr) {
-					console.error(renderErr);
-					return;
-				}
-
-				pug.renderFile('./src/client/docs/layout.pug', Object.assign({}, vars, {
-					content
-				}), (renderErr2, html) => {
-					if (renderErr2) {
-						console.error(renderErr2);
-						return;
-					}
-					const i18n = new I18nReplacer(lang);
-					html = html.replace(i18n.pattern, i18n.replacement);
-					html = fa(html);
-					const htmlPath = `./built/client/docs/${lang}/${name}.html`;
-					mkdirp(path.dirname(htmlPath), (mkdirErr) => {
-						if (mkdirErr) {
-							console.error(mkdirErr);
-							return;
-						}
-						fs.writeFileSync(htmlPath, html, 'utf-8');
-					});
-				});
-			});
-		});
-	});
-});
-
 gulp.task('doc:styles', () =>
 	gulp.src('./src/client/docs/**/*.styl')
 		.pipe(stylus())
diff --git a/src/client/docs/layout.pug b/src/client/docs/layout.pug
index 1d9ebcb4cd..4186d2d365 100644
--- a/src/client/docs/layout.pug
+++ b/src/client/docs/layout.pug
@@ -10,24 +10,24 @@ html(lang= lang)
 		block meta
 
 		//- FontAwesome style
-		style #{common.facss}
+		style #{facss}
 
 	body
 		nav
 			ul
-				each doc in common.docs
+				each doc in docs
 					li: a(href=`/docs/${lang}/${doc.name}`)= doc.title[lang] || doc.title['ja']
 			section
 				h2 API
 				ul
 					li Entities
 						ul
-							each entity in common.entities
-								li: a(href=`/docs/${lang}/api/entities/${common.kebab(entity)}`)= entity
+							each entity in entities
+								li: a(href=`/docs/${lang}/api/entities/${kebab(entity)}`)= entity
 					li Endpoints
 						ul
-							each endpoint in common.endpoints
-								li: a(href=`/docs/${lang}/api/endpoints/${common.kebab(endpoint)}`)= endpoint
+							each endpoint in endpoints
+								li: a(href=`/docs/${lang}/api/endpoints/${kebab(endpoint)}`)= endpoint
 		main
 			article
 				block main
@@ -38,4 +38,4 @@ html(lang= lang)
 				p
 					| %i18n:docs.edit-this-page-on-github%
 					a(href=src target="_blank") %i18n:docs.edit-this-page-on-github-link%
-				small= common.copyright
+				small= copyright
diff --git a/src/client/docs/style.styl b/src/client/docs/style.styl
index bc165f8728..3b13617588 100644
--- a/src/client/docs/style.styl
+++ b/src/client/docs/style.styl
@@ -10,7 +10,7 @@ main
 	margin 0 0 0 256px
 	padding 64px
 	width 100%
-	max-width 768px
+	max-width 800px
 
 	section
 		margin 32px 0
diff --git a/src/client/docs/vars.ts b/src/client/docs/vars.ts
deleted file mode 100644
index 93082767e3..0000000000
--- a/src/client/docs/vars.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import * as fs from 'fs';
-import * as util from 'util';
-import * as glob from 'glob';
-import * as yaml from 'js-yaml';
-import * as licenseChecker from 'license-checker';
-import * as tmp from 'tmp';
-
-import { fa } from '../../build/fa';
-import config from '../../config';
-import { licenseHtml } from '../../build/license';
-const constants = require('../../const.json');
-
-export default async function(): Promise<{ [key: string]: any }> {
-	const vars = {} as { [key: string]: any };
-
-	const endpoints = glob.sync('./src/client/docs/api/endpoints/**/*.yaml');
-	vars['endpoints'] = endpoints.map(ep => {
-		const _ep = yaml.safeLoad(fs.readFileSync(ep, 'utf-8')) as any;
-		return _ep.endpoint;
-	});
-
-	const entities = glob.sync('./src/client/docs/api/entities/**/*.yaml');
-	vars['entities'] = entities.map(x => {
-		const _x = yaml.safeLoad(fs.readFileSync(x, 'utf-8')) as any;
-		return _x.name;
-	});
-
-	const docs = glob.sync('./src/client/docs/**/*.*.pug');
-	vars['docs'] = {};
-	docs.forEach(x => {
-		const [, name, lang] = x.match(/docs\/(.+?)\.(.+?)\.pug$/);
-		if (vars['docs'][name] == null) {
-			vars['docs'][name] = {
-				name,
-				title: {}
-			};
-		}
-		vars['docs'][name]['title'][lang] = fs.readFileSync(x, 'utf-8').match(/^h1 (.+?)\r?\n/)[1];
-	});
-
-	vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
-
-	vars['config'] = config;
-
-	vars['copyright'] = constants.copyright;
-
-	vars['facss'] = fa.dom.css();
-
-	vars['license'] = licenseHtml;
-
-	const tmpObj = tmp.fileSync();
-	fs.writeFileSync(tmpObj.name, JSON.stringify({
-		licenseText: ''
-	}), 'utf-8');
-	const dependencies = await util.promisify(licenseChecker.init).bind(licenseChecker)({
-		start: __dirname + '/../../../',
-		customPath: tmpObj.name
-	});
-	tmpObj.removeCallback();
-
-	vars['dependencies'] = dependencies;
-
-	return vars;
-}
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index fd3cea7743..b1e1668769 100644
--- a/src/server/api/call.ts
+++ b/src/server/api/call.ts
@@ -31,7 +31,7 @@ export default (endpoint: string | Endpoint, user: IUser, app: IApp, data: any,
 		}
 	}
 
-	let exec = require(`${__dirname}/endpoints/${ep.name}`);
+	let exec = require(`${__dirname}/endpoints/${ep.name}`).default;
 
 	if (ep.withFile && file) {
 		exec = exec.bind(null, file);
diff --git a/src/server/api/endpoints/aggregation/posts.ts b/src/server/api/endpoints/aggregation/posts.ts
index 48e344312d..629bb19108 100644
--- a/src/server/api/endpoints/aggregation/posts.ts
+++ b/src/server/api/endpoints/aggregation/posts.ts
@@ -4,9 +4,9 @@ import Note from '../../../../models/note';
 /**
  * Aggregate notes
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit);
+	const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	const datas = await Note
diff --git a/src/server/api/endpoints/aggregation/users.ts b/src/server/api/endpoints/aggregation/users.ts
index c084404d0a..f1e41cf170 100644
--- a/src/server/api/endpoints/aggregation/users.ts
+++ b/src/server/api/endpoints/aggregation/users.ts
@@ -4,9 +4,9 @@ import User from '../../../../models/user';
 /**
  * Aggregate users
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit);
+	const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	const users = await User
diff --git a/src/server/api/endpoints/aggregation/users/activity.ts b/src/server/api/endpoints/aggregation/users/activity.ts
index d4c716d65b..4afa6d1203 100644
--- a/src/server/api/endpoints/aggregation/users/activity.ts
+++ b/src/server/api/endpoints/aggregation/users/activity.ts
@@ -7,9 +7,9 @@ import Note from '../../../../../models/note';
 /**
  * Aggregate activity of a user
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 365, limitErr] = $.num.optional().range(1, 365).get(params.limit);
+	const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/aggregation/users/followers.ts b/src/server/api/endpoints/aggregation/users/followers.ts
index 847f376079..484c58ad3d 100644
--- a/src/server/api/endpoints/aggregation/users/followers.ts
+++ b/src/server/api/endpoints/aggregation/users/followers.ts
@@ -8,7 +8,7 @@ import FollowedLog from '../../../../../models/followed-log';
 /**
  * Aggregate followers of a user
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/aggregation/users/following.ts b/src/server/api/endpoints/aggregation/users/following.ts
index 6c52752f98..16812a9f09 100644
--- a/src/server/api/endpoints/aggregation/users/following.ts
+++ b/src/server/api/endpoints/aggregation/users/following.ts
@@ -8,7 +8,7 @@ import FollowingLog from '../../../../../models/following-log';
 /**
  * Aggregate following of a user
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/aggregation/users/post.ts b/src/server/api/endpoints/aggregation/users/post.ts
index 28ba1482bf..4965d3c7ac 100644
--- a/src/server/api/endpoints/aggregation/users/post.ts
+++ b/src/server/api/endpoints/aggregation/users/post.ts
@@ -5,7 +5,7 @@ import Note from '../../../../../models/note';
 /**
  * Aggregate note of a user
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/aggregation/users/reaction.ts b/src/server/api/endpoints/aggregation/users/reaction.ts
index adb5acfb4e..20b5e93d54 100644
--- a/src/server/api/endpoints/aggregation/users/reaction.ts
+++ b/src/server/api/endpoints/aggregation/users/reaction.ts
@@ -5,7 +5,7 @@ import Reaction from '../../../../../models/note-reaction';
 /**
  * Aggregate reaction of a user
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts
index c7bc91a079..3b3eafa699 100644
--- a/src/server/api/endpoints/app/create.ts
+++ b/src/server/api/endpoints/app/create.ts
@@ -59,7 +59,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Create an app
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'nameId' parameter
 	const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId);
 	if (nameIdErr) return rej('invalid nameId param');
@@ -78,7 +78,7 @@ module.exports = async (params: any, user: ILocalUser) => new Promise(async (res
 
 	// Get 'callbackUrl' parameter
 	// TODO: Check it is valid url
-	const [callbackUrl = null, callbackUrlErr] = $.str.optional().nullable().get(params.callbackUrl);
+	const [callbackUrl = null, callbackUrlErr] = $.str.optional.nullable.get(params.callbackUrl);
 	if (callbackUrlErr) return rej('invalid callbackUrl param');
 
 	// Generate secret
diff --git a/src/server/api/endpoints/app/name_id/available.ts b/src/server/api/endpoints/app/name_id/available.ts
index 58101a7e6a..9f6e8dc34e 100644
--- a/src/server/api/endpoints/app/name_id/available.ts
+++ b/src/server/api/endpoints/app/name_id/available.ts
@@ -40,7 +40,7 @@ import { isValidNameId } from '../../../../../models/app';
  * @param {any} params
  * @return {Promise<any>}
  */
-module.exports = async (params: any) => new Promise(async (res, rej) => {
+export default async (params: any) => new Promise(async (res, rej) => {
 	// Get 'nameId' parameter
 	const [nameId, nameIdErr] = $.str.pipe(isValidNameId).get(params.nameId);
 	if (nameIdErr) return rej('invalid nameId param');
diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts
index 2b98a3f142..2b8a929543 100644
--- a/src/server/api/endpoints/app/show.ts
+++ b/src/server/api/endpoints/app/show.ts
@@ -35,15 +35,15 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Show an app
  */
-module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
 	const isSecure = user != null && app == null;
 
 	// Get 'appId' parameter
-	const [appId, appIdErr] = $.type(ID).optional().get(params.appId);
+	const [appId, appIdErr] = $.type(ID).optional.get(params.appId);
 	if (appIdErr) return rej('invalid appId param');
 
 	// Get 'nameId' parameter
-	const [nameId, nameIdErr] = $.str.optional().get(params.nameId);
+	const [nameId, nameIdErr] = $.str.optional.get(params.nameId);
 	if (nameIdErr) return rej('invalid nameId param');
 
 	if (appId === undefined && nameId === undefined) {
diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts
index fc6cbc473d..d14c853415 100644
--- a/src/server/api/endpoints/auth/accept.ts
+++ b/src/server/api/endpoints/auth/accept.ts
@@ -32,7 +32,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Accept
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'token' parameter
 	const [token, tokenErr] = $.str.get(params.token);
 	if (tokenErr) return rej('invalid token param');
diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts
index 5a4d99ff3b..f70495fa4f 100644
--- a/src/server/api/endpoints/auth/session/generate.ts
+++ b/src/server/api/endpoints/auth/session/generate.ts
@@ -44,7 +44,7 @@ import config from '../../../../../config';
  * @param {any} params
  * @return {Promise<any>}
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'appSecret' parameter
 	const [appSecret, appSecretErr] = $.str.get(params.appSecret);
 	if (appSecretErr) return rej('invalid appSecret param');
diff --git a/src/server/api/endpoints/auth/session/show.ts b/src/server/api/endpoints/auth/session/show.ts
index 3d3b6bbf61..3716b49c5a 100644
--- a/src/server/api/endpoints/auth/session/show.ts
+++ b/src/server/api/endpoints/auth/session/show.ts
@@ -45,7 +45,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Show a session
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'token' parameter
 	const [token, tokenErr] = $.str.get(params.token);
 	if (tokenErr) return rej('invalid token param');
diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts
index 3ea48fbe34..8d67c2bdad 100644
--- a/src/server/api/endpoints/auth/session/userkey.ts
+++ b/src/server/api/endpoints/auth/session/userkey.ts
@@ -49,7 +49,7 @@ import { pack } from '../../../../../models/user';
  * @param {any} params
  * @return {Promise<any>}
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'appSecret' parameter
 	const [appSecret, appSecretErr] = $.str.get(params.appSecret);
 	if (appSecretErr) return rej('invalid appSecret param');
diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts
index 9caad273c8..66ac30c4cf 100644
--- a/src/server/api/endpoints/drive.ts
+++ b/src/server/api/endpoints/drive.ts
@@ -4,7 +4,7 @@ import { ILocalUser } from '../../../models/user';
 /**
  * Get drive information
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Calculate drive usage
 	const usage = await DriveFile
 		.aggregate([{
diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts
index efce750747..c0b6c3b069 100644
--- a/src/server/api/endpoints/drive/files.ts
+++ b/src/server/api/endpoints/drive/files.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get drive files
  */
-module.exports = async (params: any, user: ILocalUser) => {
+export default async (params: any, user: ILocalUser) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) throw 'invalid limit param';
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) throw 'invalid sinceId param';
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) throw 'invalid untilId param';
 
 	// Check if both of sinceId and untilId is specified
@@ -24,11 +24,11 @@ module.exports = async (params: any, user: ILocalUser) => {
 	}
 
 	// Get 'folderId' parameter
-	const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId);
+	const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
 	if (folderIdErr) throw 'invalid folderId param';
 
 	// Get 'type' parameter
-	const [type, typeErr] = $.str.optional().match(/^[a-zA-Z\/\-\*]+$/).get(params.type);
+	const [type, typeErr] = $.str.optional.match(/^[a-zA-Z\/\-\*]+$/).get(params.type);
 	if (typeErr) throw 'invalid type param';
 
 	// Construct query
diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts
index db2626af09..5a2bef38ce 100644
--- a/src/server/api/endpoints/drive/files/create.ts
+++ b/src/server/api/endpoints/drive/files/create.ts
@@ -7,7 +7,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Create a file
  */
-module.exports = async (file: any, params: any, user: ILocalUser): Promise<any> => {
+export default async (file: any, params: any, user: ILocalUser): Promise<any> => {
 	if (file == null) {
 		throw 'file is required';
 	}
@@ -28,7 +28,7 @@ module.exports = async (file: any, params: any, user: ILocalUser): Promise<any>
 	}
 
 	// Get 'folderId' parameter
-	const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId);
+	const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
 	if (folderIdErr) throw 'invalid folderId param';
 
 	function cleanup() {
diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts
index 17eb0eb4b9..992f0286f1 100644
--- a/src/server/api/endpoints/drive/files/delete.ts
+++ b/src/server/api/endpoints/drive/files/delete.ts
@@ -7,7 +7,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Delete a file
  */
-module.exports = async (params: any, user: ILocalUser) => {
+export default async (params: any, user: ILocalUser) => {
 	// Get 'fileId' parameter
 	const [fileId, fileIdErr] = $.type(ID).get(params.fileId);
 	if (fileIdErr) throw 'invalid fileId param';
diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts
index 75ab91f0a1..ed76749739 100644
--- a/src/server/api/endpoints/drive/files/find.ts
+++ b/src/server/api/endpoints/drive/files/find.ts
@@ -5,13 +5,13 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Find a file(s)
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'name' parameter
 	const [name, nameErr] = $.str.get(params.name);
 	if (nameErr) return rej('invalid name param');
 
 	// Get 'folderId' parameter
-	const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId);
+	const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
 	if (folderIdErr) return rej('invalid folderId param');
 
 	// Issue query
diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts
index e7dca486c5..2e4ac06bf6 100644
--- a/src/server/api/endpoints/drive/files/show.ts
+++ b/src/server/api/endpoints/drive/files/show.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Show a file
  */
-module.exports = async (params: any, user: ILocalUser) => {
+export default async (params: any, user: ILocalUser) => {
 	// Get 'fileId' parameter
 	const [fileId, fileIdErr] = $.type(ID).get(params.fileId);
 	if (fileIdErr) throw 'invalid fileId param';
diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts
index 825683b214..73a193a56b 100644
--- a/src/server/api/endpoints/drive/files/update.ts
+++ b/src/server/api/endpoints/drive/files/update.ts
@@ -7,7 +7,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Update a file
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'fileId' parameter
 	const [fileId, fileIdErr] = $.type(ID).get(params.fileId);
 	if (fileIdErr) return rej('invalid fileId param');
@@ -24,12 +24,12 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	// Get 'name' parameter
-	const [name, nameErr] = $.str.optional().pipe(validateFileName).get(params.name);
+	const [name, nameErr] = $.str.optional.pipe(validateFileName).get(params.name);
 	if (nameErr) return rej('invalid name param');
 	if (name) file.filename = name;
 
 	// Get 'folderId' parameter
-	const [folderId, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId);
+	const [folderId, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
 	if (folderIdErr) return rej('invalid folderId param');
 
 	if (folderId !== undefined) {
diff --git a/src/server/api/endpoints/drive/files/upload_from_url.ts b/src/server/api/endpoints/drive/files/upload_from_url.ts
index cb617d851f..7218b345b0 100644
--- a/src/server/api/endpoints/drive/files/upload_from_url.ts
+++ b/src/server/api/endpoints/drive/files/upload_from_url.ts
@@ -9,14 +9,14 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Create a file from a URL
  */
-module.exports = async (params: any, user: ILocalUser): Promise<any> => {
+export default async (params: any, user: ILocalUser): Promise<any> => {
 	// Get 'url' parameter
 	// TODO: Validate this url
 	const [url, urlErr] = $.str.get(params.url);
 	if (urlErr) throw 'invalid url param';
 
 	// Get 'folderId' parameter
-	const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId);
+	const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
 	if (folderIdErr) throw 'invalid folderId param';
 
 	return pack(await uploadFromUrl(url, user, folderId));
diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts
index 3413778950..70f581f424 100644
--- a/src/server/api/endpoints/drive/folders.ts
+++ b/src/server/api/endpoints/drive/folders.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get drive folders
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
@@ -24,7 +24,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	// Get 'folderId' parameter
-	const [folderId = null, folderIdErr] = $.type(ID).optional().nullable().get(params.folderId);
+	const [folderId = null, folderIdErr] = $.type(ID).optional.nullable.get(params.folderId);
 	if (folderIdErr) return rej('invalid folderId param');
 
 	// Construct query
diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts
index 8f06b0f668..60c7383895 100644
--- a/src/server/api/endpoints/drive/folders/create.ts
+++ b/src/server/api/endpoints/drive/folders/create.ts
@@ -6,13 +6,13 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Create drive folder
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'name' parameter
-	const [name = '無題のフォルダー', nameErr] = $.str.optional().pipe(isValidFolderName).get(params.name);
+	const [name = '無題のフォルダー', nameErr] = $.str.optional.pipe(isValidFolderName).get(params.name);
 	if (nameErr) return rej('invalid name param');
 
 	// Get 'parentId' parameter
-	const [parentId = null, parentIdErr] = $.type(ID).optional().nullable().get(params.parentId);
+	const [parentId = null, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId);
 	if (parentIdErr) return rej('invalid parentId param');
 
 	// If the parent folder is specified
diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts
index b3238b5c32..2e4a09b80d 100644
--- a/src/server/api/endpoints/drive/folders/find.ts
+++ b/src/server/api/endpoints/drive/folders/find.ts
@@ -5,13 +5,13 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Find a folder(s)
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'name' parameter
 	const [name, nameErr] = $.str.get(params.name);
 	if (nameErr) return rej('invalid name param');
 
 	// Get 'parentId' parameter
-	const [parentId = null, parentIdErr] = $.type(ID).optional().nullable().get(params.parentId);
+	const [parentId = null, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId);
 	if (parentIdErr) return rej('invalid parentId param');
 
 	// Issue query
diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts
index c9b4930a76..edc8cb6f1c 100644
--- a/src/server/api/endpoints/drive/folders/show.ts
+++ b/src/server/api/endpoints/drive/folders/show.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Show a folder
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'folderId' parameter
 	const [folderId, folderIdErr] = $.type(ID).get(params.folderId);
 	if (folderIdErr) return rej('invalid folderId param');
diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts
index f126c09f5b..744d8fcb12 100644
--- a/src/server/api/endpoints/drive/folders/update.ts
+++ b/src/server/api/endpoints/drive/folders/update.ts
@@ -6,7 +6,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Update a folder
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'folderId' parameter
 	const [folderId, folderIdErr] = $.type(ID).get(params.folderId);
 	if (folderIdErr) return rej('invalid folderId param');
@@ -23,12 +23,12 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	// Get 'name' parameter
-	const [name, nameErr] = $.str.optional().pipe(isValidFolderName).get(params.name);
+	const [name, nameErr] = $.str.optional.pipe(isValidFolderName).get(params.name);
 	if (nameErr) return rej('invalid name param');
 	if (name) folder.name = name;
 
 	// Get 'parentId' parameter
-	const [parentId, parentIdErr] = $.type(ID).optional().nullable().get(params.parentId);
+	const [parentId, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId);
 	if (parentIdErr) return rej('invalid parentId param');
 	if (parentId !== undefined) {
 		if (parentId === null) {
diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts
index 515f74645a..a7aef926f0 100644
--- a/src/server/api/endpoints/drive/stream.ts
+++ b/src/server/api/endpoints/drive/stream.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get drive stream
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
@@ -24,7 +24,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	// Get 'type' parameter
-	const [type, typeErr] = $.str.optional().match(/^[a-zA-Z\/\-\*]+$/).get(params.type);
+	const [type, typeErr] = $.str.optional.match(/^[a-zA-Z\/\-\*]+$/).get(params.type);
 	if (typeErr) return rej('invalid type param');
 
 	// Construct query
diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts
index 3e45b8da53..ffd399d446 100644
--- a/src/server/api/endpoints/following/create.ts
+++ b/src/server/api/endpoints/following/create.ts
@@ -6,7 +6,7 @@ import create from '../../../../services/following/create';
 /**
  * Follow a user
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const follower = user;
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts
index 0af8813cf9..46fe13b2a9 100644
--- a/src/server/api/endpoints/following/delete.ts
+++ b/src/server/api/endpoints/following/delete.ts
@@ -6,7 +6,7 @@ import deleteFollowing from '../../../../services/following/delete';
 /**
  * Unfollow a user
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const follower = user;
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts
index a09e32e4d9..722fdac607 100644
--- a/src/server/api/endpoints/following/requests/accept.ts
+++ b/src/server/api/endpoints/following/requests/accept.ts
@@ -5,7 +5,7 @@ import User, { ILocalUser } from '../../../../../models/user';
 /**
  * Accept a follow request
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [followerId, followerIdErr] = $.type(ID).get(params.userId);
 	if (followerIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts
index f2a40854c2..88c1b57f43 100644
--- a/src/server/api/endpoints/following/requests/cancel.ts
+++ b/src/server/api/endpoints/following/requests/cancel.ts
@@ -5,7 +5,7 @@ import User, { pack, ILocalUser } from '../../../../../models/user';
 /**
  * Cancel a follow request
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [followeeId, followeeIdErr] = $.type(ID).get(params.userId);
 	if (followeeIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts
index 287c5a8e46..dfe9858f3a 100644
--- a/src/server/api/endpoints/following/requests/list.ts
+++ b/src/server/api/endpoints/following/requests/list.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Get all pending received follow requests
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const reqs = await FollowRequest.find({
 		followeeId: user._id
 	});
diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts
index 69dddf1355..7b7db2909d 100644
--- a/src/server/api/endpoints/following/requests/reject.ts
+++ b/src/server/api/endpoints/following/requests/reject.ts
@@ -5,7 +5,7 @@ import User, { ILocalUser } from '../../../../../models/user';
 /**
  * Reject a follow request
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [followerId, followerIdErr] = $.type(ID).get(params.userId);
 	if (followerIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/following/stalk.ts b/src/server/api/endpoints/following/stalk.ts
index b9d19d57b4..19c71cfd5a 100644
--- a/src/server/api/endpoints/following/stalk.ts
+++ b/src/server/api/endpoints/following/stalk.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Stalk a user
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const follower = user;
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/following/unstalk.ts b/src/server/api/endpoints/following/unstalk.ts
index 255f22ca1f..03ee107835 100644
--- a/src/server/api/endpoints/following/unstalk.ts
+++ b/src/server/api/endpoints/following/unstalk.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Unstalk a user
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const follower = user;
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index 17af9d6a9a..01dfccc71c 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -15,7 +15,7 @@ const max = 5;
 /**
  * Get trends of hashtags
  */
-module.exports = () => new Promise(async (res, rej) => {
+export default () => new Promise(async (res, rej) => {
 	//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
 	const data = await Note.aggregate([{
 		$match: {
diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts
index 5c769a02fd..150ab34a0f 100644
--- a/src/server/api/endpoints/i.ts
+++ b/src/server/api/endpoints/i.ts
@@ -4,7 +4,7 @@ import { IApp } from '../../../models/app';
 /**
  * Show myself
  */
-module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
 	const isSecure = user != null && app == null;
 
 	// Serialize
diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts
index 61f13c4c61..fef045948a 100644
--- a/src/server/api/endpoints/i/2fa/done.ts
+++ b/src/server/api/endpoints/i/2fa/done.ts
@@ -2,7 +2,7 @@ import $ from 'cafy';
 import * as speakeasy from 'speakeasy';
 import User, { ILocalUser } from '../../../../../models/user';
 
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'token' parameter
 	const [token, tokenErr] = $.str.get(params.token);
 	if (tokenErr) return rej('invalid token param');
diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts
index d05892c84b..ac703f5b0a 100644
--- a/src/server/api/endpoints/i/2fa/register.ts
+++ b/src/server/api/endpoints/i/2fa/register.ts
@@ -5,7 +5,7 @@ import * as QRCode from 'qrcode';
 import User, { ILocalUser } from '../../../../../models/user';
 import config from '../../../../../config';
 
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'password' parameter
 	const [password, passwordErr] = $.str.get(params.password);
 	if (passwordErr) return rej('invalid password param');
diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts
index fc197cb1e4..7d99ee397d 100644
--- a/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/src/server/api/endpoints/i/2fa/unregister.ts
@@ -2,7 +2,7 @@ import $ from 'cafy';
 import * as bcrypt from 'bcryptjs';
 import User, { ILocalUser } from '../../../../../models/user';
 
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'password' parameter
 	const [password, passwordErr] = $.str.get(params.password);
 	if (passwordErr) return rej('invalid password param');
diff --git a/src/server/api/endpoints/i/authorized_apps.ts b/src/server/api/endpoints/i/authorized_apps.ts
index cfc93c1518..24c4b58f7f 100644
--- a/src/server/api/endpoints/i/authorized_apps.ts
+++ b/src/server/api/endpoints/i/authorized_apps.ts
@@ -6,17 +6,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get authorized apps of my account
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'sort' parameter
-	const [sort = 'desc', sortError] = $.str.optional().or('desc asc').get(params.sort);
+	const [sort = 'desc', sortError] = $.str.optional.or('desc asc').get(params.sort);
 	if (sortError) return rej('invalid sort param');
 
 	// Get tokens
diff --git a/src/server/api/endpoints/i/change_password.ts b/src/server/api/endpoints/i/change_password.ts
index 9851fa895a..698db5a6e4 100644
--- a/src/server/api/endpoints/i/change_password.ts
+++ b/src/server/api/endpoints/i/change_password.ts
@@ -5,7 +5,7 @@ import User, { ILocalUser } from '../../../../models/user';
 /**
  * Change password
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'currentPasword' parameter
 	const [currentPassword, currentPasswordErr] = $.str.get(params.currentPasword);
 	if (currentPasswordErr) return rej('invalid currentPasword param');
diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts
index dc343afaed..75b456ca58 100644
--- a/src/server/api/endpoints/i/favorites.ts
+++ b/src/server/api/endpoints/i/favorites.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get favorited notes
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts
index ce283fe48f..1073b0369f 100644
--- a/src/server/api/endpoints/i/notifications.ts
+++ b/src/server/api/endpoints/i/notifications.ts
@@ -9,30 +9,30 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get notifications
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'following' parameter
 	const [following = false, followingError] =
-		$.bool.optional().get(params.following);
+		$.bool.optional.get(params.following);
 	if (followingError) return rej('invalid following param');
 
 	// Get 'markAsRead' parameter
-	const [markAsRead = true, markAsReadErr] = $.bool.optional().get(params.markAsRead);
+	const [markAsRead = true, markAsReadErr] = $.bool.optional.get(params.markAsRead);
 	if (markAsReadErr) return rej('invalid markAsRead param');
 
 	// Get 'type' parameter
-	const [type, typeErr] = $.arr($.str).optional().unique().get(params.type);
+	const [type, typeErr] = $.arr($.str).optional.unique().get(params.type);
 	if (typeErr) return rej('invalid type param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts
index 7f4a45e1f5..1edc571737 100644
--- a/src/server/api/endpoints/i/pin.ts
+++ b/src/server/api/endpoints/i/pin.ts
@@ -6,7 +6,7 @@ import { pack } from '../../../../models/user';
 /**
  * Pin note
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/i/regenerate_token.ts b/src/server/api/endpoints/i/regenerate_token.ts
index 3ffab5428e..1f68e3abd7 100644
--- a/src/server/api/endpoints/i/regenerate_token.ts
+++ b/src/server/api/endpoints/i/regenerate_token.ts
@@ -7,7 +7,7 @@ import generateUserToken from '../../common/generate-native-user-token';
 /**
  * Regenerate native token
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'password' parameter
 	const [password, passwordErr] = $.str.get(params.password);
 	if (passwordErr) return rej('invalid password param');
diff --git a/src/server/api/endpoints/i/signin_history.ts b/src/server/api/endpoints/i/signin_history.ts
index 4ab9881f34..1315a2cfb6 100644
--- a/src/server/api/endpoints/i/signin_history.ts
+++ b/src/server/api/endpoints/i/signin_history.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get signin history of my account
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts
index 57b050ebc4..08d17fa854 100644
--- a/src/server/api/endpoints/i/update.ts
+++ b/src/server/api/endpoints/i/update.ts
@@ -8,63 +8,63 @@ import { IApp } from '../../../../models/app';
 /**
  * Update myself
  */
-module.exports = async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
 	const isSecure = user != null && app == null;
 
 	const updates = {} as any;
 
 	// Get 'name' parameter
-	const [name, nameErr] = $.str.optional().nullable().pipe(isValidName).get(params.name);
+	const [name, nameErr] = $.str.optional.nullable.pipe(isValidName).get(params.name);
 	if (nameErr) return rej('invalid name param');
 	if (name) updates.name = name;
 
 	// Get 'description' parameter
-	const [description, descriptionErr] = $.str.optional().nullable().pipe(isValidDescription).get(params.description);
+	const [description, descriptionErr] = $.str.optional.nullable.pipe(isValidDescription).get(params.description);
 	if (descriptionErr) return rej('invalid description param');
 	if (description !== undefined) updates.description = description;
 
 	// Get 'location' parameter
-	const [location, locationErr] = $.str.optional().nullable().pipe(isValidLocation).get(params.location);
+	const [location, locationErr] = $.str.optional.nullable.pipe(isValidLocation).get(params.location);
 	if (locationErr) return rej('invalid location param');
 	if (location !== undefined) updates['profile.location'] = location;
 
 	// Get 'birthday' parameter
-	const [birthday, birthdayErr] = $.str.optional().nullable().pipe(isValidBirthday).get(params.birthday);
+	const [birthday, birthdayErr] = $.str.optional.nullable.pipe(isValidBirthday).get(params.birthday);
 	if (birthdayErr) return rej('invalid birthday param');
 	if (birthday !== undefined) updates['profile.birthday'] = birthday;
 
 	// Get 'avatarId' parameter
-	const [avatarId, avatarIdErr] = $.type(ID).optional().nullable().get(params.avatarId);
+	const [avatarId, avatarIdErr] = $.type(ID).optional.nullable.get(params.avatarId);
 	if (avatarIdErr) return rej('invalid avatarId param');
 	if (avatarId !== undefined) updates.avatarId = avatarId;
 
 	// Get 'bannerId' parameter
-	const [bannerId, bannerIdErr] = $.type(ID).optional().nullable().get(params.bannerId);
+	const [bannerId, bannerIdErr] = $.type(ID).optional.nullable.get(params.bannerId);
 	if (bannerIdErr) return rej('invalid bannerId param');
 	if (bannerId !== undefined) updates.bannerId = bannerId;
 
 	// Get 'wallpaperId' parameter
-	const [wallpaperId, wallpaperIdErr] = $.type(ID).optional().nullable().get(params.wallpaperId);
+	const [wallpaperId, wallpaperIdErr] = $.type(ID).optional.nullable.get(params.wallpaperId);
 	if (wallpaperIdErr) return rej('invalid wallpaperId param');
 	if (wallpaperId !== undefined) updates.wallpaperId = wallpaperId;
 
 	// Get 'isLocked' parameter
-	const [isLocked, isLockedErr] = $.bool.optional().get(params.isLocked);
+	const [isLocked, isLockedErr] = $.bool.optional.get(params.isLocked);
 	if (isLockedErr) return rej('invalid isLocked param');
 	if (isLocked != null) updates.isLocked = isLocked;
 
 	// Get 'isBot' parameter
-	const [isBot, isBotErr] = $.bool.optional().get(params.isBot);
+	const [isBot, isBotErr] = $.bool.optional.get(params.isBot);
 	if (isBotErr) return rej('invalid isBot param');
 	if (isBot != null) updates.isBot = isBot;
 
 	// Get 'isCat' parameter
-	const [isCat, isCatErr] = $.bool.optional().get(params.isCat);
+	const [isCat, isCatErr] = $.bool.optional.get(params.isCat);
 	if (isCatErr) return rej('invalid isCat param');
 	if (isCat != null) updates.isCat = isCat;
 
 	// Get 'autoWatch' parameter
-	const [autoWatch, autoWatchErr] = $.bool.optional().get(params.autoWatch);
+	const [autoWatch, autoWatchErr] = $.bool.optional.get(params.autoWatch);
 	if (autoWatchErr) return rej('invalid autoWatch param');
 	if (autoWatch != null) updates['settings.autoWatch'] = autoWatch;
 
diff --git a/src/server/api/endpoints/i/update_client_setting.ts b/src/server/api/endpoints/i/update_client_setting.ts
index 6d6e8ed24a..edcd3e7423 100644
--- a/src/server/api/endpoints/i/update_client_setting.ts
+++ b/src/server/api/endpoints/i/update_client_setting.ts
@@ -5,13 +5,13 @@ import event from '../../../../publishers/stream';
 /**
  * Update myself
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'name' parameter
 	const [name, nameErr] = $.str.get(params.name);
 	if (nameErr) return rej('invalid name param');
 
 	// Get 'value' parameter
-	const [value, valueErr] = $.any.nullable().get(params.value);
+	const [value, valueErr] = $.any.nullable.get(params.value);
 	if (valueErr) return rej('invalid value param');
 
 	const x: any = {};
diff --git a/src/server/api/endpoints/i/update_home.ts b/src/server/api/endpoints/i/update_home.ts
index 511a647d88..03795ae8d9 100644
--- a/src/server/api/endpoints/i/update_home.ts
+++ b/src/server/api/endpoints/i/update_home.ts
@@ -2,7 +2,7 @@ import $ from 'cafy';
 import User, { ILocalUser } from '../../../../models/user';
 import event from '../../../../publishers/stream';
 
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'home' parameter
 	const [home, homeErr] = $.arr(
 		$.obj.strict()
diff --git a/src/server/api/endpoints/i/update_mobile_home.ts b/src/server/api/endpoints/i/update_mobile_home.ts
index b1f25624fd..a2e2a13465 100644
--- a/src/server/api/endpoints/i/update_mobile_home.ts
+++ b/src/server/api/endpoints/i/update_mobile_home.ts
@@ -2,7 +2,7 @@ import $ from 'cafy';
 import User, { ILocalUser } from '../../../../models/user';
 import event from '../../../../publishers/stream';
 
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'home' parameter
 	const [home, homeErr] = $.arr(
 		$.obj.strict()
diff --git a/src/server/api/endpoints/i/update_widget.ts b/src/server/api/endpoints/i/update_widget.ts
index 82bb04d1f4..006916c9fe 100644
--- a/src/server/api/endpoints/i/update_widget.ts
+++ b/src/server/api/endpoints/i/update_widget.ts
@@ -2,7 +2,7 @@ import $ from 'cafy';
 import User, { ILocalUser } from '../../../../models/user';
 import event from '../../../../publishers/stream';
 
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'id' parameter
 	const [id, idErr] = $.str.get(params.id);
 	if (idErr) return rej('invalid id param');
diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts
index 713ba9dd7f..a1bd015864 100644
--- a/src/server/api/endpoints/messaging/history.ts
+++ b/src/server/api/endpoints/messaging/history.ts
@@ -7,9 +7,9 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Show messaging history
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	const mute = await Mute.find({
diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts
index 3eb20ec06b..6ac1cc980a 100644
--- a/src/server/api/endpoints/messaging/messages.ts
+++ b/src/server/api/endpoints/messaging/messages.ts
@@ -7,7 +7,7 @@ import read from '../../common/read-messaging-message';
 /**
  * Get messages
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [recipientId, recipientIdErr] = $.type(ID).get(params.userId);
 	if (recipientIdErr) return rej('invalid userId param');
@@ -26,19 +26,19 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	// Get 'markAsRead' parameter
-	const [markAsRead = true, markAsReadErr] = $.bool.optional().get(params.markAsRead);
+	const [markAsRead = true, markAsReadErr] = $.bool.optional.get(params.markAsRead);
 	if (markAsReadErr) return rej('invalid markAsRead param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts
index b3e4f6a8cd..bcbbd9a1f4 100644
--- a/src/server/api/endpoints/messaging/messages/create.ts
+++ b/src/server/api/endpoints/messaging/messages/create.ts
@@ -14,7 +14,7 @@ import config from '../../../../../config';
 /**
  * Create a message
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [recipientId, recipientIdErr] = $.type(ID).get(params.userId);
 	if (recipientIdErr) return rej('invalid userId param');
@@ -38,11 +38,11 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	// Get 'text' parameter
-	const [text, textErr] = $.str.optional().pipe(isValidText).get(params.text);
+	const [text, textErr] = $.str.optional.pipe(isValidText).get(params.text);
 	if (textErr) return rej('invalid text');
 
 	// Get 'fileId' parameter
-	const [fileId, fileIdErr] = $.type(ID).optional().get(params.fileId);
+	const [fileId, fileIdErr] = $.type(ID).optional.get(params.fileId);
 	if (fileIdErr) return rej('invalid fileId param');
 
 	let file = null;
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index ce460d0b8b..64cf9db55d 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -38,7 +38,7 @@ const client = require('../../../../built/client/meta.json');
 /**
  * Show core info
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default () => new Promise(async (res, rej) => {
 	const meta: any = (await Meta.findOne()) || {};
 
 	res({
diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts
index 415745e2c3..0f8d8fb09d 100644
--- a/src/server/api/endpoints/mute/create.ts
+++ b/src/server/api/endpoints/mute/create.ts
@@ -5,7 +5,7 @@ import Mute from '../../../../models/mute';
 /**
  * Mute a user
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const muter = user;
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts
index 2d1d286585..bc3160c157 100644
--- a/src/server/api/endpoints/mute/delete.ts
+++ b/src/server/api/endpoints/mute/delete.ts
@@ -5,7 +5,7 @@ import Mute from '../../../../models/mute';
 /**
  * Unmute a user
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	const muter = user;
 
 	// Get 'userId' parameter
diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts
index 8b0171be33..89287c50a0 100644
--- a/src/server/api/endpoints/mute/list.ts
+++ b/src/server/api/endpoints/mute/list.ts
@@ -6,17 +6,17 @@ import { getFriendIds } from '../../common/get-friends';
 /**
  * Get muted users of a user
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'iknow' parameter
-	const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow);
+	const [iknow = false, iknowErr] = $.bool.optional.get(params.iknow);
 	if (iknowErr) return rej('invalid iknow param');
 
 	// Get 'limit' parameter
-	const [limit = 30, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 30, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'cursor' parameter
-	const [cursor = null, cursorErr] = $.type(ID).optional().get(params.cursor);
+	const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor);
 	if (cursorErr) return rej('invalid cursor param');
 
 	// Construct query
diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts
index 7687afd0c7..8e946c9466 100644
--- a/src/server/api/endpoints/my/apps.ts
+++ b/src/server/api/endpoints/my/apps.ts
@@ -5,13 +5,13 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get my apps
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	const query = {
diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts
index 5554e53aa4..dd8d09dcc4 100644
--- a/src/server/api/endpoints/notes.ts
+++ b/src/server/api/endpoints/notes.ts
@@ -7,41 +7,41 @@ import Note, { pack } from '../../../models/note';
 /**
  * Get all notes
  */
-module.exports = (params: any) => new Promise(async (res, rej) => {
+export default (params: any) => new Promise(async (res, rej) => {
 	// Get 'local' parameter
-	const [local, localErr] = $.bool.optional().get(params.local);
+	const [local, localErr] = $.bool.optional.get(params.local);
 	if (localErr) return rej('invalid local param');
 
 	// Get 'reply' parameter
-	const [reply, replyErr] = $.bool.optional().get(params.reply);
+	const [reply, replyErr] = $.bool.optional.get(params.reply);
 	if (replyErr) return rej('invalid reply param');
 
 	// Get 'renote' parameter
-	const [renote, renoteErr] = $.bool.optional().get(params.renote);
+	const [renote, renoteErr] = $.bool.optional.get(params.renote);
 	if (renoteErr) return rej('invalid renote param');
 
 	// Get 'media' parameter
-	const [media, mediaErr] = $.bool.optional().get(params.media);
+	const [media, mediaErr] = $.bool.optional.get(params.media);
 	if (mediaErr) return rej('invalid media param');
 
 	// Get 'poll' parameter
-	const [poll, pollErr] = $.bool.optional().get(params.poll);
+	const [poll, pollErr] = $.bool.optional.get(params.poll);
 	if (pollErr) return rej('invalid poll param');
 
 	// Get 'bot' parameter
-	//const [bot, botErr] = $.bool.optional().get(params.bot);
+	//const [bot, botErr] = $.bool.optional.get(params.bot);
 	//if (botErr) return rej('invalid bot param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts
index b2bc6a2e72..e47b977279 100644
--- a/src/server/api/endpoints/notes/conversation.ts
+++ b/src/server/api/endpoints/notes/conversation.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Show conversation of a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Lookup note
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 64f3b5ce26..45fc77503e 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -1,68 +1,126 @@
 import $ from 'cafy'; import ID from '../../../../cafy-id';
 import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
 import User, { ILocalUser, IUser } from '../../../../models/user';
-import DriveFile from '../../../../models/drive-file';
+import DriveFile, { IDriveFile } from '../../../../models/drive-file';
 import create from '../../../../services/note/create';
 import { IApp } from '../../../../models/app';
+import getParams from '../../get-params';
+
+export const meta = {
+	name: 'notes/create',
+
+	desc: {
+		ja: '投稿します。'
+	},
+
+	params: {
+		visibility: $.str.optional.or(['public', 'home', 'followers', 'specified', 'private']).note({
+			default: 'public',
+			desc: {
+				ja: '投稿の公開範囲'
+			}
+		}),
+
+		visibleUserIds: $.arr($.type(ID)).optional.unique().min(1).note({
+			desc: {
+				ja: '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー'
+			}
+		}),
+
+		text: $.str.optional.nullable.pipe(isValidText).note({
+			default: null,
+			desc: {
+				ja: '投稿内容'
+			}
+		}),
+
+		cw: $.str.optional.nullable.pipe(isValidCw).note({
+			desc: {
+				ja: 'コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。'
+			}
+		}),
+
+		viaMobile: $.bool.optional.note({
+			default: false,
+			desc: {
+				ja: 'モバイルデバイスからの投稿か否か。'
+			}
+		}),
+
+		geo: $.obj({
+			coordinates: $.arr().length(2)
+				.item(0, $.num.range(-180, 180))
+				.item(1, $.num.range(-90, 90)),
+			altitude: $.num.nullable,
+			accuracy: $.num.nullable,
+			altitudeAccuracy: $.num.nullable,
+			heading: $.num.nullable.range(0, 360),
+			speed: $.num.nullable
+		}).optional.nullable.strict().note({
+			desc: {
+				ja: '位置情報'
+			},
+			ref: 'geo'
+		}),
+
+		mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({
+			desc: {
+				ja: '添付するメディア'
+			}
+		}),
+
+		renoteId: $.type(ID).optional.note({
+			desc: {
+				ja: 'Renote対象'
+			}
+		}),
+
+		poll: $.obj({
+			choices: $.arr($.str)
+				.unique()
+				.range(2, 10)
+				.each(c => c.length > 0 && c.length < 50)
+		}).optional.strict().note({
+			desc: {
+				ja: 'アンケート'
+			},
+			ref: 'poll'
+		})
+	},
+
+	res: {
+		type: 'object',
+		object: {
+			createdNote: {
+				type: 'entity(Note)',
+				desc: {
+					ja: '作成した投稿'
+				}
+			}
+		}
+	}
+};
 
 /**
  * Create a note
  */
-module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
-	// Get 'visibility' parameter
-	const [visibility = 'public', visibilityErr] = $.str.optional().or(['public', 'home', 'followers', 'specified', 'private']).get(params.visibility);
-	if (visibilityErr) return rej('invalid visibility');
-
-	// Get 'visibleUserIds' parameter
-	const [visibleUserIds, visibleUserIdsErr] = $.arr($.type(ID)).optional().unique().min(1).get(params.visibleUserIds);
-	if (visibleUserIdsErr) return rej('invalid visibleUserIds');
+export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) return rej(psErr);
 
 	let visibleUsers: IUser[] = [];
-	if (visibleUserIds !== undefined) {
-		visibleUsers = await Promise.all(visibleUserIds.map(id => User.findOne({
+	if (ps.visibleUserIds !== undefined) {
+		visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({
 			_id: id
 		})));
 	}
 
-	// Get 'text' parameter
-	const [text = null, textErr] = $.str.optional().nullable().pipe(isValidText).get(params.text);
-	if (textErr) return rej('invalid text');
-
-	// Get 'cw' parameter
-	const [cw, cwErr] = $.str.optional().nullable().pipe(isValidCw).get(params.cw);
-	if (cwErr) return rej('invalid cw');
-
-	// Get 'viaMobile' parameter
-	const [viaMobile = false, viaMobileErr] = $.bool.optional().get(params.viaMobile);
-	if (viaMobileErr) return rej('invalid viaMobile');
-
-	// Get 'tags' parameter
-	const [tags = [], tagsErr] = $.arr($.str.range(1, 32)).optional().unique().get(params.tags);
-	if (tagsErr) return rej('invalid tags');
-
-	// Get 'geo' parameter
-	const [geo, geoErr] = $.obj.optional().nullable().strict()
-		.have('coordinates', $.arr().length(2)
-			.item(0, $.num.range(-180, 180))
-			.item(1, $.num.range(-90, 90)))
-		.have('altitude', $.num.nullable())
-		.have('accuracy', $.num.nullable())
-		.have('altitudeAccuracy', $.num.nullable())
-		.have('heading', $.num.nullable().range(0, 360))
-		.have('speed', $.num.nullable())
-		.get(params.geo);
-	if (geoErr) return rej('invalid geo');
-
-	// Get 'mediaIds' parameter
-	const [mediaIds, mediaIdsErr] = $.arr($.type(ID)).optional().unique().range(1, 4).get(params.mediaIds);
-	if (mediaIdsErr) return rej('invalid mediaIds');
-
-	let files = [];
-	if (mediaIds !== undefined) {
+	let files: IDriveFile[] = [];
+	if (ps.mediaIds !== undefined) {
 		// Fetch files
 		// forEach だと途中でエラーなどがあっても return できないので
 		// 敢えて for を使っています。
-		for (const mediaId of mediaIds) {
+		for (const mediaId of ps.mediaIds) {
 			// Fetch file
 			// SELECT _id
 			const entity = await DriveFile.findOne({
@@ -80,15 +138,11 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async
 		files = null;
 	}
 
-	// Get 'renoteId' parameter
-	const [renoteId, renoteIdErr] = $.type(ID).optional().get(params.renoteId);
-	if (renoteIdErr) return rej('invalid renoteId');
-
 	let renote: INote = null;
-	if (renoteId !== undefined) {
+	if (ps.renoteId !== undefined) {
 		// Fetch renote to note
 		renote = await Note.findOne({
-			_id: renoteId
+			_id: ps.renoteId
 		});
 
 		if (renote == null) {
@@ -99,7 +153,7 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async
 	}
 
 	// Get 'replyId' parameter
-	const [replyId, replyIdErr] = $.type(ID).optional().get(params.replyId);
+	const [replyId, replyIdErr] = $.type(ID).optional.get(params.replyId);
 	if (replyIdErr) return rej('invalid replyId');
 
 	let reply: INote = null;
@@ -119,17 +173,8 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async
 		}
 	}
 
-	// Get 'poll' parameter
-	const [poll, pollErr] = $.obj.optional().strict()
-		.have('choices', $.arr($.str)
-			.unique()
-			.range(2, 10)
-			.each(c => c.length > 0 && c.length < 50))
-		.get(params.poll);
-	if (pollErr) return rej('invalid poll');
-
-	if (poll) {
-		(poll as any).choices = (poll as any).choices.map((choice: string, i: number) => ({
+	if (ps.poll) {
+		(ps.poll as any).choices = (ps.poll as any).choices.map((choice: string, i: number) => ({
 			id: i, // IDを付与
 			text: choice.trim(),
 			votes: 0
@@ -137,7 +182,7 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async
 	}
 
 	// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
-	if ((text === undefined || text === null) && files === null && renote === null && poll === undefined) {
+	if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) {
 		return rej('text, mediaIds, renoteId or poll is required');
 	}
 
@@ -145,17 +190,16 @@ module.exports = (params: any, user: ILocalUser, app: IApp) => new Promise(async
 	const note = await create(user, {
 		createdAt: new Date(),
 		media: files,
-		poll,
-		text,
+		poll: ps.poll,
+		text: ps.text,
 		reply,
 		renote,
-		cw,
-		tags,
+		cw: ps.cw,
 		app,
-		viaMobile,
-		visibility,
+		viaMobile: ps.viaMobile,
+		visibility: ps.visibility,
 		visibleUsers,
-		geo
+		geo: ps.geo
 	});
 
 	const noteObj = await pack(note, user);
diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts
index 70bcdbaab9..49907e4449 100644
--- a/src/server/api/endpoints/notes/delete.ts
+++ b/src/server/api/endpoints/notes/delete.ts
@@ -6,7 +6,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Delete a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts
index 23f7ac5f8d..9ec75dbba6 100644
--- a/src/server/api/endpoints/notes/favorites/create.ts
+++ b/src/server/api/endpoints/notes/favorites/create.ts
@@ -6,7 +6,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Favorite a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts
index 7d2d2b4cb5..f9916905bf 100644
--- a/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/src/server/api/endpoints/notes/favorites/delete.ts
@@ -6,7 +6,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Unfavorite a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts
index 24ffdbcba7..6de83ddad0 100644
--- a/src/server/api/endpoints/notes/global-timeline.ts
+++ b/src/server/api/endpoints/notes/global-timeline.ts
@@ -7,25 +7,25 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get timeline of global
  */
-module.exports = async (params: any, user: ILocalUser) => {
+export default async (params: any, user: ILocalUser) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) throw 'invalid limit param';
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) throw 'invalid sinceId param';
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) throw 'invalid untilId param';
 
 	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
+	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
 	if (sinceDateErr) throw 'invalid sinceDate param';
 
 	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
+	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
 	if (untilDateErr) throw 'invalid untilDate param';
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
@@ -34,7 +34,7 @@ module.exports = async (params: any, user: ILocalUser) => {
 	}
 
 	// Get 'mediaOnly' parameter
-	const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
+	const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
 	if (mediaOnlyErr) throw 'invalid mediaOnly param';
 
 	// ミュートしているユーザーを取得
diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts
index 48490638d2..3e96877c74 100644
--- a/src/server/api/endpoints/notes/local-timeline.ts
+++ b/src/server/api/endpoints/notes/local-timeline.ts
@@ -7,25 +7,25 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get timeline of local
  */
-module.exports = async (params: any, user: ILocalUser) => {
+export default async (params: any, user: ILocalUser) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) throw 'invalid limit param';
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) throw 'invalid sinceId param';
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) throw 'invalid untilId param';
 
 	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
+	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
 	if (sinceDateErr) throw 'invalid sinceDate param';
 
 	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
+	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
 	if (untilDateErr) throw 'invalid untilDate param';
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
@@ -34,7 +34,7 @@ module.exports = async (params: any, user: ILocalUser) => {
 	}
 
 	// Get 'mediaOnly' parameter
-	const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
+	const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
 	if (mediaOnlyErr) throw 'invalid mediaOnly param';
 
 	// ミュートしているユーザーを取得
diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts
index 45511603af..82e3371ffa 100644
--- a/src/server/api/endpoints/notes/mentions.ts
+++ b/src/server/api/endpoints/notes/mentions.ts
@@ -7,22 +7,22 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get mentions of myself
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'following' parameter
 	const [following = false, followingError] =
-		$.bool.optional().get(params.following);
+		$.bool.optional.get(params.following);
 	if (followingError) return rej('invalid following param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts
index 640140c3d1..f448bb66fc 100644
--- a/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -6,13 +6,13 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Get recommended polls
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get votes
diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts
index 72ac6bb202..b86d319b37 100644
--- a/src/server/api/endpoints/notes/polls/vote.ts
+++ b/src/server/api/endpoints/notes/polls/vote.ts
@@ -10,7 +10,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Vote poll of a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts
index d3b2d43432..5e15ed3ec6 100644
--- a/src/server/api/endpoints/notes/reactions.ts
+++ b/src/server/api/endpoints/notes/reactions.ts
@@ -6,21 +6,21 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Show reactions of a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'sort' parameter
-	const [sort = 'desc', sortError] = $.str.optional().or('desc asc').get(params.sort);
+	const [sort = 'desc', sortError] = $.str.optional.or('desc asc').get(params.sort);
 	if (sortError) return rej('invalid sort param');
 
 	// Lookup note
diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts
index 33e457e308..8b22e53225 100644
--- a/src/server/api/endpoints/notes/reactions/create.ts
+++ b/src/server/api/endpoints/notes/reactions/create.ts
@@ -3,22 +3,40 @@ import Note from '../../../../../models/note';
 import create from '../../../../../services/note/reaction/create';
 import { validateReaction } from '../../../../../models/note-reaction';
 import { ILocalUser } from '../../../../../models/user';
+import getParams from '../../../get-params';
+
+export const meta = {
+	name: 'notes/reactions/create',
+
+	desc: {
+		ja: '投稿にリアクションします。'
+	},
+
+	params: {
+		noteId: $.type(ID).note({
+			desc: {
+				ja: '対象の投稿'
+			}
+		}),
+
+		reaction: $.str.pipe(validateReaction.ok).note({
+			desc: {
+				ja: 'リアクションの種類'
+			}
+		})
+	}
+};
 
 /**
  * React to a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
-	// Get 'noteId' parameter
-	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
-	if (noteIdErr) return rej('invalid noteId param');
-
-	// Get 'reaction' parameter
-	const [reaction, reactionErr] = $.str.pipe(validateReaction.ok).get(params.reaction);
-	if (reactionErr) return rej('invalid reaction param');
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) return rej(psErr);
 
 	// Fetch reactee
 	const note = await Note.findOne({
-		_id: noteId
+		_id: ps.noteId
 	});
 
 	if (note === null) {
@@ -26,7 +44,7 @@ module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej)
 	}
 
 	try {
-		await create(user, note, reaction);
+		await create(user, note, ps.reaction);
 	} catch (e) {
 		rej(e);
 	}
diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts
index 1f2d662511..81671751a0 100644
--- a/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/src/server/api/endpoints/notes/reactions/delete.ts
@@ -6,7 +6,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Unreact to a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts
index 4aaf1d322b..baefde3159 100644
--- a/src/server/api/endpoints/notes/replies.ts
+++ b/src/server/api/endpoints/notes/replies.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get replies of a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Lookup note
diff --git a/src/server/api/endpoints/notes/reposts.ts b/src/server/api/endpoints/notes/reposts.ts
index ea3f174e1a..118e684a87 100644
--- a/src/server/api/endpoints/notes/reposts.ts
+++ b/src/server/api/endpoints/notes/reposts.ts
@@ -5,21 +5,21 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Show a renotes of a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts
index 20c628b84d..badaa7afc0 100644
--- a/src/server/api/endpoints/notes/search.ts
+++ b/src/server/api/endpoints/notes/search.ts
@@ -5,17 +5,17 @@ import { ILocalUser } from '../../../../models/user';
 import { pack } from '../../../../models/note';
 import es from '../../../../db/elasticsearch';
 
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'query' parameter
 	const [query, queryError] = $.str.get(params.query);
 	if (queryError) return rej('invalid query param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 30).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	es.search({
diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts
index 9be7cfffb6..a6fcae5932 100644
--- a/src/server/api/endpoints/notes/search_by_tag.ts
+++ b/src/server/api/endpoints/notes/search_by_tag.ts
@@ -8,65 +8,65 @@ import { pack } from '../../../../models/note';
 /**
  * Search notes by tag
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'tag' parameter
 	const [tag, tagError] = $.str.get(params.tag);
 	if (tagError) return rej('invalid tag param');
 
 	// Get 'includeUserIds' parameter
-	const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional().get(params.includeUserIds);
+	const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds);
 	if (includeUserIdsErr) return rej('invalid includeUserIds param');
 
 	// Get 'excludeUserIds' parameter
-	const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional().get(params.excludeUserIds);
+	const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds);
 	if (excludeUserIdsErr) return rej('invalid excludeUserIds param');
 
 	// Get 'includeUserUsernames' parameter
-	const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional().get(params.includeUserUsernames);
+	const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames);
 	if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param');
 
 	// Get 'excludeUserUsernames' parameter
-	const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional().get(params.excludeUserUsernames);
+	const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames);
 	if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param');
 
 	// Get 'following' parameter
-	const [following = null, followingErr] = $.bool.optional().nullable().get(params.following);
+	const [following = null, followingErr] = $.bool.optional.nullable.get(params.following);
 	if (followingErr) return rej('invalid following param');
 
 	// Get 'mute' parameter
-	const [mute = 'mute_all', muteErr] = $.str.optional().get(params.mute);
+	const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute);
 	if (muteErr) return rej('invalid mute param');
 
 	// Get 'reply' parameter
-	const [reply = null, replyErr] = $.bool.optional().nullable().get(params.reply);
+	const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply);
 	if (replyErr) return rej('invalid reply param');
 
 	// Get 'renote' parameter
-	const [renote = null, renoteErr] = $.bool.optional().nullable().get(params.renote);
+	const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote);
 	if (renoteErr) return rej('invalid renote param');
 
 	// Get 'media' parameter
-	const [media = null, mediaErr] = $.bool.optional().nullable().get(params.media);
+	const [media = null, mediaErr] = $.bool.optional.nullable.get(params.media);
 	if (mediaErr) return rej('invalid media param');
 
 	// Get 'poll' parameter
-	const [poll = null, pollErr] = $.bool.optional().nullable().get(params.poll);
+	const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll);
 	if (pollErr) return rej('invalid poll param');
 
 	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
+	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
 	if (sinceDateErr) throw 'invalid sinceDate param';
 
 	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
+	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
 	if (untilDateErr) throw 'invalid untilDate param';
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 30).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	if (includeUserUsernames != null) {
diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts
index 1ba7145996..0a27494962 100644
--- a/src/server/api/endpoints/notes/show.ts
+++ b/src/server/api/endpoints/notes/show.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Show a note
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'noteId' parameter
 	const [noteId, noteIdErr] = $.type(ID).get(params.noteId);
 	if (noteIdErr) return rej('invalid noteId param');
diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts
index 18c0acd379..43194c78c0 100644
--- a/src/server/api/endpoints/notes/timeline.ts
+++ b/src/server/api/endpoints/notes/timeline.ts
@@ -4,48 +4,81 @@ import Mute from '../../../../models/mute';
 import { getFriends } from '../../common/get-friends';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+
+export const meta = {
+	name: 'notes/timeline',
+
+	desc: {
+		ja: 'タイムラインを取得します。'
+	},
+
+	params: {
+		limit: $.num.optional.range(1, 100).note({
+			default: 10,
+			desc: {
+				ja: '最大数'
+			}
+		}),
+
+		sinceId: $.type(ID).optional.note({
+			desc: {
+				ja: '指定すると、この投稿を基点としてより新しい投稿を取得します'
+			}
+		}),
+
+		untilId: $.type(ID).optional.note({
+			desc: {
+				ja: '指定すると、この投稿を基点としてより古い投稿を取得します'
+			}
+		}),
+
+		sinceDate: $.num.optional.note({
+			desc: {
+				ja: '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
+			}
+		}),
+
+		untilDate: $.num.optional.note({
+			desc: {
+				ja: '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
+			}
+		}),
+
+		includeMyRenotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				ja: '自分の行ったRenoteを含めるかどうか'
+			}
+		}),
+
+		includeRenotedMyNotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				ja: 'Renoteされた自分の投稿を含めるかどうか'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				ja: 'true にすると、メディアが添付された投稿だけ取得します'
+			}
+		}),
+	}
+};
 
 /**
  * Get timeline of myself
  */
-module.exports = async (params: any, user: ILocalUser) => {
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
-	if (limitErr) throw 'invalid limit param';
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
-	if (sinceIdErr) throw 'invalid sinceId param';
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
-	if (untilIdErr) throw 'invalid untilId param';
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
+export default async (params: any, user: ILocalUser) => {
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	// Get 'includeMyRenotes' parameter
-	const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional().get(params.includeMyRenotes);
-	if (includeMyRenotesErr) throw 'invalid includeMyRenotes param';
-
-	// Get 'includeRenotedMyNotes' parameter
-	const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
-	if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
-
-	// Get 'mediaOnly' parameter
-	const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
-	if (mediaOnlyErr) throw 'invalid mediaOnly param';
-
 	const [followings, mutedUserIds] = await Promise.all([
 		// フォローを取得
 		// Fetch following
@@ -107,7 +140,7 @@ module.exports = async (params: any, user: ILocalUser) => {
 	// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
 	// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
 
-	if (includeMyRenotes === false) {
+	if (ps.includeMyRenotes === false) {
 		query.$and.push({
 			$or: [{
 				userId: { $ne: user._id }
@@ -123,7 +156,7 @@ module.exports = async (params: any, user: ILocalUser) => {
 		});
 	}
 
-	if (includeRenotedMyNotes === false) {
+	if (ps.includeRenotedMyNotes === false) {
 		query.$and.push({
 			$or: [{
 				'_renote.userId': { $ne: user._id }
@@ -139,29 +172,29 @@ module.exports = async (params: any, user: ILocalUser) => {
 		});
 	}
 
-	if (mediaOnly) {
+	if (ps.mediaOnly) {
 		query.$and.push({
 			mediaIds: { $exists: true, $ne: [] }
 		});
 	}
 
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 	//#endregion
@@ -169,7 +202,7 @@ module.exports = async (params: any, user: ILocalUser) => {
 	// Issue query
 	const timeline = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/trend.ts b/src/server/api/endpoints/notes/trend.ts
index 9c0a1bb112..ae66234c0b 100644
--- a/src/server/api/endpoints/notes/trend.ts
+++ b/src/server/api/endpoints/notes/trend.ts
@@ -6,29 +6,29 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get trend notes
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'reply' parameter
-	const [reply, replyErr] = $.bool.optional().get(params.reply);
+	const [reply, replyErr] = $.bool.optional.get(params.reply);
 	if (replyErr) return rej('invalid reply param');
 
 	// Get 'renote' parameter
-	const [renote, renoteErr] = $.bool.optional().get(params.renote);
+	const [renote, renoteErr] = $.bool.optional.get(params.renote);
 	if (renoteErr) return rej('invalid renote param');
 
 	// Get 'media' parameter
-	const [media, mediaErr] = $.bool.optional().get(params.media);
+	const [media, mediaErr] = $.bool.optional.get(params.media);
 	if (mediaErr) return rej('invalid media param');
 
 	// Get 'poll' parameter
-	const [poll, pollErr] = $.bool.optional().get(params.poll);
+	const [poll, pollErr] = $.bool.optional.get(params.poll);
 	if (pollErr) return rej('invalid poll param');
 
 	const query = {
diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts
index 8aa800b712..8a59857392 100644
--- a/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -8,25 +8,25 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * Get timeline of a user list
  */
-module.exports = async (params: any, user: ILocalUser) => {
+export default async (params: any, user: ILocalUser) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) throw 'invalid limit param';
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) throw 'invalid sinceId param';
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) throw 'invalid untilId param';
 
 	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
+	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
 	if (sinceDateErr) throw 'invalid sinceDate param';
 
 	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
+	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
 	if (untilDateErr) throw 'invalid untilDate param';
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
@@ -35,15 +35,15 @@ module.exports = async (params: any, user: ILocalUser) => {
 	}
 
 	// Get 'includeMyRenotes' parameter
-	const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional().get(params.includeMyRenotes);
+	const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional.get(params.includeMyRenotes);
 	if (includeMyRenotesErr) throw 'invalid includeMyRenotes param';
 
 	// Get 'includeRenotedMyNotes' parameter
-	const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
+	const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional.get(params.includeRenotedMyNotes);
 	if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
 
 	// Get 'mediaOnly' parameter
-	const [mediaOnly, mediaOnlyErr] = $.bool.optional().get(params.mediaOnly);
+	const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
 	if (mediaOnlyErr) throw 'invalid mediaOnly param';
 
 	// Get 'listId' parameter
diff --git a/src/server/api/endpoints/notifications/mark_as_read_all.ts b/src/server/api/endpoints/notifications/mark_as_read_all.ts
index faaaf65a2d..6e2b13fa8c 100644
--- a/src/server/api/endpoints/notifications/mark_as_read_all.ts
+++ b/src/server/api/endpoints/notifications/mark_as_read_all.ts
@@ -5,7 +5,7 @@ import User, { ILocalUser } from '../../../../models/user';
 /**
  * Mark as read all notifications
  */
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Update documents
 	await Notification.update({
 		notifieeId: user._id,
diff --git a/src/server/api/endpoints/reversi/games.ts b/src/server/api/endpoints/reversi/games.ts
index 1455f191f7..11da7b3c0c 100644
--- a/src/server/api/endpoints/reversi/games.ts
+++ b/src/server/api/endpoints/reversi/games.ts
@@ -2,21 +2,21 @@ import $ from 'cafy'; import ID from '../../../../cafy-id';
 import ReversiGame, { pack } from '../../../../models/reversi-game';
 import { ILocalUser } from '../../../../models/user';
 
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'my' parameter
-	const [my = false, myErr] = $.bool.optional().get(params.my);
+	const [my = false, myErr] = $.bool.optional.get(params.my);
 	if (myErr) return rej('invalid my param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Check if both of sinceId and untilId is specified
diff --git a/src/server/api/endpoints/reversi/games/show.ts b/src/server/api/endpoints/reversi/games/show.ts
index d70ee547a2..ba52066040 100644
--- a/src/server/api/endpoints/reversi/games/show.ts
+++ b/src/server/api/endpoints/reversi/games/show.ts
@@ -3,7 +3,7 @@ import ReversiGame, { pack } from '../../../../../models/reversi-game';
 import Reversi from '../../../../../reversi/core';
 import { ILocalUser } from '../../../../../models/user';
 
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'gameId' parameter
 	const [gameId, gameIdErr] = $.type(ID).get(params.gameId);
 	if (gameIdErr) return rej('invalid gameId param');
diff --git a/src/server/api/endpoints/reversi/invitations.ts b/src/server/api/endpoints/reversi/invitations.ts
index d7727071ae..179abe8cd9 100644
--- a/src/server/api/endpoints/reversi/invitations.ts
+++ b/src/server/api/endpoints/reversi/invitations.ts
@@ -1,7 +1,7 @@
 import Matching, { pack as packMatching } from '../../../../models/reversi-matching';
 import { ILocalUser } from '../../../../models/user';
 
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Find session
 	const invitations = await Matching.find({
 		childId: user._id
diff --git a/src/server/api/endpoints/reversi/match.ts b/src/server/api/endpoints/reversi/match.ts
index 907df7cf43..20473d9a4b 100644
--- a/src/server/api/endpoints/reversi/match.ts
+++ b/src/server/api/endpoints/reversi/match.ts
@@ -5,7 +5,7 @@ import User, { ILocalUser } from '../../../../models/user';
 import publishUserStream, { publishReversiStream } from '../../../../publishers/stream';
 import { eighteight } from '../../../../reversi/maps';
 
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [childId, childIdErr] = $.type(ID).get(params.userId);
 	if (childIdErr) return rej('invalid userId param');
diff --git a/src/server/api/endpoints/reversi/match/cancel.ts b/src/server/api/endpoints/reversi/match/cancel.ts
index 1c9c799dbe..470d848b53 100644
--- a/src/server/api/endpoints/reversi/match/cancel.ts
+++ b/src/server/api/endpoints/reversi/match/cancel.ts
@@ -1,7 +1,7 @@
 import Matching from '../../../../../models/reversi-matching';
 import { ILocalUser } from '../../../../../models/user';
 
-module.exports = (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	await Matching.remove({
 		parentId: user._id
 	});
diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts
index 74ab6ba945..ca11f67012 100644
--- a/src/server/api/endpoints/stats.ts
+++ b/src/server/api/endpoints/stats.ts
@@ -3,7 +3,7 @@ import Meta from '../../../models/meta';
 /**
  * Get the misskey's statistics
  */
-module.exports = () => new Promise(async (res, rej) => {
+export default () => new Promise(async (res, rej) => {
 	const meta = await Meta.findOne();
 
 	res(meta.stats);
diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts
index f04a77fa4d..c7bf1bfbe5 100644
--- a/src/server/api/endpoints/sw/register.ts
+++ b/src/server/api/endpoints/sw/register.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../models/user';
 /**
  * subscribe service worker
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'endpoint' parameter
 	const [endpoint, endpointErr] = $.str.get(params.endpoint);
 	if (endpointErr) return rej('invalid endpoint param');
diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts
index aad3adc514..ff12b797ed 100644
--- a/src/server/api/endpoints/username/available.ts
+++ b/src/server/api/endpoints/username/available.ts
@@ -5,7 +5,7 @@ import { validateUsername } from '../../../../models/user';
 /**
  * Check available username
  */
-module.exports = async (params: any) => new Promise(async (res, rej) => {
+export default async (params: any) => new Promise(async (res, rej) => {
 	// Get 'username' parameter
 	const [username, usernameError] = $.str.pipe(validateUsername).get(params.username);
 	if (usernameError) return rej('invalid username param');
diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts
index b8df6f3ecf..d7e85d3cbe 100644
--- a/src/server/api/endpoints/users.ts
+++ b/src/server/api/endpoints/users.ts
@@ -4,17 +4,17 @@ import User, { pack, ILocalUser } from '../../../models/user';
 /**
  * Lists all users
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'sort' parameter
-	const [sort, sortError] = $.str.optional().or('+follower|-follower').get(params.sort);
+	const [sort, sortError] = $.str.optional.or('+follower|-follower').get(params.sort);
 	if (sortError) return rej('invalid sort param');
 
 	// Construct query
diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts
index 53133ee969..f726976774 100644
--- a/src/server/api/endpoints/users/followers.ts
+++ b/src/server/api/endpoints/users/followers.ts
@@ -7,21 +7,21 @@ import { getFriendIds } from '../../common/get-friends';
 /**
  * Get followers of a user
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
 
 	// Get 'iknow' parameter
-	const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow);
+	const [iknow = false, iknowErr] = $.bool.optional.get(params.iknow);
 	if (iknowErr) return rej('invalid iknow param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'cursor' parameter
-	const [cursor = null, cursorErr] = $.type(ID).optional().get(params.cursor);
+	const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor);
 	if (cursorErr) return rej('invalid cursor param');
 
 	// Lookup user
diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts
index 2061200198..1b2eb9c878 100644
--- a/src/server/api/endpoints/users/following.ts
+++ b/src/server/api/endpoints/users/following.ts
@@ -7,21 +7,21 @@ import { getFriendIds } from '../../common/get-friends';
 /**
  * Get following users of a user
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
 
 	// Get 'iknow' parameter
-	const [iknow = false, iknowErr] = $.bool.optional().get(params.iknow);
+	const [iknow = false, iknowErr] = $.bool.optional.get(params.iknow);
 	if (iknowErr) return rej('invalid iknow param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'cursor' parameter
-	const [cursor = null, cursorErr] = $.type(ID).optional().get(params.cursor);
+	const [cursor = null, cursorErr] = $.type(ID).optional.get(params.cursor);
 	if (cursorErr) return rej('invalid cursor param');
 
 	// Lookup user
diff --git a/src/server/api/endpoints/users/get_frequently_replied_users.ts b/src/server/api/endpoints/users/get_frequently_replied_users.ts
index ba8779d334..98d662d29c 100644
--- a/src/server/api/endpoints/users/get_frequently_replied_users.ts
+++ b/src/server/api/endpoints/users/get_frequently_replied_users.ts
@@ -2,13 +2,13 @@ import $ from 'cafy'; import ID from '../../../../cafy-id';
 import Note from '../../../../models/note';
 import User, { pack, ILocalUser } from '../../../../models/user';
 
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
 	const [userId, userIdErr] = $.type(ID).get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Lookup user
diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts
index cdd1a0d813..f3776afee0 100644
--- a/src/server/api/endpoints/users/lists/create.ts
+++ b/src/server/api/endpoints/users/lists/create.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Create a user list
  */
-module.exports = async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'title' parameter
 	const [title, titleErr] = $.str.range(1, 100).get(params.title);
 	if (titleErr) return rej('invalid title param');
diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts
index bf8391d863..88fa75a111 100644
--- a/src/server/api/endpoints/users/lists/list.ts
+++ b/src/server/api/endpoints/users/lists/list.ts
@@ -4,7 +4,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Add a user to a user list
  */
-module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Fetch lists
 	const userLists = await UserList.find({
 		userId: me._id,
diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts
index d8a3b3c9aa..0e7caea064 100644
--- a/src/server/api/endpoints/users/lists/push.ts
+++ b/src/server/api/endpoints/users/lists/push.ts
@@ -9,7 +9,7 @@ import { deliver } from '../../../../../queue';
 /**
  * Add a user to a user list
  */
-module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'listId' parameter
 	const [listId, listIdErr] = $.type(ID).get(params.listId);
 	if (listIdErr) return rej('invalid listId param');
diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts
index e4ae239613..4716dd8211 100644
--- a/src/server/api/endpoints/users/lists/show.ts
+++ b/src/server/api/endpoints/users/lists/show.ts
@@ -5,7 +5,7 @@ import { ILocalUser } from '../../../../../models/user';
 /**
  * Show a user list
  */
-module.exports = async (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default async (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'listId' parameter
 	const [listId, listIdErr] = $.type(ID).get(params.listId);
 	if (listIdErr) return rej('invalid listId param');
diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts
index 222a8d950a..6d8d85ff5c 100644
--- a/src/server/api/endpoints/users/notes.ts
+++ b/src/server/api/endpoints/users/notes.ts
@@ -6,13 +6,13 @@ import User, { ILocalUser } from '../../../../models/user';
 /**
  * Get notes of a user
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'userId' parameter
-	const [userId, userIdErr] = $.type(ID).optional().get(params.userId);
+	const [userId, userIdErr] = $.type(ID).optional.get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
 
 	// Get 'username' parameter
-	const [username, usernameErr] = $.str.optional().get(params.username);
+	const [username, usernameErr] = $.str.optional.get(params.username);
 	if (usernameErr) return rej('invalid username param');
 
 	if (userId === undefined && username === undefined) {
@@ -20,7 +20,7 @@ module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) =
 	}
 
 	// Get 'host' parameter
-	const [host, hostErr] = $.str.optional().get(params.host);
+	const [host, hostErr] = $.str.optional.get(params.host);
 	if (hostErr) return rej('invalid host param');
 
 	if (userId === undefined && host === undefined) {
@@ -28,31 +28,31 @@ module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) =
 	}
 
 	// Get 'includeReplies' parameter
-	const [includeReplies = true, includeRepliesErr] = $.bool.optional().get(params.includeReplies);
+	const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies);
 	if (includeRepliesErr) return rej('invalid includeReplies param');
 
 	// Get 'withMedia' parameter
-	const [withMedia = false, withMediaErr] = $.bool.optional().get(params.withMedia);
+	const [withMedia = false, withMediaErr] = $.bool.optional.get(params.withMedia);
 	if (withMediaErr) return rej('invalid withMedia param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional().get(params.sinceId);
+	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
 	if (sinceIdErr) return rej('invalid sinceId param');
 
 	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional().get(params.untilId);
+	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
 	if (untilIdErr) return rej('invalid untilId param');
 
 	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional().get(params.sinceDate);
+	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
 	if (sinceDateErr) throw 'invalid sinceDate param';
 
 	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional().get(params.untilDate);
+	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
 	if (untilDateErr) throw 'invalid untilDate param';
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts
index 1d0d889f11..b7402b7fb1 100644
--- a/src/server/api/endpoints/users/recommendation.ts
+++ b/src/server/api/endpoints/users/recommendation.ts
@@ -7,13 +7,13 @@ import Mute from '../../../../models/mute';
 /**
  * Get recommended users
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// ID list of the user itself and other users who the user follows
diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts
index e29c8d32f1..d443d35b47 100644
--- a/src/server/api/endpoints/users/search.ts
+++ b/src/server/api/endpoints/users/search.ts
@@ -5,13 +5,13 @@ const escapeRegexp = require('escape-regexp');
 /**
  * Search a user
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'query' parameter
 	const [query, queryError] = $.str.pipe(x => x != '').get(params.query);
 	if (queryError) return rej('invalid query param');
 
 	// Get 'max' parameter
-	const [max = 10, maxErr] = $.num.optional().range(1, 30).get(params.max);
+	const [max = 10, maxErr] = $.num.optional.range(1, 30).get(params.max);
 	if (maxErr) return rej('invalid max param');
 
 	const escapedQuery = escapeRegexp(query);
diff --git a/src/server/api/endpoints/users/search_by_username.ts b/src/server/api/endpoints/users/search_by_username.ts
index 937f9af589..56030a000d 100644
--- a/src/server/api/endpoints/users/search_by_username.ts
+++ b/src/server/api/endpoints/users/search_by_username.ts
@@ -4,17 +4,17 @@ import User, { pack, ILocalUser } from '../../../../models/user';
 /**
  * Search a user by username
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	// Get 'query' parameter
 	const [query, queryError] = $.str.get(params.query);
 	if (queryError) return rej('invalid query param');
 
 	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional().min(0).get(params.offset);
+	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
 	if (offsetErr) return rej('invalid offset param');
 
 	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional().range(1, 100).get(params.limit);
+	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
 	if (limitErr) return rej('invalid limit param');
 
 	let users = await User
diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts
index bf7e2a2312..276e7c325b 100644
--- a/src/server/api/endpoints/users/show.ts
+++ b/src/server/api/endpoints/users/show.ts
@@ -7,23 +7,23 @@ const cursorOption = { fields: { data: false } };
 /**
  * Show user(s)
  */
-module.exports = (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	let user;
 
 	// Get 'userId' parameter
-	const [userId, userIdErr] = $.type(ID).optional().get(params.userId);
+	const [userId, userIdErr] = $.type(ID).optional.get(params.userId);
 	if (userIdErr) return rej('invalid userId param');
 
 	// Get 'userIds' parameter
-	const [userIds, userIdsErr] = $.arr($.type(ID)).optional().get(params.userIds);
+	const [userIds, userIdsErr] = $.arr($.type(ID)).optional.get(params.userIds);
 	if (userIdsErr) return rej('invalid userIds param');
 
 	// Get 'username' parameter
-	const [username, usernameErr] = $.str.optional().get(params.username);
+	const [username, usernameErr] = $.str.optional.get(params.username);
 	if (usernameErr) return rej('invalid username param');
 
 	// Get 'host' parameter
-	const [host, hostErr] = $.str.optional().nullable().get(params.host);
+	const [host, hostErr] = $.str.optional.nullable.get(params.host);
 	if (hostErr) return rej('invalid host param');
 
 	if (userIds) {
diff --git a/src/server/api/get-params.ts b/src/server/api/get-params.ts
new file mode 100644
index 0000000000..e495e3ef3e
--- /dev/null
+++ b/src/server/api/get-params.ts
@@ -0,0 +1,27 @@
+import { Context } from 'cafy';
+
+type Defs = {
+	params: { [key: string]: Context<any> }
+};
+
+export default function <T extends Defs>(defs: T, params: any): [{
+	[P in keyof T['params']]: ReturnType<T['params'][P]['get']>[0];
+}, Error] {
+	const x: any = {};
+	let err: Error = null;
+	Object.keys(defs.params).some(k => {
+		const [v, e] = defs.params[k].get(params[k]);
+		if (e) {
+			err = e;
+			return true;
+		} else {
+			if (v === undefined && defs.params[k].data.default) {
+				x[k] = defs.params[k].data.default;
+			} else {
+				x[k] = v;
+			}
+			return false;
+		}
+	});
+	return [x, err];
+}
diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts
index e65cc87b12..e670709634 100644
--- a/src/server/web/docs.ts
+++ b/src/server/web/docs.ts
@@ -2,12 +2,165 @@
  * Docs
  */
 
+import * as fs from 'fs';
+import * as path from 'path';
 import ms = require('ms');
 import * as Router from 'koa-router';
 import * as send from 'koa-send';
+import { Context } from 'cafy';
+import * as glob from 'glob';
+import * as yaml from 'js-yaml';
+import ObjectContext from 'cafy/built/types/object';
+import config from '../../config';
+import I18n from '../../build/i18n';
+import { fa } from '../../build/fa';
+import { licenseHtml } from '../../build/license';
+const constants = require('../../const.json');
 
 const docs = `${__dirname}/../../client/docs/`;
 
+async function genVars(lang: string): Promise<{ [key: string]: any }> {
+	const vars = {} as { [key: string]: any };
+
+	vars['lang'] = lang;
+
+	const endpoints = glob.sync('./built/server/api/endpoints/**/*.js');
+	vars['endpoints'] = endpoints.map(ep => require('../../../' + ep)).filter(x => x.meta).map(x => x.meta.name);
+
+	const entities = glob.sync('./src/client/docs/api/entities/**/*.yaml');
+	vars['entities'] = entities.map(x => {
+		const _x = yaml.safeLoad(fs.readFileSync(x, 'utf-8')) as any;
+		return _x.name;
+	});
+
+	const docs = glob.sync('./src/client/docs/**/*.*.pug');
+	vars['docs'] = {};
+	docs.forEach(x => {
+		const [, name, lang] = x.match(/docs\/(.+?)\.(.+?)\.pug$/);
+		if (vars['docs'][name] == null) {
+			vars['docs'][name] = {
+				name,
+				title: {}
+			};
+		}
+		vars['docs'][name]['title'][lang] = fs.readFileSync(x, 'utf-8').match(/^h1 (.+?)\r?\n/)[1];
+	});
+
+	vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
+
+	vars['config'] = config;
+
+	vars['copyright'] = constants.copyright;
+
+	vars['facss'] = fa.dom.css();
+
+	vars['license'] = licenseHtml;
+
+	const i18n = new I18n(lang);
+	vars['i18n'] = (key: string) => i18n.get(null, key);
+
+	return vars;
+}
+
+// WIP type
+const parseEPDefParam = (key: string, param: Context) => {
+	return Object.assign({
+		name: key,
+		type: param.getType()
+	}, param.data);
+};
+
+const parseParam = (param: any) => {
+	const id = param.type.match(/^id\((.+?)\)|^id/);
+	const entity = param.type.match(/^entity\((.+?)\)/);
+	const isObject = /^object/.test(param.type);
+	const isDate = /^date/.test(param.type);
+	const isArray = /\[\]$/.test(param.type);
+	if (id) {
+		param.kind = 'id';
+		param.type = 'string';
+		param.entity = id[1];
+		if (isArray) {
+			param.type += '[]';
+		}
+	}
+	if (entity) {
+		param.kind = 'entity';
+		param.type = 'object';
+		param.entity = entity[1];
+		if (isArray) {
+			param.type += '[]';
+		}
+	}
+	if (isObject) {
+		param.kind = 'object';
+	}
+	if (isDate) {
+		param.kind = 'date';
+		param.type = 'string';
+		if (isArray) {
+			param.type += '[]';
+		}
+	}
+
+	if (param.optional) {
+		param.type += '?';
+	}
+
+	return param;
+};
+
+const sortParams = (params: Array<{name: string}>) => {
+	params.sort((a, b) => {
+		if (a.name < b.name)
+			return -1;
+		if (a.name > b.name)
+			return 1;
+		return 0;
+	});
+	return params;
+};
+
+// WIP type
+const extractEPDefs = (params: Context[]) => {
+	let defs: any[] = [];
+
+	params.forEach(param => {
+		if (param.data && param.data.ref) {
+			const props = (param as ObjectContext<any>).props;
+			defs.push({
+				name: param.data.ref,
+				params: sortParams(Object.keys(props).map(k => parseEPDefParam(k, props[k])))
+			});
+
+			const childDefs = extractEPDefs(Object.keys(props).map(k => props[k]));
+
+			defs = defs.concat(childDefs);
+		}
+	});
+
+	return sortParams(defs);
+};
+
+const extractDefs = (params: any[]) => {
+	let defs: any[] = [];
+
+	params.forEach(param => {
+		if (param.def) {
+			defs.push({
+				name: param.defName,
+				params: sortParams(param.def.map((p: any) => parseParam(p)))
+			});
+
+			const childDefs = extractDefs(param.def);
+
+			defs = defs.concat(childDefs);
+		}
+	});
+
+	return sortParams(defs);
+};
+
 const router = new Router();
 
 router.get('/assets/*', async ctx => {
@@ -18,10 +171,45 @@ router.get('/assets/*', async ctx => {
 	});
 });
 
-router.get('*', async ctx => {
-	await send(ctx, `${ctx.params[0]}.html`, {
-		root: docs
-	});
+router.get('/*/api/endpoints/*', async ctx => {
+	const lang = ctx.params[0];
+	const ep = require('../../../built/server/api/endpoints/' + ctx.params[1]).meta;
+
+	const vars = {
+		title: ep.name,
+		endpoint: ep.name,
+		url: {
+			host: config.api_url,
+			path: ep.name
+		},
+		desc: ep.desc,
+		// @ts-ignore
+		params: sortParams(Object.keys(ep.params).map(k => parseEPDefParam(k, ep.params[k]))),
+		paramDefs: extractEPDefs(Object.keys(ep.params).map(k => ep.params[k])),
+	};
+
+	await ctx.render('../../../../src/client/docs/api/endpoints/view', Object.assign(await genVars(lang), vars));
+});
+
+router.get('/*/api/entities/*', async ctx => {
+	const lang = ctx.params[0];
+	const entity = ctx.params[1];
+
+	const x = yaml.safeLoad(fs.readFileSync(path.resolve('./src/client/docs/api/entities/' + entity + '.yaml'), 'utf-8')) as any;
+
+	await ctx.render('../../../../src/client/docs/api/entities/view', Object.assign(await genVars(lang), {
+		name: x.name,
+		desc: x.desc,
+		props: sortParams(x.props.map((p: any) => parseParam(p))),
+		propDefs: extractDefs(x.props)
+	}));
+});
+
+router.get('/*/*', async ctx => {
+	const lang = ctx.params[0];
+	const doc = ctx.params[1];
+
+	await ctx.render('../../../../src/client/docs/' + doc + '.' + lang, await genVars(lang));
 });
 
 export default router;
diff --git a/webpack.config.ts b/webpack.config.ts
index 3c426ebb49..1e49043764 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -19,7 +19,7 @@ const constants = require('./src/const.json');
 import config from './src/config';
 import { licenseHtml } from './src/build/license';
 
-import locales from './locales';
+const locales = require('./locales');
 const meta = require('./package.json');
 const version = meta.clientVersion;
 const codename = meta.codename;