diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c6ad01a6de..c3933a417e 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -529,6 +529,10 @@ pluginInstallWarn: "信頼できないプラグインはインストールしな
 deck: "デッキ"
 undeck: "デッキ解除"
 useBlurEffectForModal: "モーダルにぼかし効果を使用"
+generateAccessToken: "アクセストークンの発行"
+permission: "権限"
+enableAll: "全て有効にする"
+disableAll: "全て無効にする"
 
 _theme:
   explore: "テーマを探す"
diff --git a/src/client/components/token-generate-window.vue b/src/client/components/token-generate-window.vue
new file mode 100644
index 0000000000..5486ae92e7
--- /dev/null
+++ b/src/client/components/token-generate-window.vue
@@ -0,0 +1,89 @@
+<template>
+<x-window ref="window" :width="400" :height="450" :no-padding="true" @closed="() => { $emit('closed'); destroyDom(); }" :with-ok-button="true" :ok-button-disabled="false" @ok="ok()" :can-close="false">
+	<template #header>{{ title || $t('generateAccessToken') }}</template>
+	<div class="ugkkpisj">
+		<div>
+			<mk-input v-model="name">{{ $t('name') }}</mk-input>
+		</div>
+		<div>
+			<div style="margin-bottom: 16px;"><b>{{ $t('permission') }}</b></div>
+			<mk-button inline @click="disableAll">{{ $t('disableAll') }}</mk-button>
+			<mk-button inline @click="enableAll">{{ $t('enableAll') }}</mk-button>
+			<mk-switch v-for="kind in kinds" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</mk-switch>
+		</div>
+	</div>
+</x-window>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import { kinds } from '../../misc/api-permissions';
+import XWindow from './window.vue';
+import MkInput from './ui/input.vue';
+import MkTextarea from './ui/textarea.vue';
+import MkSwitch from './ui/switch.vue';
+import MkButton from './ui/button.vue';
+
+export default Vue.extend({
+	components: {
+		XWindow,
+		MkInput,
+		MkTextarea,
+		MkSwitch,
+		MkButton,
+	},
+
+	props: {
+		title: {
+			type: String,
+			required: false,
+			default: null
+		}
+	},
+
+	data() {
+		return {
+			name: null,
+			permissions: {},
+			kinds
+		};
+	},
+
+	created() {
+		for (const kind of this.kinds) {
+			Vue.set(this.permissions, kind, false);
+		}
+	},
+
+	methods: {
+		ok() {
+			this.$emit('ok', {
+				name: this.name,
+				permissions: Object.keys(this.permissions).filter(p => this.permissions[p])
+			});
+			this.$refs.window.close();
+		},
+
+		disableAll() {
+			for (const p in this.permissions) {
+				this.permissions[p] = false;
+			}
+		},
+
+		enableAll() {
+			for (const p in this.permissions) {
+				this.permissions[p] = true;
+			}
+		}
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.ugkkpisj {
+	> div {
+		padding: 24px;
+		border-top: solid 1px var(--divider);
+	}
+}
+</style>
diff --git a/src/client/pages/my-settings/api.vue b/src/client/pages/my-settings/api.vue
index 79b459fb5e..44f099ea1d 100644
--- a/src/client/pages/my-settings/api.vue
+++ b/src/client/pages/my-settings/api.vue
@@ -2,9 +2,7 @@
 <section class="_card">
 	<div class="_title"><fa :icon="faKey"/> API</div>
 	<div class="_content">
-		<mk-input :value="$store.state.i.token" readonly>
-			<span>{{ $t('token') }}</span>
-		</mk-input>
+		<mk-button @click="generateToken">{{ $t('generateAccessToken') }}</mk-button>
 		<mk-button @click="regenerateToken"><fa :icon="faSyncAlt"/> {{ $t('regenerate') }}</mk-button>
 	</div>
 </section>
@@ -26,6 +24,22 @@ export default Vue.extend({
 		};
 	},
 	methods: {
+		async generateToken() {
+			this.$root.new(await import('../../components/token-generate-window.vue').then(m => m.default), {
+			}).$on('ok', async ({ name, permissions }) => {
+				const { token } = await this.$root.api('miauth/gen-token', {
+					session: null,
+					name: name,
+					permission: permissions,
+				});
+
+				this.$root.dialog({
+					type: 'success',
+					title: this.$t('token'),
+					text: token
+				});
+			});
+		},
 		regenerateToken() {
 			this.$root.dialog({
 				title: this.$t('password'),
diff --git a/src/server/api/kinds.ts b/src/misc/api-permissions.ts
similarity index 100%
rename from src/server/api/kinds.ts
rename to src/misc/api-permissions.ts
diff --git a/src/server/api/endpoints/miauth/gen-token.ts b/src/server/api/endpoints/miauth/gen-token.ts
index d8c74ec8d7..6476e79d77 100644
--- a/src/server/api/endpoints/miauth/gen-token.ts
+++ b/src/server/api/endpoints/miauth/gen-token.ts
@@ -13,7 +13,7 @@ export const meta = {
 
 	params: {
 		session: {
-			validator: $.str
+			validator: $.nullable.str
 		},
 
 		name: {
@@ -52,4 +52,8 @@ export default define(meta, async (ps, user) => {
 		iconUrl: ps.iconUrl,
 		permission: ps.permission,
 	});
+
+	return {
+		token: accessToken
+	};
 });
diff --git a/src/server/api/index.ts b/src/server/api/index.ts
index 93c6cfdac0..aec4a97379 100644
--- a/src/server/api/index.ts
+++ b/src/server/api/index.ts
@@ -78,7 +78,7 @@ router.post('/miauth/:session/check', async ctx => {
 		session: ctx.params.session
 	});
 
-	if (token && !token.fetched) {
+	if (token && token.session != null && !token.fetched) {
 		AccessTokens.update(token.id, {
 			fetched: true
 		});
diff --git a/src/server/api/openapi/description.ts b/src/server/api/openapi/description.ts
index 15831b3c9a..e0527dd1ae 100644
--- a/src/server/api/openapi/description.ts
+++ b/src/server/api/openapi/description.ts
@@ -1,6 +1,6 @@
 import endpoints from '../endpoints';
 import * as locale from '../../../../locales/';
-import { kinds as kindsList } from '../kinds';
+import { kinds as kindsList } from '../../../misc/api-permissions';
 
 export interface IKindInfo {
 	endpoints: string[];