diff --git a/API.md b/API.md
index 6407ea12..71fd66b0 100644
--- a/API.md
+++ b/API.md
@@ -222,10 +222,10 @@ data.
 
     ```json5
     {
-        "names":        [<name1>, <name2>, ...],
-        "category":     <category>,
-        "implications": [<name1>, <name2>, ...],
-        "suggestions":  [<name1>, <name2>, ...]
+        "names":        [<name1>, <name2>, ...],    // optional
+        "category":     <category>,                 // optional
+        "implications": [<name1>, <name2>, ...],    // optional
+        "suggestions":  [<name1>, <name2>, ...]     // optional
     }
     ```
 
@@ -384,10 +384,16 @@ data.
     {
         "name": <user-name>,
         "password": <user-password>,
-        "email": <email>
+        "email": <email>,               // optional
+        "rank": <rank>,                 // optional
+        "avatarStyle": <avatar-style>   // optional
     }
     ```
 
+- **Files**
+
+    - `avatar` - the content of the new avatar (optional).
+
 - **Output**
 
     ```json5
@@ -399,17 +405,23 @@ data.
 
 - **Errors**
 
-    - such user already exists (names are case insensitive)
-    - either user name, password or email are invalid
+    - a user with such name already exists (names are case insensitive)
+    - either user name, password, email or rank are invalid
+    - the user is trying to update their or someone else's rank to higher than
+      their own
+    - avatar is missing for manual avatar style
     - privileges are too low
 
 - **Description**
 
     Creates a new user using specified parameters. Names and passwords must
     match `user_name_regex` and `password_regex` from server's configuration,
