diff --git a/CHANGELOG.md b/CHANGELOG.md
index f929f39a3..b9479bcbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@ ChangeLog
 
 unreleasded
 ----------
+* APIドキュメント刷新
 * /api/v1/instance/peers 復活
 * 「返信が遷移後も残り続ける問題を修正」([9beddc9](https://github.com/syuilo/misskey/commit/9beddc941a716f1322ae0b7d71d159edd642a399)) によって遷移前に返信が表示されなくなった問題を修正
 * デッキモードにてユーザーのプロフィールを連続で見たとき、アクティビティや画像が前のユーザーのもののまま表示される問題を修正
diff --git a/assets/api-doc.png b/assets/api-doc.png
new file mode 100644
index 000000000..e8f225245
Binary files /dev/null and b/assets/api-doc.png differ
diff --git a/package.json b/package.json
index 8f38b0a1a..ed14c0020 100644
--- a/package.json
+++ b/package.json
@@ -102,7 +102,7 @@
 		"bcryptjs": "2.4.3",
 		"bee-queue": "1.2.2",
 		"bootstrap-vue": "2.0.0-rc.11",
-		"cafy": "14.0.1",
+		"cafy": "15.1.0",
 		"chai": "4.2.0",
 		"chai-http": "4.2.1",
 		"chalk": "2.4.2",
diff --git a/src/client/assets/redoc.html b/src/client/assets/redoc.html
new file mode 100644
index 000000000..9803464cb
--- /dev/null
+++ b/src/client/assets/redoc.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>Misskey API</title>
+		<!-- needed for adaptive design -->
+		<meta charset="utf-8"/>
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+		<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
+
+		<!--
+		ReDoc doesn't change outer page styles
+		-->
+		<style>
+			body {
+				margin: 0;
+				padding: 0;
+			}
+		</style>
+	</head>
+	<body>
+		<redoc spec-url='/api.json'></redoc>
+		<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
+	</body>
+</html>
diff --git a/src/docs/api.ja-JP.md b/src/docs/api.ja-JP.md
index ecc80cc05..b8e84e670 100644
--- a/src/docs/api.ja-JP.md
+++ b/src/docs/api.ja-JP.md
@@ -74,7 +74,7 @@ APIはすべてリクエストのパラメータ・レスポンスともにJSON
 
 ストリーミングAPIも提供しています。
 
-APIリファレンスもご確認ください。
+[APIリファレンス](/api-doc)もご確認ください。
 
 ### レートリミット
 Misskey APIにはレートリミットがあり、短時間のうちに多数のリクエストを送信すると、一定時間APIを利用することができなくなることがあります。
diff --git a/src/docs/api/endpoints/style.styl b/src/docs/api/endpoints/style.styl
deleted file mode 100644
index 56ad29162..000000000
--- a/src/docs/api/endpoints/style.styl
+++ /dev/null
@@ -1,40 +0,0 @@
-@import "../style"
-
-#url
-	padding 8px 12px 8px 8px
-	font-family Consolas, 'Courier New', Courier, Monaco, monospace
-	color #fff
-	background #222e40
-	border-radius 4px
-	overflow auto
-	white-space nowrap
-
-	> .method
-		display inline-block
-		margin 0 8px 0 0
-		padding 0 6px
-		color #fff
-		background #17afc7
-		border-radius 4px
-		user-select none
-		pointer-events none
-
-	> .host
-		opacity 0.7
-
-#stability
-	padding 8px 12px
-	color #fff
-	border-radius 4px
-
-	&.deprecated
-		background #f42443
-
-	&.experimental
-		background #f2781a
-
-	&.stable
-		background #3dcc90
-
-	> b
-		margin-left 4px
diff --git a/src/docs/api/endpoints/view.pug b/src/docs/api/endpoints/view.pug
deleted file mode 100644
index 696ec4050..000000000
--- a/src/docs/api/endpoints/view.pug
+++ /dev/null
@@ -1,81 +0,0 @@
-extends ../../base
-include ../mixins
-
-block meta
-	link(rel="stylesheet" href="/docs/assets/api/endpoints/style.css")
-
-block main
-	h1= title
-
-	p#url
-		span.method POST
-		span.host
-			= endpointUrl.host
-			| /
-		span.path= endpointUrl.path
-
-	- var stability = endpoint.stability || 'experimental';
-	p#stability(class=stability)
-		| Stability:
-		b= stability
-
-	if endpoint.desc
-		p#desc= endpoint.desc[lang] || endpoint.desc['ja-JP']
-
-	if endpoint.requireCredential
-		div.ui.info: p
-			i.fas.fa-id-card-alt(style="margin-right: 4px")
-			= i18n('docs.api.endpoints.require-credential')
-
-		if endpoint.kind
-			div.ui.info: p
-				i.fas.fa-unlock-alt(style="margin-right: 4px")
-				!= i18n('docs.api.endpoints.require-permission').replace('{permission}', `<code>${endpoint.kind}</code>`)
-
-	if endpoint.limit
-		div.ui.info.warn: p
-			i.far.fa-clock(style="margin-right: 4px")
-			b!= i18n('docs.api.endpoints.has-limit')
-			if endpoint.limit.duration
-				!= i18n('docs.api.endpoints.duration-limit').replace('{duration}', endpoint.limit.duration).replace('{max}', endpoint.limit.max)
-			if endpoint.limit.minInterval
-				!= i18n('docs.api.endpoints.min-interval-limit').replace('{interval}', endpoint.limit.minInterval)
-
-	if params && Object.keys(params).length > 0
-		section
-			h2= i18n('docs.api.endpoints.params')
-			+propTable(params)
-
-			if paramDefs
-				each paramDef in paramDefs
-					section(id= paramDef.name)
-						h3= paramDef.name
-						+propTable(paramDef.params)
-	if params && Object.keys(params).length == 0
-		section
-			h2= i18n('docs.api.endpoints.params')
-			p= i18n('docs.api.endpoints.no-params')
-
-	if res
-		section
-			h2= i18n('docs.api.endpoints.res')
-
-			if resProps
-				+propTable(resProps)
-
-				if resDefs
-					each resDef in resDefs
-						section(id= resDef.name)
-							h3= resDef.name
-							+propTable(resDef.props)
-			else
-				if res.type.startsWith('entity')
-					a(href=`/docs/${lang}/api/entities/${kebab(res.entity)}`)= res.entity
-
-block footer
-	div.ui.info: p
-		i.fas.fa-info-circle(style="margin-right: 4px")
-		= i18n('docs.api.endpoints.generated')
-	p
-		= i18n('docs.api.endpoints.show-src')
-		a(href=src target="_blank")= i18n('docs.api.endpoints.show-src-link')
diff --git a/src/docs/api/entities/drive-file.yaml b/src/docs/api/entities/drive-file.yaml
deleted file mode 100644
index 0c2195ac0..000000000
--- a/src/docs/api/entities/drive-file.yaml
+++ /dev/null
@@ -1,90 +0,0 @@
-name: "DriveFile"
-
-desc:
-  ja-JP: "ドライブのファイル。"
-  en-US: "A file of Drive."
-
-props:
-  id:
-    type: "id"
-    optional: false
-    desc:
-      ja-JP: "ファイルID"
-      en-US: "The ID of this file"
-
-  createdAt:
-    type: "date"
-    optional: false
-    desc:
-      ja-JP: "アップロード日時"
-      en-US: "The upload date of this file"
-
-  userId:
-    type: "id(User)"
-    optional: false
-    desc:
-      ja-JP: "所有者ID"
-      en-US: "The ID of the owner of this file"
-
-  user:
-    type: "entity(User)"
-    optional: true
-    desc:
-      ja-JP: "所有者"
-      en-US: "The owner of this file"
-
-  name:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "ファイル名"
-      en-US: "The name of this file"
-
-  md5:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "ファイルのMD5ハッシュ値"
-      en-US: "The md5 hash value of this file"
-
-  type:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "ファイルの種類"
-      en-US: "The type of this file"
-
-  datasize:
-    type: "number"
-    optional: false
-    desc:
-      ja-JP: "ファイルサイズ(bytes)"
-      en-US: "The size of this file (bytes)"
-
-  url:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "ファイルのURL"
-      en-US: "The URL of this file"
-
-  folderId:
-    type: "id(DriveFolder)"
-    optional: true
-    desc:
-      ja-JP: "フォルダID"
-      en-US: "The ID of the folder of this file"
-
-  folder:
-    type: "entity(DriveFolder)"
-    optional: true
-    desc:
-      ja-JP: "フォルダ"
-      en-US: "The folder of this file"
-
-  isSensitive:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "このメディアが「閲覧注意」(NSFW)かどうか"
-      en-US: "Whether this media is NSFW"
diff --git a/src/docs/api/entities/drive-folder.yaml b/src/docs/api/entities/drive-folder.yaml
deleted file mode 100644
index e3dfd2ca0..000000000
--- a/src/docs/api/entities/drive-folder.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: "DriveFolder"
-
-desc:
-  ja-JP: "ドライブのフォルダを表します。"
-  en-US: "A folder of Drive."
-
-props:
-  id:
-    type: "id"
-    optional: false
-    desc:
-      ja-JP: "フォルダID"
-      en-US: "The ID of this folder"
-
-  createdAt:
-    type: "date"
-    optional: false
-    desc:
-      ja-JP: "作成日時"
-      en-US: "The created date of this folder"
-
-  userId:
-    type: "id(User)"
-    optional: false
-    desc:
-      ja-JP: "所有者ID"
-      en-US: "The ID of the owner of this folder"
-
-  parentId:
-    type: "entity(DriveFolder)"
-    optional: false
-    desc:
-      ja-JP: "親フォルダのID (ルートなら null)"
-      en-US: "The ID of parent folder"
-
-  name:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "フォルダ名"
-      en-US: "The name of this folder"
diff --git a/src/docs/api/entities/note.yaml b/src/docs/api/entities/note.yaml
deleted file mode 100644
index ce0c5de2e..000000000
--- a/src/docs/api/entities/note.yaml
+++ /dev/null
@@ -1,211 +0,0 @@
-name: "Note"
-
-desc:
-  ja-JP: "投稿。"
-  en-US: "A note."
-
-props:
-  id:
-    type: "id"
-    optional: false
-    desc:
-      ja-JP: "投稿ID"
-      en-US: "The ID of this note"
-
-  createdAt:
-    type: "date"
-    optional: false
-    desc:
-      ja-JP: "投稿日時"
-      en-US: "The posted date of this note"
-
-  viaMobile:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "モバイル端末から投稿したか否か(自己申告であることに留意)"
-      en-US: "Whether this note sent via a mobile device"
-
-  localOnly:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "ローカルのみに公開する投稿か否か"
-      en-US: "Whether this note is no federation"
-
-  text:
-    type: "string"
-    optional: true
-    desc:
-      ja-JP: "投稿の本文"
-      en-US: "The text of this note"
-
-  fileIds:
-    type: "id(DriveFile)[]"
-    optional: true
-    desc:
-      ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)"
-      en-US: "The IDs of the attached files (empty array for response if no files is attached)"
-
-  files:
-    type: "entity(DriveFile)[]"
-    optional: true
-    desc:
-      ja-JP: "添付されているファイル"
-      en-US: "The attached files"
-
-  userId:
-    type: "id(User)"
-    optional: false
-    desc:
-      ja-JP: "投稿者ID"
-      en-US: "The ID of author of this note"
-
-  user:
-    type: "entity(User)"
-    optional: true
-    desc:
-      ja-JP: "投稿者"
-      en-US: "The author of this note"
-
-  myReaction:
-    type: "string"
-    optional: true
-    desc:
-      ja-JP: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>"
-      en-US: "The your <a href='/docs/api/reactions'>reaction</a> of this note"
-
-  renoteCount:
-    type: "number"
-    optional: false
-    desc:
-      ja-JP: "この投稿がRenoteされた数"
-      en-US: "The number of renotes for this post"
-
-  repliesCount:
-    type: "number"
-    optional: false
-    desc:
-      ja-JP: "この投稿に返信された数"
-      en-US: "The number of replies to this post"
-
-  reactionCounts:
-    type: "object"
-    optional: false
-    desc:
-      ja-JP: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト"
-
-  replyId:
-    type: "id(Note)"
-    optional: true
-    desc:
-      ja-JP: "返信した投稿のID"
-      en-US: "The ID of the replyed note"
-
-  reply:
-    type: "entity(Note)"
-    optional: true
-    desc:
-      ja-JP: "返信した投稿"
-      en-US: "The replyed note"
-
-  renoteId:
-    type: "id(Note)"
-    optional: true
-    desc:
-      ja-JP: "引用した投稿のID"
-      en-US: "The ID of the quoted note"
-
-  renote:
-    type: "entity(Note)"
-    optional: true
-    desc:
-      ja-JP: "引用した投稿"
-      en-US: "The quoted note"
-
-  poll:
-    type: "object"
-    optional: true
-    desc:
-      ja-JP: "投票"
-      en-US: "The poll"
-
-    props:
-      choices:
-        type: "object[]"
-        optional: false
-        desc:
-          ja-JP: "投票の選択肢"
-          en-US: "The choices of this poll"
-
-        props:
-          id:
-            type: "number"
-            optional: false
-            desc:
-              ja-JP: "選択肢ID"
-              en-US: "The ID of this choice"
-
-          isVoted:
-            type: "boolean"
-            optional: true
-            desc:
-              ja-JP: "自分がこの選択肢に投票したかどうか"
-              en-US: "Whether you voted to this choice"
-
-          text:
-            type: "string"
-            optional: false
-            desc:
-              ja-JP: "選択肢本文"
-              en-US: "The text of this choice"
-
-          votes:
-            type: "number"
-            optional: false
-            desc:
-              ja-JP: "この選択肢に投票された数"
-              en-US: "The number voted for this choice"
-  geo:
-    type: "object"
-    optional: true
-    desc:
-      ja-JP: "位置情報"
-      en-US: "Geo location"
-
-    props:
-      coordinates:
-        type: "number[]"
-        optional: false
-        desc:
-          ja-JP: "座標。最初に経度:-180〜180で表す。最後に緯度:-90〜90で表す。"
-
-      altitude:
-        type: "number"
-        optional: false
-        desc:
-          ja-JP: "高度。メートル単位で表す。"
-
-      accuracy:
-        type: "number"
-        optional: false
-        desc:
-          ja-JP: "緯度、経度の精度。メートル単位で表す。"
-
-      altitudeAccuracy:
-        type: "number"
-        optional: false
-        desc:
-          ja-JP: "高度の精度。メートル単位で表す。"
-
-      heading:
-        type: "number"
-        optional: false
-        desc:
-          ja-JP: "方角。0〜360の角度で表す。0が北、90が東、180が南、270が西。"
-
-      speed:
-        type: "number"
-        optional: false
-        desc:
-          ja-JP: "速度。メートル / 秒数で表す。"
diff --git a/src/docs/api/entities/style.styl b/src/docs/api/entities/style.styl
deleted file mode 100644
index bddf0f53a..000000000
--- a/src/docs/api/entities/style.styl
+++ /dev/null
@@ -1 +0,0 @@
-@import "../style"
diff --git a/src/docs/api/entities/user.yaml b/src/docs/api/entities/user.yaml
deleted file mode 100644
index e3755d858..000000000
--- a/src/docs/api/entities/user.yaml
+++ /dev/null
@@ -1,174 +0,0 @@
-name: "User"
-
-desc:
-  ja-JP: "ユーザー。"
-  en-US: "A user."
-
-props:
-  id:
-    type: "id"
-    optional: false
-    desc:
-      ja-JP: "ユーザーID"
-      en-US: "The ID of this user"
-
-  createdAt:
-    type: "date"
-    optional: false
-    desc:
-      ja-JP: "アカウント作成日時"
-      en-US: "The registered date of this user"
-
-  username:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "ユーザー名"
-      en-US: "The username of this user"
-
-  description:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "アカウントの説明(自己紹介)"
-      en-US: "The description of this user"
-
-  avatarId:
-    type: "id(DriveFile)"
-    optional: true
-    desc:
-      ja-JP: "アバターのID"
-      en-US: "The ID of the avatar of this user"
-
-  avatarUrl:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "アバターのURL"
-      en-US: "The URL of the avatar of this user"
-
-  bannerId:
-    type: "id(DriveFile)"
-    optional: true
-    desc:
-      ja-JP: "バナーのID"
-      en-US: "The ID of the banner of this user"
-
-  bannerUrl:
-    type: "string"
-    optional: false
-    desc:
-      ja-JP: "バナーのURL"
-      en-US: "The URL of the banner of this user"
-
-  followersCount:
-    type: "number"
-    optional: false
-    desc:
-      ja-JP: "フォロワーの数"
-      en-US: "The number of the followers for this user"
-
-  followingCount:
-    type: "number"
-    optional: false
-    desc:
-      ja-JP: "フォローしているユーザーの数"
-      en-US: "The number of the following users for this user"
-
-  isFollowing:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "自分がこのユーザーをフォローしているか"
-
-  isFollowed:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "自分がこのユーザーにフォローされているか"
-
-  isMuted:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "自分がこのユーザーをミュートしているか"
-      en-US: "Whether you muted this user"
-
-  notesCount:
-    type: "number"
-    optional: false
-    desc:
-      ja-JP: "投稿の数"
-      en-US: "The number of the notes of this user"
-
-  pinnedNotes:
-    type: "entity(Note)[]"
-    optional: true
-    desc:
-      ja-JP: "ピン留めされた投稿"
-      en-US: "The pinned note of this user"
-
-  pinnedNoteIds:
-    type: "id(Note)[]"
-    optional: true
-    desc:
-      ja-JP: "ピン留めされた投稿のID"
-      en-US: "The ID of the pinned note of this user"
-
-  host:
-    type: "string | null"
-    optional: false
-    desc:
-      ja-JP: "ホスト (例: example.com:3000)"
-      en-US: "Host (e.g. example.com:3000)"
-
-  twitter:
-    type: "object"
-    optional: true
-    desc:
-      ja-JP: "連携されているTwitterアカウント情報"
-      en-US: "The info of the connected twitter account of this user"
-
-    props:
-      userId:
-        type: "string"
-        optional: false
-        desc:
-          ja-JP: "ユーザーID"
-          en-US: "The user ID"
-
-      screenName:
-        type: "string"
-        optional: false
-        desc:
-          ja-JP: "ユーザー名"
-          en-US: "The screen name of this user"
-
-  isBot:
-    type: "boolean"
-    optional: true
-    desc:
-      ja-JP: "botか否か(自己申告であることに留意)"
-      en-US: "Whether is bot or not"
-
-  profile:
-    type: "object"
-    optional: false
-    desc:
-      ja-JP: "プロフィール"
-      en-US: "The profile of this user"
-
-    props:
-      location:
-        type: "string"
-        optional: true
-        desc:
-          ja-JP: "場所"
-          en-US: "The location of this user"
-
-      birthday:
-        type: "string"
-        optional: true
-        desc:
-          ja-JP: "誕生日 (YYYY-MM-DD)"
-          en-US: "The birthday of this user (YYYY-MM-DD)"
diff --git a/src/docs/api/entities/view.pug b/src/docs/api/entities/view.pug
deleted file mode 100644
index 1f166d053..000000000
--- a/src/docs/api/entities/view.pug
+++ /dev/null
@@ -1,20 +0,0 @@
-extends ../../base
-include ../mixins
-
-block meta
-	link(rel="stylesheet" href="/docs/assets/api/entities/style.css")
-
-block main
-	h1= name
-
-	p#desc= desc[lang] || desc['ja-JP']
-
-	section
-		h2= i18n('docs.api.entities.properties')
-		+propTable(props)
-
-		if propDefs
-			each propDef in propDefs
-				section(id= propDef.name)
-					h3= propDef.name
-					+propTable(propDef.props)
diff --git a/src/docs/api/mixins.pug b/src/docs/api/mixins.pug
deleted file mode 100644
index 563739d52..000000000
--- a/src/docs/api/mixins.pug
+++ /dev/null
@@ -1,34 +0,0 @@
-mixin type(prop)
-	i= prop.type
-	if prop.kind == 'id'
-		if prop.entity
-			|  (
-			a(href=`/docs/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
-			|  ID)
-		else
-			|  (ID)
-	else if prop.kind == 'entity'
-		|   (
-		a(href=`/docs/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
-		| )
-	else if prop.kind == 'object'
-		if prop.hasDef
-			|  (
-			a(href=`#${prop.name}`)= prop.name
-			| )
-	else if prop.kind == 'date'
-		|  (Date)
-
-mixin propTable(props)
-	table.props
-		thead: tr
-			th= i18n('docs.api.props.name')
-			th= i18n('docs.api.props.type')
-			th= i18n('docs.api.props.description')
-		tbody
-			each prop in props
-				tr
-					td.name= prop.name
-					td.type
-						+type(prop)
-					td.desc!= prop.desc ? prop.desc[lang] || prop.desc['ja-JP'] : null
diff --git a/src/docs/api/style.styl b/src/docs/api/style.styl
deleted file mode 100644
index 3675a4da6..000000000
--- a/src/docs/api/style.styl
+++ /dev/null
@@ -1,11 +0,0 @@
-@import "../style"
-
-table.props
-	.name
-		font-weight bold
-
-	.name
-	.type
-	.optional
-		font-family Consolas, 'Courier New', Courier, Monaco, monospace
-
diff --git a/src/docs/base.pug b/src/docs/base.pug
index 41eb80a64..7d6af89de 100644
--- a/src/docs/base.pug
+++ b/src/docs/base.pug
@@ -17,17 +17,6 @@ html(lang= lang)
 			ul
 				each doc in docs
 					li: a(href=`/docs/${lang}/${doc.name}`)= doc.title[lang] || doc.title['ja-JP']
-			section
-				h2 API
-				ul
-					li Entities
-						ul
-							each entity in entities
-								li: a(href=`/docs/${lang}/api/entities/${kebab(entity)}`)= entity
-					li Endpoints
-						ul
-							each endpoint in endpoints
-								li: a(href=`/docs/${lang}/api/endpoints/${kebab(endpoint.name)}`)= endpoint.name
 		main
 			article
 				block main
diff --git a/src/misc/cafy-id.ts b/src/misc/cafy-id.ts
index eb5822a7d..bc8fe4ea2 100644
--- a/src/misc/cafy-id.ts
+++ b/src/misc/cafy-id.ts
@@ -26,6 +26,8 @@ export type ObjectId = mongo.ObjectID;
  * ID
  */
 export default class ID<Maybe = string> extends Context<string | Maybe> {
+	public readonly name = 'ID';
+
 	constructor(optional = false, nullable = false) {
 		super(optional, nullable);
 
@@ -38,7 +40,7 @@ export default class ID<Maybe = string> extends Context<string | Maybe> {
 	}
 
 	public getType() {
-		return super.getType('string');
+		return super.getType('String');
 	}
 
 	public makeOptional(): ID<undefined> {
diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts
index fdfaea1ff..00af69703 100644
--- a/src/server/api/api-handler.ts
+++ b/src/server/api/api-handler.ts
@@ -26,7 +26,7 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res)
 		call(endpoint.name, user, app, body, (ctx.req as any).file).then(res => {
 			reply(res);
 		}).catch(e => {
-			reply(e.kind == 'client' ? 400 : 500, e);
+			reply(e.httpStatusCode ? e.httpStatusCode :  e.kind == 'client' ? 400 : 500, e);
 		});
 	}).catch(() => {
 		reply(403, new ApiError({
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index 106f0937b..4b3d4cfbe 100644
--- a/src/server/api/call.ts
+++ b/src/server/api/call.ts
@@ -21,6 +21,7 @@ export default async (endpoint: string, user: IUser, app: IApp, data: any, file?
 			message: 'No such endpoint.',
 			code: 'NO_SUCH_ENDPOINT',
 			id: 'f8080b67-5f9c-4eb7-8c18-7f1eeae8f709',
+			httpStatusCode: 404
 		});
 	}
 
@@ -33,6 +34,7 @@ export default async (endpoint: string, user: IUser, app: IApp, data: any, file?
 			message: 'Credential required.',
 			code: 'CREDENTIAL_REQUIRED',
 			id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+			httpStatusCode: 401
 		});
 	}
 
@@ -63,6 +65,7 @@ export default async (endpoint: string, user: IUser, app: IApp, data: any, file?
 				message: 'Rate limit exceeded. Please try again later.',
 				code: 'RATE_LIMIT_EXCEEDED',
 				id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
+				httpStatusCode: 429
 			});
 		});
 	}
diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts
index 6765a63e9..2873dd3c1 100644
--- a/src/server/api/endpoints.ts
+++ b/src/server/api/endpoints.ts
@@ -2,18 +2,30 @@ import { Context } from 'cafy';
 import * as path from 'path';
 import * as glob from 'glob';
 
+export type Param = {
+	validator: Context<any>;
+	transform?: any;
+	default?: any;
+	desc?: { [key: string]: string };
+	ref?: string;
+};
+
 export interface IEndpointMeta {
 	stability?: string; //'deprecated' | 'experimental' | 'stable';
 
 	desc?: { [key: string]: string };
 
+	tags?: string[];
+
 	params?: {
+		[key: string]: Param;
+	};
+
+	errors?: {
 		[key: string]: {
-			validator: Context<any>;
-			transform?: any;
-			default?: any;
-			desc?: { [key: string]: string };
-			ref?: string;
+			message: string;
+			code: string;
+			id: string;
 		};
 	};
 
diff --git a/src/server/api/endpoints/admin/abuse-user-reports.ts b/src/server/api/endpoints/admin/abuse-user-reports.ts
index 602171abb..d9fe3429c 100644
--- a/src/server/api/endpoints/admin/abuse-user-reports.ts
+++ b/src/server/api/endpoints/admin/abuse-user-reports.ts
@@ -4,6 +4,8 @@ import Report, { packMany } from '../../../../models/abuse-user-report';
 import define from '../../define';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts
index 318b2de1b..8ed417a42 100644
--- a/src/server/api/endpoints/admin/drive/files.ts
+++ b/src/server/api/endpoints/admin/drive/files.ts
@@ -4,6 +4,8 @@ import define from '../../../define';
 import { fallback } from '../../../../../prelude/symbol';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: false,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/drive/show-file.ts b/src/server/api/endpoints/admin/drive/show-file.ts
index 9b993584e..405b6d44c 100644
--- a/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/src/server/api/endpoints/admin/drive/show-file.ts
@@ -5,6 +5,8 @@ import DriveFile from '../../../../../models/drive-file';
 import { ApiError } from '../../../error';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/emoji/add.ts b/src/server/api/endpoints/admin/emoji/add.ts
index b84e9d34d..99439f89a 100644
--- a/src/server/api/endpoints/admin/emoji/add.ts
+++ b/src/server/api/endpoints/admin/emoji/add.ts
@@ -7,6 +7,8 @@ export const meta = {
 		'ja-JP': 'カスタム絵文字を追加します。'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts
index f51a536fa..954f8f96c 100644
--- a/src/server/api/endpoints/admin/emoji/list.ts
+++ b/src/server/api/endpoints/admin/emoji/list.ts
@@ -7,6 +7,8 @@ export const meta = {
 		'ja-JP': 'カスタム絵文字を取得します。'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/emoji/remove.ts b/src/server/api/endpoints/admin/emoji/remove.ts
index 9699a0012..4c69dffba 100644
--- a/src/server/api/endpoints/admin/emoji/remove.ts
+++ b/src/server/api/endpoints/admin/emoji/remove.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': 'カスタム絵文字を削除します。'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/emoji/update.ts b/src/server/api/endpoints/admin/emoji/update.ts
index 7d065eadd..38d90c65a 100644
--- a/src/server/api/endpoints/admin/emoji/update.ts
+++ b/src/server/api/endpoints/admin/emoji/update.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': 'カスタム絵文字を更新します。'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/federation/remove-all-following.ts b/src/server/api/endpoints/admin/federation/remove-all-following.ts
index 1a272aeb0..98afdfc2a 100644
--- a/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -5,6 +5,8 @@ import User from '../../../../../models/user';
 import deleteFollowing from '../../../../../services/following/delete';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/federation/update-instance.ts b/src/server/api/endpoints/admin/federation/update-instance.ts
index 50eb4ba13..0d127b53b 100644
--- a/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -3,6 +3,8 @@ import define from '../../../define';
 import Instance from '../../../../../models/instance';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/invite.ts b/src/server/api/endpoints/admin/invite.ts
index 3c2b32349..28aa30195 100644
--- a/src/server/api/endpoints/admin/invite.ts
+++ b/src/server/api/endpoints/admin/invite.ts
@@ -7,6 +7,8 @@ export const meta = {
 		'ja-JP': '招待コードを発行します。'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/moderators/add.ts b/src/server/api/endpoints/admin/moderators/add.ts
index 58515381d..2271bcd1a 100644
--- a/src/server/api/endpoints/admin/moderators/add.ts
+++ b/src/server/api/endpoints/admin/moderators/add.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Mark a user as moderator.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireAdmin: true,
 
diff --git a/src/server/api/endpoints/admin/moderators/remove.ts b/src/server/api/endpoints/admin/moderators/remove.ts
index 75627fb63..84143d3e3 100644
--- a/src/server/api/endpoints/admin/moderators/remove.ts
+++ b/src/server/api/endpoints/admin/moderators/remove.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Unmark a user as moderator.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireAdmin: true,
 
diff --git a/src/server/api/endpoints/admin/queue/clear.ts b/src/server/api/endpoints/admin/queue/clear.ts
index 9a1e56595..f0fd00f1a 100644
--- a/src/server/api/endpoints/admin/queue/clear.ts
+++ b/src/server/api/endpoints/admin/queue/clear.ts
@@ -2,6 +2,8 @@ import define from '../../../define';
 import { destroy } from '../../../../../queue';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/remove-abuse-user-report.ts b/src/server/api/endpoints/admin/remove-abuse-user-report.ts
index c0c40c85d..fa17e2c93 100644
--- a/src/server/api/endpoints/admin/remove-abuse-user-report.ts
+++ b/src/server/api/endpoints/admin/remove-abuse-user-report.ts
@@ -4,6 +4,8 @@ import define from '../../define';
 import AbuseUserReport from '../../../../models/abuse-user-report';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/reset-password.ts b/src/server/api/endpoints/admin/reset-password.ts
index b274dc2a7..73901d835 100644
--- a/src/server/api/endpoints/admin/reset-password.ts
+++ b/src/server/api/endpoints/admin/reset-password.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'ja-JP': '指定したユーザーのパスワードをリセットします。',
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/show-user.ts b/src/server/api/endpoints/admin/show-user.ts
index 5fcf4c386..985f71a87 100644
--- a/src/server/api/endpoints/admin/show-user.ts
+++ b/src/server/api/endpoints/admin/show-user.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': '指定したユーザーの情報を取得します。',
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts
index 9d1c5ea98..5feb1b4fd 100644
--- a/src/server/api/endpoints/admin/show-users.ts
+++ b/src/server/api/endpoints/admin/show-users.ts
@@ -4,6 +4,8 @@ import define from '../../define';
 import { fallback } from '../../../../prelude/symbol';
 
 export const meta = {
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/silence-user.ts b/src/server/api/endpoints/admin/silence-user.ts
index 532025563..2557d8de6 100644
--- a/src/server/api/endpoints/admin/silence-user.ts
+++ b/src/server/api/endpoints/admin/silence-user.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Make silence a user.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts
index 321c3955e..83b39fb0b 100644
--- a/src/server/api/endpoints/admin/suspend-user.ts
+++ b/src/server/api/endpoints/admin/suspend-user.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Suspend a user.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/unsilence-user.ts b/src/server/api/endpoints/admin/unsilence-user.ts
index bb039eb8d..01bf41aae 100644
--- a/src/server/api/endpoints/admin/unsilence-user.ts
+++ b/src/server/api/endpoints/admin/unsilence-user.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Unsilence a user.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/unsuspend-user.ts b/src/server/api/endpoints/admin/unsuspend-user.ts
index 4732e4436..5da35f28e 100644
--- a/src/server/api/endpoints/admin/unsuspend-user.ts
+++ b/src/server/api/endpoints/admin/unsuspend-user.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Unsuspend a user.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/unverify-user.ts b/src/server/api/endpoints/admin/unverify-user.ts
index 857cceb1e..d3ca05cb3 100644
--- a/src/server/api/endpoints/admin/unverify-user.ts
+++ b/src/server/api/endpoints/admin/unverify-user.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Mark a user as unverified.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index 1fb87d0a9..46fdeff75 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -7,6 +7,8 @@ export const meta = {
 		'ja-JP': 'インスタンスの設定を更新します。'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/update-remote-user.ts b/src/server/api/endpoints/admin/update-remote-user.ts
index 8d1db33ba..a74685912 100644
--- a/src/server/api/endpoints/admin/update-remote-user.ts
+++ b/src/server/api/endpoints/admin/update-remote-user.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Update specified remote user information.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/admin/verify-user.ts b/src/server/api/endpoints/admin/verify-user.ts
index 77db8e6e6..f67b6c3bf 100644
--- a/src/server/api/endpoints/admin/verify-user.ts
+++ b/src/server/api/endpoints/admin/verify-user.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Mark a user as verified.'
 	},
 
+	tags: ['admin'],
+
 	requireCredential: true,
 	requireModerator: true,
 
diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts
index 6de36a1d1..978e9f64b 100644
--- a/src/server/api/endpoints/aggregation/hashtags.ts
+++ b/src/server/api/endpoints/aggregation/hashtags.ts
@@ -3,6 +3,8 @@ import define from '../../define';
 import fetchMeta from '../../../../misc/fetch-meta';
 
 export const meta = {
+	tags: ['hashtags'],
+
 	requireCredential: false,
 };
 
diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts
index 8dd9f96f9..72c853790 100644
--- a/src/server/api/endpoints/ap/show.ts
+++ b/src/server/api/endpoints/ap/show.ts
@@ -10,6 +10,8 @@ import Resolver from '../../../../remote/activitypub/resolver';
 import { ApiError } from '../../error';
 
 export const meta = {
+	tags: ['federation'],
+
 	desc: {
 		'ja-JP': 'URIを指定してActivityPubオブジェクトを参照します。'
 	},
diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts
index ef8bd6607..67b1b8150 100644
--- a/src/server/api/endpoints/app/create.ts
+++ b/src/server/api/endpoints/app/create.ts
@@ -4,6 +4,8 @@ import App, { pack } from '../../../../models/app';
 import define from '../../define';
 
 export const meta = {
+	tags: ['app'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts
index 72755ce01..f3f5b843b 100644
--- a/src/server/api/endpoints/app/show.ts
+++ b/src/server/api/endpoints/app/show.ts
@@ -5,6 +5,8 @@ import define from '../../define';
 import { ApiError } from '../../error';
 
 export const meta = {
+	tags: ['app'],
+
 	params: {
 		appId: {
 			validator: $.type(ID),
diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts
index 964ee6559..cedf7821f 100644
--- a/src/server/api/endpoints/auth/accept.ts
+++ b/src/server/api/endpoints/auth/accept.ts
@@ -8,6 +8,8 @@ import define from '../../define';
 import { ApiError } from '../../error';
 
 export const meta = {
+	tags: ['auth'],
+
 	requireCredential: true,
 
 	secure: true,
diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts
index 9f0f18471..b6bfb5590 100644
--- a/src/server/api/endpoints/auth/session/generate.ts
+++ b/src/server/api/endpoints/auth/session/generate.ts
@@ -7,6 +7,8 @@ import define from '../../../define';
 import { ApiError } from '../../../error';
 
 export const meta = {
+	tags: ['auth'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/auth/session/show.ts b/src/server/api/endpoints/auth/session/show.ts
index 5e9e68c2c..4287950f3 100644
--- a/src/server/api/endpoints/auth/session/show.ts
+++ b/src/server/api/endpoints/auth/session/show.ts
@@ -4,6 +4,8 @@ import define from '../../../define';
 import { ApiError } from '../../../error';
 
 export const meta = {
+	tags: ['auth'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts
index 17d62886a..d4c3d7777 100644
--- a/src/server/api/endpoints/auth/session/userkey.ts
+++ b/src/server/api/endpoints/auth/session/userkey.ts
@@ -7,6 +7,8 @@ import define from '../../../define';
 import { ApiError } from '../../../error';
 
 export const meta = {
+	tags: ['auth'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/blocking/create.ts b/src/server/api/endpoints/blocking/create.ts
index 71c3ed316..e723cb038 100644
--- a/src/server/api/endpoints/blocking/create.ts
+++ b/src/server/api/endpoints/blocking/create.ts
@@ -16,6 +16,8 @@ export const meta = {
 		'en-US': 'Block a user.'
 	},
 
+	tags: ['blocking', 'users'],
+
 	limit: {
 		duration: ms('1hour'),
 		max: 100
diff --git a/src/server/api/endpoints/blocking/delete.ts b/src/server/api/endpoints/blocking/delete.ts
index 426d88c1c..2a9fdc5e2 100644
--- a/src/server/api/endpoints/blocking/delete.ts
+++ b/src/server/api/endpoints/blocking/delete.ts
@@ -16,6 +16,8 @@ export const meta = {
 		'en-US': 'Unblock a user.'
 	},
 
+	tags: ['blocking', 'users'],
+
 	limit: {
 		duration: ms('1hour'),
 		max: 100
diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts
index 088b2c488..2921f48f2 100644
--- a/src/server/api/endpoints/blocking/list.ts
+++ b/src/server/api/endpoints/blocking/list.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Get blocking users.'
 	},
 
+	tags: ['blocking', 'account'],
+
 	requireCredential: true,
 
 	kind: 'following-read',
diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts
index 11f86d0ba..9dad942e0 100644
--- a/src/server/api/endpoints/charts/active-users.ts
+++ b/src/server/api/endpoints/charts/active-users.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'アクティブユーザーのチャートを取得します。'
 	},
 
+	tags: ['charts', 'users'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -24,7 +26,14 @@ export const meta = {
 				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts
index 9244ef748..5438d7648 100644
--- a/src/server/api/endpoints/charts/drive.ts
+++ b/src/server/api/endpoints/charts/drive.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'ドライブのチャートを取得します。'
 	},
 
+	tags: ['charts', 'drive'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -24,7 +26,14 @@ export const meta = {
 				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts
index c98c9f6cb..c7b34f101 100644
--- a/src/server/api/endpoints/charts/federation.ts
+++ b/src/server/api/endpoints/charts/federation.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'フェデレーションのチャートを取得します。'
 	},
 
+	tags: ['charts'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -24,7 +26,14 @@ export const meta = {
 				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts
index 5905fdc1a..4db6e6240 100644
--- a/src/server/api/endpoints/charts/hashtag.ts
+++ b/src/server/api/endpoints/charts/hashtag.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'ハッシュタグごとのチャートを取得します。'
 	},
 
+	tags: ['charts', 'hashtags'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -31,7 +33,14 @@ export const meta = {
 				'ja-JP': '対象のハッシュタグ'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts
index e47334c7c..3fe85f086 100644
--- a/src/server/api/endpoints/charts/instance.ts
+++ b/src/server/api/endpoints/charts/instance.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'インスタンスごとのチャートを取得します。'
 	},
 
+	tags: ['charts'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -32,7 +34,14 @@ export const meta = {
 				'en-US': 'Target instance host'
 			}
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts
index fa1917f75..48b1d0f66 100644
--- a/src/server/api/endpoints/charts/network.ts
+++ b/src/server/api/endpoints/charts/network.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'ネットワークのチャートを取得します。'
 	},
 
+	tags: ['charts'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -24,7 +26,14 @@ export const meta = {
 				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts
index 006a8d6e2..d254bb854 100644
--- a/src/server/api/endpoints/charts/notes.ts
+++ b/src/server/api/endpoints/charts/notes.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': '投稿のチャートを取得します。'
 	},
 
+	tags: ['charts', 'notes'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -24,7 +26,14 @@ export const meta = {
 				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts
index 6383ddf2f..32645b38c 100644
--- a/src/server/api/endpoints/charts/user/drive.ts
+++ b/src/server/api/endpoints/charts/user/drive.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'ja-JP': 'ユーザーごとのドライブのチャートを取得します。'
 	},
 
+	tags: ['charts', 'drive', 'users'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -34,7 +36,14 @@ export const meta = {
 				'en-US': 'Target user ID'
 			}
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts
index 9443b56a4..73f0c1e4f 100644
--- a/src/server/api/endpoints/charts/user/following.ts
+++ b/src/server/api/endpoints/charts/user/following.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'ja-JP': 'ユーザーごとのフォロー/フォロワーのチャートを取得します。'
 	},
 
+	tags: ['charts', 'users', 'following'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -34,7 +36,14 @@ export const meta = {
 				'en-US': 'Target user ID'
 			}
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts
index da642fc76..6a95911b1 100644
--- a/src/server/api/endpoints/charts/user/notes.ts
+++ b/src/server/api/endpoints/charts/user/notes.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'ja-JP': 'ユーザーごとの投稿のチャートを取得します。'
 	},
 
+	tags: ['charts', 'users', 'notes'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -34,7 +36,14 @@ export const meta = {
 				'en-US': 'Target user ID'
 			}
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts
index e440b3ae3..51ff83f20 100644
--- a/src/server/api/endpoints/charts/user/reactions.ts
+++ b/src/server/api/endpoints/charts/user/reactions.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'ja-JP': 'ユーザーごとの被リアクション数のチャートを取得します。'
 	},
 
+	tags: ['charts', 'users', 'reactions'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -34,7 +36,14 @@ export const meta = {
 				'en-US': 'Target user ID'
 			}
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts
index ea32b7afd..0b1b18bbe 100644
--- a/src/server/api/endpoints/charts/users.ts
+++ b/src/server/api/endpoints/charts/users.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'ja-JP': 'ユーザーのチャートを取得します。'
 	},
 
+	tags: ['charts', 'users'],
+
 	params: {
 		span: {
 			validator: $.str.or(['day', 'hour']),
@@ -24,7 +26,14 @@ export const meta = {
 				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+		},
+	},
 };
 
 export default define(meta, async (ps) => {
diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts
index 54afe55cb..9c4482f91 100644
--- a/src/server/api/endpoints/drive.ts
+++ b/src/server/api/endpoints/drive.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'en-US': 'Get drive information.'
 	},
 
+	tags: ['drive', 'account'],
+
 	requireCredential: true,
 
 	kind: 'drive-read'
diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts
index 1c12f0dc0..f108e820e 100644
--- a/src/server/api/endpoints/drive/files.ts
+++ b/src/server/api/endpoints/drive/files.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Get files of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
@@ -38,7 +40,14 @@ export const meta = {
 		type: {
 			validator: $.optional.str.match(/^[a-zA-Z\/\-\*]+$/)
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'DriveFile',
+		},
+	},
 };
 
 export default define(meta, async (ps, user) => {
diff --git a/src/server/api/endpoints/drive/files/attached_notes.ts b/src/server/api/endpoints/drive/files/attached_notes.ts
index 506444b18..c1d828b05 100644
--- a/src/server/api/endpoints/drive/files/attached_notes.ts
+++ b/src/server/api/endpoints/drive/files/attached_notes.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get the notes that specified file of drive attached.'
 	},
 
+	tags: ['drive', 'notes'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
diff --git a/src/server/api/endpoints/drive/files/check_existence.ts b/src/server/api/endpoints/drive/files/check_existence.ts
index aafd46fb2..12680d541 100644
--- a/src/server/api/endpoints/drive/files/check_existence.ts
+++ b/src/server/api/endpoints/drive/files/check_existence.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'en-US': 'Returns whether the file with the given MD5 hash exists in the user\'s drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts
index c96e4bbf1..b2979c488 100644
--- a/src/server/api/endpoints/drive/files/create.ts
+++ b/src/server/api/endpoints/drive/files/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Upload a file to drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	limit: {
@@ -35,7 +37,7 @@ export const meta = {
 		},
 
 		isSensitive: {
-			validator: $.optional.or($.bool, $.str),
+			validator: $.optional.either($.bool, $.str),
 			default: false,
 			transform: (v: any): boolean => v === true || v === 'true',
 			desc: {
@@ -45,7 +47,7 @@ export const meta = {
 		},
 
 		force: {
-			validator: $.optional.or($.bool, $.str),
+			validator: $.optional.either($.bool, $.str),
 			default: false,
 			transform: (v: any): boolean => v === true || v === 'true',
 			desc: {
@@ -54,6 +56,10 @@ export const meta = {
 		}
 	},
 
+	res: {
+		type: 'DriveFile',
+	},
+
 	errors: {
 		invalidFileName: {
 			message: 'Invalid file name.',
diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts
index 2dc493efe..dd4e187fc 100644
--- a/src/server/api/endpoints/drive/files/delete.ts
+++ b/src/server/api/endpoints/drive/files/delete.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'Delete a file of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-write',
diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts
index 6eb909b9d..0d4102a48 100644
--- a/src/server/api/endpoints/drive/files/find.ts
+++ b/src/server/api/endpoints/drive/files/find.ts
@@ -6,6 +6,8 @@ import define from '../../../define';
 export const meta = {
 	requireCredential: true,
 
+	tags: ['drive'],
+
 	kind: 'drive-read',
 
 	params: {
diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts
index f844d434f..c2442c65c 100644
--- a/src/server/api/endpoints/drive/files/show.ts
+++ b/src/server/api/endpoints/drive/files/show.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'Get specified file of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
@@ -37,6 +39,10 @@ export const meta = {
 		}
 	},
 
+	res: {
+		type: 'DriveFile',
+	},
+
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts
index f24cc6bcd..c8803bec3 100644
--- a/src/server/api/endpoints/drive/files/update.ts
+++ b/src/server/api/endpoints/drive/files/update.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Update specified file of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-write',
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 8d25530dd..93a9fa62f 100644
--- a/src/server/api/endpoints/drive/files/upload_from_url.ts
+++ b/src/server/api/endpoints/drive/files/upload_from_url.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'ja-JP': 'ドライブに指定されたURLに存在するファイルをアップロードします。'
 	},
 
+	tags: ['drive'],
+
 	limit: {
 		duration: ms('1hour'),
 		max: 60
diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts
index 698206085..a0b972f71 100644
--- a/src/server/api/endpoints/drive/folders.ts
+++ b/src/server/api/endpoints/drive/folders.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Get folders of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts
index a54c780b8..5fab0b91a 100644
--- a/src/server/api/endpoints/drive/folders/create.ts
+++ b/src/server/api/endpoints/drive/folders/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Create a folder of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-write',
diff --git a/src/server/api/endpoints/drive/folders/delete.ts b/src/server/api/endpoints/drive/folders/delete.ts
index 26a71d57d..9f22bf9ea 100644
--- a/src/server/api/endpoints/drive/folders/delete.ts
+++ b/src/server/api/endpoints/drive/folders/delete.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'Delete specified folder of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-write',
diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts
index 25d89f9fb..17769567a 100644
--- a/src/server/api/endpoints/drive/folders/find.ts
+++ b/src/server/api/endpoints/drive/folders/find.ts
@@ -4,6 +4,8 @@ import DriveFolder, { pack } from '../../../../../models/drive-folder';
 import define from '../../../define';
 
 export const meta = {
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts
index 0d98158b9..033743622 100644
--- a/src/server/api/endpoints/drive/folders/show.ts
+++ b/src/server/api/endpoints/drive/folders/show.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'en-US': 'Get specified folder of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts
index 585664ba0..1dc0e6387 100644
--- a/src/server/api/endpoints/drive/folders/update.ts
+++ b/src/server/api/endpoints/drive/folders/update.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Update specified folder of drive.'
 	},
 
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-write',
diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts
index d5872470f..f6586a852 100644
--- a/src/server/api/endpoints/drive/stream.ts
+++ b/src/server/api/endpoints/drive/stream.ts
@@ -4,6 +4,8 @@ import DriveFile, { packMany } from '../../../../models/drive-file';
 import define from '../../define';
 
 export const meta = {
+	tags: ['drive'],
+
 	requireCredential: true,
 
 	kind: 'drive-read',
diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts
index 34da9dff7..f81f81822 100644
--- a/src/server/api/endpoints/federation/instances.ts
+++ b/src/server/api/endpoints/federation/instances.ts
@@ -3,6 +3,8 @@ import define from '../../define';
 import Instance from '../../../../models/instance';
 
 export const meta = {
+	tags: ['federation'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/federation/show-instance.ts b/src/server/api/endpoints/federation/show-instance.ts
index e443abcda..e7f68620a 100644
--- a/src/server/api/endpoints/federation/show-instance.ts
+++ b/src/server/api/endpoints/federation/show-instance.ts
@@ -3,6 +3,8 @@ import define from '../../define';
 import Instance from '../../../../models/instance';
 
 export const meta = {
+	tags: ['federation'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts
index a4e68f580..81b239955 100644
--- a/src/server/api/endpoints/following/create.ts
+++ b/src/server/api/endpoints/following/create.ts
@@ -16,6 +16,8 @@ export const meta = {
 		'en-US': 'Follow a user.'
 	},
 
+	tags: ['following', 'users'],
+
 	limit: {
 		duration: ms('1hour'),
 		max: 100
diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts
index faa42cacf..8f8249b1e 100644
--- a/src/server/api/endpoints/following/delete.ts
+++ b/src/server/api/endpoints/following/delete.ts
@@ -16,6 +16,8 @@ export const meta = {
 		'en-US': 'Unfollow a user.'
 	},
 
+	tags: ['following', 'users'],
+
 	limit: {
 		duration: ms('1hour'),
 		max: 100
diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts
index cc42ca9c3..0975990c0 100644
--- a/src/server/api/endpoints/following/requests/accept.ts
+++ b/src/server/api/endpoints/following/requests/accept.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Accept a follow request.'
 	},
 
+	tags: ['following', 'account'],
+
 	requireCredential: true,
 
 	kind: 'following-write',
diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts
index eb3abfa3e..371f9f0ed 100644
--- a/src/server/api/endpoints/following/requests/cancel.ts
+++ b/src/server/api/endpoints/following/requests/cancel.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'en-US': 'Cancel a follow request.'
 	},
 
+	tags: ['following', 'account'],
+
 	requireCredential: true,
 
 	kind: 'following-write',
diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts
index f66027764..c9bcedf92 100644
--- a/src/server/api/endpoints/following/requests/list.ts
+++ b/src/server/api/endpoints/following/requests/list.ts
@@ -7,6 +7,8 @@ export const meta = {
 		'en-US': 'Get all pending received follow requests.'
 	},
 
+	tags: ['following', 'account'],
+
 	requireCredential: true,
 
 	kind: 'following-read'
diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts
index 908e64acc..5e59d4bc9 100644
--- a/src/server/api/endpoints/following/requests/reject.ts
+++ b/src/server/api/endpoints/following/requests/reject.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Reject a follow request.'
 	},
 
+	tags: ['following', 'account'],
+
 	requireCredential: true,
 
 	kind: 'following-write',
diff --git a/src/server/api/endpoints/games/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts
index b188ba996..e3c22c761 100644
--- a/src/server/api/endpoints/games/reversi/games.ts
+++ b/src/server/api/endpoints/games/reversi/games.ts
@@ -4,6 +4,8 @@ import ReversiGame, { pack } from '../../../../../models/games/reversi/game';
 import define from '../../../define';
 
 export const meta = {
+	tags: ['games'],
+
 	params: {
 		limit: {
 			validator: $.optional.num.range(1, 100),
diff --git a/src/server/api/endpoints/games/reversi/games/show.ts b/src/server/api/endpoints/games/reversi/games/show.ts
index d70ab8de9..766ca9011 100644
--- a/src/server/api/endpoints/games/reversi/games/show.ts
+++ b/src/server/api/endpoints/games/reversi/games/show.ts
@@ -6,6 +6,8 @@ import define from '../../../../define';
 import { ApiError } from '../../../../error';
 
 export const meta = {
+	tags: ['games'],
+
 	params: {
 		gameId: {
 			validator: $.type(ID),
diff --git a/src/server/api/endpoints/games/reversi/games/surrender.ts b/src/server/api/endpoints/games/reversi/games/surrender.ts
index 954ae07eb..446210894 100644
--- a/src/server/api/endpoints/games/reversi/games/surrender.ts
+++ b/src/server/api/endpoints/games/reversi/games/surrender.ts
@@ -6,6 +6,8 @@ import define from '../../../../define';
 import { ApiError } from '../../../../error';
 
 export const meta = {
+	tags: ['games'],
+
 	desc: {
 		'ja-JP': '指定したリバーシの対局で投了します。'
 	},
diff --git a/src/server/api/endpoints/games/reversi/invitations.ts b/src/server/api/endpoints/games/reversi/invitations.ts
index 034c84bdd..c20477057 100644
--- a/src/server/api/endpoints/games/reversi/invitations.ts
+++ b/src/server/api/endpoints/games/reversi/invitations.ts
@@ -2,6 +2,8 @@ import Matching, { pack as packMatching } from '../../../../../models/games/reve
 import define from '../../../define';
 
 export const meta = {
+	tags: ['games'],
+
 	requireCredential: true
 };
 
diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts
index 6913ee13d..e66765944 100644
--- a/src/server/api/endpoints/games/reversi/match.ts
+++ b/src/server/api/endpoints/games/reversi/match.ts
@@ -9,6 +9,8 @@ import { ApiError } from '../../../error';
 import { getUser } from '../../../common/getters';
 
 export const meta = {
+	tags: ['games'],
+
 	requireCredential: true,
 
 	params: {
diff --git a/src/server/api/endpoints/games/reversi/match/cancel.ts b/src/server/api/endpoints/games/reversi/match/cancel.ts
index 03b76babd..fb230032d 100644
--- a/src/server/api/endpoints/games/reversi/match/cancel.ts
+++ b/src/server/api/endpoints/games/reversi/match/cancel.ts
@@ -2,6 +2,8 @@ import Matching from '../../../../../../models/games/reversi/matching';
 import define from '../../../../define';
 
 export const meta = {
+	tags: ['games'],
+
 	requireCredential: true
 };
 
diff --git a/src/server/api/endpoints/hashtags/list.ts b/src/server/api/endpoints/hashtags/list.ts
index bfeba1d16..5939c1019 100644
--- a/src/server/api/endpoints/hashtags/list.ts
+++ b/src/server/api/endpoints/hashtags/list.ts
@@ -3,6 +3,8 @@ import define from '../../define';
 import Hashtag from '../../../../models/hashtag';
 
 export const meta = {
+	tags: ['hashtags'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts
index eff50f413..ff7ca44f5 100644
--- a/src/server/api/endpoints/hashtags/search.ts
+++ b/src/server/api/endpoints/hashtags/search.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': 'ハッシュタグを検索します。'
 	},
 
+	tags: ['hashtags'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index bf036642d..8b8dd7024 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -16,6 +16,8 @@ const requiredUsers = 3; // 最低何人がそのタグを投稿している必
 const max = 5;
 
 export const meta = {
+	tags: ['hashtags'],
+
 	requireCredential: false,
 };
 
diff --git a/src/server/api/endpoints/hashtags/users.ts b/src/server/api/endpoints/hashtags/users.ts
index b77bdf6aa..f589c37b8 100644
--- a/src/server/api/endpoints/hashtags/users.ts
+++ b/src/server/api/endpoints/hashtags/users.ts
@@ -5,6 +5,8 @@ import define from '../../define';
 export const meta = {
 	requireCredential: false,
 
+	tags: ['hashtags', 'users'],
+
 	params: {
 		tag: {
 			validator: $.str,
diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts
index 7ef4ddd26..b7fe404ec 100644
--- a/src/server/api/endpoints/i.ts
+++ b/src/server/api/endpoints/i.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': '自分のアカウント情報を取得します。'
 	},
 
+	tags: ['account'],
+
 	requireCredential: true,
 
 	params: {},
diff --git a/src/server/api/endpoints/i/clear-follow-request-notification.ts b/src/server/api/endpoints/i/clear-follow-request-notification.ts
index 2c656965d..38c6ec1ce 100644
--- a/src/server/api/endpoints/i/clear-follow-request-notification.ts
+++ b/src/server/api/endpoints/i/clear-follow-request-notification.ts
@@ -2,6 +2,8 @@ import User from '../../../../models/user';
 import define from '../../define';
 
 export const meta = {
+	tags: ['account', 'following'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts
index b9bef38c6..7ea6f7b96 100644
--- a/src/server/api/endpoints/i/favorites.ts
+++ b/src/server/api/endpoints/i/favorites.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Get favorited notes'
 	},
 
+	tags: ['account', 'notes', 'favorites'],
+
 	requireCredential: true,
 
 	kind: 'favorites-read',
diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts
index 693d27996..3d038e5d3 100644
--- a/src/server/api/endpoints/i/notifications.ts
+++ b/src/server/api/endpoints/i/notifications.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get notifications.'
 	},
 
+	tags: ['account', 'notifications'],
+
 	requireCredential: true,
 
 	kind: 'account-read',
diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts
index 0b6b1b0db..8d853d45c 100644
--- a/src/server/api/endpoints/i/pin.ts
+++ b/src/server/api/endpoints/i/pin.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'ja-JP': '指定した投稿をピン留めします。'
 	},
 
+	tags: ['account', 'notes'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/i/read_all_messaging_messages.ts b/src/server/api/endpoints/i/read_all_messaging_messages.ts
index 6bc6e16b4..bbbfa0d7b 100644
--- a/src/server/api/endpoints/i/read_all_messaging_messages.ts
+++ b/src/server/api/endpoints/i/read_all_messaging_messages.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Mark all talk messages as read.'
 	},
 
+	tags: ['account', 'messaging'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/i/read_all_unread_notes.ts b/src/server/api/endpoints/i/read_all_unread_notes.ts
index 16db17cb0..742c2d990 100644
--- a/src/server/api/endpoints/i/read_all_unread_notes.ts
+++ b/src/server/api/endpoints/i/read_all_unread_notes.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Mark all messages as read.'
 	},
 
+	tags: ['account'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/i/unpin.ts b/src/server/api/endpoints/i/unpin.ts
index 472e81156..184d46f2c 100644
--- a/src/server/api/endpoints/i/unpin.ts
+++ b/src/server/api/endpoints/i/unpin.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'ja-JP': '指定した投稿のピン留めを解除します。'
 	},
 
+	tags: ['account', 'notes'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts
index df7da4677..22e76925b 100644
--- a/src/server/api/endpoints/i/update.ts
+++ b/src/server/api/endpoints/i/update.ts
@@ -20,6 +20,8 @@ export const meta = {
 		'en-US': 'Update myself'
 	},
 
+	tags: ['account'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts
index e42bf79c5..0425ef021 100644
--- a/src/server/api/endpoints/messaging/history.ts
+++ b/src/server/api/endpoints/messaging/history.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Show messaging history.'
 	},
 
+	tags: ['messaging'],
+
 	requireCredential: true,
 
 	kind: 'messaging-read',
diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts
index 34d3f4cfd..f1408f87f 100644
--- a/src/server/api/endpoints/messaging/messages.ts
+++ b/src/server/api/endpoints/messaging/messages.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get messages of messaging.'
 	},
 
+	tags: ['messaging'],
+
 	requireCredential: true,
 
 	kind: 'messaging-read',
diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts
index 7ff652629..cf32b4461 100644
--- a/src/server/api/endpoints/messaging/messages/create.ts
+++ b/src/server/api/endpoints/messaging/messages/create.ts
@@ -19,6 +19,8 @@ export const meta = {
 		'en-US': 'Create a message of messaging.'
 	},
 
+	tags: ['messaging'],
+
 	requireCredential: true,
 
 	kind: 'messaging-write',
diff --git a/src/server/api/endpoints/messaging/messages/delete.ts b/src/server/api/endpoints/messaging/messages/delete.ts
index 5bc492c19..0ca12846c 100644
--- a/src/server/api/endpoints/messaging/messages/delete.ts
+++ b/src/server/api/endpoints/messaging/messages/delete.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'Delete a message.'
 	},
 
+	tags: ['messaging'],
+
 	requireCredential: true,
 
 	kind: 'messaging-write',
diff --git a/src/server/api/endpoints/messaging/messages/read.ts b/src/server/api/endpoints/messaging/messages/read.ts
index 98f195d75..aa8ecdc4f 100644
--- a/src/server/api/endpoints/messaging/messages/read.ts
+++ b/src/server/api/endpoints/messaging/messages/read.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Mark as read a message of messaging.'
 	},
 
+	tags: ['messaging'],
+
 	requireCredential: true,
 
 	kind: 'messaging-write',
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index 82769506c..07a475d4c 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'Get the information of this instance.'
 	},
 
+	tags: ['meta'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts
index 3b2546176..7eaee90a0 100644
--- a/src/server/api/endpoints/mute/create.ts
+++ b/src/server/api/endpoints/mute/create.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Mute a user'
 	},
 
+	tags: ['mute', 'users'],
+
 	requireCredential: true,
 
 	kind: 'account/write',
diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts
index 93af5834b..1a03f6371 100644
--- a/src/server/api/endpoints/mute/delete.ts
+++ b/src/server/api/endpoints/mute/delete.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Unmute a user'
 	},
 
+	tags: ['mute', 'users'],
+
 	requireCredential: true,
 
 	kind: 'account/write',
diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts
index 84699c237..da7189b48 100644
--- a/src/server/api/endpoints/mute/list.ts
+++ b/src/server/api/endpoints/mute/list.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Get muted users.'
 	},
 
+	tags: ['mute', 'account'],
+
 	requireCredential: true,
 
 	kind: 'account/read',
diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts
index 0eeeb5f85..1a936c918 100644
--- a/src/server/api/endpoints/my/apps.ts
+++ b/src/server/api/endpoints/my/apps.ts
@@ -3,6 +3,8 @@ import App, { pack } from '../../../../models/app';
 import define from '../../define';
 
 export const meta = {
+	tags: ['account', 'app'],
+
 	desc: {
 		'ja-JP': '自分のアプリケーション一覧を取得します。',
 		'en-US': 'Get my apps'
diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts
index f8d6fdd2b..b4716ac3e 100644
--- a/src/server/api/endpoints/notes.ts
+++ b/src/server/api/endpoints/notes.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': '投稿を取得します。'
 	},
 
+	tags: ['notes'],
+
 	params: {
 		local: {
 			validator: $.optional.bool,
diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts
index 2f08de9e8..702d8dc43 100644
--- a/src/server/api/endpoints/notes/conversation.ts
+++ b/src/server/api/endpoints/notes/conversation.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Show conversation of a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: false,
 
 	params: {
@@ -34,6 +36,13 @@ export const meta = {
 		},
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index d24cd7b12..a4f262bda 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -25,6 +25,8 @@ export const meta = {
 		'ja-JP': '投稿します。'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	limit: {
@@ -175,7 +177,7 @@ export const meta = {
 		type: 'object',
 		props: {
 			createdNote: {
-				type: 'entity(Note)',
+				type: 'Note',
 				desc: {
 					'ja-JP': '作成した投稿'
 				}
diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts
index 8ca5a3bb0..399f9288d 100644
--- a/src/server/api/endpoints/notes/delete.ts
+++ b/src/server/api/endpoints/notes/delete.ts
@@ -15,6 +15,8 @@ export const meta = {
 		'en-US': 'Delete a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	kind: 'note-write',
diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts
index 26e3e34d5..9cde1a7dc 100644
--- a/src/server/api/endpoints/notes/favorites/create.ts
+++ b/src/server/api/endpoints/notes/favorites/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Favorite a note.'
 	},
 
+	tags: ['favorites'],
+
 	requireCredential: true,
 
 	kind: 'favorite-write',
diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts
index d79a3d88c..e2c787f3b 100644
--- a/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/src/server/api/endpoints/notes/favorites/delete.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Unfavorite a note.'
 	},
 
+	tags: ['favorites'],
+
 	requireCredential: true,
 
 	kind: 'favorite-write',
diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts
index 08cc677ab..0d2187bb0 100644
--- a/src/server/api/endpoints/notes/featured.ts
+++ b/src/server/api/endpoints/notes/featured.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'en-US': 'Get featured notes.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: false,
 
 	params: {
@@ -20,7 +22,14 @@ export const meta = {
 				'ja-JP': '最大数'
 			}
 		}
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
 };
 
 export default define(meta, async (ps, user) => {
diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts
index 8670f8edb..b62d8e25a 100644
--- a/src/server/api/endpoints/notes/global-timeline.ts
+++ b/src/server/api/endpoints/notes/global-timeline.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'ja-JP': 'グローバルタイムラインを取得します。'
 	},
 
+	tags: ['notes'],
+
 	params: {
 		withFiles: {
 			validator: $.optional.bool,
@@ -51,6 +53,13 @@ export const meta = {
 		},
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		gtlDisabled: {
 			message: 'Global timeline has been disabled.',
diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts
index f3050caec..153436b9b 100644
--- a/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'ja-JP': 'ハイブリッドタイムラインを取得します。'
 	},
 
+	tags: ['notes'],
+
 	params: {
 		limit: {
 			validator: $.optional.num.range(1, 100),
@@ -92,6 +94,13 @@ export const meta = {
 		},
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		stlDisabled: {
 			message: 'Social timeline has been disabled.',
diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts
index 44328ebed..06c6d7a53 100644
--- a/src/server/api/endpoints/notes/local-timeline.ts
+++ b/src/server/api/endpoints/notes/local-timeline.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'ja-JP': 'ローカルタイムラインを取得します。'
 	},
 
+	tags: ['notes'],
+
 	params: {
 		withFiles: {
 			validator: $.optional.bool,
@@ -67,6 +69,13 @@ export const meta = {
 		},
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		ltlDisabled: {
 			message: 'Local timeline has been disabled.',
diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts
index 090f184f7..91333174e 100644
--- a/src/server/api/endpoints/notes/mentions.ts
+++ b/src/server/api/endpoints/notes/mentions.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get mentions of myself.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	params: {
@@ -39,7 +41,14 @@ export const meta = {
 		visibility: {
 			validator: $.optional.str,
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
 };
 
 export default define(meta, async (ps, user) => {
diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts
index 61a1840b8..0a05e89c6 100644
--- a/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'en-US': 'Get recommended polls.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	params: {
diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts
index 00840579d..60007db13 100644
--- a/src/server/api/endpoints/notes/polls/vote.ts
+++ b/src/server/api/endpoints/notes/polls/vote.ts
@@ -18,6 +18,8 @@ export const meta = {
 		'en-US': 'Vote poll of a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	kind: 'vote-write',
diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts
index 74f22cc77..c67dd6d82 100644
--- a/src/server/api/endpoints/notes/reactions.ts
+++ b/src/server/api/endpoints/notes/reactions.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Show reactions of a note.'
 	},
 
+	tags: ['notes', 'reactions'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts
index 816c6a9c2..291e10bbd 100644
--- a/src/server/api/endpoints/notes/reactions/create.ts
+++ b/src/server/api/endpoints/notes/reactions/create.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'React to a note.'
 	},
 
+	tags: ['reactions', 'notes'],
+
 	requireCredential: true,
 
 	kind: 'reaction-write',
diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts
index 1afa6526f..2ccfb9329 100644
--- a/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/src/server/api/endpoints/notes/reactions/delete.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'en-US': 'Unreact to a note.'
 	},
 
+	tags: ['reactions', 'notes'],
+
 	requireCredential: true,
 
 	kind: 'reaction-write',
diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts
index b4c99b1f4..15dcf55dc 100644
--- a/src/server/api/endpoints/notes/renotes.ts
+++ b/src/server/api/endpoints/notes/renotes.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Show a renotes of a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: false,
 
 	params: {
@@ -39,6 +41,13 @@ export const meta = {
 		}
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts
index 080917f6f..c80fd7320 100644
--- a/src/server/api/endpoints/notes/replies.ts
+++ b/src/server/api/endpoints/notes/replies.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'en-US': 'Get replies of a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: false,
 
 	params: {
@@ -32,7 +34,14 @@ export const meta = {
 			validator: $.optional.num.min(0),
 			default: 0
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
 };
 
 export default define(meta, async (ps, user) => {
diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts
index 3eec2e318..edc8a1456 100644
--- a/src/server/api/endpoints/notes/search.ts
+++ b/src/server/api/endpoints/notes/search.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'en-US': 'Search notes.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: false,
 
 	params: {
@@ -30,6 +32,13 @@ export const meta = {
 		}
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		searchingNotAvailable: {
 			message: 'Searching not available.',
diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts
index a4fd1903e..0030e2e37 100644
--- a/src/server/api/endpoints/notes/search_by_tag.ts
+++ b/src/server/api/endpoints/notes/search_by_tag.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'ja-JP': '指定されたタグが付けられた投稿を取得します。'
 	},
 
+	tags: ['notes', 'hashtags'],
+
 	params: {
 		tag: {
 			validator: $.optional.str,
@@ -100,7 +102,14 @@ export const meta = {
 			validator: $.optional.num.range(1, 30),
 			default: 10
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
 };
 
 export default define(meta, async (ps, me) => {
diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts
index f4fd06c03..6d8dc73ff 100644
--- a/src/server/api/endpoints/notes/show.ts
+++ b/src/server/api/endpoints/notes/show.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: false,
 
 	params: {
@@ -26,6 +28,10 @@ export const meta = {
 		}
 	},
 
+	res: {
+		type: 'Note',
+	},
+
 	errors: {
 		noSuchNote: {
 			message: 'No such note.',
diff --git a/src/server/api/endpoints/notes/state.ts b/src/server/api/endpoints/notes/state.ts
index 07e35480f..494480284 100644
--- a/src/server/api/endpoints/notes/state.ts
+++ b/src/server/api/endpoints/notes/state.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'en-US': 'Get state of a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	params: {
diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts
index cb6900a10..9ec8070f7 100644
--- a/src/server/api/endpoints/notes/timeline.ts
+++ b/src/server/api/endpoints/notes/timeline.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get timeline of myself.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	params: {
@@ -91,7 +93,14 @@ export const meta = {
 				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		},
-	}
+	},
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
 };
 
 export default define(meta, async (ps, user) => {
diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts
index 1969b924a..45e96fbda 100644
--- a/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -14,6 +14,8 @@ export const meta = {
 		'en-US': 'Get timeline of a user list.'
 	},
 
+	tags: ['notes', 'lists'],
+
 	requireCredential: true,
 
 	params: {
@@ -102,6 +104,13 @@ export const meta = {
 		},
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
diff --git a/src/server/api/endpoints/notes/watching/create.ts b/src/server/api/endpoints/notes/watching/create.ts
index 7361237e8..2b2de1bd3 100644
--- a/src/server/api/endpoints/notes/watching/create.ts
+++ b/src/server/api/endpoints/notes/watching/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Watch a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/notes/watching/delete.ts b/src/server/api/endpoints/notes/watching/delete.ts
index 33fbef570..512db793e 100644
--- a/src/server/api/endpoints/notes/watching/delete.ts
+++ b/src/server/api/endpoints/notes/watching/delete.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Unwatch a note.'
 	},
 
+	tags: ['notes'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/notifications/mark_all_as_read.ts b/src/server/api/endpoints/notifications/mark_all_as_read.ts
index 91e10b46e..e5df64828 100644
--- a/src/server/api/endpoints/notifications/mark_all_as_read.ts
+++ b/src/server/api/endpoints/notifications/mark_all_as_read.ts
@@ -9,6 +9,8 @@ export const meta = {
 		'en-US': 'Mark all notifications as read.'
 	},
 
+	tags: ['notifications', 'account'],
+
 	requireCredential: true,
 
 	kind: 'notification-write'
diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts
index 43d7c4495..fa67ce25f 100644
--- a/src/server/api/endpoints/stats.ts
+++ b/src/server/api/endpoints/stats.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'en-US': 'Get the instance\'s statistics'
 	},
 
+	tags: ['meta'],
+
 	params: {
 	}
 };
diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts
index 870c13555..0b81b06ab 100644
--- a/src/server/api/endpoints/sw/register.ts
+++ b/src/server/api/endpoints/sw/register.ts
@@ -4,6 +4,8 @@ import define from '../../define';
 import fetchMeta from '../../../../misc/fetch-meta';
 
 export const meta = {
+	tags: ['account'],
+
 	requireCredential: true,
 
 	params: {
diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts
index a651bc4d5..1d098eb39 100644
--- a/src/server/api/endpoints/username/available.ts
+++ b/src/server/api/endpoints/username/available.ts
@@ -4,6 +4,8 @@ import { validateUsername } from '../../../../models/user';
 import define from '../../define';
 
 export const meta = {
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts
index f7e7a1001..7147a3541 100644
--- a/src/server/api/endpoints/users.ts
+++ b/src/server/api/endpoints/users.ts
@@ -7,6 +7,8 @@ import { getHideUserIds } from '../common/get-hide-users';
 const nonnull = { $ne: null as any };
 
 export const meta = {
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts
index cb5b04762..32c75fc8d 100644
--- a/src/server/api/endpoints/users/followers.ts
+++ b/src/server/api/endpoints/users/followers.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get followers of a user.'
 	},
 
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts
index 04abe5f4b..3ce103621 100644
--- a/src/server/api/endpoints/users/following.ts
+++ b/src/server/api/endpoints/users/following.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Get following users of a user.'
 	},
 
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
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 0b90e489c..6f9c746ac 100644
--- a/src/server/api/endpoints/users/get_frequently_replied_users.ts
+++ b/src/server/api/endpoints/users/get_frequently_replied_users.ts
@@ -9,6 +9,8 @@ import { ApiError } from '../../error';
 import { getUser } from '../../common/getters';
 
 export const meta = {
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts
index a3d91b6c2..00d2538c9 100644
--- a/src/server/api/endpoints/users/lists/create.ts
+++ b/src/server/api/endpoints/users/lists/create.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'en-US': 'Create a user list'
 	},
 
+	tags: ['lists'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/users/lists/delete.ts b/src/server/api/endpoints/users/lists/delete.ts
index 6aaf4701d..d8faaa928 100644
--- a/src/server/api/endpoints/users/lists/delete.ts
+++ b/src/server/api/endpoints/users/lists/delete.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'en-US': 'Delete a user list'
 	},
 
+	tags: ['lists'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts
index 5bd662878..632f4e7e5 100644
--- a/src/server/api/endpoints/users/lists/list.ts
+++ b/src/server/api/endpoints/users/lists/list.ts
@@ -6,6 +6,8 @@ export const meta = {
 		'ja-JP': '自分の作成したユーザーリスト一覧を取得します。'
 	},
 
+	tags: ['lists', 'account'],
+
 	requireCredential: true,
 
 	kind: 'account-read'
diff --git a/src/server/api/endpoints/users/lists/pull.ts b/src/server/api/endpoints/users/lists/pull.ts
index 7c438ff3d..0eee1975d 100644
--- a/src/server/api/endpoints/users/lists/pull.ts
+++ b/src/server/api/endpoints/users/lists/pull.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'en-US': 'Remove a user to a user list.'
 	},
 
+	tags: ['lists', 'users'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts
index c63bbb6ac..6b47f9dc7 100644
--- a/src/server/api/endpoints/users/lists/push.ts
+++ b/src/server/api/endpoints/users/lists/push.ts
@@ -16,6 +16,8 @@ export const meta = {
 		'en-US': 'Add a user to a user list.'
 	},
 
+	tags: ['lists', 'users'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts
index 3bd8ce657..2f3c8889a 100644
--- a/src/server/api/endpoints/users/lists/show.ts
+++ b/src/server/api/endpoints/users/lists/show.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'en-US': 'Show a user list.'
 	},
 
+	tags: ['lists', 'account'],
+
 	requireCredential: true,
 
 	kind: 'account-read',
diff --git a/src/server/api/endpoints/users/lists/update.ts b/src/server/api/endpoints/users/lists/update.ts
index 842ae189e..589769314 100644
--- a/src/server/api/endpoints/users/lists/update.ts
+++ b/src/server/api/endpoints/users/lists/update.ts
@@ -10,6 +10,8 @@ export const meta = {
 		'en-US': 'Update a user list'
 	},
 
+	tags: ['lists'],
+
 	requireCredential: true,
 
 	kind: 'account-write',
diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts
index 1c5882d5b..f8942fb2d 100644
--- a/src/server/api/endpoints/users/notes.ts
+++ b/src/server/api/endpoints/users/notes.ts
@@ -11,6 +11,8 @@ export const meta = {
 		'ja-JP': '指定したユーザーのタイムラインを取得します。'
 	},
 
+	tags: ['users', 'notes'],
+
 	params: {
 		userId: {
 			validator: $.type(ID),
@@ -124,6 +126,13 @@ export const meta = {
 		},
 	},
 
+	res: {
+		type: 'array',
+		items: {
+			type: 'Note',
+		},
+	},
+
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts
index dc37bc56b..2261ee7df 100644
--- a/src/server/api/endpoints/users/recommendation.ts
+++ b/src/server/api/endpoints/users/recommendation.ts
@@ -15,6 +15,8 @@ export const meta = {
 		'ja-JP': 'おすすめのユーザー一覧を取得します。'
 	},
 
+	tags: ['users'],
+
 	requireCredential: true,
 
 	kind: 'account-read',
diff --git a/src/server/api/endpoints/users/relation.ts b/src/server/api/endpoints/users/relation.ts
index 5b7b4d8e4..f4121aa0d 100644
--- a/src/server/api/endpoints/users/relation.ts
+++ b/src/server/api/endpoints/users/relation.ts
@@ -8,11 +8,13 @@ export const meta = {
 		'ja-JP': 'ユーザー間のリレーションを取得します。'
 	},
 
+	tags: ['users'],
+
 	requireCredential: true,
 
 	params: {
 		userId: {
-			validator: $.or($.type(ID), $.arr($.type(ID)).unique()),
+			validator: $.either($.type(ID), $.arr($.type(ID)).unique()),
 			transform: (v: any): ObjectId | ObjectId[] => Array.isArray(v) ? v.map(x => transform(x)) : transform(v),
 			desc: {
 				'ja-JP': 'ユーザーID (配列でも可)'
diff --git a/src/server/api/endpoints/users/report-abuse.ts b/src/server/api/endpoints/users/report-abuse.ts
index 36f797f5e..0f23f8f0c 100644
--- a/src/server/api/endpoints/users/report-abuse.ts
+++ b/src/server/api/endpoints/users/report-abuse.ts
@@ -12,6 +12,8 @@ export const meta = {
 		'ja-JP': '指定したユーザーを迷惑なユーザーであると報告します。'
 	},
 
+	tags: ['users'],
+
 	requireCredential: true,
 
 	params: {
diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts
index 6fd390f28..a6b50b687 100644
--- a/src/server/api/endpoints/users/search.ts
+++ b/src/server/api/endpoints/users/search.ts
@@ -8,6 +8,8 @@ export const meta = {
 		'ja-JP': 'ユーザーを検索します。'
 	},
 
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts
index 4f870b8b9..4e59945eb 100644
--- a/src/server/api/endpoints/users/show.ts
+++ b/src/server/api/endpoints/users/show.ts
@@ -13,6 +13,8 @@ export const meta = {
 		'ja-JP': '指定したユーザーの情報を取得します。'
 	},
 
+	tags: ['users'],
+
 	requireCredential: false,
 
 	params: {
@@ -42,6 +44,10 @@ export const meta = {
 		}
 	},
 
+	res: {
+		type: 'User',
+	},
+
 	errors: {
 		failedToResolveRemoteUser: {
 			message: 'Failed to resolve remote user.',
diff --git a/src/server/api/error.ts b/src/server/api/error.ts
index 6b5c44cac..ca441d581 100644
--- a/src/server/api/error.ts
+++ b/src/server/api/error.ts
@@ -3,14 +3,16 @@ export class ApiError extends Error {
 	public code: string;
 	public id: string;
 	public kind: string;
+	public httpStatusCode?: number;
 	public info?: any;
 
-	constructor(e?: { message: string, code: string, id: string, kind?: 'client' | 'server' }, info?: any) {
+	constructor(e?: { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number }, info?: any) {
 		if (e == null) e = {
 			message: 'Internal error occurred. Please contact us if the error persists.',
 			code: 'INTERNAL_ERROR',
 			id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
-			kind: 'server'
+			kind: 'server',
+			httpStatusCode: 500
 		};
 
 		super(e.message);
@@ -18,6 +20,7 @@ export class ApiError extends Error {
 		this.code = e.code;
 		this.id = e.id;
 		this.kind = e.kind || 'client';
+		this.httpStatusCode = e.httpStatusCode;
 		this.info = info;
 	}
 }
diff --git a/src/server/api/gen-openapi-spec.ts b/src/server/api/gen-openapi-spec.ts
new file mode 100644
index 000000000..3b0669087
--- /dev/null
+++ b/src/server/api/gen-openapi-spec.ts
@@ -0,0 +1,507 @@
+import endpoints from './endpoints';
+import { Context } from 'cafy';
+import config from '../../config';
+
+const basicErrors = {
+	'400': {
+		'INVALID_PARAM': {
+			value: {
+				error: {
+					message: 'Invalid param.',
+					code: 'INVALID_PARAM',
+					id: '3d81ceae-475f-4600-b2a8-2bc116157532',
+				}
+			}
+		}
+	},
+	'401': {
+		'CREDENTIAL_REQUIRED': {
+			value: {
+				error: {
+					message: 'Credential required.',
+					code: 'CREDENTIAL_REQUIRED',
+					id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+				}
+			}
+		}
+	},
+	'403': {
+		'AUTHENTICATION_FAILED': {
+			value: {
+				error: {
+					message: 'Authentication failed. Please ensure your token is correct.',
+					code: 'AUTHENTICATION_FAILED',
+					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
+				}
+			}
+		}
+	},
+	'418': {
+		'I_AM_AI': {
+			value: {
+				error: {
+					message: 'You sent a request to Ai-chan, Misskey\'s showgirl, instead of the server.',
+					code: 'I_AM_AI',
+					id: '60c46cd1-f23a-46b1-bebe-5d2b73951a84',
+				}
+			}
+		}
+	},
+	'429': {
+		'RATE_LIMIT_EXCEEDED': {
+			value: {
+				error: {
+					message: 'Rate limit exceeded. Please try again later.',
+					code: 'RATE_LIMIT_EXCEEDED',
+					id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
+				}
+			}
+		}
+	},
+	'500': {
+		'INTERNAL_ERROR': {
+			value: {
+				error: {
+					message: 'Internal error occurred. Please contact us if the error persists.',
+					code: 'INTERNAL_ERROR',
+					id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
+				}
+			}
+		}
+	}
+};
+
+const schemas = {
+	Error: {
+		type: 'object',
+		properties: {
+			error: {
+				type: 'object',
+				description: 'An error object.',
+				properties: {
+					code: {
+						type: 'string',
+						description: 'An error code.',
+					},
+					message: {
+						type: 'string',
+						description: 'An error message.',
+					},
+					id: {
+						type: 'string',
+						format: 'uuid',
+						description: 'An error ID. This ID is static.',
+					}
+				},
+				required: ['code', 'id', 'message']
+			},
+		},
+		required: ['error']
+	},
+
+	User: {
+		type: 'object',
+		properties: {
+			id: {
+				type: 'string',
+				format: 'id',
+				description: 'The unique identifier for this User.'
+			},
+			username: {
+				type: 'string',
+				description: 'The screen name, handle, or alias that this user identifies themselves with.',
+				example: 'ai'
+			},
+			name: {
+				type: 'string',
+				nullable: true,
+				description: 'The name of the user, as they’ve defined it.',
+				example: '藍'
+			},
+			host: {
+				type: 'string',
+				nullable: true,
+				example: 'misskey.example.com'
+			},
+			description: {
+				type: 'string',
+				nullable: true,
+				description: 'The user-defined UTF-8 string describing their account.',
+				example: 'Hi masters, I am Ai!'
+			},
+			createdAt: {
+				type: 'string',
+				format: 'date-time',
+				description: 'The date that the user account was created on Misskey.'
+			},
+			followersCount: {
+				type: 'number',
+				description: 'The number of followers this account currently has.'
+			},
+			followingCount: {
+				type: 'number',
+				description: 'The number of users this account is following.'
+			},
+			notesCount: {
+				type: 'number',
+				description: 'The number of Notes (including renotes) issued by the user.'
+			},
+			isBot: {
+				type: 'boolean',
+				description: 'Whether this account is a bot.'
+			},
+			isCat: {
+				type: 'boolean',
+				description: 'Whether this account is a cat.'
+			},
+			isAdmin: {
+				type: 'boolean',
+				description: 'Whether this account is the admin.'
+			},
+			isVerified: {
+				type: 'boolean'
+			},
+			isLocked: {
+				type: 'boolean'
+			},
+		},
+		required: ['id', 'name', 'username', 'createdAt']
+	},
+
+	Note: {
+		type: 'object',
+		properties: {
+			id: {
+				type: 'string',
+				format: 'id',
+				description: 'The unique identifier for this Note.'
+			},
+			createdAt: {
+				type: 'string',
+				format: 'date-time',
+				description: 'The date that the Note was created on Misskey.'
+			},
+			text: {
+				type: 'string'
+			},
+			cw: {
+				type: 'string'
+			},
+			userId: {
+				type: 'string',
+				format: 'id',
+			},
+			user: {
+				$ref: '#/components/schemas/User'
+			},
+			replyId: {
+				type: 'string',
+				format: 'id',
+			},
+			renoteId: {
+				type: 'string',
+				format: 'id',
+			},
+			reply: {
+				$ref: '#/components/schemas/Note'
+			},
+			renote: {
+				$ref: '#/components/schemas/Note'
+			},
+			viaMobile: {
+				type: 'boolean'
+			},
+			visibility: {
+				type: 'string'
+			},
+		},
+		required: ['id', 'userId', 'createdAt']
+	},
+
+	DriveFile: {
+		type: 'object',
+		properties: {
+			id: {
+				type: 'string',
+				format: 'id',
+				description: 'The unique identifier for this Drive file.'
+			},
+			createdAt: {
+				type: 'string',
+				format: 'date-time',
+				description: 'The date that the Drive file was created on Misskey.'
+			},
+			name: {
+				type: 'string',
+				description: 'The file name with extension.',
+				example: 'lenna.jpg'
+			},
+			type: {
+				type: 'string',
+				description: 'The MIME type of this Drive file.',
+				example: 'image/jpeg'
+			},
+			md5: {
+				type: 'string',
+				format: 'md5',
+				description: 'The MD5 hash of this Drive file.',
+				example: '15eca7fba0480996e2245f5185bf39f2'
+			},
+			datasize: {
+				type: 'number',
+				description: 'The size of this Drive file. (bytes)',
+				example: 51469
+			},
+			folderId: {
+				type: 'string',
+				format: 'id',
+				nullable: true,
+				description: 'The parent folder ID of this Drive file.',
+			},
+			isSensitive: {
+				type: 'boolean',
+				description: 'Whether this Drive file is sensitive.',
+			},
+		},
+		required: ['id', 'createdAt', 'name', 'type', 'datasize', 'md5']
+	}
+};
+
+export function genOpenapiSpec(lang = 'ja-JP') {
+	const spec = {
+		openapi: '3.0.0',
+
+		info: {
+			version: 'v1',
+			title: 'Misskey API',
+			description: 'Misskey is a decentralized microblogging platform.',
+			'x-logo': { url: '/assets/api-doc.png' }
+		},
+
+		servers: [{
+			url: config.api_url
+		}],
+
+		paths: {} as any,
+
+		components: {
+			schemas: schemas,
+
+			securitySchemes: {
+				ApiKeyAuth: {
+					type: 'apiKey',
+					in: 'body',
+					name: 'i'
+				}
+			}
+		}
+	};
+
+	function genProps(props: { [key: string]: Context & { desc: any, default: any }; }) {
+		const properties = {} as any;
+
+		const kvs = Object.entries(props);
+
+		for (const kv of kvs) {
+			properties[kv[0]] = genProp(kv[1], kv[1].desc, kv[1].default);
+		}
+
+		return properties;
+	}
+
+	function genProp(param: Context, desc?: string, _default?: any): any {
+		const required = param.name === 'Object' ? (param as any).props ? Object.entries((param as any).props).filter(([k, v]: any) => !v.isOptional).map(([k, v]) => k) : [] : [];
+		return {
+			description: desc,
+			default: _default,
+			...(_default ? { default: _default } : {}),
+			type: param.name === 'ID' ? 'string' : param.name.toLowerCase(),
+			...(param.name === 'ID' ? { example: 'xxxxxxxxxxxxxxxxxxxxxxxx', format: 'id' } : {}),
+			nullable: param.isNullable,
+			...(param.name === 'String' ? {
+				...((param as any).enum ? { enum: (param as any).enum } : {}),
+				...((param as any).minLength ? { minLength: (param as any).minLength } : {}),
+				...((param as any).maxLength ? { maxLength: (param as any).maxLength } : {}),
+			} : {}),
+			...(param.name === 'Number' ? {
+				...((param as any).minimum ? { minimum: (param as any).minimum } : {}),
+				...((param as any).maximum ? { maximum: (param as any).maximum } : {}),
+			} : {}),
+			...(param.name === 'Object' ? {
+				...(required.length > 0 ? { required } : {}),
+				properties: (param as any).props ? genProps((param as any).props) : {}
+			} : {}),
+			...(param.name === 'Array' ? {
+				items: (param as any).ctx ? genProp((param as any).ctx) : {}
+			} : {})
+		};
+	}
+
+	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
+		const porops = {} as any;
+		const errors = {} as any;
+
+		if (endpoint.meta.errors) {
+			for (const e of Object.values(endpoint.meta.errors)) {
+				errors[e.code] = {
+					value: {
+						error: e
+					}
+				};
+			}
+		}
+
+		if (endpoint.meta.params) {
+			for (const kv of Object.entries(endpoint.meta.params)) {
+				if (kv[1].desc) (kv[1].validator as any).desc = kv[1].desc[lang];
+				if (kv[1].default) (kv[1].validator as any).default = kv[1].default;
+				porops[kv[0]] = kv[1].validator;
+			}
+		}
+
+		const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
+
+		const resSchema = endpoint.meta.res ? renderType(endpoint.meta.res) : {};
+
+		function renderType(x: any) {
+			const res = {} as any;
+
+			if (['User', 'Note', 'DriveFile'].includes(x.type)) {
+				res['$ref'] = `#/components/schemas/${x.type}`;
+			} else if (x.type === 'object') {
+				res['type'] = 'object';
+				if (x.props) {
+					const props = {} as any;
+					for (const kv of Object.entries(x.props)) {
+						props[kv[0]] = renderType(kv[1]);
+					}
+					res['properties'] = props;
+				}
+			} else if (x.type === 'array') {
+				res['type'] = 'array';
+				if (x.items) {
+					res['items'] = renderType(x.items);
+				}
+			} else {
+				res['type'] = x.type;
+			}
+
+			return res;
+		}
+
+		const info = {
+			summary: endpoint.name,
+			description: endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.',
+			...(endpoint.meta.tags ? {
+				tags: endpoint.meta.tags
+			} : {}),
+			...(endpoint.meta.requireCredential ? {
+				security: [{
+					ApiKeyAuth: []
+				}]
+			} : {}),
+			requestBody: {
+				required: true,
+				content: {
+					'application/json': {
+						schema: {
+							type: 'object',
+							...(required.length > 0 ? { required } : {}),
+							properties: endpoint.meta.params ? genProps(porops) : {}
+						}
+					}
+				}
+			},
+			responses: {
+				...(endpoint.meta.res ? {
+					'200': {
+						description: 'OK (with results)',
+						content: {
+							'application/json': {
+								schema: resSchema
+							}
+						}
+					}
+				} : {
+					'204': {
+						description: 'OK (without any results)',
+					}
+				}),
+				'400': {
+					description: 'Client error',
+					content: {
+						'application/json': {
+							schema: {
+								$ref: '#/components/schemas/Error'
+							},
+							examples: { ...errors, ...basicErrors['400'] }
+						}
+					}
+				},
+				'401': {
+					description: 'Authentication error',
+					content: {
+						'application/json': {
+							schema: {
+								$ref: '#/components/schemas/Error'
+							},
+							examples: basicErrors['401']
+						}
+					}
+				},
+				'403': {
+					description: 'Forbiddon error',
+					content: {
+						'application/json': {
+							schema: {
+								$ref: '#/components/schemas/Error'
+							},
+							examples: basicErrors['403']
+						}
+					}
+				},
+				'418': {
+					description: 'I\'m Ai',
+					content: {
+						'application/json': {
+							schema: {
+								$ref: '#/components/schemas/Error'
+							},
+							examples: basicErrors['418']
+						}
+					}
+				},
+				...(endpoint.meta.limit ? {
+					'429': {
+						description: 'To many requests',
+						content: {
+							'application/json': {
+								schema: {
+									$ref: '#/components/schemas/Error'
+								},
+								examples: basicErrors['429']
+							}
+						}
+					}
+				} : {}),
+				'500': {
+					description: 'Internal server error',
+					content: {
+						'application/json': {
+							schema: {
+								$ref: '#/components/schemas/Error'
+							},
+							examples: basicErrors['500']
+						}
+					}
+				},
+			}
+		};
+
+		spec.paths['/' + endpoint.name] = {
+			post: info
+		};
+	}
+
+	return spec;
+}
diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts
index c0e03f914..414aac02c 100644
--- a/src/server/web/docs.ts
+++ b/src/server/web/docs.ts
@@ -10,11 +10,9 @@ import ms = require('ms');
 import * as Router from 'koa-router';
 import * as send from 'koa-send';
 import * as glob from 'glob';
-import * as yaml from 'js-yaml';
 import config from '../../config';
 import { licenseHtml } from '../../misc/license';
 import { copyright } from '../../const.json';
-import endpoints from '../api/endpoints';
 import * as locales from '../../../locales';
 import * as nestedProperty from 'nested-property';
 
@@ -33,14 +31,6 @@ async function genVars(lang: string): Promise<{ [key: string]: any }> {
 
 	const cwd = path.resolve(__dirname + '/../../../') + '/';
 
-	vars['endpoints'] = endpoints;
-
-	const entities = glob.sync('src/docs/api/entities/**/*.yaml', { cwd });
-	vars['entities'] = entities.map(x => {
-		const _x = yaml.safeLoad(fs.readFileSync(cwd + x, 'utf-8'));
-		return _x.name;
-	});
-
 	const docs = glob.sync(`src/docs/**/*.${lang}.md`, { cwd });
 	vars['docs'] = {};
 	for (const x of docs) {
@@ -67,82 +57,6 @@ async function genVars(lang: string): Promise<{ [key: string]: any }> {
 	return vars;
 }
 
-// WIP type
-const parseParamDefinition = (key: string, x: any) => {
-	return Object.assign({
-		name: key,
-		type: x.validator.getType()
-	}, x);
-};
-
-const parsePropDefinition = (key: string, prop: any) => {
-	const id = prop.type.match(/^id\((.+?)\)|^id/);
-	const entity = prop.type.match(/^entity\((.+?)\)/);
-	const isObject = /^object/.test(prop.type);
-	const isDate = /^date/.test(prop.type);
-	const isArray = /\[\]$/.test(prop.type);
-	if (id) {
-		prop.kind = 'id';
-		prop.type = 'string';
-		prop.entity = id[1];
-		if (isArray) {
-			prop.type += '[]';
-		}
-	}
-	if (entity) {
-		prop.kind = 'entity';
-		prop.type = 'object';
-		prop.entity = entity[1];
-		if (isArray) {
-			prop.type += '[]';
-		}
-	}
-	if (isObject) {
-		prop.kind = 'object';
-		if (prop.props) {
-			prop.hasDef = true;
-		}
-	}
-	if (isDate) {
-		prop.kind = 'date';
-		prop.type = 'string';
-		if (isArray) {
-			prop.type += '[]';
-		}
-	}
-
-	if (prop.optional) {
-		prop.type += '?';
-	}
-
-	prop.name = key;
-
-	return prop;
-};
-
-const sortParams = (params: { name: string }[]) => {
-	return params;
-};
-
-const extractPropDefRef = (props: any[]) => {
-	let defs: any[] = [];
-
-	for (const [k, v] of Object.entries(props)) {
-		if (v.props) {
-			defs.push({
-				name: k,
-				props: sortParams(Object.entries(v.props).map(([k, v]) => parsePropDefinition(k, v)))
-			});
-
-			const childDefs = extractPropDefRef(v.props);
-
-			defs = defs.concat(childDefs);
-		}
-	}
-
-	return sortParams(defs);
-};
-
 const router = new Router();
 
 router.get('/assets/*', async ctx => {
@@ -152,49 +66,6 @@ router.get('/assets/*', async ctx => {
 	});
 });
 
-router.get('/*/api/endpoints/*', async ctx => {
-	const lang = getLang(ctx.params[0]);
-	const name = ctx.params[1];
-	const ep = endpoints.find(e => e.name === name);
-
-	const vars = {
-		id: `api/endpoints/${name}`,
-		title: name,
-		endpoint: ep.meta,
-		endpointUrl: {
-			host: config.api_url,
-			path: name
-		},
-		// @ts-ignore
-		params: ep.meta.params ? sortParams(Object.entries(ep.meta.params).map(([k, v]) => parseParamDefinition(k, v))) : null,
-		res: ep.meta.res,
-		resProps: ep.meta.res && ep.meta.res.props ? sortParams(Object.entries(ep.meta.res.props).map(([k, v]) => parsePropDefinition(k, v))) : null,
-		resDefs: null as any, //extractPropDefRef(Object.entries(ep.res.props).map(([k, v]) => parsePropDefinition(k, v)))
-		src: `https://github.com/syuilo/misskey/tree/master/src/server/api/endpoints/${name}.ts`
-	};
-
-	await ctx.render('../../../../src/docs/api/endpoints/view', Object.assign(await genVars(lang), vars));
-
-	ctx.set('Cache-Control', 'public, max-age=300');
-});
-
-router.get('/*/api/entities/*', async ctx => {
-	const lang = getLang(ctx.params[0]);
-	const entity = ctx.params[1];
-
-	const x = yaml.safeLoad(fs.readFileSync(path.resolve(`${__dirname}/../../../src/docs/api/entities/${entity}.yaml`), 'utf-8'));
-
-	await ctx.render('../../../../src/docs/api/entities/view', Object.assign(await genVars(lang), {
-		id: `api/entities/${entity}`,
-		name: x.name,
-		desc: x.desc,
-		props: sortParams(Object.entries(x.props).map(([k, v]) => parsePropDefinition(k, v))),
-		propDefs: extractPropDefRef(x.props)
-	}));
-
-	ctx.set('Cache-Control', 'public, max-age=300');
-});
-
 router.get('/*/*', async ctx => {
 	const lang = getLang(ctx.params[0]);
 	const doc = ctx.params[1];
diff --git a/src/server/web/index.ts b/src/server/web/index.ts
index 589e476d6..0270dde61 100644
--- a/src/server/web/index.ts
+++ b/src/server/web/index.ts
@@ -21,6 +21,7 @@ import getNoteSummary from '../../misc/get-note-summary';
 import fetchMeta from '../../misc/fetch-meta';
 import Emoji from '../../models/emoji';
 import * as pkg from '../../../package.json';
+import { genOpenapiSpec } from '../api/gen-openapi-spec';
 
 const client = `${__dirname}/../../client/`;
 
@@ -83,10 +84,19 @@ router.get('/manifest.json', async ctx => {
 
 // Docs
 router.use('/docs', docs.routes());
+router.get('/api-doc', async ctx => {
+	await send(ctx as any, '/assets/redoc.html', {
+		root: client
+	});
+});
 
 // URL preview endpoint
 router.get('/url', require('./url-preview'));
 
+router.get('/api.json', async ctx => {
+	ctx.body = genOpenapiSpec();
+});
+
 const getFeed = async (acct: string) => {
 	const { username, host } = parseAcct(acct);
 	const user = await User.findOne({