diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js
index 5810c6fddab79d0b8ccec78ef530309aa3a3b6e2..f8a30a48644694fb9a747f11ec7dffbc3230059b 100644
--- a/packages/component-fixture-manager/src/fixtures/fragments.js
+++ b/packages/component-fixture-manager/src/fixtures/fragments.js
@@ -4,8 +4,11 @@ const {
   reviewer,
   answerReviewer,
   recReviewer,
+  handlingEditor,
+  admin,
 } = require('./userData')
 const { standardCollID } = require('./collectionIDs')
+const { user } = require('./userData')
 
 const chance = new Chance()
 const fragments = {
@@ -38,6 +41,48 @@ const fragments = {
         createdOn: chance.timestamp(),
         updatedOn: chance.timestamp(),
       },
+      {
+        recommendation: 'minor',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: chance.bool(),
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: handlingEditor.id,
+        createdOn: chance.timestamp(),
+        updatedOn: chance.timestamp(),
+      },
+      {
+        recommendation: 'publish',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: chance.bool(),
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: admin.id,
+        createdOn: chance.timestamp(),
+        updatedOn: chance.timestamp(),
+      },
     ],
     authors: [
       {
@@ -67,6 +112,7 @@ const fragments = {
       },
     ],
     save: jest.fn(() => fragments.fragment),
+    owners: [user.id],
   },
 }
 
diff --git a/packages/component-helper-service/src/services/Collection.js b/packages/component-helper-service/src/services/Collection.js
index fd58224e13bbee24983e2becb01b82c24c3dc1cc..51ab28826eec2d9b37eee43915cca6f494394df9 100644
--- a/packages/component-helper-service/src/services/Collection.js
+++ b/packages/component-helper-service/src/services/Collection.js
@@ -7,12 +7,27 @@ class Collection {
     this.collection = collection
   }
 
-  async updateStatusByRecommendation({ recommendation }) {
-    let newStatus = 'pendingApproval'
-    if (['minor', 'major'].includes(recommendation))
-      newStatus = 'revisionRequested'
+  async updateStatusByRecommendation({
+    recommendation,
+    isHandlingEditor = false,
+  }) {
+    let newStatus
+    if (isHandlingEditor) {
+      newStatus = 'pendingApproval'
+      if (['minor', 'major'].includes(recommendation)) {
+        newStatus = 'revisionRequested'
+      }
+    } else {
+      if (recommendation === 'minor') {
+        newStatus = 'reviewCompleted'
+      }
 
-    await this.updateStatus({ newStatus })
+      if (recommendation === 'major') {
+        newStatus = 'reviewersInvited'
+      }
+    }
+
+    this.updateStatus({ newStatus })
   }
 
   async updateFinalStatusByRecommendation({ recommendation }) {
diff --git a/packages/component-helper-service/src/services/Fragment.js b/packages/component-helper-service/src/services/Fragment.js
index 37c3a22eaabad96e243408d537e87d245e0a6045..078aa44fa9202807bc264fb2fe2eebb6d355f931 100644
--- a/packages/component-helper-service/src/services/Fragment.js
+++ b/packages/component-helper-service/src/services/Fragment.js
@@ -81,6 +81,15 @@ class Fragment {
           inv => inv.role === 'reviewer' && inv.hasAnswer === false,
         )
   }
+
+  getHeRequestToRevision() {
+    const { fragment: { recommendations = [] } } = this
+    return recommendations.find(
+      rec =>
+        rec.recommendationType === 'editorRecommendation' &&
+        (rec.recommendation === 'minor' || rec.recommendation === 'major'),
+    )
+  }
 }
 
 module.exports = Fragment
