diff --git a/CHANGELOG b/CHANGELOG
index 286091afacf566901e9aa48acc0393de10101d29..52a41c7df3d954791736891f2b0df2a9c07a647f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -50,7 +50,7 @@ v 7.8.0 (unreleased)
   - 
   - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
   - 
-  - 
+  - Edit group members via API
   - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
   - 
   - 
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 9f01b5506416de40bfc7cc05ba5d1ce2cac5b2ec..3c1858e697d90be7c7662425b55f08359c0d9904 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -152,6 +152,20 @@ Parameters:
 - `user_id` (required) - The ID of a user to add
 - `access_level` (required) - Project access level
 
+### Edit group team member
+
+Updates a group team member to a specified access level.
+
+```
+PUT /groups/:id/members/:user_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a group
+- `user_id` (required) - The ID of a group member
+- `access_level` (required) - Project access level
+
 ### Remove user team member
 
 Removes user from user team.
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
index 4373070083a4038234c4563c2053de6b6f616caf..c9c9ccbcb2e77708a4e3cca05c21ada82efd0f70 100644
--- a/lib/api/group_members.rb
+++ b/lib/api/group_members.rb
@@ -40,6 +40,30 @@ module API
         present member.user, with: Entities::GroupMember, group: group
       end
 
+      # Update group member
+      #
+      # Parameters:
+      #   id (required) - The ID of a group
+      #   user_id (required) - The ID of a group member
+      #   access_level (required) - Project access level
+      # Example Request:
+      #   PUT /groups/:id/members/:user_id
+      put ':id/members/:user_id' do
+        group = find_group(params[:id])
+        authorize! :manage_group, group
+        required_attributes! [:access_level]
+
+        team_member = group.group_members.find_by(user_id: params[:user_id])
+        not_found!('User can not be found') if team_member.nil?
+
+        if team_member.update_attributes(access_level: params[:access_level])
+          @member = team_member.user
+          present @member, with: Entities::GroupMember, group: group
+        else
+          handle_member_errors team_member.errors
+        end
+      end
+
       # Remove member.
       #
       # Parameters:
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 8fa30460ba69786dbfcb8d017487096a04e57ba4..a50ee4659a33fccba70fe159a79c8ee7fb32c92d 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -238,5 +238,10 @@ module API
     def secret_token
       File.read(Rails.root.join('.gitlab_shell_secret'))
     end
+
+    def handle_member_errors(errors)
+      error!(errors[:access_level], 422) if errors[:access_level].any?
+      not_found!(errors)
+    end
   end
 end
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
index 1e890f9e19919f06b8543a6244059ac8ff5dec32..73cf062155b889d277799f30dffdf9ef3639e3ea 100644
--- a/lib/api/project_members.rb
+++ b/lib/api/project_members.rb
@@ -4,14 +4,6 @@ module API
     before { authenticate! }
 
     resource :projects do
-      helpers do
-        def handle_project_member_errors(errors)
-          if errors[:access_level].any?
-            error!(errors[:access_level], 422)
-          end
-          not_found!(errors)
-        end
-      end
 
       # Get a project team members
       #
@@ -66,7 +58,7 @@ module API
           @member = team_member.user
           present @member, with: Entities::ProjectMember, project: user_project
         else
-          handle_project_member_errors team_member.errors
+          handle_member_errors team_member.errors
         end
       end
 
@@ -89,7 +81,7 @@ module API
           @member = team_member.user
           present @member, with: Entities::ProjectMember, project: user_project
         else
-          handle_project_member_errors team_member.errors
+          handle_member_errors team_member.errors
         end
       end
 
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
index 4957186f6056d0b4e5269082a8ac537debbeb06c..43d26d67efe8810eda975dd1542eeee5a9b0ed31 100644
--- a/spec/requests/api/group_members_spec.rb
+++ b/spec/requests/api/group_members_spec.rb
@@ -104,6 +104,69 @@ describe API::API, api: true  do
     end
   end
 
+  describe 'PUT /groups/:id/members/:user_id' do
+    context 'when not a member of the group' do
+      it 'should return a 409 error if the user is not a group member' do
+        put(
+          api("/groups/#{group_no_members.id}/members/#{developer.id}",
+              owner), access_level: GroupMember::MASTER
+        )
+        expect(response.status).to eq(404)
+      end
+    end
+
+    context 'when a member of the group' do
+      it 'should return ok and update member access level' do
+        put(
+          api("/groups/#{group_with_members.id}/members/#{reporter.id}",
+              owner),
+          access_level: GroupMember::MASTER
+        )
+
+        expect(response.status).to eq(200)
+
+        get api("/groups/#{group_with_members.id}/members", owner)
+        json_reporter = json_response.find do |e|
+          e['id'] == reporter.id
+        end
+
+        expect(json_reporter['access_level']).to eq(GroupMember::MASTER)
+      end
+
+      it 'should not allow guest to modify group members' do
+        put(
+          api("/groups/#{group_with_members.id}/members/#{developer.id}",
+              guest),
+          access_level: GroupMember::MASTER
+        )
+
+        expect(response.status).to eq(403)
+
+        get api("/groups/#{group_with_members.id}/members", owner)
+        json_developer = json_response.find do |e|
+          e['id'] == developer.id
+        end
+
+        expect(json_developer['access_level']).to eq(GroupMember::DEVELOPER)
+      end
+
+      it 'should return a 400 error when access level is not given' do
+        put(
+          api("/groups/#{group_with_members.id}/members/#{master.id}", owner)
+        )
+        expect(response.status).to eq(400)
+      end
+
+      it 'should return a 422 error when access level is not known' do
+        put(
+          api("/groups/#{group_with_members.id}/members/#{master.id}", owner),
+          access_level: 1234
+        )
+        expect(response.status).to eq(422)
+      end
+    end
+  end
+
   describe "DELETE /groups/:id/members/:user_id" do
     context "when not a member of the group" do
       it "should not delete guest's membership of group_with_members" do