diff --git a/src/common/remote/activitypub/context.ts b/src/common/remote/activitypub/context.ts
new file mode 100644
index 0000000000..b56f727ae7
--- /dev/null
+++ b/src/common/remote/activitypub/context.ts
@@ -0,0 +1,5 @@
+export default [
+	'https://www.w3.org/ns/activitystreams',
+	'https://w3id.org/security/v1',
+	{ Hashtag: 'as:Hashtag' }
+];
diff --git a/src/models/post.ts b/src/models/post.ts
index 6c853e4f81..64c46c2635 100644
--- a/src/models/post.ts
+++ b/src/models/post.ts
@@ -47,6 +47,7 @@ export type IPost = {
 		heading: number;
 		speed: number;
 	};
+	tags: string[];
 };
 
 /**
diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts
index 07ff407a76..6618c291f7 100644
--- a/src/server/activitypub/index.ts
+++ b/src/server/activitypub/index.ts
@@ -1,11 +1,13 @@
 import * as express from 'express';
 
+import post from './post';
 import user from './user';
 import inbox from './inbox';
 
 const app = express();
 app.disable('x-powered-by');
 
+app.use(post);
 app.use(user);
 app.use(inbox);
 
diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts
new file mode 100644
index 0000000000..4fb3a1319b
--- /dev/null
+++ b/src/server/activitypub/post.ts
@@ -0,0 +1,85 @@
+import * as express from 'express';
+import context from '../../common/remote/activitypub/context';
+import parseAcct from '../../common/user/parse-acct';
+import config from '../../conf';
+import DriveFile from '../../models/drive-file';
+import Post from '../../models/post';
+import User from '../../models/user';
+
+const app = express();
+app.disable('x-powered-by');
+
+app.get('/@:user/:post', async (req, res, next) => {
+	const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']);
+	if (!(['application/activity+json', 'application/ld+json'] as Array<any>).includes(accepted)) {
+		return next();
+	}
+
+	const { username, host } = parseAcct(req.params.user);
+	if (host !== null) {
+		return res.sendStatus(422);
+	}
+
+	const user = await User.findOne({
+		usernameLower: username.toLowerCase(),
+		host: null
+	});
+	if (user === null) {
+		return res.sendStatus(404);
+	}
+
+	const post = await Post.findOne({
+		_id: req.params.post,
+		userId: user._id
+	});
+	if (post === null) {
+		return res.sendStatus(404);
+	}
+
+	const asyncFiles = DriveFile.find({ _id: { $in: post.mediaIds } });
+	let inReplyTo;
+
+	if (post.replyId) {
+		const inReplyToPost = await Post.findOne({
+			_id: post.replyId,
+		});
+
+		if (inReplyToPost !== null) {
+			const inReplyToUser = await User.findOne({
+				_id: post.userId,
+			});
+
+			if (inReplyToUser !== null) {
+				inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`;
+			}
+		}
+	} else {
+		inReplyTo = null;
+	}
+
+	const attributedTo = `${config.url}/@${user.username}`;
+
+	res.json({
+		'@context': context,
+		id: `${attributedTo}/${post._id}`,
+		type: 'Note',
+		attributedTo,
+		content: post.textHtml,
+		published: post.createdAt.toISOString(),
+		to: 'https://www.w3.org/ns/activitystreams#Public',
+		cc: `${attributedTo}/followers`,
+		inReplyTo,
+		attachment: (await asyncFiles).map(({ _id, contentType }) => ({
+			type: 'Document',
+			mediaType: contentType,
+			url: `${config.drive_url}/${_id}`
+		})),
+		tag: post.tags.map(tag => ({
+			type: 'Hashtag',
+			href: `${config.url}/search?q=#${encodeURIComponent(tag)}`,
+			name: '#' + tag
+		}))
+	});
+});
+
+export default app;
diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts
index 488de93a92..ef365c2078 100644
--- a/src/server/activitypub/user.ts
+++ b/src/server/activitypub/user.ts
@@ -1,6 +1,7 @@
 import * as express from 'express';
 import config from '../../conf';
 import { extractPublic } from '../../crypto_key';
+import context from '../../common/remote/activitypub/context';
 import parseAcct from '../../common/user/parse-acct';
 import User, { ILocalAccount } from '../../models/user';
 
@@ -33,10 +34,7 @@ app.get('/@:user', async (req, res, next) => {
 	}
 
 	res.json({
-		'@context': [
-			'https://www.w3.org/ns/activitystreams',
-			'https://w3id.org/security/v1'
-		],
+		'@context': context,
 		type: 'Person',
 		id,
 		inbox: `${id}/inbox`,