diff --git a/API.md b/API.md
index a6564b6..3da187e 100644
--- a/API.md
+++ b/API.md
@@ -34,8 +34,8 @@
         - [Getting post](#getting-post)
         - [Deleting post](#deleting-post)
         - [Rating post](#rating-post)
-        - ~~Adding post to favorites~~
-        - ~~Removing post from favorites~~
+        - [Adding post to favorites](#adding-post-to-favorites)
+        - [Removing post from favorites](#removing-post-from-favorites)
         - [Getting featured post](#getting-featured-post)
         - [Featuring post](#featuring-post)
     - Comments
@@ -590,6 +590,44 @@ data.
     and 1.
 
 
+## Adding post to favorites
+- **Request**
+
+    `POST /post/<id>/favorite`
+
+- **Output**
+
+    A [detailed post resource](#detailed-post).
+
+- **Errors**
+
+    - post does not exist
+    - privileges are too low
+
+- **Description**
+
+    Marks the post as favorite for authenticated user.
+
+
+## Removing post from favorites
+- **Request**
+
+    `DELETE /post/<id>/favorite`
+
+- **Output**
+
+    A [detailed post resource](#detailed-post).
+
+- **Errors**
+
+    - post does not exist
+    - privileges are too low
+
+- **Description**
+
+    Unmarks the post as favorite for authenticated user.
+
+
 ## Getting featured post
 - **Request**
 
diff --git a/config.yaml.dist b/config.yaml.dist
index 016fcd1..b35205b 100644
--- a/config.yaml.dist
+++ b/config.yaml.dist
@@ -94,6 +94,7 @@ privileges:
     'posts:feature':                mod
     'posts:delete':                 mod
     'posts:score':                  regular_user
+    'posts:favorite':               regular_user
 
     'tags:create':                  regular_user
     'tags:edit:names':              power_user
diff --git a/server/szurubooru/api/__init__.py b/server/szurubooru/api/__init__.py
index 9479520..fb7324b 100644
--- a/server/szurubooru/api/__init__.py
+++ b/server/szurubooru/api/__init__.py
@@ -17,7 +17,8 @@ from szurubooru.api.comment_api import (
 from szurubooru.api.post_api import (
     PostDetailApi,
     PostFeatureApi,
-    PostScoreApi)
+    PostScoreApi,
+    PostFavoriteApi)
 from szurubooru.api.snapshot_api import SnapshotListApi
 from szurubooru.api.info_api import InfoApi
 from szurubooru.api.context import Context, Request
diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py
index 0c08e82..df1c32e 100644
--- a/server/szurubooru/api/post_api.py
+++ b/server/szurubooru/api/post_api.py
@@ -1,5 +1,5 @@
 from szurubooru.api.base_api import BaseApi
-from szurubooru.func import auth, tags, posts, snapshots, scores
+from szurubooru.func import auth, tags, posts, snapshots, favorites, scores
 
 class PostDetailApi(BaseApi):
     def get(self, ctx, post_id):
@@ -51,3 +51,18 @@ class PostScoreApi(BaseApi):
         scores.delete_score(post, ctx.user)
         ctx.session.commit()
         return posts.serialize_post_with_details(post, ctx.user)
+
+class PostFavoriteApi(BaseApi):
+    def post(self, ctx, post_id):
+        auth.verify_privilege(ctx.user, 'posts:favorite')
+        post = posts.get_post_by_id(post_id)
+        favorites.set_favorite(post, ctx.user)
+        ctx.session.commit()
+        return posts.serialize_post_with_details(post, ctx.user)
+
+    def delete(self, ctx, post_id):
+        auth.verify_privilege(ctx.user, 'posts:favorite')
+        post = posts.get_post_by_id(post_id)
+        favorites.unset_favorite(post, ctx.user)
+        ctx.session.commit()
+        return posts.serialize_post_with_details(post, ctx.user)
diff --git a/server/szurubooru/app.py b/server/szurubooru/app.py
index 6a459f6..85ccda8 100644
--- a/server/szurubooru/app.py
+++ b/server/szurubooru/app.py
@@ -67,6 +67,7 @@ def create_app():
 
     app.add_route('/post/{post_id}', api.PostDetailApi())
     app.add_route('/post/{post_id}/score', api.PostScoreApi())
+    app.add_route('/post/{post_id}/favorite', api.PostFavoriteApi())
 
     app.add_route('/comments/', api.CommentListApi())
     app.add_route('/comment/{comment_id}', api.CommentDetailApi())
diff --git a/server/szurubooru/db/post.py b/server/szurubooru/db/post.py
index 4d8deb0..8d6b496 100644
--- a/server/szurubooru/db/post.py
+++ b/server/szurubooru/db/post.py
@@ -128,6 +128,16 @@ class Post(Base):
             .filter(PostScore.post_id == self.post_id) \
             .one()[0] or 0
 
+    favorite_count = column_property(
+        select([func.count(PostFavorite.post_id)]) \
+        .where(PostFavorite.post_id == post_id) \
+        .correlate_except(PostFavorite))
+
+    last_favorite_time = column_property(
+        select([func.max(PostFavorite.time)]) \
+        .where(PostFavorite.post_id == post_id) \
+        .correlate_except(PostFavorite))
+
     feature_count = column_property(
         select([func.count(PostFeature.post_id)]) \
         .where(PostFeature.post_id == post_id) \
@@ -139,11 +149,8 @@ class Post(Base):
         .correlate_except(PostFeature))
 
     # TODO: wire these
-    #favorite_count = Column('auto_fav_count', Integer, nullable=False, default=0)
     #comment_count = Column('auto_comment_count', Integer, nullable=False, default=0)
     #note_count = Column('auto_note_count', Integer, nullable=False, default=0)
-    #last_fav_time = Column(
-    #    'auto_fav_time', Integer, nullable=False, default=0)
     #last_comment_edit_time = Column(
     #    'auto_comment_creation_time', Integer, nullable=False, default=0)
     #last_comment_creation_time = Column(
diff --git a/server/szurubooru/func/favorites.py b/server/szurubooru/func/favorites.py
new file mode 100644
index 0000000..9c3dda7
--- /dev/null
+++ b/server/szurubooru/func/favorites.py
@@ -0,0 +1,36 @@
+import datetime
+from szurubooru import db
+from szurubooru.func import util
+
+def _get_table_info(entity):
+    resource_type, _, _ = util.get_resource_info(entity)
+    if resource_type == 'post':
+        return db.PostFavorite, lambda table: table.post_id
+    else:
+        assert False
+
+def _get_fav_entity(entity, user):
+    table, get_column = _get_table_info(entity)
+    return db.session \
+        .query(table) \
+        .filter(get_column(table) == get_column(entity)) \
+        .filter(table.user_id == user.user_id) \
+        .one_or_none()
+
+def has_favorited(entity, user):
+    return _get_fav_entity(entity, user) is not None
+
+def unset_favorite(entity, user):
+    fav_entity = _get_fav_entity(entity, user)
+    if fav_entity:
+        db.session.delete(fav_entity)
+
+def set_favorite(entity, user):
+    fav_entity = _get_fav_entity(entity, user)
+    if not fav_entity:
+        table, get_column = _get_table_info(entity)
+        fav_entity = table()
+        setattr(fav_entity, get_column(table).name, get_column(entity))
+        fav_entity.user = user
+        fav_entity.time = datetime.datetime.now()
+        db.session.add(fav_entity)
diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py
index 3a3f7a3..7e1524a 100644
--- a/server/szurubooru/func/posts.py
+++ b/server/szurubooru/func/posts.py
@@ -32,7 +32,7 @@ def serialize_post(post, authenticated_user):
         'score': post.score,
         'featureCount': post.feature_count,
         'lastFeatureTime': post.last_feature_time,
-        'favoritedBy': [users.serialize_user(rel, authenticated_user) \
+        'favoritedBy': [users.serialize_user(rel.user, authenticated_user) \
             for rel in post.favorited_by],
     }
 
diff --git a/server/szurubooru/tests/api/test_post_favoriting.py b/server/szurubooru/tests/api/test_post_favoriting.py
new file mode 100644
index 0000000..53ed660
--- /dev/null
+++ b/server/szurubooru/tests/api/test_post_favoriting.py
@@ -0,0 +1,124 @@
+import datetime
+import pytest
+from szurubooru import api, db, errors
+from szurubooru.func import util, posts
+
+@pytest.fixture
+def test_ctx(config_injector, context_factory, user_factory, post_factory):
+    config_injector({
+        'ranks': ['anonymous', 'regular_user'],
+        'rank_names': {'anonymous': 'Peasant', 'regular_user': 'Lord'},
+        'privileges': {'posts:favorite': 'regular_user'},
+        'thumbnails': {'avatar_width': 200},
+    })
+    db.session.flush()
+    ret = util.dotdict()
+    ret.context_factory = context_factory
+    ret.user_factory = user_factory
+    ret.post_factory = post_factory
+    ret.api = api.PostFavoriteApi()
+    return ret
+
+def test_simple_rating(test_ctx, fake_datetime):
+    post = test_ctx.post_factory()
+    db.session.add(post)
+    db.session.commit()
+    with fake_datetime('1997-12-01'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=test_ctx.user_factory()),
+            post.post_id)
+    assert 'post' in result
+    assert 'id' in result['post']
+    post = db.session.query(db.Post).one()
+    assert db.session.query(db.PostFavorite).count() == 1
+    assert post is not None
+    assert post.favorite_count == 1
+
+def test_removing_from_favorites(test_ctx, fake_datetime):
+    user = test_ctx.user_factory()
+    post = test_ctx.post_factory()
+    db.session.add(post)
+    db.session.commit()
+    with fake_datetime('1997-12-01'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    with fake_datetime('1997-12-02'):
+        result = test_ctx.api.delete(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    post = db.session.query(db.Post).one()
+    assert db.session.query(db.PostFavorite).count() == 0
+    assert post.favorite_count == 0
+
+def test_favoriting_twice(test_ctx, fake_datetime):
+    user = test_ctx.user_factory()
+    post = test_ctx.post_factory()
+    db.session.add(post)
+    db.session.commit()
+    with fake_datetime('1997-12-01'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    with fake_datetime('1997-12-02'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    post = db.session.query(db.Post).one()
+    assert db.session.query(db.PostFavorite).count() == 1
+    assert post.favorite_count == 1
+
+def test_removing_twice(test_ctx, fake_datetime):
+    user = test_ctx.user_factory()
+    post = test_ctx.post_factory()
+    db.session.add(post)
+    db.session.commit()
+    with fake_datetime('1997-12-01'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    with fake_datetime('1997-12-02'):
+        result = test_ctx.api.delete(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    with fake_datetime('1997-12-02'):
+        result = test_ctx.api.delete(
+            test_ctx.context_factory(user=user),
+            post.post_id)
+    post = db.session.query(db.Post).one()
+    assert db.session.query(db.PostFavorite).count() == 0
+    assert post.favorite_count == 0
+
+def test_favorites_from_multiple_users(test_ctx, fake_datetime):
+    user1 = test_ctx.user_factory()
+    user2 = test_ctx.user_factory()
+    post = test_ctx.post_factory()
+    db.session.add_all([user1, user2, post])
+    db.session.commit()
+    with fake_datetime('1997-12-01'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=user1),
+            post.post_id)
+    with fake_datetime('1997-12-02'):
+        result = test_ctx.api.post(
+            test_ctx.context_factory(user=user2),
+            post.post_id)
+    post = db.session.query(db.Post).one()
+    assert db.session.query(db.PostFavorite).count() == 2
+    assert post.favorite_count == 2
+    assert post.last_favorite_time == datetime.datetime(1997, 12, 2)
+
+def test_trying_to_update_non_existing(test_ctx):
+    with pytest.raises(posts.PostNotFoundError):
+        test_ctx.api.post(
+            test_ctx.context_factory(user=test_ctx.user_factory()), 5)
+
+def test_trying_to_rate_without_privileges(test_ctx):
+    post = test_ctx.post_factory()
+    db.session.add(post)
+    db.session.commit()
+    with pytest.raises(errors.AuthError):
+        test_ctx.api.post(
+            test_ctx.context_factory(
+                user=test_ctx.user_factory(rank='anonymous')),
+            post.post_id)
diff --git a/server/szurubooru/tests/api/test_post_rating.py b/server/szurubooru/tests/api/test_post_rating.py
index 26e01db..65486e5 100644
--- a/server/szurubooru/tests/api/test_post_rating.py
+++ b/server/szurubooru/tests/api/test_post_rating.py
@@ -1,4 +1,3 @@
-import datetime
 import pytest
 from szurubooru import api, db, errors
 from szurubooru.func import util, posts, scores