-    respectively. Email address is optional. If the user happens to be the
-    first user ever created, they're granted highest available rank, becoming
-    an administrator. Subsequent users will be given the rank indicated by
+    respectively. Email address, rank and avatar fields are optional. Avatar
+    style can be either `gravatar` or `manual`. `manual` avatar style requires
+    client to pass also `avatar` file - see [file uploads](#file-uploads) for
+    details. If the rank is empty and the user happens to be the first user
+    ever created, they're granted highest available rank, becoming an
+    administrator, whereas subsequent users will be given the rank indicated by
     `default_rank` in the server's configuration.
 
 
@@ -422,17 +434,17 @@ data.
 
     ```json5
     {
-        "name": <user-name>,
-        "password": <user-password>,
-        "email": <email>,
-        "rank": <rank>,
-        "avatarStyle": <avatar-style>
+        "name": <user-name>,            // optional
+        "password": <user-password>,    // optional
+        "email": <email>,               // optional
+        "rank": <rank>,                 // optional
+        "avatarStyle": <avatar-style>   // optional
     }
     ```
 
 - **Files**
 
-    - `avatar` - the content of the new avatar.
+    - `avatar` - the content of the new avatar (optional).
 
 - **Output**
 
@@ -446,12 +458,12 @@ data.
 - **Errors**
 
     - the user does not exist
-    - the user with new name already exists (names are case insensitive)
+    - a user with new name already exists (names are case insensitive)
     - either user name, password, email or rank are invalid
     - the user is trying to update their or someone else's rank to higher than
       their own
-    - privileges are too low
     - avatar is missing for manual avatar style
+    - privileges are too low
 
 - **Description**
 
diff --git a/server/szurubooru/api/user_api.py b/server/szurubooru/api/user_api.py
index 24941553..deb7bd9e 100644
--- a/server/szurubooru/api/user_api.py
+++ b/server/szurubooru/api/user_api.py
@@ -54,9 +54,20 @@ class UserListApi(BaseApi):
 
         name = ctx.get_param_as_string('name', required=True)
         password = ctx.get_param_as_string('password', required=True)
-        email = ctx.get_param_as_string('email', required=True)
+        email = ctx.get_param_as_string('email', required=False, default='')
 
         user = users.create_user(ctx.session, name, password, email, ctx.user)
+
+        if ctx.has_param('rank'):
+            users.update_rank(
+                ctx.session, user, ctx.get_param_as_string('rank'), ctx.user)
+
+        if ctx.has_param('avatarStyle'):
+            users.update_avatar(
+                user,
+                ctx.get_param_as_string('avatarStyle'),
+                ctx.get_file('avatar'))
+
         ctx.session.add(user)
         ctx.session.commit()
         return {'user': _serialize_user(ctx.user, user)}
@@ -94,7 +105,8 @@ class UserDetailApi(BaseApi):
 
         if ctx.has_param('rank'):
             auth.verify_privilege(ctx.user, 'users:edit:%s:rank' % infix)
-            users.update_rank(user, ctx.get_param_as_string('rank'), ctx.user)
+            users.update_rank(
+                ctx.session, user, ctx.get_param_as_string('rank'), ctx.user)
 
         if ctx.has_param('avatarStyle'):
             auth.verify_privilege(ctx.user, 'users:edit:%s:avatar' % infix)
diff --git a/server/szurubooru/tests/api/test_user_creating.py b/server/szurubooru/tests/api/test_user_creating.py
index 4eecd649..85372be9 100644
--- a/server/szurubooru/tests/api/test_user_creating.py
+++ b/server/szurubooru/tests/api/test_user_creating.py
@@ -1,8 +1,13 @@
 import datetime
 import pytest
-from szurubooru import api, db, errors
+from szurubooru import api, config, db, errors
 from szurubooru.util import auth, misc, users
 
+EMPTY_PIXEL = \
+    b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00' \
+    b'\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x01\x00\x2c\x00\x00\x00\x00' \
+    b'\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b'
+
 def get_user(session, name):
     return session.query(db.User).filter_by(name=name).first()
 
@@ -14,7 +19,7 @@ def test_ctx(
         'user_name_regex': '.{3,}',
         'password_regex': '.{3,}',
         'default_rank': 'regular_user',
-        'thumbnails': {'avatar_width': 200},
+        'thumbnails': {'avatar_width': 200, 'avatar_height': 200},
         'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
         'rank_names': {},
         'privileges': {'users:create': 'anonymous'},
@@ -63,7 +68,7 @@ def test_first_user_becomes_admin_others_not(test_ctx):
                 'email': 'asd@asd.asd',
                 'password': 'oks',
             },
-            user=test_ctx.user_factory(rank='regular_user')))
+            user=test_ctx.user_factory(rank='anonymous')))
     result2 = test_ctx.api.post(
         test_ctx.context_factory(
             input={
@@ -71,7 +76,7 @@ def test_first_user_becomes_admin_others_not(test_ctx):
                 'email': 'asd@asd.asd',
                 'password': 'sok',
             },
-            user=test_ctx.user_factory(rank='regular_user')))
+            user=test_ctx.user_factory(rank='anonymous')))
     assert result1['user']['rank'] == 'admin'
     assert result2['user']['rank'] == 'regular_user'
     first_user = get_user(test_ctx.session, 'chewie1')
@@ -79,6 +84,18 @@ def test_first_user_becomes_admin_others_not(test_ctx):
     assert first_user.rank == 'admin'
     assert other_user.rank == 'regular_user'
 
+def test_first_user_does_not_become_admin_if_they_dont_wish_so(test_ctx):
+    result = test_ctx.api.post(
+        test_ctx.context_factory(
+            input={
+                'name': 'chewie1',
+                'email': 'asd@asd.asd',
+                'password': 'oks',
+                'rank': 'regular_user',
+            },
+            user=test_ctx.user_factory(rank='anonymous')))
+    assert result['user']['rank'] == 'regular_user'
+
 def test_creating_user_that_already_exists(test_ctx):
     test_ctx.api.post(
         test_ctx.context_factory(
@@ -107,8 +124,8 @@ def test_creating_user_that_already_exists(test_ctx):
                 },
                 user=test_ctx.user_factory(rank='regular_user')))
 
-@pytest.mark.parametrize('field', ['name', 'email', 'password'])
-def test_missing_field(test_ctx, field):
+@pytest.mark.parametrize('field', ['name', 'password'])
+def test_missing_mandatory_field(test_ctx, field):
     input = {
         'name': 'chewie',
         'email': 'asd@asd.asd',
@@ -121,6 +138,24 @@ def test_missing_field(test_ctx, field):
                 input=input,
                 user=test_ctx.user_factory(rank='regular_user')))
 
