diff --git a/ee/app/models/ee/member.rb b/ee/app/models/ee/member.rb
index 2d2847b2e15134a045a91cf052ea2fb763564404..0537e3d0f30fddc636a05a7b3ed931725408c8a0 100644
--- a/ee/app/models/ee/member.rb
+++ b/ee/app/models/ee/member.rb
@@ -33,6 +33,13 @@ module Member
       scope :with_csv_entity_associations, -> do
         includes(:user, source: [:route, :parent])
       end
+
+      scope :awaiting_or_invited_for_group, -> (group) do
+        awaiting
+        .or(::Member.invite)
+        .in_hierarchy(group)
+        .includes(:user)
+      end
     end
 
     override :notification_service
diff --git a/ee/lib/api/entities/pending_member.rb b/ee/lib/api/entities/pending_member.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8d1529c52964bff95da2bdba5922834609e784d
--- /dev/null
+++ b/ee/lib/api/entities/pending_member.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module API
+  module Entities
+    class PendingMember < Grape::Entity
+      expose :id
+      expose :user_name, as: :name, if: -> (_) { user.present? }
+      expose :user_username, as: :username, if: -> (_) { user.present? }
+      expose :email
+      expose :web_url, if: -> (_) { user.present? }
+      expose :invite?, as: :invited
+
+      expose :avatar_url do |_|
+        user&.avatar_url || GravatarService.new.execute(email)
+      end
+
+      expose :approved do |member|
+        member.active?
+      end
+
+      def email
+        object.invite_email || object.user.email
+      end
+
+      def web_url
+        Gitlab::Routing.url_helpers.user_url(user)
+      end
+
+      def user
+        object.user
+      end
+    end
+  end
+end
diff --git a/ee/lib/ee/api/members.rb b/ee/lib/ee/api/members.rb
index cbf8a292e4bd5d5ae41292f712a7f0fccf153c9b..c2e894196fa302eef1d930a8eb8497903113941d 100644
--- a/ee/lib/ee/api/members.rb
+++ b/ee/lib/ee/api/members.rb
@@ -94,6 +94,21 @@ module Members
             end
           end
 
+          desc 'Lists all pending members for a group including invited users'
+          params do
+            use :pagination
+          end
+          get ":id/pending_members" do
+            group = find_group!(params[:id])
+
+            bad_request! unless group.root?
+            bad_request! unless can?(current_user, :admin_group_member, group)
+
+            members = ::Member.awaiting_or_invited_for_group(group)
+
+            present paginate(members), with: ::API::Entities::PendingMember
+          end
+
           desc 'Gets a list of billable users of root group.' do
             success Entities::Member
           end
diff --git a/ee/spec/lib/api/entities/pending_member_spec.rb b/ee/spec/lib/api/entities/pending_member_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c230f6e7cf0b620539a5a1f500bea47ec74f9a0d
--- /dev/null
+++ b/ee/spec/lib/api/entities/pending_member_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::PendingMember do
+  subject(:pending_member) { described_class.new(member).as_json }
+
+  context 'with a user present' do
+    let(:member) { create(:group_member, :awaiting) }
+
+    it 'exposes correct attributes' do
+      expect(pending_member.keys).to match_array [
+        :id,
+        :name,
+        :username,
+        :email,
+        :avatar_url,
+        :web_url,
+        :approved,
+        :invited
+      ]
+    end
+  end
+
+  context 'with no user present' do
+    let(:member) { create(:group_member, :invited) }
+
+    it 'exposes correct attributes' do
+      expect(pending_member.keys).to match_array [
+        :id,
+        :email,
+        :avatar_url,
+        :approved,
+        :invited
+      ]
+    end
+  end
+end
diff --git a/ee/spec/models/member_spec.rb b/ee/spec/models/member_spec.rb
index 81c2ce7233a08f0b6aadb1d81d820485bf41f2f8..207888f51e26dcd3eddc082b5a761749648f2c13 100644
--- a/ee/spec/models/member_spec.rb
+++ b/ee/spec/models/member_spec.rb
@@ -245,4 +245,23 @@
       end
     end
   end
+
+  describe '.awaiting_or_invited_for_group' do
+    let_it_be(:active_group_member) { create(:group_member, group: group) }
+    let_it_be(:awaiting_group_member) { create(:group_member, :awaiting, group: group) }
+    let_it_be(:awaiting_subgroup_member) { create(:group_member, :awaiting, group: sub_group) }
+    let_it_be(:awaiting_project_member) { create(:project_member, :awaiting, project: project) }
+    let_it_be(:awaiting_invited_member) { create(:group_member, :awaiting, :invited, group: group) }
+    let_it_be(:active_invited_member) { create(:group_member, :invited, group: group) }
+
+    it 'returns the correct members' do
+      expect(described_class.awaiting_or_invited_for_group(group)).to match_array [
+        awaiting_group_member,
+        awaiting_subgroup_member,
+        awaiting_project_member,
+        awaiting_invited_member,
+        active_invited_member
+      ]
+    end
+  end
 end
diff --git a/ee/spec/requests/api/members_spec.rb b/ee/spec/requests/api/members_spec.rb
index b98e4233953a7483044ada9cf7163fbafe92f0b0..2721d86c55e5fa96b28355f605efc9ae91f6ac49 100644
--- a/ee/spec/requests/api/members_spec.rb
+++ b/ee/spec/requests/api/members_spec.rb
@@ -1103,6 +1103,72 @@
         end
       end
     end
+
+    describe 'GET /groups/:id/pending_members' do
+      let(:url) { "/groups/#{group.id}/pending_members" }
+
+      context 'when the current user is not authorized' do
+        it 'returns a bad request response' do
+          get api(url, not_an_owner)
+
+          expect(response).to have_gitlab_http_status(:bad_request)
+        end
+      end
+
+      context 'when the current user is authorized' do
+        let_it_be(:pending_group_member) { create(:group_member, :awaiting, group: group) }
+        let_it_be(:pending_subgroup_member) { create(:group_member, :awaiting, group: subgroup) }
+        let_it_be(:pending_project_member) { create(:project_member, :awaiting, project: project) }
+        let_it_be(:pending_invited_member) { create(:group_member, :awaiting, :invited, group: group) }
+
+        it 'returns only pending members' do
+          create(:group_member, group: group)
+
+          get api(url, owner)
+
+          expect(json_response.map { |m| m['id'] }).to match_array [
+            pending_group_member.id,
+            pending_subgroup_member.id,
+            pending_project_member.id,
+            pending_invited_member.id
+          ]
+        end
+
+        it 'includes activated invited members' do
+          pending_invited_member.activate!
+
+          get api(url, owner)
+
+          expect(json_response.map { |m| m['id'] }).to match_array [
+            pending_group_member.id,
+            pending_subgroup_member.id,
+            pending_project_member.id,
+            pending_invited_member.id
+          ]
+        end
+
+        it 'paginates the response' do
+          get api(url, owner)
+
+          expect_paginated_array_response(*[
+            pending_group_member.id,
+            pending_subgroup_member.id,
+            pending_project_member.id,
+            pending_invited_member.id
+          ])
+        end
+
+        context 'when the group ID is a subgroup' do
+          let(:url) { "/groups/#{subgroup.id}/pending_members" }
+
+          it 'returns a bad request response' do
+            get api(url, owner)
+
+            expect(response).to have_gitlab_http_status(:bad_request)
+          end
+        end
+      end
+    end
   end
 
   context 'filtering project and group members' do