diff --git a/packages/component-user-manager/src/Users.js b/packages/component-user-manager/src/Users.js
index 581d7b87972085670892b8004f22cb54442a5861..9cc9c3f43fdda0bf00af9765aa5d28ca43bcb008 100644
--- a/packages/component-user-manager/src/Users.js
+++ b/packages/component-user-manager/src/Users.js
@@ -1,3 +1,5 @@
+const config = require('config')
+const jwt = require('jsonwebtoken')
 const bodyParser = require('body-parser')
 const orcidRoutes = require('./routes/users/linkOrcid')
 
@@ -167,6 +169,7 @@ const Users = app => {
    * @apiGroup Users
    * @apiSuccessExample {json} Success
    *    HTTP/1.1 200 OK
+   *    { users:
    *    [{
    *      "id": "a6184463-b17a-42f8-b02b-ae1d755cdc6b",
    *      "type": "user",
@@ -201,6 +204,7 @@ const Users = app => {
    *      "isSubmitting": false,
    *      "isCorresponding": false,
    *    }]
+   *    }
    * @apiErrorExample {json} List errors
    *    HTTP/1.1 403 Unauthorized
    */
@@ -209,9 +213,65 @@ const Users = app => {
     authBearer,
     require(`./routes/users/get`)(app.locals.models),
   )
+  /**
+   * @api {post} /api/users Create user
+   * @apiGroup Users
+   * @apiParamExample {json} Body
+   *    {
+   *      "password": "currentPassword",
+   *      "email": "email@example.com",
+   *      "firstName": "John",
+   *      "lastName": "Smith",
+   *      "affiliation": "UCLA",
+   *      "title": "Mr"
+   *    }
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 200 OK
+   *    {
+   *      "id": "a6184463-b17a-42f8-b02b-ae1d755cdc6b",
+   *      "type": "user",
+   *      "admin": false,
+   *      "email": "email@example.com",
+   *      "teams": [],
+   *      "username": "email@example.com",
+   *      "fragments": [],
+   *      "collections": [],
+   *      "isConfirmed": true,
+   *      "editorInChief": false,
+   *      "handlingEditor": false,
+   *      "notifications": {
+   *        "email": {
+   *           "system": true,
+   *           "user": true
+   *         }
+   *       }
+   *    }
+   * @apiErrorExample {json} Reset password errors
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   */
+  app.post(
+    '/api/users',
+    authorize,
+    require('./routes/users/post')(app.locals.models),
+  )
 
   // register ORCID authentication strategy
   orcidRoutes(app)
 }
 
+const authorize = async (req, res, next) => {
+  if (req.headers.authorization) {
+    const [, bToken] = req.headers.authorization.split(' ')
+    try {
+      const payload = jwt.verify(bToken, config.get('pubsweet-server.secret'))
+      req.user = payload.id
+      return next()
+    } catch (e) {
+      return res.status(403).json({ error: 'Unauthorized' })
+    }
+  }
+  return next()
+}
+
 module.exports = Users
diff --git a/packages/component-user-manager/src/routes/users/post.js b/packages/component-user-manager/src/routes/users/post.js
new file mode 100644
index 0000000000000000000000000000000000000000..32870956242c4642678383795848b6bb96946d80
--- /dev/null
+++ b/packages/component-user-manager/src/routes/users/post.js
@@ -0,0 +1,52 @@
+const { pick } = require('lodash')
+const Chance = require('chance')
+
+const chance = new Chance()
+
+module.exports = models => async (req, res) => {
+  if (req.user) {
+    const admin = await models.User.find(req.user)
+    if (!admin.admin) {
+      return res.status(403).json({ error: 'Unauthorized' })
+    }
+  } else {
+    if (!req.body.agreeTC) {
+      return res.status(403).json({
+        error: 'Terms & Conditions must be read and approved.',
+      })
+    }
+    req.body = pick(req.body, [
+      'email',
+      'title',
+      'country',
+      'firstName',
+      'lastName',
+      'password',
+      'affiliation',
+    ])
+    req.body = {
+      ...req.body,
+      admin: false,
+      isActive: true,
+      isConfirmed: false,
+      handlingEditor: false,
+      editorInChief: false,
+      username: req.body.email,
+      confirmationToken: chance.hash(),
+      notifications: {
+        email: {
+          system: true,
+          user: true,
+        },
+      },
+    }
+  }
+  let user = new models.User(req.body)
+
+  try {
+    user = await user.save()
+    return res.status(201).json(user)
+  } catch (err) {
+    return res.status(400).json({ error: err.message })
+  }
+}
diff --git a/packages/components-faraday/src/components/SignUp/utils.js b/packages/components-faraday/src/components/SignUp/utils.js
index bf27a045194a52240357280db2cd268a42a4f497..3a9fd998810f022e1f270db62c10ab020a8bf29b 100644
--- a/packages/components-faraday/src/components/SignUp/utils.js
+++ b/packages/components-faraday/src/components/SignUp/utils.js
@@ -5,28 +5,8 @@ import { loginUser } from 'pubsweet-component-login/actions'
 
 import { handleFormError } from '../utils'
 
-const generatePasswordHash = () =>
-  Array.from({ length: 4 }, () =>
-    Math.random()
-      .toString(36)
-      .slice(4),
-  ).join('')
-
 export const parseSignupAuthor = ({ token, confirmPassword, ...values }) => ({
   ...values,
-  admin: false,
-  isActive: true,
-  isConfirmed: false,
-  editorInChief: false,
-  handlingEditor: false,
-  username: values.email,
-  confirmationToken: generatePasswordHash(),
-  notifications: {
-    email: {
-      system: true,
-      user: true,
-    },
-  },
 })
 
 export const parseSearchParams = url => {