+@pytest.mark.parametrize('field', ['rank', 'email', 'avatarStyle'])
+def test_missing_optional_field(test_ctx, tmpdir, field):
+    config.config['data_dir'] = str(tmpdir.mkdir('data'))
+    config.config['data_url'] = 'http://example.com/data/'
+    input = {
+        'name': 'chewie',
+        'email': 'asd@asd.asd',
+        'password': 'oks',
+        'rank': 'mod',
+        'avatarStyle': 'manual',
+    }
+    del input[field]
+    test_ctx.api.post(
+        test_ctx.context_factory(
+            input=input,
+            files={'avatar': EMPTY_PIXEL},
+            user=test_ctx.user_factory(rank='mod')))
+
 @pytest.mark.parametrize('input', [
     {'name': '.'},
     {'name': 'x' * 51},
@@ -134,7 +169,55 @@ def test_invalid_inputs(test_ctx, input):
     user = test_ctx.user_factory(name='u1', rank='admin')
     test_ctx.session.add(user)
     with pytest.raises(errors.ValidationError):
+        real_input={
+            'name': 'chewie',
+            'email': 'asd@asd.asd',
+            'password': 'oks',
+        }
+        for key, value in input.items():
+            real_input[key] = value
         test_ctx.api.post(
-            test_ctx.context_factory(input=input, user=user))
+            test_ctx.context_factory(input=real_input, user=user))
 
-# TODO: support avatar and avatarStyle
+def test_mods_trying_to_become_admin(test_ctx):
+    user1 = test_ctx.user_factory(name='u1', rank='mod')
+    user2 = test_ctx.user_factory(name='u2', rank='mod')
+    test_ctx.session.add_all([user1, user2])
+    context = test_ctx.context_factory(input={
+            'name': 'chewie',
+            'email': 'asd@asd.asd',
+            'password': 'oks',
+            'rank': 'admin',
+        }, user=user1)
+    with pytest.raises(errors.AuthError):
+        test_ctx.api.post(context)
+
+def test_admin_creating_mod_account(test_ctx):
+    user = test_ctx.user_factory(rank='admin')
+    test_ctx.session.add(user)
+    context = test_ctx.context_factory(input={
+            'name': 'chewie',
+            'email': 'asd@asd.asd',
+            'password': 'oks',
+            'rank': 'mod',
+        }, user=user)
+    result = test_ctx.api.post(context)
+    assert result['user']['rank'] == 'mod'
+
+def test_uploading_avatar(test_ctx, tmpdir):
+    config.config['data_dir'] = str(tmpdir.mkdir('data'))
+    config.config['data_url'] = 'http://example.com/data/'
+    response = test_ctx.api.post(
+        test_ctx.context_factory(
+            input={
+                'name': 'chewie',
+                'email': 'asd@asd.asd',
+                'password': 'oks',
+                'avatarStyle': 'manual',
+            },
+            files={'avatar': EMPTY_PIXEL},
+            user=test_ctx.user_factory(rank='mod')))
+    user = get_user(test_ctx.session, 'chewie')
+    assert user.avatar_style == user.AVATAR_MANUAL
+    assert response['user']['avatarUrl'] == \
+        'http://example.com/data/avatars/chewie.jpg'
diff --git a/server/szurubooru/util/users.py b/server/szurubooru/util/users.py
index c451eeeb..6ae457ad 100644
--- a/server/szurubooru/util/users.py
+++ b/server/szurubooru/util/users.py
@@ -66,14 +66,14 @@ def update_email(user, email):
         raise InvalidEmailError('E-mail is invalid.')
     user.email = email
 
-def update_rank(user, rank, authenticated_user):
+def update_rank(session, user, rank, authenticated_user):
     rank = rank.strip()
     available_ranks = config.config['ranks']
     if not rank in available_ranks:
         raise InvalidRankError(
             'Rank %r is invalid. Valid ranks: %r' % (rank, available_ranks))
     if available_ranks.index(authenticated_user.rank) \
-            < available_ranks.index(rank):
+            < available_ranks.index(rank) and session.query(db.User).count() > 0:
         raise errors.AuthError('Trying to set higher rank than your own.')
     user.rank = rank