diff --git a/doc/api/vulnerability_exports.md b/doc/api/vulnerability_exports.md
index f53a0ca08a3778b92f742107bac1cf10a6b2405e..2c9ac5d65ebf9a3273392afe16b6eeae9597e336 100644
--- a/doc/api/vulnerability_exports.md
+++ b/doc/api/vulnerability_exports.md
@@ -42,7 +42,7 @@ POST /security/projects/:id/vulnerability_exports
 curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/projects/1/vulnerability_exports
 ```
 
-The created vulnerability export will be automatically deleted after 1 hour.
+The created vulnerability export is automatically deleted after 1 hour.
 
 Example response:
 
@@ -51,6 +51,53 @@ Example response:
   "id": 2,
   "created_at": "2020-03-30T09:35:38.746Z",
   "project_id": 1,
+  "group_id": null,
+  "format": "csv",
+  "status": "created",
+  "started_at": null,
+  "finished_at": null,
+  "_links": {
+    "self": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2",
+    "download": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download"
+  }
+}
+```
+
+## Create a group-level vulnerability export
+
+Creates a new vulnerability export for a group.
+
+Vulnerability export permissions inherit permissions from their group. If a group is
+private and a user isn't a member of the group to which the vulnerability
+belongs, requests to that group return a `404 Not Found` status code.
+Vulnerability exports can be only accessed by the export's author.
+
+If an authenticated user doesn't have permission to
+[create a new vulnerability](../user/permissions.md#group-members-permissions),
+this request results in a `403` status code.
+
+```plaintext
+POST /security/groups/:id/vulnerability_exports
+```
+
+| Attribute           | Type              | Required   | Description                                                                                                                  |
+| ------------------- | ----------------- | ---------- | -----------------------------------------------------------------------------------------------------------------------------|
+| `id`                | integer or string | yes        | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the group which the authenticated user is a member of |
+
+```shell
+curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/groups/1/vulnerability_exports
+```
+
+The created vulnerability export is automatically deleted after 1 hour.
+
+Example response:
+
+```json
+{
+  "id": 2,
+  "created_at": "2020-03-30T09:35:38.746Z",
+  "project_id": null,
+  "group_id": 1,
   "format": "csv",
   "status": "created",
   "started_at": null,
@@ -83,6 +130,7 @@ Example response:
   "id": 2,
   "created_at": "2020-03-30T09:35:38.746Z",
   "project_id": null,
+  "group_id": null,
   "format": "csv",
   "status": "created",
   "started_at": null,
@@ -119,6 +167,7 @@ Example response:
   "id": 2,
   "created_at": "2020-03-30T09:35:38.746Z",
   "project_id": 1,
+  "group_id": null,
   "format": "csv",
   "status": "finished",
   "started_at": "2020-03-30T09:36:54.469Z",
diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb
index f235bcb972b72af97f70febca09e9952f7f0351e..3b9658ba1f26ad5d0176c2a799868918e7f8b66a 100644
--- a/ee/app/policies/ee/group_policy.rb
+++ b/ee/app/policies/ee/group_policy.rb
@@ -202,6 +202,8 @@ module GroupPolicy
 
       rule { security_dashboard_enabled & developer }.enable :read_group_security_dashboard
 
+      rule { can?(:read_group_security_dashboard) }.enable :create_vulnerability_export
+
       rule { admin | owner }.policy do
         enable :read_group_compliance_dashboard
         enable :read_group_credentials_inventory
diff --git a/ee/changelogs/unreleased/213013_group_level_security_reports_api.yml b/ee/changelogs/unreleased/213013_group_level_security_reports_api.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d7e3ccae5beeadbbfae15bc72e384aa9a6f9f9a4
--- /dev/null
+++ b/ee/changelogs/unreleased/213013_group_level_security_reports_api.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce a new API endpoint to generate group-level vulnerability exports
+merge_request: 31889
+author:
+type: added
diff --git a/ee/lib/api/vulnerability_exports.rb b/ee/lib/api/vulnerability_exports.rb
index f9d49508dc7589dac00f5420504e3be3e8848d54..3a388ead87a0486a5c02c3706d188604676e9cb5 100644
--- a/ee/lib/api/vulnerability_exports.rb
+++ b/ee/lib/api/vulnerability_exports.rb
@@ -38,7 +38,7 @@ def process_create_request_for(exportable)
                    default: ::Vulnerabilities::Export.formats.each_key.first,
                    values: ::Vulnerabilities::Export.formats.keys
         end
-        desc 'Generate an export of project vulnerability findings' do
+        desc 'Generate a project-level export' do
           success EE::API::Entities::VulnerabilityExport
         end
 
@@ -53,6 +53,28 @@ def process_create_request_for(exportable)
         end
       end
 
+      resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+        params do
+          requires :id, type: String, desc: 'The ID of a group'
+          optional :export_format, type: String, desc: 'The format of export to be generated',
+                   default: ::Vulnerabilities::Export.formats.each_key.first,
+                   values: ::Vulnerabilities::Export.formats.keys
+        end
+        desc 'Generate a group-level export' do
+          success EE::API::Entities::VulnerabilityExport
+        end
+
+        before do
+          not_found! unless Feature.enabled?(:first_class_vulnerabilities, user_group, default_enabled: true)
+        end
+
+        post ':id/vulnerability_exports' do
+          authorize! :create_vulnerability_export, user_group
+
+          process_create_request_for(user_group)
+        end
+      end
+
       namespace do
         before do
           not_found! unless Feature.enabled?(:first_class_vulnerabilities, default_enabled: true)
@@ -63,7 +85,7 @@ def process_create_request_for(exportable)
                    default: ::Vulnerabilities::Export.formats.each_key.first,
                    values: ::Vulnerabilities::Export.formats.keys
         end
-        desc 'Generate an instance level export' do
+        desc 'Generate an instance-level export' do
           success EE::API::Entities::VulnerabilityExport
         end
         post 'vulnerability_exports' do
@@ -73,7 +95,7 @@ def process_create_request_for(exportable)
         end
       end
 
-      desc 'Get single project vulnerability export' do
+      desc 'Get a single vulnerability export' do
         success EE::API::Entities::VulnerabilityExport
       end
       get 'vulnerability_exports/:id' do
@@ -88,7 +110,7 @@ def process_create_request_for(exportable)
                 with: EE::API::Entities::VulnerabilityExport
       end
 
-      desc 'Download single project vulnerability export'
+      desc 'Download a single vulnerability export'
       get 'vulnerability_exports/:id/download' do
         authorize! :read_vulnerability_export, vulnerability_export
 
diff --git a/ee/lib/ee/api/entities/vulnerability_export.rb b/ee/lib/ee/api/entities/vulnerability_export.rb
index 84489755eef15851a194561cec5583da4feefa25..b6d7fd86fbd1b64e86ef0e3ff44d7922c4431f7b 100644
--- a/ee/lib/ee/api/entities/vulnerability_export.rb
+++ b/ee/lib/ee/api/entities/vulnerability_export.rb
@@ -9,6 +9,7 @@ class VulnerabilityExport < Grape::Entity
         expose :id
         expose :created_at
         expose :project_id
+        expose :group_id
         expose :format
         expose :status
         expose :started_at
diff --git a/ee/spec/fixtures/api/schemas/public_api/v4/vulnerability_export.json b/ee/spec/fixtures/api/schemas/public_api/v4/vulnerability_export.json
index 6f74f38a6739ec2182fb2455dc7898295c051c72..0531ac45d32c3fe492010af7aa1d179133a2c3b7 100644
--- a/ee/spec/fixtures/api/schemas/public_api/v4/vulnerability_export.json
+++ b/ee/spec/fixtures/api/schemas/public_api/v4/vulnerability_export.json
@@ -4,6 +4,7 @@
     "id",
     "created_at",
     "project_id",
+    "group_id",
     "format",
     "status",
     "started_at",
@@ -13,7 +14,8 @@
   "properties" : {
     "id": { "type": "integer" },
     "created_at": { "type": "date" },
-    "project_id": { "type": "integer" },
+    "project_id": { "type": ["integer", "null"] },
+    "group_id": { "type": ["integer", "null"] },
     "format": {
       "type": "string",
       "enum": ["csv"]
diff --git a/ee/spec/lib/ee/api/entities/vulnerability_export_spec.rb b/ee/spec/lib/ee/api/entities/vulnerability_export_spec.rb
index 5de3be3a49316f6e1a2b9591d930990b1a5ae0ad..9aa2ac99db6f307faccc9212316caa84e4b5a135 100644
--- a/ee/spec/lib/ee/api/entities/vulnerability_export_spec.rb
+++ b/ee/spec/lib/ee/api/entities/vulnerability_export_spec.rb
@@ -14,6 +14,7 @@
       expect(subject[:id]).to eq(vulnerability_export.id)
       expect(subject[:created_at]).to eq(vulnerability_export.created_at)
       expect(subject[:project_id]).to eq(vulnerability_export.project_id)
+      expect(subject[:group_id]).to eq(vulnerability_export.group_id)
       expect(subject[:format]).to eq(vulnerability_export.format)
       expect(subject[:status]).to eq(vulnerability_export.status)
       expect(subject[:started_at]).to eq(vulnerability_export.started_at)
diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb
index d3834c75ddc5ed8e05ec453297db2a1d46f35f48..26a1a4935a388e6d1cd34fb72331d83e5ff95c19 100644
--- a/ee/spec/policies/group_policy_spec.rb
+++ b/ee/spec/policies/group_policy_spec.rb
@@ -630,7 +630,9 @@
     end
   end
 
-  describe 'read_group_security_dashboard' do
+  describe 'read_group_security_dashboard & create_vulnerability_export' do
+    let(:abilities) { %i(read_group_security_dashboard create_vulnerability_export) }
+
     before do
       stub_licensed_features(security_dashboard: true)
     end
@@ -638,57 +640,57 @@
     context 'with admin' do
       let(:current_user) { admin }
 
-      it { is_expected.to be_allowed(:read_group_security_dashboard) }
+      it { is_expected.to be_allowed(*abilities) }
     end
 
     context 'with owner' do
       let(:current_user) { owner }
 
-      it { is_expected.to be_allowed(:read_group_security_dashboard) }
+      it { is_expected.to be_allowed(*abilities) }
     end
 
     context 'with maintainer' do
       let(:current_user) { maintainer }
 
-      it { is_expected.to be_allowed(:read_group_security_dashboard) }
+      it { is_expected.to be_allowed(*abilities) }
     end
 
     context 'with developer' do
       let(:current_user) { developer }
 
-      it { is_expected.to be_allowed(:read_group_security_dashboard) }
+      it { is_expected.to be_allowed(*abilities) }
 
       context 'when security dashboard features is not available' do
         before do
           stub_licensed_features(security_dashboard: false)
         end
 
-        it { is_expected.to be_disallowed(:read_group_security_dashboard) }
+        it { is_expected.to be_disallowed(*abilities) }
       end
     end
 
     context 'with reporter' do
       let(:current_user) { reporter }
 
-      it { is_expected.to be_disallowed(:read_group_security_dashboard) }
+      it { is_expected.to be_disallowed(*abilities) }
     end
 
     context 'with guest' do
       let(:current_user) { guest }
 
-      it { is_expected.to be_disallowed(:read_group_security_dashboard) }
+      it { is_expected.to be_disallowed(*abilities) }
     end
 
     context 'with non member' do
       let(:current_user) { create(:user) }
 
-      it { is_expected.to be_disallowed(:read_group_security_dashboard) }
+      it { is_expected.to be_disallowed(*abilities) }
     end
 
     context 'with anonymous' do
       let(:current_user) { nil }
 
-      it { is_expected.to be_disallowed(:read_group_security_dashboard) }
+      it { is_expected.to be_disallowed(*abilities) }
     end
   end
 
diff --git a/ee/spec/requests/api/vulnerability_exports_spec.rb b/ee/spec/requests/api/vulnerability_exports_spec.rb
index e4949012e8945056ecdb40f03168c640f4c630f9..b8a70353faf5efb438bf83d254670007458c40a7 100644
--- a/ee/spec/requests/api/vulnerability_exports_spec.rb
+++ b/ee/spec/requests/api/vulnerability_exports_spec.rb
@@ -71,6 +71,70 @@
     end
   end
 
+  describe 'POST /security/groups/:id/vulnerability_exports' do
+    let_it_be(:group) { create(:group) }
+
+    let(:format) { 'csv' }
+    let(:request_path) { "/security/groups/#{group.id}/vulnerability_exports" }
+
+    subject(:create_vulnerability_export) { post api(request_path, user), params: { export_format: format } }
+
+    context 'when the request does not fulfill the requirements' do
+      let(:format) { 'exif' }
+
+      it 'responds with bad_request' do
+        create_vulnerability_export
+
+        expect(response).to have_gitlab_http_status(:bad_request)
+        expect(json_response).to eq('error' => 'export_format does not have a valid value')
+      end
+    end
+
+    context 'when the request fulfills the requirements' do
+      context 'when the user is not authorized to take the action' do
+        it 'responds with 403 forbidden' do
+          create_vulnerability_export
+
+          expect(response).to have_gitlab_http_status(:forbidden)
+        end
+      end
+
+      context 'when the user is authorized to take the action' do
+        let(:mock_service_object) { instance_double(VulnerabilityExports::CreateService, execute: vulnerability_export) }
+
+        before do
+          allow(VulnerabilityExports::CreateService).to receive(:new).and_return(mock_service_object)
+          group.add_developer(user)
+        end
+
+        context 'when the export creation succeeds' do
+          let(:vulnerability_export) { create(:vulnerability_export) }
+
+          it 'returns information about new vulnerability export' do
+            create_vulnerability_export
+
+            expect(response).to have_gitlab_http_status(:created)
+            expect(response).to match_response_schema('public_api/v4/vulnerability_export', dir: 'ee')
+          end
+        end
+
+        context 'when the export creation fails' do
+          let(:errors) { instance_double(ActiveModel::Errors, any?: true, messages: ['foo']) }
+          let(:vulnerability_export) { instance_double(Vulnerabilities::Export, persisted?: false, errors: errors) }
+
+          it 'returns the error message' do
+            create_vulnerability_export
+
+            expect(response).to have_gitlab_http_status(:bad_request)
+            expect(json_response).to eq('message' => ['foo'])
+          end
+        end
+      end
+    end
+
+    it_behaves_like 'forbids access to vulnerability API endpoint in case of disabled features'
+  end
+
   describe 'POST /security/vulnerability_exports' do
     let(:format) { 'csv' }
     let(:request_path) { "/security/vulnerability_exports" }