diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 288ffd39f..fd668c03d 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -415,6 +415,7 @@ accountSettings: "アカウント設定"
 promotion: "プロモーション"
 promote: "プロモート"
 numberOfDays: "日数"
+hideThisNote: "このノートを非表示"
 
 _ago:
   unknown: "謎"
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index e6b522d8d..3a02753c6 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -9,8 +9,8 @@
 >
 	<x-sub v-for="note in conversation" :key="note.id" :note="note"/>
 	<x-sub :note="appearNote.reply" class="reply-to" v-if="appearNote.reply"/>
-	<div class="pinned" v-if="pinned"><fa :icon="faThumbtack"/> {{ $t('pinnedNote') }}</div>
-	<div class="pinned" v-if="appearNote._prInjectionId_"><fa :icon="faBullhorn"/> {{ $t('promotion') }}</div>
+	<div class="info" v-if="pinned"><fa :icon="faThumbtack"/> {{ $t('pinnedNote') }}</div>
+	<div class="info" v-if="appearNote._prInjectionId_"><fa :icon="faBullhorn"/> {{ $t('promotion') }}<button class="_textButton hide" @click="readPromo()">{{ $t('hideThisNote') }}</button></div>
 	<div class="renote" v-if="isRenote">
 		<mk-avatar class="avatar" :user="note.user"/>
 		<fa :icon="faRetweet"/>
@@ -264,6 +264,13 @@ export default Vue.extend({
 	},
 
 	methods: {
+		readPromo() {
+			(this as any).$root.api('promo/read', {
+				noteId: this.appearNote.id
+			});
+			this.hideThisNote = true;
+		},
+
 		capture(withHandler = false) {
 			if (this.$store.getters.isSignedIn) {
 				this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id });
@@ -744,7 +751,9 @@ export default Vue.extend({
 		border-radius: 0 0 var(--radius) var(--radius);
 	}
 
-	> .pinned {
+	> .info {
+		display: flex;
+		align-items: center;
 		padding: 16px 32px 8px 32px;
 		line-height: 24px;
 		font-size: 90%;
@@ -758,9 +767,14 @@ export default Vue.extend({
 		> [data-icon] {
 			margin-right: 4px;
 		}
+
+		> .hide {
+			margin-left: auto;
+			color: inherit;
+		}
 	}
 
-	> .pinned + .article {
+	> .info + .article {
 		padding-top: 8px;
 	}
 
diff --git a/src/server/api/endpoints/promo/read.ts b/src/server/api/endpoints/promo/read.ts
new file mode 100644
index 000000000..9ce253374
--- /dev/null
+++ b/src/server/api/endpoints/promo/read.ts
@@ -0,0 +1,50 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { getNote } from '../../common/getters';
+import { PromoReads } from '../../../../models';
+import { genId } from '../../../../misc/gen-id';
+
+export const meta = {
+	tags: ['notes'],
+
+	requireCredential: true as const,
+
+	params: {
+		noteId: {
+			validator: $.type(ID),
+		}
+	},
+
+	errors: {
+		noSuchNote: {
+			message: 'No such note.',
+			code: 'NO_SUCH_NOTE',
+			id: 'd785b897-fcd3-4fe9-8fc3-b85c26e6c932'
+		},
+	}
+};
+
+export default define(meta, async (ps, user) => {
+	const note = await getNote(ps.noteId).catch(e => {
+		if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+		throw e;
+	});
+
+	const exist = await PromoReads.findOne({
+		noteId: note.id,
+		userId: user.id
+	});
+
+	if (exist != null) {
+		return;
+	}
+
+	await PromoReads.save({
+		id: genId(),
+		createdAt: new Date(),
+		noteId: note.id,
+		userId: user.id
+	});
+});