diff --git a/packages/component-manuscript-manager/config/authsome-helpers.js b/packages/component-manuscript-manager/config/authsome-helpers.js
index 55148df349bc9807c0d6121fe30593dd021d237b..658f47bb662ab0e414324831966daae0bf525f0c 100644
--- a/packages/component-manuscript-manager/config/authsome-helpers.js
+++ b/packages/component-manuscript-manager/config/authsome-helpers.js
@@ -122,7 +122,6 @@ module.exports = {
   setPublicStatuses,
   getTeamsByPermissions,
   filterRefusedInvitations,
-  //
   isOwner,
   isHandlingEditor,
   getUserPermissions,
diff --git a/packages/component-manuscript-manager/config/authsome-mode.js b/packages/component-manuscript-manager/config/authsome-mode.js
index 20e0d691891e9804d44e7912a821e560b3ccdaf9..2c80868ddf05885664bdaa937bcf571bb32d264a 100644
--- a/packages/component-manuscript-manager/config/authsome-mode.js
+++ b/packages/component-manuscript-manager/config/authsome-mode.js
@@ -147,6 +147,10 @@ async function authenticatedUser(user, operation, object, context) {
       }
     }
 
+    if (get(object, 'type') === 'user') {
+      return true
+    }
+
     // allow HE to get reviewer invitations
     if (get(object, 'fragment.type') === 'fragment') {
       const collectionId = get(object, 'fragment.collectionId')
@@ -215,6 +219,14 @@ async function authenticatedUser(user, operation, object, context) {
     if (get(object, 'type') === 'user' && get(object, 'id') === user.id) {
       return true
     }
+
+    // allow owner to submit a revision
+    if (
+      get(object, 'path') ===
+      '/api/collections/:collectionId/fragments/:fragmentId/submit'
+    ) {
+      return helpers.isOwner({ user, object: object.fragment })
+    }
   }
 
   if (operation === 'DELETE') {
@@ -224,6 +236,10 @@ async function authenticatedUser(user, operation, object, context) {
     ) {
       return helpers.isHandlingEditor({ user, object })
     }
+
+    if (get(object, 'type') === 'collection') {
+      return helpers.isOwner({ user, object })
+    }
   }
 
   // If no individual permissions exist (above), fallback to unauthenticated
diff --git a/packages/component-manuscript-manager/config/default.js b/packages/component-manuscript-manager/config/default.js
index 276960b2fa5ce4133c55e91766a40dc4f69ea69f..241cb498d07eda9741434edeffd69545a01e56a0 100644
--- a/packages/component-manuscript-manager/config/default.js
+++ b/packages/component-manuscript-manager/config/default.js
@@ -40,11 +40,11 @@ module.exports = {
     },
     heInvited: {
       public: 'Submitted',
-      private: 'HE Invited',
+      private: 'Handling Editor Invited',
     },
     heAssigned: {
-      public: 'HE Assigned',
-      private: 'HE Assigned',
+      public: 'Handling Editor Assigned',
+      private: 'Handling Editor Assigned',
     },
     reviewersInvited: {
       public: 'Reviewers Invited',
@@ -54,10 +54,18 @@ module.exports = {
       public: 'Under Review',
       private: 'Under Review',
     },
+    reviewCompleted: {
+      public: 'Under Review',
+      private: 'Review Completed',
+    },
     pendingApproval: {
       public: 'Under Review',
       private: 'Pending Approval',
     },
+    revisionRequested: {
+      public: 'Revision Requested',
+      private: 'Revision Requested',
+    },
     rejected: {
       public: 'Rejected',
       private: 'Rejected',
diff --git a/packages/component-manuscript-manager/config/test.js b/packages/component-manuscript-manager/config/test.js
index 6869d659a3af2b1b643561889f4f81d4c486d1cf..9dad34bbfb2175e75ee07570b88965415556241f 100644
--- a/packages/component-manuscript-manager/config/test.js
+++ b/packages/component-manuscript-manager/config/test.js
@@ -41,11 +41,11 @@ module.exports = {
     },
     heInvited: {
       public: 'Submitted',
-      private: 'HE Invited',
+      private: 'Handling Editor Invited',
     },
     heAssigned: {
-      public: 'HE Assigned',
-      private: 'HE Assigned',
+      public: 'Handling Editor Assigned',
+      private: 'Handling Editor Assigned',
     },
     reviewersInvited: {
       public: 'Reviewers Invited',
@@ -55,10 +55,18 @@ module.exports = {
       public: 'Under Review',
       private: 'Under Review',
     },
+    reviewCompleted: {
+      public: 'Under Review',
+      private: 'Review Completed',
+    },
     pendingApproval: {
       public: 'Under Review',
       private: 'Pending Approval',
     },
+    revisionRequested: {
+      public: 'Revision Requested',
+      private: 'Revision Requested',
+    },
     rejected: {
       public: 'Rejected',
       private: 'Rejected',
diff --git a/packages/component-manuscript-manager/index.js b/packages/component-manuscript-manager/index.js
index 0c04f744119898af8a4ae1dc44a6eabbc5c2b24b..63ee7dbde1665993000139f654d8669b7465f8cb 100644
--- a/packages/component-manuscript-manager/index.js
+++ b/packages/component-manuscript-manager/index.js
@@ -1,5 +1,6 @@
 module.exports = {
   backend: () => app => {
     require('./src/FragmentsRecommendations')(app)
+    require('./src/Fragments')(app)
   },
 }
diff --git a/packages/component-manuscript-manager/src/Fragments.js b/packages/component-manuscript-manager/src/Fragments.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e3569166d7dd7e7e76f9082412ab3bc8219c16a
--- /dev/null
+++ b/packages/component-manuscript-manager/src/Fragments.js
@@ -0,0 +1,33 @@
+const bodyParser = require('body-parser')
+
+const Fragments = app => {
+  app.use(bodyParser.json())
+  const basePath = '/api/collections/:collectionId/fragments/:fragmentId/submit'
+  const routePath = './routes/fragments'
+  const authBearer = app.locals.passport.authenticate('bearer', {
+    session: false,
+  })
+  /**
+   * @api {patch} /api/collections/:collectionId/fragments/:fragmentId/submit Submit a revision for a manuscript
+   * @apiGroup FragmentsRecommendations
+   * @apiParam {collectionId} collectionId Collection id
+   * @apiParam {fragmentId} fragmentId Fragment id
+   * @apiSuccessExample {json} Success
+   *   HTTP/1.1 200 OK
+   *   {
+   *
+   *   }
+   * @apiErrorExample {json} Invite user errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   *    HTTP/1.1 500 Internal Server Error
+   */
+  app.patch(
+    `${basePath}`,
+    authBearer,
+    require(`${routePath}/patch`)(app.locals.models),
+  )
+}
+
+module.exports = Fragments
diff --git a/packages/component-manuscript-manager/src/routes/fragments/patch.js b/packages/component-manuscript-manager/src/routes/fragments/patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..2dfa426ea13a3fad0cd3a3b36d2e5248280990d7
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragments/patch.js
@@ -0,0 +1,52 @@
+const {
+  services,
+  Fragment,
+  Collection,
+  authsome: authsomeHelper,
+} = require('pubsweet-component-helper-service')
+
+module.exports = models => async (req, res) => {
+  const { collectionId, fragmentId } = req.params
+  let collection, fragment
+
+  try {
+    collection = await models.Collection.find(collectionId)
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Collection and fragment do not match.`,
+      })
+    fragment = await models.Fragment.find(fragmentId)
+
+    const authsome = authsomeHelper.getAuthsome(models)
+    const target = {
+      fragment,
+      path: req.route.path,
+    }
+    const canPatch = await authsome.can(req.user, 'PATCH', target)
+    if (!canPatch)
+      return res.status(403).json({
+        error: 'Unauthorized.',
+      })
+
+    const collectionHelper = new Collection({ collection })
+    const fragmentHelper = new Fragment({ fragment })
+
+    const heRecommendation = fragmentHelper.getHeRequestToRevision()
+    if (!heRecommendation) {
+      return res.status(400).json({
+        error: 'No Handling Editor recommendation has been found.',
+      })
+    }
+
+    collectionHelper.updateStatusByRecommendation({
+      recommendation: heRecommendation.recommendation,
+    })
+
+    return res.status(200).json(fragment)
+  } catch (e) {
+    const notFoundError = await services.handleNotFoundError(e, 'Item')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
index 302b9512403e40758a0b3acc15c5fcb397bbe3ec..53ae3e90411ee519901840258d8776a40ef9aeee 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
@@ -106,7 +106,10 @@ module.exports = models => async (req, res) => {
       })
     }
   } else if (recommendationType === 'editorRecommendation') {
-    collectionHelper.updateStatusByRecommendation({ recommendation })
+    collectionHelper.updateStatusByRecommendation({
+      recommendation,
+      isHandlingEditor: true,
+    })
     email.setupReviewersEmail({
       recommendation,
       agree: true,
diff --git a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..054d38e3feec67bb3dc33eafb4b4b5d430f4cfe5
--- /dev/null
+++ b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js
@@ -0,0 +1,133 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
+
+const { Model, fixtures } = fixturesService
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendNotificationEmail: jest.fn(),
+}))
+const reqBody = {}
+
+const path = '../routes/fragments/patch'
+const route = {
+  path: '/api/collections/:collectionId/fragments/:fragmentId/submit',
+}
+describe('Patch fragments route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
+  it('should return success when the parameters are correct', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+  })
+  it('should return an error when the fragmentId does not match the collectionId', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    collection.fragments.length = 0
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Collection and fragment do not match.')
+  })
+  it('should return an error when the collection does not exist', async () => {
+    const { user } = testFixtures.users
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: 'invalid-id',
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Item not found')
+  })
+  it('should return an error when no HE recommendation exists', async () => {
+    const { user } = testFixtures.users
+    const { fragment } = testFixtures.fragments
+    const { collection } = testFixtures.collections
+    fragment.recommendations.length = 0
+
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      'No Handling Editor recommendation has been found.',
+    )
+  })
+  it('should return an error when the request user is not the owner', async () => {
+    const { author } = testFixtures.users
+    const { fragment } = testFixtures.fragments
+    const { collection } = testFixtures.collections
+    fragment.recommendations.length = 0
+
+    const res = await requests.sendRequest({
+      body,
+      userId: author.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(403)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Unauthorized.')
+  })
+})
diff --git a/packages/component-user-manager/src/routes/fragmentsUsers/post.js b/packages/component-user-manager/src/routes/fragmentsUsers/post.js
index 8dc8df3ed862ef5fc369ee1f6c6e4496809658d7..8d9b8a4ce8475e3658f5632cc4e9267e02a3198f 100644
--- a/packages/component-user-manager/src/routes/fragmentsUsers/post.js
+++ b/packages/component-user-manager/src/routes/fragmentsUsers/post.js
@@ -49,6 +49,7 @@ module.exports = models => async (req, res) => {
   const baseUrl = services.getBaseUrl(req)
   const UserModel = models.User
   const teamHelper = new Team({ TeamModel: models.Team, fragmentId })
+  const fragmentHelper = new Fragment({ fragment })
 
   try {
     let user = await UserModel.findByEmail(email)
@@ -71,6 +72,12 @@ module.exports = models => async (req, res) => {
       })
     }
 
+    await fragmentHelper.addAuthor({
+      user,
+      isSubmitting,
+      isCorresponding,
+    })
+
     return res.status(200).json({
       ...pick(user, authorKeys),
       isSubmitting,
@@ -102,7 +109,6 @@ module.exports = models => async (req, res) => {
         objectType: 'fragment',
       })
 
-      const fragmentHelper = new Fragment({ fragment })
       await fragmentHelper.addAuthor({
         user: newUser,
         isSubmitting,
diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js
index 56a74c08ce67bdfbb5b3f0328e25b67d44aea051..2c80868ddf05885664bdaa937bcf571bb32d264a 100644
--- a/packages/xpub-faraday/config/authsome-mode.js
+++ b/packages/xpub-faraday/config/authsome-mode.js
@@ -219,6 +219,14 @@ async function authenticatedUser(user, operation, object, context) {
     if (get(object, 'type') === 'user' && get(object, 'id') === user.id) {
       return true
     }
+
+    // allow owner to submit a revision
+    if (
+      get(object, 'path') ===
+      '/api/collections/:collectionId/fragments/:fragmentId/submit'
+    ) {
+      return helpers.isOwner({ user, object: object.fragment })
+    }
   }
 
   if (operation === 'DELETE') {