diff --git a/app/models/group.rb b/app/models/group.rb
index aa37ae26a006ccd5c69d2721f447b8ac66a74e51..a2fd4d4a1f36df2d3d5b0c66bb95283c67a997be 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -898,6 +898,10 @@ def execute_hooks(data, hooks_scope)
     # TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904
   end
 
+  def find_or_initialize_integration(integration)
+    Integration.find_or_initialize_non_project_specific_integration(integration, group_id: id)
+  end
+
   def execute_integrations(data, hooks_scope)
     integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend
       integration.async_execute(data)
diff --git a/app/services/integrations/update_service.rb b/app/services/integrations/update_service.rb
index ba8af9c5cfbca0b47404ec7c29b73e06f2627218..ba5a547814ee67a48099c3a4eafbfb5cab98168d 100644
--- a/app/services/integrations/update_service.rb
+++ b/app/services/integrations/update_service.rb
@@ -14,11 +14,15 @@ def initialize(current_user:, integration:, attributes:)
     def execute
       return error('Integration not found.', :not_found) unless integration
 
-      if handle_inherited_settings?
-        handle_inherited_settings
-      else
-        handle_default_settings
-      end
+      response = if handle_inherited_settings?
+                   handle_inherited_settings
+                 else
+                   handle_default_settings
+                 end
+
+      PropagateIntegrationWorker.perform_async(integration.id) unless response.error? || integration.project_level?
+
+      response
     end
 
     private
diff --git a/doc/api/group_integrations.md b/doc/api/group_integrations.md
new file mode 100644
index 0000000000000000000000000000000000000000..638b6e997ab31857a5ee0b78bd20a751ed5ccfe9
--- /dev/null
+++ b/doc/api/group_integrations.md
@@ -0,0 +1,1968 @@
+---
+stage: Foundations
+group: Import and Integrate
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
+title: Group integrations API
+---
+
+DETAILS:
+**Tier:** Free, Premium, Ultimate
+**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328496) in GitLab 17.9.
+
+Use this API to work with external services that integrate with GitLab.
+
+This API requires an access token with the Maintainer or Owner role.
+
+## List all active integrations
+
+Get a list of all active group integrations. The `vulnerability_events` field is only available for GitLab Enterprise Edition.
+
+```plaintext
+GET /groups/:id/integrations
+```
+
+Example response:
+
+```json
+[
+  {
+    "id": 75,
+    "title": "Jenkins CI",
+    "slug": "jenkins",
+    "created_at": "2019-11-20T11:20:25.297Z",
+    "updated_at": "2019-11-20T12:24:37.498Z",
+    "active": true,
+    "commit_events": true,
+    "push_events": true,
+    "issues_events": true,
+    "alert_events": true,
+    "confidential_issues_events": true,
+    "merge_requests_events": true,
+    "tag_push_events": false,
+    "deployment_events": false,
+    "note_events": true,
+    "confidential_note_events": true,
+    "pipeline_events": true,
+    "wiki_page_events": true,
+    "job_events": true,
+    "comment_on_event_enabled": true,
+    "inherited": false,
+    "vulnerability_events": true
+  },
+  {
+    "id": 76,
+    "title": "Alerts endpoint",
+    "slug": "alerts",
+    "created_at": "2019-11-20T11:20:25.297Z",
+    "updated_at": "2019-11-20T12:24:37.498Z",
+    "active": true,
+    "commit_events": true,
+    "push_events": true,
+    "issues_events": true,
+    "alert_events": true,
+    "confidential_issues_events": true,
+    "merge_requests_events": true,
+    "tag_push_events": true,
+    "deployment_events": false,
+    "note_events": true,
+    "confidential_note_events": true,
+    "pipeline_events": true,
+    "wiki_page_events": true,
+    "job_events": true,
+    "comment_on_event_enabled": true,
+    "inherited": false,
+    "vulnerability_events": true
+  }
+]
+```
+
+## Asana
+
+### Set up Asana
+
+Set up the Asana integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/asana
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `api_key` | string | yes | User API token. The user must have access to the task. All comments are attributed to this user. |
+| `restrict_to_branch` | string | no | Comma-separated list of branches to be automatically inspected. Leave blank to include all branches. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Asana
+
+Disable the Asana integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/asana
+```
+
+### Get Asana settings
+
+Get the Asana integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/asana
+```
+
+## Assembla
+
+### Set up Assembla
+
+Set up the Assembla integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/assembla
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The authentication token. |
+| `subdomain` | string | no | The subdomain setting. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Assembla
+
+Disable the Assembla integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/assembla
+```
+
+### Get Assembla settings
+
+Get the Assembla integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/assembla
+```
+
+## Atlassian Bamboo
+
+### Set up Atlassian Bamboo
+
+Set up the Atlassian Bamboo integration for a group.
+
+You must configure automatic revision labeling and a repository trigger in Bamboo.
+
+```plaintext
+PUT /groups/:id/integrations/bamboo
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `bamboo_url` | string | yes | Bamboo root URL (for example, `https://bamboo.example.com`). |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `build_key` | string | yes | Bamboo build plan key (for example, `KEY`). |
+| `username` | string | yes | User with API access to the Bamboo server. |
+| `password` | string | yes | Password of the user. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Atlassian Bamboo
+
+Disable the Atlassian Bamboo integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/bamboo
+```
+
+### Get Atlassian Bamboo settings
+
+Get the Atlassian Bamboo integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/bamboo
+```
+
+## Bugzilla
+
+### Set up Bugzilla
+
+Set up the Bugzilla integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/bugzilla
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes |  URL of the new issue. |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Bugzilla
+
+Disable the Bugzilla integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/bugzilla
+```
+
+### Get Bugzilla settings
+
+Get the Bugzilla integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/bugzilla
+```
+
+## Buildkite
+
+### Set up Buildkite
+
+Set up the Buildkite integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/buildkite
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | Buildkite project GitLab token. |
+| `project_url` | string | yes | Pipeline URL (for example, `https://buildkite.com/example/pipeline`). |
+| `enable_ssl_verification` | boolean | no | **Deprecated:** This parameter has no effect because SSL verification is always enabled. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Buildkite
+
+Disable the Buildkite integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/buildkite
+```
+
+### Get Buildkite settings
+
+Get the Buildkite integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/buildkite
+```
+
+## Campfire Classic
+
+You can integrate with Campfire Classic. However, Campfire Classic is an old product that is
+[no longer sold](https://gitlab.com/gitlab-org/gitlab/-/issues/329337) by Basecamp.
+
+### Set up Campfire Classic
+
+Set up the Campfire Classic integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/campfire
+```
+
+Parameters:
+
+| Parameter     | Type    | Required | Description                                                                                 |
+|---------------|---------|----------|---------------------------------------------------------------------------------------------|
+| `token`       | string  | yes     | API authentication token from Campfire Classic. To get the token, sign in to Campfire Classic and select **My info**. |
+| `subdomain`   | string  | no    | `.campfirenow.com` subdomain when you're signed in. |
+| `room`        | string  | no    | ID portion of the Campfire Classic room URL. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Campfire Classic
+
+Disable the Campfire Classic integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/campfire
+```
+
+### Get Campfire Classic settings
+
+Get the Campfire Classic integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/campfire
+```
+
+## ClickUp
+
+### Set up ClickUp
+
+Set up the ClickUp integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/clickup
+```
+
+Parameters:
+
+| Parameter     | Type   | Required | Description    |
+| ------------- | ------ | -------- | -------------- |
+| `issues_url`  | string | yes     | URL of the issue.     |
+| `project_url` | string | yes     | URL of the project.   |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable ClickUp
+
+Disable the ClickUp integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/clickup
+```
+
+### Get ClickUp settings
+
+Get the ClickUp integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/clickup
+```
+
+## Confluence Workspace
+
+### Set up Confluence Workspace
+
+Set up the Confluence Workspace integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/confluence
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `confluence_url` | string | yes | URL of the Confluence Workspace hosted on `atlassian.net`. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Confluence Workspace
+
+Disable the Confluence Workspace integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/confluence
+```
+
+### Get Confluence Workspace settings
+
+Get the Confluence Workspace integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/confluence
+```
+
+## Custom issue tracker
+
+### Set up a custom issue tracker
+
+Set up a custom issue tracker for a group.
+
+```plaintext
+PUT /groups/:id/integrations/custom-issue-tracker
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes |  URL of the new issue. |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable a custom issue tracker
+
+Disable a custom issue tracker for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/custom-issue-tracker
+```
+
+### Get custom issue tracker settings
+
+Get the custom issue tracker settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/custom-issue-tracker
+```
+
+## Datadog
+
+### Set up Datadog
+
+Set up the Datadog integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/datadog
+```
+
+Parameters:
+
+| Parameter              | Type    | Required | Description                                                                                                                                                                            |
+|------------------------|---------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `api_key`              | string  | yes     | API key used for authentication with Datadog.                                                                                                                                          |
+| `api_url`              | string  | no    | (Advanced) The full URL for your Datadog site.                                                                                                                                          |
+| `datadog_env`          | string  | no    | For self-managed deployments, set the `env%` tag for all the data sent to Datadog.                                                                                                      |
+| `datadog_service`      | string  | no    | Tag all data from this GitLab instance in Datadog. Can be used when managing several self-managed deployments.                                                                          |
+| `datadog_site`         | string  | no    | The Datadog site to send data to. To send data to the EU site, use `datadoghq.eu`.                                                                                                      |
+| `datadog_tags`         | string  | no    | Custom tags in Datadog. Specify one tag per line in the format `key:value\nkey2:value2`                                                                                                 |
+| `archive_trace_events` | boolean | no    | When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Datadog
+
+Disable the Datadog integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/datadog
+```
+
+### Get Datadog settings
+
+Get the Datadog integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/datadog
+```
+
+## Diffblue Cover
+
+### Set up Diffblue Cover
+
+Set up the Diffblue Cover integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/diffblue-cover
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `diffblue_license_key` | string | yes | Diffblue Cover license key. |
+| `diffblue_access_token_name` | string | yes | Access token name used by Diffblue Cover in pipelines. |
+| `diffblue_access_token_secret` | string  | yes | Access token secret used by Diffblue Cover in pipelines. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Diffblue Cover
+
+Disable the Diffblue Cover integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/diffblue-cover
+```
+
+### Get Diffblue Cover settings
+
+Get the Diffblue Cover integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/diffblue-cover
+```
+
+## Discord Notifications
+
+### Set up Discord Notifications
+
+Set up Discord Notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/discord
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | Discord webhook (for example, `https://discord.com/api/webhooks/...`). |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `confidential_issue_channel` | string | no | The webhook override to receive notifications for confidential issue events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `confidential_note_channel` | string | no | The webhook override to receive notifications for confidential note events. |
+| `deployment_events` | boolean | no | Enable notifications for deployment events. |
+| `deployment_channel` | string | no | The webhook override to receive notifications for deployment events. |
+| `group_confidential_mentions_events` | boolean | no | Enable notifications for group confidential mention events. |
+| `group_confidential_mentions_channel` | string | no | The webhook override to receive notifications for group confidential mention events. |
+| `group_mentions_events` | boolean | no | Enable notifications for group mention events. |
+| `group_mentions_channel` | string | no | The webhook override to receive notifications for group mention events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `issue_channel` | string | no | The webhook override to receive notifications for issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `merge_request_channel` | string | no | The webhook override to receive notifications for merge request events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `note_channel` | string | no | The webhook override to receive notifications for note events. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `pipeline_channel` | string | no | The webhook override to receive notifications for pipeline events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `push_channel` | string | no | The webhook override to receive notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `tag_push_channel` | string | no | The webhook override to receive notifications for tag push events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `wiki_page_channel` | string | no | The webhook override to receive notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Discord Notifications
+
+Disable Discord Notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/discord
+```
+
+### Get Discord Notifications settings
+
+Get the Discord Notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/discord
+```
+
+## Drone
+
+### Set up Drone
+
+Set up the Drone integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/drone-ci
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | Drone CI project specific token. |
+| `drone_url` | string | yes | `http://drone.example.com`. |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Drone
+
+Disable the Drone integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/drone-ci
+```
+
+### Get Drone settings
+
+Get the Drone integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/drone-ci
+```
+
+## Emails on push
+
+### Set up emails on push
+
+Set up the emails on push integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/emails-on-push
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Emails separated by whitespace. |
+| `disable_diffs` | boolean | no | Disable code diffs. |
+| `send_from_committer_email` | boolean | no | Send from committer. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. Notifications are always fired for tag pushes. The default value is `all`. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable emails on push
+
+Disable the emails on push integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/emails-on-push
+```
+
+### Get emails on push settings
+
+Get the emails on push integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/emails-on-push
+```
+
+## Engineering Workflow Management (EWM)
+
+### Set up EWM
+
+Set up the EWM integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/ewm
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes | URL of the new issue. |
+| `project_url`   | string | yes | URL of the project. |
+| `issues_url`    | string | yes | URL of the issue. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable EWM
+
+Disable the EWM integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/ewm
+```
+
+### Get EWM settings
+
+Get the EWM integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/ewm
+```
+
+## External wiki
+
+### Set up an external wiki
+
+Set up an external wiki for a group.
+
+```plaintext
+PUT /groups/:id/integrations/external-wiki
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `external_wiki_url` | string | yes | URL of the external wiki. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable an external wiki
+
+Disable an external wiki for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/external-wiki
+```
+
+### Get external wiki settings
+
+Get the external wiki settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/external-wiki
+```
+
+## GitGuardian
+
+DETAILS:
+**Tier:** Premium, Ultimate
+**Offering:** GitLab Self-Managed, GitLab Dedicated
+
+FLAG:
+On GitLab Self-Managed, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../administration/feature_flags.md) named `git_guardian_integration`.
+On GitLab.com, this feature is not available. On GitLab Dedicated, this feature is available.
+
+[GitGuardian](https://www.gitguardian.com/) is a cybersecurity service that detects sensitive data such as API keys
+and passwords in source code repositories.
+It scans Git repositories, alerts on policy violations, and helps organizations
+fix security issues before hackers can exploit them.
+
+You can configure GitLab to reject commits based on GitGuardian policies.
+
+### Known issues
+
+- Pushes can be delayed or can time out. With the GitGuardian integration, pushes are sent to a third-party, and GitLab has no control over the connection with GitGuardian or the GitGuardian process.
+- Due to a [GitGuardian API limitation](https://api.gitguardian.com/docs#operation/multiple_scan), the integration ignores files over the size of 1 MB. They are not scanned.
+- If a pushed file has a name over 256 characters long the push won't go through.
+  For more information, see [GitGuardian API documentation](https://api.gitguardian.com/docs#operation/multiple_scan) .
+
+Troubleshooting steps on [the integration page](../user/project/integrations/git_guardian.md#troubleshooting)
+show how to mitigate some of these problems.
+
+### Set up GitGuardian
+
+Set up the GitGuardian integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/git-guardian
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description                                   |
+| --------- | ---- | -------- |-----------------------------------------------|
+| `token` | string | yes | GitGuardian API token with `scan` scope. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable GitGuardian
+
+Disable the GitGuardian integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/git-guardian
+```
+
+### Get GitGuardian settings
+
+Get the GitGuardian integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/git-guardian
+```
+
+## GitHub
+
+DETAILS:
+**Tier:** Premium, Ultimate
+**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
+
+### Set up GitHub
+
+Set up the GitHub integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/github
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | GitHub API token with `repo:status` OAuth scope. |
+| `repository_url` | string | yes | GitHub repository URL. |
+| `static_context` | boolean | no | Append the hostname of your GitLab instance to the [status check name](../user/project/integrations/github.md#static-or-dynamic-status-check-names). |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable GitHub
+
+Disable the GitHub integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/github
+```
+
+### Get GitHub settings
+
+Get the GitHub integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/github
+```
+
+## GitLab for Jira Cloud app
+
+The GitLab for Jira Cloud app integration is enabled or disabled automatically through [group linking and unlinking in Jira](../integration/jira/connect-app.md#configure-the-gitlab-for-jira-cloud-app). You cannot enable or disable the integration with the GitLab integrations form or the API.
+
+### Update integration for a group
+
+Use this API endpoint to update an integration you create with group linking in Jira.
+
+```plaintext
+PUT /groups/:id/integrations/jira-cloud-app
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `jira_cloud_app_service_ids` | string | no | Jira Service Management Service IDs. Use commas (`,`) to separate multiple IDs. |
+| `jira_cloud_app_enable_deployment_gating` | boolean | no | Enables deployment gating for blocked GitLab deployments from Jira Service Management. |
+| `jira_cloud_app_deployment_gating_environments` | string | no | The environments (production, staging, testing, or development) to enable deployment gating. Required if deployment gating is enabled. Use commas (`,`) to separate multiple environments. |
+
+### Get GitLab for Jira Cloud app settings
+
+Get the GitLab for Jira Cloud app integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/jira-cloud-app
+```
+
+## GitLab for Slack app
+
+### Set up GitLab for Slack app
+
+Update the GitLab for Slack app integration for a group.
+
+You cannot create a GitLab for Slack app through the API because the integration
+requires an OAuth 2.0 token that you cannot get from the GitLab API alone.
+Instead, you must [install the app](../user/project/integrations/gitlab_slack_application.md#install-the-gitlab-for-slack-app) from the GitLab UI.
+You can then use this API endpoint to update the integration.
+
+```plaintext
+PUT /groups/:id/integrations/gitlab-slack-application
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `channel` | string | no | Default channel to use if no other channel is configured. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `alert_events` | boolean | no | Enable notifications for alert events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `deployment_events` | boolean | no | Enable notifications for deployment events. |
+| `incidents_events` | boolean | no | Enable notifications for incident events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `vulnerability_events` | boolean | no | Enable notifications for vulnerability events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `labels_to_be_notified` | string | no | Labels to send notifications for. If not set, receive notifications for all events. |
+| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. Defaults to `match_any`. |
+| `push_channel` | string | no | Name of the channel to receive notifications for push events. |
+| `issue_channel` | string | no | Name of the channel to receive notifications for issue events. |
+| `confidential_issue_channel` | string | no | Name of the channel to receive notifications for confidential issue events. |
+| `merge_request_channel` | string | no | Name of the channel to receive notifications for merge request events. |
+| `note_channel` | string | no | Name of the channel to receive notifications for note events. |
+| `confidential_note_channel` | string | no | Name of the channel to receive notifications for confidential note events. |
+| `tag_push_channel` | string | no | Name of the channel to receive notifications for tag push events. |
+| `pipeline_channel` | string | no | Name of the channel to receive notifications for pipeline events. |
+| `wiki_page_channel` | string | no | Name of the channel to receive notifications for wiki page events. |
+| `deployment_channel` | string | no | Name of the channel to receive notifications for deployment events. |
+| `incident_channel` | string | no | Name of the channel to receive notifications for incident events. |
+| `vulnerability_channel` | string | no | Name of the channel to receive notifications for vulnerability events. |
+| `alert_channel` | string | no | Name of the channel to receive notifications for alert events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable GitLab for Slack app
+
+Disable the GitLab for Slack app integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/gitlab-slack-application
+```
+
+### Get GitLab for Slack app settings
+
+Get the GitLab for Slack app integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/gitlab-slack-application
+```
+
+## Google Chat
+
+### Set up Google Chat
+
+Set up the Google Chat integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/hangouts-chat
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Hangouts Chat webhook (for example, `https://chat.googleapis.com/v1/spaces...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Google Chat
+
+Disable the Google Chat integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/hangouts-chat
+```
+
+### Get Google Chat settings
+
+Get the Google Chat integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/hangouts-chat
+```
+
+## Google Artifact Management
+
+DETAILS:
+**Tier:** Free, Premium, Ultimate
+**Offering:** GitLab.com
+**Status:** Beta
+
+This feature is in [beta](../policy/experiment-beta-support.md).
+
+### Set up Google Artifact Management
+
+Set up the Google Artifact Management integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/google-cloud-platform-artifact-registry
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `artifact_registry_project_id` | string | yes | ID of the Google Cloud project. |
+| `artifact_registry_location` | string | yes | Location of the Artifact Registry repository. |
+| `artifact_registry_repositories` | string | yes | Repository of Artifact Registry. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Google Artifact Management
+
+Disable the Google Artifact Management integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/google-cloud-platform-artifact-registry
+```
+
+### Get Google Artifact Management settings
+
+Get the Google Artifact Management integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/google-cloud-platform-artifact-registry
+```
+
+## Google Cloud Identity and Access Management (IAM)
+
+DETAILS:
+**Tier:** Free, Premium, Ultimate
+**Offering:** GitLab.com
+**Status:** Beta
+
+This feature is in [beta](../policy/experiment-beta-support.md).
+
+### Set up Google Cloud Identity and Access Management
+
+Set up the Google Cloud Identity and Access Management integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/google-cloud-platform-workload-identity-federation
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `workload_identity_federation_project_id` | string | yes | Google Cloud project ID for the Workload Identity Federation. |
+| `workload_identity_federation_project_number` | integer | yes | Google Cloud project number for the Workload Identity Federation. |
+| `workload_identity_pool_id` | string | yes | ID of the Workload Identity Pool. |
+| `workload_identity_pool_provider_id` | string | yes | ID of the Workload Identity Pool provider. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Google Cloud Identity and Access Management
+
+Disable the Google Cloud Identity and Access Management integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/google-cloud-platform-workload-identity-federation
+```
+
+### Get Google Cloud Identity and Access Management
+
+Get the settings for the Google Cloud Identity and Access Management for a group.
+
+```plaintext
+GET /groups/:id/integration/google-cloud-platform-workload-identity-federation
+```
+
+## Harbor
+
+### Set up Harbor
+
+Set up the Harbor integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/harbor
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url` | string | yes | The base URL to the Harbor instance linked to the GitLab project. For example, `https://demo.goharbor.io`. |
+| `project_name` | string | yes | The name of the project in the Harbor instance. For example, `testproject`. |
+| `username` | string | yes | The username created in the Harbor interface. |
+| `password` | string | yes | The password of the user. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Harbor
+
+Disable the Harbor integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/harbor
+```
+
+### Get Harbor settings
+
+Get the Harbor integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/harbor
+```
+
+## irker (IRC gateway)
+
+### Set up irker
+
+Set up the irker integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/irker
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Recipients or channels separated by whitespaces. |
+| `default_irc_uri` | string | no | `irc://irc.network.net:6697/`. |
+| `server_host` | string | no | localhost. |
+| `server_port` | integer | no | 6659. |
+| `colorize_messages` | boolean | no | Colorize messages. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable irker
+
+Disable the irker integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/irker
+```
+
+### Get irker settings
+
+Get the irker integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/irker
+```
+
+## JetBrains TeamCity
+
+### Set up JetBrains TeamCity
+
+Set up the JetBrains TeamCity integration for a group.
+
+The build configuration in TeamCity must use the build number format `%build.vcs.number%`.
+In the advanced settings for VCS root, configure monitoring for all branches so merge requests can build.
+
+```plaintext
+PUT /groups/:id/integrations/teamcity
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `teamcity_url` | string | yes | TeamCity root URL (for example, `https://teamcity.example.com`). |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `build_type` | string | yes | Build configuration ID. |
+| `username` | string | yes | A user with permissions to trigger a manual build. |
+| `password` | string | yes | The password of the user. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable JetBrains TeamCity
+
+Disable the JetBrains TeamCity integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/teamcity
+```
+
+### Get JetBrains TeamCity settings
+
+Get the JetBrains TeamCity integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/teamcity
+```
+
+## Jira
+
+### Set up Jira
+
+Set up the Jira integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/jira
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url`           | string | yes | The URL to the Jira project which is being linked to this GitLab project (for example, `https://jira.example.com`). |
+| `api_url`   | string | no | The base URL to the Jira instance API. Web URL value is used if not set (for example, `https://jira-api.example.com`). |
+| `username`      | string | no   | The email or username to be used with Jira. For Jira Cloud use an email, for Jira Data Center and Jira Server use a username. Required when using Basic authentication (`jira_auth_type` is `0`). |
+| `password`      | string | yes  | The Jira API token, password, or personal access token to be used with Jira. When your authentication method is basic (`jira_auth_type` is `0`), use an API token for Jira Cloud or a password for Jira Data Center or Jira Server. When your authentication method is a Jira personal access token (`jira_auth_type` is `1`), use the personal access token. |
+| `active`        | boolean | no  | Activates or deactivates the integration. Defaults to `false` (deactivated). |
+| `jira_auth_type`| integer | no  | The authentication method to be used with Jira. `0` means Basic Authentication. `1` means Jira personal access token. Defaults to `0`. |
+| `jira_issue_prefix` | string | no | Prefix to match Jira issue keys. |
+| `jira_issue_regex` | string | no | Regular expression to match Jira issue keys. |
+| `jira_issue_transition_automatic` | boolean | no | Enable [automatic issue transitions](../integration/jira/issues.md#automatic-issue-transitions). Takes precedence over `jira_issue_transition_id` if enabled. Defaults to `false`. |
+| `jira_issue_transition_id` | string | no | The ID of one or more transitions for [custom issue transitions](../integration/jira/issues.md#custom-issue-transitions). Ignored if `jira_issue_transition_automatic` is enabled. Defaults to a blank string, which disables custom transitions. |
+| `commit_events` | boolean | no | Enable notifications for commit events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `comment_on_event_enabled` | boolean | no | Enable comments in Jira issues on each GitLab event (commit or merge request). |
+| `issues_enabled` | boolean | no | Enable viewing Jira issues in GitLab. |
+| `project_keys` | array of strings | no | Keys of Jira projects. When `issues_enabled` is `true`, this setting specifies which Jira projects to view issues from in GitLab. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Jira
+
+Disable the Jira integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/jira
+```
+
+### Get Jira settings
+
+Get the Jira integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/jira
+```
+
+## Matrix notifications
+
+### Set up Matrix notifications
+
+Set up Matrix notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/matrix
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `hostname`   | string | no | Custom hostname of the Matrix server. The default value is `https://matrix.org`. |
+| `token`   | string | yes | The Matrix access token (for example, `syt-zyx57W2v1u123ew11`). |
+| `room` | string | yes | Unique identifier for the target room (in the format `!qPKKM111FFKKsfoCVy:matrix.org`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Matrix notifications
+
+Disable Matrix notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/matrix
+```
+
+### Get Matrix notifications settings
+
+Get the Matrix notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/matrix
+```
+
+## Mattermost notifications
+
+### Set up Mattermost notifications
+
+Set up Mattermost notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/mattermost
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | Mattermost notifications webhook (for example, `http://mattermost.example.com/hooks/...`). |
+| `username` | string | no | Mattermost notifications username. |
+| `channel` | string | no | Default channel to use if no other channel is configured. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `labels_to_be_notified` | string | no | Labels to send notifications for. Leave blank to receive notifications for all events. |
+| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. The default value is `match_any`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `push_channel` | string | no | The name of the channel to receive notifications for push events. |
+| `issue_channel` | string | no | The name of the channel to receive notifications for issue events. |
+| `confidential_issue_channel` | string | no | The name of the channel to receive notifications for confidential issue events. |
+| `merge_request_channel` | string | no | The name of the channel to receive notifications for merge request events. |
+| `note_channel` | string | no | The name of the channel to receive notifications for note events. |
+| `confidential_note_channel` | string | no | The name of the channel to receive notifications for confidential note events. |
+| `tag_push_channel` | string | no | The name of the channel to receive notifications for tag push events. |
+| `pipeline_channel` | string | no | The name of the channel to receive notifications for pipeline events. |
+| `wiki_page_channel` | string | no | The name of the channel to receive notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Mattermost notifications
+
+Disable Mattermost notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/mattermost
+```
+
+### Get Mattermost notifications settings
+
+Get the Mattermost notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/mattermost
+```
+
+## Mattermost slash commands
+
+### Set up Mattermost slash commands
+
+Set up Mattermost slash commands for a group.
+
+```plaintext
+PUT /groups/:id/integrations/mattermost-slash-commands
+```
+
+Parameters:
+
+| Parameter | Type   | Required | Description           |
+| --------- | ------ | -------- | --------------------- |
+| `token`   | string | yes      | The Mattermost token. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Mattermost slash commands
+
+Disable Mattermost slash commands for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/mattermost-slash-commands
+```
+
+### Get Mattermost slash commands settings
+
+Get the Mattermost slash commands settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/mattermost-slash-commands
+```
+
+## Microsoft Teams notifications
+
+### Set up Microsoft Teams notifications
+
+Set up Microsoft Teams notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/microsoft-teams
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Microsoft Teams webhook (for example, `https://outlook.office.com/webhook/...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Microsoft Teams notifications
+
+Disable Microsoft Teams notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/microsoft-teams
+```
+
+### Get Microsoft Teams notifications settings
+
+Get the Microsoft Teams notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/microsoft-teams
+```
+
+## Mock CI
+
+This integration is only available in a development environment.
+For an example Mock CI server, see [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service).
+
+### Set up Mock CI
+
+Set up the Mock CI integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/mock-ci
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `mock_service_url` | string | yes | URL of the Mock CI integration. |
+| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Mock CI
+
+Disable the Mock CI integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/mock-ci
+```
+
+### Get Mock CI settings
+
+Get the Mock CI integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/mock-ci
+```
+
+## Packagist
+
+### Set up Packagist
+
+Set up the Packagist integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/packagist
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `username` | string | yes | The username of a Packagist account. |
+| `token` | string | yes | API token to the Packagist server. |
+| `server` | boolean | no | URL of the Packagist server. Leave blank for the default `<https://packagist.org>`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Packagist
+
+Disable the Packagist integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/packagist
+```
+
+### Get Packagist settings
+
+Get the Packagist integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/packagist
+```
+
+## Phorge
+
+### Set up Phorge
+
+Set up the Phorge integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/phorge
+```
+
+Parameters:
+
+| Parameter       | Type   | Required | Description           |
+|-----------------|--------|----------|-----------------------|
+| `issues_url`    | string | yes     | URL of the issue.     |
+| `project_url`   | string | yes     | URL of the project.   |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Phorge
+
+Disable the Phorge integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/phorge
+```
+
+### Get Phorge settings
+
+Get the Phorge integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/phorge
+```
+
+## Pipeline status emails
+
+### Set up pipeline status emails
+
+Set up pipeline status emails for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pipelines-email
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `recipients` | string | yes | Comma-separated list of recipient email addresses. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `notify_only_default_branch` | boolean | no | Send notifications for the default branch. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable pipeline status emails
+
+Disable pipeline status emails for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pipelines-email
+```
+
+### Get pipeline status emails settings
+
+Get the pipeline status emails settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pipelines-email
+```
+
+## Pivotal Tracker
+
+### Set up Pivotal Tracker
+
+Set up the Pivotal Tracker integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pivotaltracker
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The Pivotal Tracker token. |
+| `restrict_to_branch` | boolean | no | Comma-separated list of branches to automatically inspect. Leave blank to include all branches. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Pivotal Tracker
+
+Disable the Pivotal Tracker integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pivotaltracker
+```
+
+### Get Pivotal Tracker settings
+
+Get the Pivotal Tracker integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pivotaltracker
+```
+
+## Pumble
+
+### Set up Pumble
+
+Set up the Pumble integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pumble
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Pumble webhook (for example, `https://api.pumble.com/workspaces/x/...`). |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default is `default`. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Pumble
+
+Disable the Pumble integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pumble
+```
+
+### Get Pumble settings
+
+Get the Pumble integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pumble
+```
+
+## Pushover
+
+### Set up Pushover
+
+Set up the Pushover integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/pushover
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `api_key` | string | yes | Your application key. |
+| `user_key` | string | yes | Your user key. |
+| `priority` | string | yes | The priority. |
+| `device` | string | no | Leave blank for all active devices. |
+| `sound` | string | no | The sound of the notification. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Pushover
+
+Disable the Pushover integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/pushover
+```
+
+### Get Pushover settings
+
+Get the Pushover integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/pushover
+```
+
+## Redmine
+
+### Set up Redmine
+
+Set up the Redmine integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/redmine
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `new_issue_url` | string | yes | URL of the new issue. |
+| `project_url` | string | yes | URL of the project. |
+| `issues_url` | string | yes | URL of the issue. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Redmine
+
+Disable the Redmine integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/redmine
+```
+
+### Get Redmine settings
+
+Get the Redmine integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/redmine
+```
+
+## Slack notifications
+
+### Set up Slack notifications
+
+Set up Slack notifications for a group.
+
+```plaintext
+PUT /groups/:id/integrations/slack
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | Slack notifications webhook (for example, `https://hooks.slack.com/services/...`). |
+| `username` | string | no | Slack notifications username. |
+| `channel` | string | no | Default channel to use if no other channel is configured. |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `labels_to_be_notified` | string | no | Labels to send notifications for. Leave blank to receive notifications for all events. |
+| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. The default value is `match_any`. |
+| `alert_channel` | string | no | The name of the channel to receive notifications for alert events. |
+| `alert_events` | boolean | no | Enable notifications for alert events. |
+| `commit_events` | boolean | no | Enable notifications for commit events. |
+| `confidential_issue_channel` | string | no | The name of the channel to receive notifications for confidential issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `confidential_note_channel` | string | no | The name of the channel to receive notifications for confidential note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `deployment_channel` | string | no | The name of the channel to receive notifications for deployment events. |
+| `deployment_events` | boolean | no | Enable notifications for deployment events. |
+| `incident_channel` | string | no | The name of the channel to receive notifications for incident events. |
+| `incidents_events` | boolean | no | Enable notifications for incident events. |
+| `issue_channel` | string | no | The name of the channel to receive notifications for issue events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `job_events` | boolean | no | Enable notifications for job events. |
+| `merge_request_channel` | string | no | The name of the channel to receive notifications for merge request events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `note_channel` | string | no | The name of the channel to receive notifications for note events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `pipeline_channel` | string | no | The name of the channel to receive notifications for pipeline events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `push_channel` | string | no | The name of the channel to receive notifications for push events. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `tag_push_channel` | string | no | The name of the channel to receive notifications for tag push events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `wiki_page_channel` | string | no | The name of the channel to receive notifications for wiki page events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Slack notifications
+
+Disable Slack notifications for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/slack
+```
+
+### Get Slack notifications settings
+
+Get the Slack notifications settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/slack
+```
+
+## Slack slash commands
+
+### Set up Slack slash commands
+
+Set up Slack slash commands for a group.
+
+```plaintext
+PUT /groups/:id/integrations/slack-slash-commands
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | yes | The Slack token. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Slack slash commands
+
+Disable Slack slash commands for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/slack-slash-commands
+```
+
+### Get Slack slash commands settings
+
+Get the Slack slash commands settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/slack-slash-commands
+```
+
+Example response:
+
+```json
+{
+  "id": 4,
+  "title": "Slack slash commands",
+  "slug": "slack-slash-commands",
+  "created_at": "2017-06-27T05:51:39-07:00",
+  "updated_at": "2017-06-27T05:51:39-07:00",
+  "active": true,
+  "push_events": true,
+  "issues_events": true,
+  "confidential_issues_events": true,
+  "merge_requests_events": true,
+  "tag_push_events": true,
+  "note_events": true,
+  "job_events": true,
+  "pipeline_events": true,
+  "comment_on_event_enabled": false,
+  "inherited": false,
+  "properties": {
+    "token": "<your_access_token>"
+  }
+}
+```
+
+## Squash TM
+
+### Set up Squash TM
+
+Set up the Squash TM integration settings for a group.
+
+```plaintext
+PUT /groups/:id/integrations/squash-tm
+```
+
+Parameters:
+
+| Parameter               | Type   | Required | Description                   |
+|-------------------------|--------|----------|-------------------------------|
+| `url`                   | string | yes      | URL of the Squash TM webhook. |
+| `token`                 | string | no       | Secret token.                 |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Squash TM
+
+Disable the Squash TM integration for a group. Integration settings are preserved.
+
+```plaintext
+DELETE /groups/:id/integrations/squash-tm
+```
+
+### Get Squash TM settings
+
+Get the Squash TM integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/squash-tm
+```
+
+## Telegram
+
+### Set up Telegram
+
+Set up the Telegram integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/telegram
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `hostname`   | string | no | Custom hostname of the Telegram API. The default value is `https://api.telegram.org`. |
+| `token`   | string | yes | The Telegram bot token (for example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`). |
+| `room` | string | yes | Unique identifier for the target chat or the username of the target channel (in the format `@channelusername`). |
+| `thread` | integer | no | Unique identifier for the target message thread (topic in a forum supergroup). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | yes | Enable notifications for push events. |
+| `issues_events` | boolean | yes | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | yes | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | yes | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | yes | Enable notifications for tag push events. |
+| `note_events` | boolean | yes | Enable notifications for note events. |
+| `confidential_note_events` | boolean | yes | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | yes | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | yes | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Telegram
+
+Disable the Telegram integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/telegram
+```
+
+### Get Telegram settings
+
+Get the Telegram integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/telegram
+```
+
+## Unify Circuit
+
+### Set up Unify Circuit
+
+Set up the Unify Circuit integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/unify-circuit
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Unify Circuit webhook (for example, `https://circuit.com/rest/v2/webhooks/incoming/...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Unify Circuit
+
+Disable the Unify Circuit integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/unify-circuit
+```
+
+### Get Unify Circuit settings
+
+Get the Unify Circuit integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/unify-circuit
+```
+
+## Webex Teams
+
+### Set up Webex Teams
+
+Set up Webex Teams for a group.
+
+```plaintext
+PUT /groups/:id/integrations/webex-teams
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `webhook` | string | yes | The Webex Teams webhook (for example, `https://api.ciscospark.com/v1/webhooks/incoming/...`). |
+| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. |
+| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. |
+| `push_events` | boolean | no | Enable notifications for push events. |
+| `issues_events` | boolean | no | Enable notifications for issue events. |
+| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. |
+| `merge_requests_events` | boolean | no | Enable notifications for merge request events. |
+| `tag_push_events` | boolean | no | Enable notifications for tag push events. |
+| `note_events` | boolean | no | Enable notifications for note events. |
+| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. |
+| `pipeline_events` | boolean | no | Enable notifications for pipeline events. |
+| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable Webex Teams
+
+Disable Webex Teams for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/webex-teams
+```
+
+### Get Webex Teams settings
+
+Get the Webex Teams settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/webex-teams
+```
+
+## YouTrack
+
+### Set up YouTrack
+
+Set up the YouTrack integration for a group.
+
+```plaintext
+PUT /groups/:id/integrations/youtrack
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `issues_url` | string | yes | URL of the issue. |
+| `project_url` | string | yes | URL of the project. |
+| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. |
+
+### Disable YouTrack
+
+Disable the YouTrack integration for a group. Integration settings are reset.
+
+```plaintext
+DELETE /groups/:id/integrations/youtrack
+```
+
+### Get YouTrack settings
+
+Get the YouTrack integration settings for a group.
+
+```plaintext
+GET /groups/:id/integrations/youtrack
+```
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index 58338c8d00969c304bac20e9a00d8f2b7390e3b6..3f4e7b2eee774e26b29035d68020604593fa596d 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -2,14 +2,14 @@
 stage: Foundations
 group: Import and Integrate
 info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
-title: Integrations API
+title: Project integrations API
 ---
 
 DETAILS:
 **Tier:** Free, Premium, Ultimate
 **Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated
 
-This API enables you to work with external services that integrate with GitLab.
+Use this API to work with external services that integrate with GitLab.
 
 This API requires an access token with the Maintainer or Owner role.
 
diff --git a/doc/development/integrations/_index.md b/doc/development/integrations/_index.md
index c6435ecc2ab0e24fb24cb021213a82f100a97faa..69ce8123b7579d0442e89b7954fbd1364c9289a1 100644
--- a/doc/development/integrations/_index.md
+++ b/doc/development/integrations/_index.md
@@ -360,7 +360,7 @@ To expose the integration in the [REST API](../../api/integrations.md):
    'foo-bar' => ::Integrations::FooBar.api_arguments
    ```
 
-1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration, and document all properties.
+1. Update the reference documentation in `doc/api/integrations.md` and `doc/api/group_integrations.md`, add a new section for your integration, and document all properties.
 
 You can also refer to our [REST API style guide](../api_styleguide.md).
 
@@ -450,8 +450,8 @@ In the major milestone of intended removal (M.0), disable the integration and de
 - Remove the integration from `Integration::INTEGRATION_NAMES`.
 - Delete the integration model's `#execute` and `#test` methods (if defined), but keep the model.
 - Add a post-migration to delete the integration records from PostgreSQL (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114721)).
-- [Mark the integration documentation as removed](../documentation/styleguide/deprecations_and_removals.md#remove-a-page).
-- [Update the integration API documentation](../../api/integrations.md).
+- [Mark the integration documentation as removed](../../development/documentation/styleguide/deprecations_and_removals.md#remove-a-page).
+- Update the [project](../../api/integrations.md) and [group](../../api/group_integrations.md) integrations API pages.
 
 In the next minor release (M.1):
 
diff --git a/ee/lib/ee/api/entities/project_integration_basic.rb b/ee/lib/ee/api/entities/integration_basic.rb
similarity index 87%
rename from ee/lib/ee/api/entities/project_integration_basic.rb
rename to ee/lib/ee/api/entities/integration_basic.rb
index 6824962fc0080a4baa6872bcf7e8e6c8fc8edaa0..86567bfbce53b82c17a700d5cc356b820d4aec55 100644
--- a/ee/lib/ee/api/entities/project_integration_basic.rb
+++ b/ee/lib/ee/api/entities/integration_basic.rb
@@ -3,7 +3,7 @@
 module EE
   module API
     module Entities
-      module ProjectIntegrationBasic
+      module IntegrationBasic
         extend ActiveSupport::Concern
 
         prepended do
diff --git a/ee/spec/lib/ee/api/entities/project_integration_basic_spec.rb b/ee/spec/lib/ee/api/entities/integration_basic_spec.rb
similarity index 67%
rename from ee/spec/lib/ee/api/entities/project_integration_basic_spec.rb
rename to ee/spec/lib/ee/api/entities/integration_basic_spec.rb
index 3ef177f380549bc75ec97dc539f4e22dd8d036be..c52e6cfc1e67d4ed5ecac49bc4da7df8a7d201d3 100644
--- a/ee/spec/lib/ee/api/entities/project_integration_basic_spec.rb
+++ b/ee/spec/lib/ee/api/entities/integration_basic_spec.rb
@@ -2,11 +2,11 @@
 
 require 'spec_helper'
 
-RSpec.describe ::EE::API::Entities::ProjectIntegrationBasic, feature_category: :integrations do
+RSpec.describe ::EE::API::Entities::IntegrationBasic, feature_category: :integrations do
   let_it_be(:integration) { create(:jenkins_integration) }
 
   let(:entity) do
-    API::Entities::ProjectIntegrationBasic.new(integration)
+    API::Entities::IntegrationBasic.new(integration)
   end
 
   subject(:representation) { entity.as_json }
diff --git a/ee/spec/requests/api/integrations_spec.rb b/ee/spec/requests/api/integrations_spec.rb
index ff0e660075e4d9fcd5fbbe1d9294374ccc375447..ee504f3b3623024a76e9e3ee178459bc00cec1cf 100644
--- a/ee/spec/requests/api/integrations_spec.rb
+++ b/ee/spec/requests/api/integrations_spec.rb
@@ -96,19 +96,31 @@ def stub_allow_list_license(allowed:)
       integration = params[:integration]
 
       describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
-        it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration do
+        it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration,
+          parent_resource_name: 'project' do
+          let(:parent_resource) { project }
+          let(:integrations_map) { project_integrations_map }
+
           it_behaves_like 'observes allow list settings', allowed_status: :ok, blocked_status: :bad_request
         end
       end
 
       describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
-        it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration do
+        it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration,
+          parent_resource_name: 'project' do
+          let(:parent_resource) { project }
+          let(:integrations_map) { project_integrations_map }
+
           it_behaves_like 'observes allow list settings', allowed_status: :no_content, blocked_status: :not_found
         end
       end
 
       describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
-        it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration do
+        it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration,
+          parent_resource_name: 'project' do
+          let(:parent_resource) { project }
+          let(:integrations_map) { project_integrations_map }
+
           it_behaves_like 'observes allow list settings', allowed_status: :ok, blocked_status: :not_found
         end
       end
diff --git a/lib/api/entities/project_integration.rb b/lib/api/entities/integration.rb
similarity index 64%
rename from lib/api/entities/project_integration.rb
rename to lib/api/entities/integration.rb
index f4709ce6dabbe90676cc00c5c0e34ebdd5f62511..48ade64963ca7ed1f3907ed12e09d7959b1acbba 100644
--- a/lib/api/entities/project_integration.rb
+++ b/lib/api/entities/integration.rb
@@ -2,11 +2,11 @@
 
 module API
   module Entities
-    class ProjectIntegration < Entities::ProjectIntegrationBasic
+    class Integration < Entities::IntegrationBasic
       # Expose serialized properties
-      expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, options|
+      expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, _|
         integration.api_field_names.index_with do |name|
-          integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend
+          integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend -- we're exposing the fields that are allowed to be exposed
         end
       end
     end
diff --git a/lib/api/entities/project_integration_basic.rb b/lib/api/entities/integration_basic.rb
similarity index 94%
rename from lib/api/entities/project_integration_basic.rb
rename to lib/api/entities/integration_basic.rb
index e57263fafa27f19fc397cbdf8a283b59575ffe35..633687c0a6b9591de514f3ea466101b8cb80eb8d 100644
--- a/lib/api/entities/project_integration_basic.rb
+++ b/lib/api/entities/integration_basic.rb
@@ -2,7 +2,7 @@
 
 module API
   module Entities
-    class ProjectIntegrationBasic < Grape::Entity
+    class IntegrationBasic < Grape::Entity
       expose :id, documentation: { type: 'integer', example: 75 }
       expose :title, documentation: { type: 'string', example: 'Jenkins CI' }
       expose :slug, documentation: { type: 'integer', example: 'jenkins' } do |integration|
@@ -33,4 +33,4 @@ class ProjectIntegrationBasic < Grape::Entity
   end
 end
 
-API::Entities::ProjectIntegrationBasic.prepend_mod
+API::Entities::IntegrationBasic.prepend_mod
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b32d7d89e364acfec6ceefee7f32207ca5886cca..74a9120a4774ec890573d811d180d615d8270c79 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -399,10 +399,14 @@ def authorize_admin_project
       authorize! :admin_project, user_project
     end
 
-    def authorize_admin_integrations
+    def authorize_admin_project_integrations
       authorize! :admin_integrations, user_project
     end
 
+    def authorize_admin_group_integrations
+      authorize! :admin_integrations, user_group
+    end
+
     def authorize_admin_group
       authorize! :admin_group, user_group
     end
diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb
index d5284c56d3d91f41bf00966dcd388b9c1abeb5fe..c32f77983f15c61e18d940fe2333b5abf68173a4 100644
--- a/lib/api/integrations.rb
+++ b/lib/api/integrations.rb
@@ -45,7 +45,7 @@ def integration_attributes(integration)
     end
 
     # The API officially documents only the `:id/integrations` API paths.
-    # We support the older `id:/services` path for backwards-compatibility in API V4.
+    # We support the older `id:/services` path for project integrations for backwards-compatibility in API V4.
     # The support for `:id/services` can be dropped if we create an API V5.
     [':id/services', ':id/integrations'].each do |path|
       params do
@@ -53,134 +53,15 @@ def integration_attributes(integration)
       end
       resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
         before { authenticate! }
-        before { authorize_admin_integrations }
-
-        desc 'List all active integrations' do
-          detail 'Get a list of all active project integrations.'
-          success Entities::ProjectIntegrationBasic
-          failure [
-            { code: 401, message: 'Unauthorized' },
-            { code: 404, message: 'Not found' }
-          ]
-          is_array true
-          tags INTEGRATIONS_TAGS
-        end
-        get path do
-          integrations = user_project.integrations.active
-
-          present integrations, with: Entities::ProjectIntegrationBasic
-        end
-
-        INTEGRATIONS.each do |slug, settings|
-          desc "Create/Edit #{slug.titleize} integration" do
-            detail "Set #{slug.titleize} integration for a project."
-            success Entities::ProjectIntegrationBasic
-            failure [
-              { code: 400, message: 'Bad request' },
-              { code: 401, message: 'Unauthorized' },
-              { code: 404, message: 'Not found' },
-              { code: 422, message: 'Unprocessable entity' }
-            ]
-            tags INTEGRATIONS_TAGS
-          end
-          params do
-            settings.each do |setting|
-              if setting[:required]
-                requires setting[:name], type: setting[:type], desc: setting[:desc]
-              else
-                optional setting[:name], type: setting[:type], desc: setting[:desc]
-              end
-            end
-          end
-          put "#{path}/#{slug}" do
-            integration = user_project.find_or_initialize_integration(slug.underscore)
-
-            render_api_error!('400 Integration not available', 400) if integration.nil?
-
-            params = declared_params(include_missing: false).merge(active: true)
-
-            unless integration.manual_activation? || integration.is_a?(::Integrations::Prometheus)
-              if integration.new_record?
-                render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422)
-              end
-
-              params.delete(:active)
-            end
-
-            result = ::Integrations::UpdateService.new(
-              current_user: current_user, integration: integration, attributes: params
-            ).execute
-
-            if result.success?
-              present integration, with: Entities::ProjectIntegration
-            else
-              render_api_error!(result.message, 400)
-            end
-          end
-        end
-
-        desc "Disable an integration" do
-          detail "Disable the integration for a project. Integration settings are preserved."
-          success code: 204
-          failure [
-            { code: 400, message: 'Bad request' },
-            { code: 401, message: 'Unauthorized' },
-            { code: 404, message: 'Not found' }
-          ]
-          tags INTEGRATIONS_TAGS
-        end
-        params do
-          requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
-        end
-        delete "#{path}/:slug" do
-          integration = user_project.find_or_initialize_integration(params[:slug].underscore)
-
-          not_found!('Integration') unless integration&.persisted?
+        before { authorize_admin_project_integrations }
 
-          if integration.is_a?(::Integrations::JiraCloudApp)
-            render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422)
-          end
-
-          destroy_conditionally!(integration) do
-            attrs = integration_attributes(integration).index_with do |attr|
-              column = if integration.attribute_present?(attr)
-                         integration.column_for_attribute(attr)
-                       elsif integration.data_fields_present?
-                         integration.data_fields.column_for_attribute(attr)
-                       end
-
-              case column
-              when nil, ActiveRecord::ConnectionAdapters::NullColumn
-                nil
-              else
-                column.default
-              end
-            end.merge(active: false)
-
-            render_api_error!('400 Bad Request', 400) unless integration.update(attrs)
+        helpers do
+          def fetch_parent_resource
+            user_project
           end
         end
 
-        desc "Get an integration settings" do
-          detail "Get the integration settings for a project."
-          success Entities::ProjectIntegration
-          failure [
-            { code: 400, message: 'Bad request' },
-            { code: 401, message: 'Unauthorized' },
-            { code: 404, message: 'Not found' }
-          ]
-          tags INTEGRATIONS_TAGS
-        end
-        params do
-          requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
-        end
-        get "#{path}/:slug" do
-          integration = user_project.find_or_initialize_integration(params[:slug].underscore)
-
-          not_found!('Integration') unless integration&.persisted?
-
-          present integration, with: Entities::ProjectIntegration
-        end
+        mount IntegratableOperations, with: { path: path }
       end
 
       SLASH_COMMAND_INTEGRATIONS.each do |integration_slug, settings|
@@ -229,6 +110,26 @@ def slash_command_integration(project, integration_slug, params)
       end
     end
 
+    # New API endpoints should use the `:id/integrations` path exclusively.
+    ':id/integrations'.tap do |path|
+      params do
+        requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group'
+      end
+
+      resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+        before { authenticate! }
+        before { authorize_admin_group_integrations }
+
+        helpers do
+          def fetch_parent_resource
+            user_group
+          end
+        end
+
+        mount IntegratableOperations, with: { path: path }
+      end
+    end
+
     desc "Trigger a global slack command" do
       detail 'Added in GitLab 9.4'
       failure [
diff --git a/lib/api/integrations/integratable_operations.rb b/lib/api/integrations/integratable_operations.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f1806f3bdd083f41611b619fd688dd7ec166e0ff
--- /dev/null
+++ b/lib/api/integrations/integratable_operations.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+module API
+  class Integrations
+    class IntegratableOperations < Grape::API # rubocop:disable API/Base -- subclassing from Grape::API is required for mounting in the main API.
+      INTEGRATIONS_TAGS = %w[integrations].freeze
+
+      before do
+        render_api_error!('400 Integration not available', 400) if disallowed_integration_at_group_level?(params[:slug])
+      end
+
+      mounted do
+        helpers do
+          def disallowed_integration_at_group_level?(slug)
+            return unless slug.present?
+
+            parent_resource.is_a?(Group) && Integration.project_specific_integration_names.include?(slug.underscore)
+          end
+
+          def integration_attributes(integration)
+            integration.fields.inject([]) do |arr, hash|
+              arr << hash[:name].to_sym
+            end
+          end
+
+          def find_or_initialize_integration(slug)
+            parent_resource.find_or_initialize_integration(slug.underscore)
+          end
+
+          def parent_resource
+            unless respond_to?(:fetch_parent_resource, true)
+              raise NotImplementedError,
+                "You must implement a `fetch_parent_resource` method that returns " \
+                  "the integratable resource in the namespace that mounts this API."
+            end
+
+            @parent_resource ||= fetch_parent_resource
+          end
+        end
+
+        desc 'List all active integrations' do
+          detail "Get a list of all active integrations."
+          success Entities::IntegrationBasic
+          failure [
+            { code: 401, message: 'Unauthorized' },
+            { code: 404, message: 'Not found' }
+          ]
+          is_array true
+          tags INTEGRATIONS_TAGS
+        end
+
+        get(configuration[:path]) do
+          integrations = parent_resource.integrations.active
+
+          present integrations, with: Entities::IntegrationBasic
+        end
+
+        INTEGRATIONS.each do |slug, settings|
+          desc "Create/Edit #{slug.titleize} integration" do
+            detail "Set #{slug.titleize} integration."
+            success Entities::IntegrationBasic
+            failure [
+              { code: 400, message: 'Bad request' },
+              { code: 401, message: 'Unauthorized' },
+              { code: 404, message: 'Not found' },
+              { code: 422, message: 'Unprocessable entity' }
+            ]
+            tags INTEGRATIONS_TAGS
+          end
+          params do
+            settings.each do |setting|
+              if setting[:required]
+                requires setting[:name], type: setting[:type], desc: setting[:desc]
+              else
+                optional setting[:name], type: setting[:type], desc: setting[:desc]
+              end
+            end
+          end
+          put("#{configuration[:path]}/#{slug}") do
+            integration = find_or_initialize_integration(slug)
+
+            params = declared_params(include_missing: false).merge(active: true)
+
+            render_api_error!('400 Integration not available', 400) if integration.nil?
+
+            manual_or_special = integration.manual_activation? || integration.is_a?(::Integrations::Prometheus)
+
+            unless manual_or_special
+              if integration.new_record?
+                render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422)
+              end
+
+              params.delete(:active)
+            end
+
+            result = ::Integrations::UpdateService.new(
+              current_user: current_user, integration: integration, attributes: params
+            ).execute
+
+            if result.success?
+              present integration, with: Entities::Integration
+            else
+              message = result.message || '400 Bad Request'
+              render_api_error!(message, 400)
+            end
+          end
+        end
+
+        desc "Disable an integration" do
+          detail "Disable the integration. Integration settings are preserved."
+          success code: 204
+          failure [
+            { code: 400, message: 'Bad request' },
+            { code: 401, message: 'Unauthorized' },
+            { code: 404, message: 'Not found' }
+          ]
+          tags INTEGRATIONS_TAGS
+        end
+        params do
+          requires :slug, type: String, values: INTEGRATIONS.keys,
+            desc: 'The name of the integration'
+        end
+        delete("#{configuration[:path]}/:slug") do
+          integration = find_or_initialize_integration(params[:slug])
+
+          not_found!('Integration') unless integration&.persisted?
+
+          if integration.is_a?(::Integrations::JiraCloudApp)
+            render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422)
+          end
+
+          destroy_conditionally!(integration) do
+            attrs = integration_attributes(integration).index_with do |attr|
+              column = if integration.attribute_present?(attr)
+                         integration.column_for_attribute(attr)
+                       elsif integration.data_fields_present?
+                         integration.data_fields.column_for_attribute(attr)
+                       end
+
+              case column
+              when nil, ActiveRecord::ConnectionAdapters::NullColumn
+                nil
+              else
+                column.default
+              end
+            end.merge(active: false)
+
+            render_api_error!('400 Bad Request', 400) unless integration.update(attrs)
+          end
+        end
+
+        desc "Get an integration settings" do
+          detail "Get the integration settings."
+          success Entities::Integration
+          failure [
+            { code: 400, message: 'Bad request' },
+            { code: 401, message: 'Unauthorized' },
+            { code: 404, message: 'Not found' }
+          ]
+          tags INTEGRATIONS_TAGS
+        end
+        params do
+          requires :slug, type: String, values: INTEGRATIONS.keys,
+            desc: 'The name of the integration'
+        end
+        get("#{configuration[:path]}/:slug") do
+          integration = find_or_initialize_integration(params[:slug])
+
+          not_found!('Integration') unless integration&.persisted?
+
+          present integration, with: Entities::Integration
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb
index 5d353bbb9943a151b3fe6b1e70699a407505f437..66ba6f87e98971da200e5e483d78d7304c8b912c 100644
--- a/spec/requests/api/integrations_spec.rb
+++ b/spec/requests/api/integrations_spec.rb
@@ -7,38 +7,37 @@
 
   let_it_be(:user) { create(:user) }
   let_it_be(:user2) { create(:user) }
-  let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace) }
-
-  let_it_be(:available_integration_names) do
-    excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
-
-    Integration.available_integration_names(include_instance_specific: false) - excluded_integrations
-  end
+  let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace, owners: [user]) }
+  let_it_be(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) }
+  let_it_be(:group, reload: true) { create(:group, owners: user) }
 
-  let_it_be(:project_integrations_map) do
-    available_integration_names.index_with do |name|
-      create(integration_factory(name), :inactive, project: project)
+  context "Project level integrations" do
+    let_it_be(:available_integration_names) do
+      excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
+      Integration.available_integration_names(include_instance_specific: false) - excluded_integrations
     end
-  end
 
-  let_it_be(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) }
+    let_it_be(:project_integrations_map) do
+      available_integration_names.index_with do |name|
+        create(integration_factory(name), :inactive, project: project)
+      end
+    end
 
-  %w[integrations services].each do |endpoint|
-    describe "GET /projects/:id/#{endpoint}" do
-      it 'returns authentication error when unauthenticated' do
-        get api("/projects/#{project.id}/#{endpoint}")
+    %w[integrations services].each do |endpoint|
+      describe "GET /projects/:id/#{endpoint}" do
+        it 'returns authentication error when unauthenticated' do
+          get api("/projects/#{project.id}/#{endpoint}")
 
-        expect(response).to have_gitlab_http_status(:unauthorized)
-      end
+          expect(response).to have_gitlab_http_status(:unauthorized)
+        end
 
-      it "returns error when authenticated but user is not a project owner" do
-        project.add_developer(user2)
-        get api("/projects/#{project.id}/#{endpoint}", user2)
+        it "returns error when authenticated but user is not a project owner" do
+          project.add_developer(user2)
+          get api("/projects/#{project.id}/#{endpoint}", user2)
 
-        expect(response).to have_gitlab_http_status(:forbidden)
-      end
+          expect(response).to have_gitlab_http_status(:forbidden)
+        end
 
-      context 'with integrations' do
         it "returns a list of all active integrations" do
           get api("/projects/#{project.id}/#{endpoint}", user)
 
@@ -51,391 +50,490 @@
           end
         end
       end
-    end
 
-    where(:integration) do
-      # The integrations API supports all project integrations.
-      # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
-      unavailable_integration_names = [
-        Integrations::GitlabSlackApplication.to_param,
-        Integrations::JiraCloudApp.to_param,
-        Integrations::Prometheus.to_param,
-        Integrations::Zentao.to_param
-      ]
-
-      names = Integration.available_integration_names(include_instance_specific: false)
-      names.reject { |name| name.in?(unavailable_integration_names) }
-    end
-
-    with_them do
-      integration = params[:integration]
-
-      describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
-        it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration
-      end
-
-      describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
-        it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration
-      end
+      where(:integration) do
+        # The integrations API supports all project integrations.
+        # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
+        unavailable_integration_names = [
+          Integrations::GitlabSlackApplication.to_param,
+          Integrations::JiraCloudApp.to_param,
+          Integrations::Prometheus.to_param,
+          Integrations::Zentao.to_param
+        ]
 
-      describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
-        it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration
+        names = Integration.available_integration_names(include_instance_specific: false)
+        names.reject { |name| unavailable_integration_names.include?(name) }
       end
-    end
 
-    describe "POST /projects/:id/#{endpoint}/:slug/trigger" do
-      describe 'Mattermost integration' do
-        let(:integration_name) { 'mattermost_slash_commands' }
+      with_them do
+        integration = params[:integration]
 
-        context 'when no integration is available' do
-          it 'returns a not found message' do
-            post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
+        describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
+          it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
+            let(:parent_resource) { project }
+            let(:integrations_map) { project_integrations_map }
+          end
+        end
 
-            expect(response).to have_gitlab_http_status(:not_found)
-            expect(json_response["error"]).to eq("404 Not Found")
+        describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
+          it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
+            let(:parent_resource) { project }
+            let(:integrations_map) { project_integrations_map }
           end
         end
 
-        context 'when the integration exists' do
-          let(:params) { { token: 'secrettoken' } }
+        describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
+          it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do
+            let(:parent_resource) { project }
+            let(:integrations_map) { project_integrations_map }
+          end
+        end
+      end
 
-          context 'when the integration is not active' do
-            before do
-              project_integrations_map[integration_name].deactivate!
-            end
+      describe "POST /projects/:id/#{endpoint}/:slug/trigger" do
+        describe 'Mattermost integration' do
+          let(:integration_name) { 'mattermost_slash_commands' }
 
-            it 'when the integration is inactive' do
-              post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+          context 'when no integration is available' do
+            it 'returns a not found message' do
+              post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
 
               expect(response).to have_gitlab_http_status(:not_found)
+              expect(json_response["error"]).to eq("404 Not Found")
             end
           end
 
-          context 'when the integration is active' do
-            before do
-              project_integrations_map[integration_name].activate!
-            end
+          context 'when the integration exists' do
+            let(:params) { { token: 'secrettoken' } }
 
-            it 'returns status 200' do
-              post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+            context 'when the integration is not active' do
+              before do
+                project_integrations_map[integration_name].deactivate!
+              end
 
-              expect(response).to have_gitlab_http_status(:ok)
+              it 'when the integration is inactive' do
+                post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+                expect(response).to have_gitlab_http_status(:not_found)
+              end
             end
-          end
 
-          context 'when the project can not be found' do
-            it 'returns a generic 404' do
-              post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
+            context 'when the integration is active' do
+              before do
+                project_integrations_map[integration_name].activate!
+              end
 
-              expect(response).to have_gitlab_http_status(:not_found)
-              expect(json_response["message"]).to eq("404 Integration Not Found")
-            end
-          end
-        end
-      end
+              it 'returns status 200' do
+                post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
 
-      describe 'Slack Integration' do
-        let(:integration_name) { 'slack_slash_commands' }
-        let(:params) { { token: 'secrettoken', text: 'help' } }
+                expect(response).to have_gitlab_http_status(:ok)
+              end
+            end
 
-        context 'when no integration is available' do
-          it 'returns a not found message' do
-            post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
+            context 'when the project can not be found' do
+              it 'returns a generic 404' do
+                post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
 
-            expect(response).to have_gitlab_http_status(:not_found)
-            expect(json_response["error"]).to eq("404 Not Found")
+                expect(response).to have_gitlab_http_status(:not_found)
+                expect(json_response["message"]).to eq("404 Integration Not Found")
+              end
+            end
           end
         end
 
-        context 'when the integration exists' do
-          context 'when the integration is not active' do
-            before do
-              project_integrations_map[integration_name].deactivate!
-            end
+        describe 'Slack Integration' do
+          let(:integration_name) { 'slack_slash_commands' }
+          let(:params) { { token: 'secrettoken', text: 'help' } }
 
-            it 'when the integration is inactive' do
-              post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+          context 'when no integration is available' do
+            it 'returns a not found message' do
+              post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
 
               expect(response).to have_gitlab_http_status(:not_found)
+              expect(json_response["error"]).to eq("404 Not Found")
             end
           end
 
-          context 'when the integration is active' do
-            before do
-              project_integrations_map[integration_name].activate!
+          context 'when the integration exists' do
+            context 'when the integration is not active' do
+              before do
+                project_integrations_map[integration_name].deactivate!
+              end
+
+              it 'when the integration is inactive' do
+                post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+                expect(response).to have_gitlab_http_status(:not_found)
+              end
             end
 
-            it 'returns status 200' do
-              post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+            context 'when the integration is active' do
+              before do
+                project_integrations_map[integration_name].activate!
+              end
 
-              expect(response).to have_gitlab_http_status(:ok)
-              expect(json_response['response_type']).to eq("ephemeral")
+              it 'returns status 200' do
+                post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+                expect(response).to have_gitlab_http_status(:ok)
+                expect(json_response['response_type']).to eq("ephemeral")
+              end
             end
-          end
 
-          context 'when the project can not be found' do
-            it 'returns a generic 404' do
-              post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
+            context 'when the project can not be found' do
+              it 'returns a generic 404' do
+                post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
 
-              expect(response).to have_gitlab_http_status(:not_found)
-              expect(json_response["message"]).to eq("404 Integration Not Found")
+                expect(response).to have_gitlab_http_status(:not_found)
+                expect(json_response["message"]).to eq("404 Integration Not Found")
+              end
             end
           end
         end
       end
-    end
 
-    describe 'Mattermost integration' do
-      let(:integration_name) { 'mattermost' }
-      let(:params) do
-        { webhook: 'https://hook.example.com', username: 'username' }
-      end
+      describe 'Mattermost integration' do
+        let(:integration_name) { 'mattermost' }
+        let(:params) do
+          { webhook: 'https://hook.example.com', username: 'username' }
+        end
 
-      before do
-        project_integrations_map[integration_name].activate!
-      end
+        before do
+          project_integrations_map[integration_name].activate!
+        end
 
-      it 'accepts a username for update' do
-        put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username')
+        it 'accepts a username for update' do
+          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username')
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['properties']['username']).to eq('new_username')
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['properties']['username']).to eq('new_username')
+        end
       end
-    end
 
-    describe 'Microsoft Teams integration' do
-      let_it_be(:group) { create(:group) }
-      let(:integration_name) { 'microsoft-teams' }
-      let(:params) do
-        {
-          webhook: 'https://hook.example.com',
-          branches_to_be_notified: 'default',
-          notify_only_broken_pipelines: false
-        }
-      end
+      describe 'Microsoft Teams integration' do
+        let_it_be(:group) { create(:group) }
+        let(:integration_name) { 'microsoft-teams' }
+        let(:params) do
+          {
+            webhook: 'https://hook.example.com',
+            branches_to_be_notified: 'default',
+            notify_only_broken_pipelines: false
+          }
+        end
 
-      before do
-        create(:microsoft_teams_integration, group: group, project: nil)
-        project.update!(namespace: group)
-        project_integrations_map[integration_name.underscore].activate!
-      end
+        before do
+          create(:microsoft_teams_integration, group: group, project: nil)
+          project.update!(namespace: group)
+          project_integrations_map[integration_name.underscore].activate!
+        end
 
-      it 'accepts branches_to_be_notified for update' do
-        put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
-          params: params.merge(branches_to_be_notified: 'all')
+        it 'accepts branches_to_be_notified for update' do
+          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+            params: params.merge(branches_to_be_notified: 'all')
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['properties']['branches_to_be_notified']).to eq('all')
-      end
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['properties']['branches_to_be_notified']).to eq('all')
+        end
 
-      it 'accepts notify_only_broken_pipelines for update' do
-        put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
-          params: params.merge(notify_only_broken_pipelines: true)
+        it 'accepts notify_only_broken_pipelines for update' do
+          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+            params: params.merge(notify_only_broken_pipelines: true)
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
-      end
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+        end
 
-      it 'accepts `use_inherited_settings` for inheritance' do
-        expect do
-          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
-            params: params.merge(use_inherited_settings: true)
-        end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil)
+        it 'accepts `use_inherited_settings` for inheritance' do
+          expect do
+            put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+              params: params.merge(use_inherited_settings: true)
+          end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil)
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['inherited']).to eq(true)
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['inherited']).to eq(true)
+        end
       end
-    end
 
-    describe 'Hangouts Chat integration' do
-      let(:integration_name) { 'hangouts-chat' }
-      let(:params) do
-        {
-          webhook: 'https://hook.example.com',
-          branches_to_be_notified: 'default'
-        }
-      end
+      describe 'Hangouts Chat integration' do
+        let(:integration_name) { 'hangouts-chat' }
+        let(:params) do
+          {
+            webhook: 'https://hook.example.com',
+            branches_to_be_notified: 'default'
+          }
+        end
 
-      before do
-        project_integrations_map[integration_name.underscore].activate!
-      end
+        before do
+          project_integrations_map[integration_name.underscore].activate!
+        end
 
-      it 'accepts branches_to_be_notified for update', :aggregate_failures do
-        put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
+        it 'accepts branches_to_be_notified for update', :aggregate_failures do
+          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['properties']['branches_to_be_notified']).to eq('all')
-      end
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['properties']['branches_to_be_notified']).to eq('all')
+        end
 
-      it 'only requires the webhook param' do
-        put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
+        it 'only requires the webhook param' do
+          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
 
-        expect(response).to have_gitlab_http_status(:ok)
+          expect(response).to have_gitlab_http_status(:ok)
+        end
       end
-    end
 
-    describe 'Jira integration' do
-      let(:integration_name) { 'jira' }
-      let(:params) do
-        { url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 }
-      end
+      describe 'Jira integration' do
+        let(:integration_name) { 'jira' }
+        let(:params) do
+          { url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 }
+        end
 
-      before do
-        project_integrations_map[integration_name].properties = params
-        project_integrations_map[integration_name].activate!
-      end
+        before do
+          project_integrations_map[integration_name].properties = params
+          project_integrations_map[integration_name].activate!
+        end
 
-      it 'returns the jira_issue_transition_id for get request' do
-        get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
+        it 'returns the jira_issue_transition_id for get request' do
+          get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1')
-      end
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1')
+        end
 
-      it 'returns the jira_issue_transition_id for put request' do
-        put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1')
+        it 'returns the jira_issue_transition_id for put request' do
+          put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1')
 
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response['properties']['jira_issue_transition_id']).to eq('1')
+          expect(response).to have_gitlab_http_status(:ok)
+          expect(json_response['properties']['jira_issue_transition_id']).to eq('1')
+        end
       end
-    end
 
-    describe 'Pipelines Email Integration' do
-      let(:integration_name) { 'pipelines-email' }
+      describe 'Pipelines Email Integration' do
+        let(:integration_name) { 'pipelines-email' }
+
+        context 'notify_only_broken_pipelines property was saved as a string' do
+          before do
+            project_integrations_map[integration_name.underscore].activate!
+          end
+
+          it 'returns boolean values for notify_only_broken_pipelines' do
+            get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
+
+            expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+          end
+        end
+      end
 
-      context 'notify_only_broken_pipelines property was saved as a string' do
+      describe 'GitLab for Slack app integration' do
         before do
-          project_integrations_map[integration_name.underscore].activate!
+          stub_application_setting(slack_app_enabled: true)
+          create(:gitlab_slack_application_integration, project: project)
         end
 
-        it 'returns boolean values for notify_only_broken_pipelines' do
-          get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
+        describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do
+          context 'for integration creation' do
+            before do
+              project.gitlab_slack_application_integration.destroy!
+            end
 
-          expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+            it 'returns 422' do
+              put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+
+              expect(response).to have_gitlab_http_status(:unprocessable_entity)
+              expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API')
+            end
+          end
+
+          context 'for integration update' do
+            before do
+              project.gitlab_slack_application_integration.update!(active: false)
+            end
+
+            it "does not enable the integration" do
+              put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+
+              expect(response).to have_gitlab_http_status(:ok)
+              expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false)
+            end
+          end
         end
-      end
-    end
 
-    describe 'GitLab for Slack app integration' do
-      before do
-        stub_application_setting(slack_app_enabled: true)
-        create(:gitlab_slack_application_integration, project: project)
-      end
+        describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do
+          it "fetches the integration and returns the correct fields" do
+            get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
 
-      describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do
-        context 'for integration creation' do
-          before do
-            project.gitlab_slack_application_integration.destroy!
+            expect(response).to have_gitlab_http_status(:ok)
+            assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration)
           end
+        end
 
-          it 'returns 422' do
-            put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+        describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do
+          it "disables the integration" do
+            expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) }
+              .to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false)
 
-            expect(response).to have_gitlab_http_status(:unprocessable_entity)
-            expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API')
+            expect(response).to have_gitlab_http_status(:no_content)
           end
         end
+      end
 
-        context 'for integration update' do
-          before do
-            project.gitlab_slack_application_integration.update!(active: false)
+      describe 'GitLab for Jira Cloud app integration' do
+        before do
+          stub_application_setting(jira_connect_application_key: 'mock_key')
+          create(:jira_cloud_app_integration, project: project)
+        end
+
+        describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do
+          context 'for integration creation' do
+            before do
+              project.jira_cloud_app_integration.destroy!
+            end
+
+            it 'returns 422' do
+              put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+
+              expect(response).to have_gitlab_http_status(:unprocessable_entity)
+              expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API')
+            end
+          end
+
+          context 'for integration update' do
+            before do
+              project.jira_cloud_app_integration.update!(active: false)
+            end
+
+            it "does not enable the integration" do
+              put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+
+              expect(response).to have_gitlab_http_status(:ok)
+              expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false)
+            end
           end
+        end
 
-          it "does not enable the integration" do
-            put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+        describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do
+          it "fetches the integration and returns the correct fields" do
+            get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
 
             expect(response).to have_gitlab_http_status(:ok)
-            expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false)
+            assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration)
           end
         end
-      end
 
-      describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do
-        it "fetches the integration and returns the correct fields" do
-          get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user)
+        describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do
+          it "does not disable the integration" do
+            expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) }
+              .not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true)
 
-          expect(response).to have_gitlab_http_status(:ok)
-          assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration)
+            expect(response).to have_gitlab_http_status(:unprocessable_entity)
+            expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API')
+          end
         end
       end
 
-      describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do
-        it "disables the integration" do
-          expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) }
-            .to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false)
+      private
 
-          expect(response).to have_gitlab_http_status(:no_content)
-        end
+      def assert_correct_response_fields(response_keys, integration)
+        assert_fields_match_integration(response_keys, integration)
+        assert_secret_fields_filtered(response_keys, integration)
       end
+
+      def assert_fields_match_integration(response_keys, integration)
+        expect(response_keys).to match_array(integration.api_field_names)
+      end
+
+      def assert_secret_fields_filtered(response_keys, integration)
+        expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty?
+      end
+    end
+  end
+
+  context "Group level integrations" do
+    let_it_be(:available_integration_names) do
+      excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param]
+      Integration.available_integration_names(include_project_specific: false, include_instance_specific: false) - excluded_integrations
     end
 
-    describe 'GitLab for Jira Cloud app integration' do
-      before do
-        stub_application_setting(jira_connect_application_key: 'mock_key')
-        create(:jira_cloud_app_integration, project: project)
+    let_it_be(:group_integrations_map) do
+      available_integration_names.index_with do |name|
+        create(integration_factory(name), :inactive, :group, group: group)
       end
+    end
 
-      describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do
-        context 'for integration creation' do
-          before do
-            project.jira_cloud_app_integration.destroy!
-          end
+    'integrations'.tap do |endpoint|
+      describe "GET /groups/:id/#{endpoint}" do
+        let_it_be(:project_only_integration) { Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES }
 
-          it 'returns 422' do
-            put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+        it 'returns authentication error when unauthenticated' do
+          get api("/groups/#{group.id}/#{endpoint}")
 
-            expect(response).to have_gitlab_http_status(:unprocessable_entity)
-            expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API')
-          end
+          expect(response).to have_gitlab_http_status(:unauthorized)
         end
 
-        context 'for integration update' do
-          before do
-            project.jira_cloud_app_integration.update!(active: false)
-          end
+        it "returns error when authenticated but user is not a group owner" do
+          group.add_developer(user2)
+          get api("/groups/#{group.id}/#{endpoint}", user2)
+
+          expect(response).to have_gitlab_http_status(:forbidden)
+        end
 
-          it "does not enable the integration" do
-            put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+        it "returns a list of all active integrations" do
+          get api("/groups/#{group.id}/#{endpoint}", user)
 
+          aggregate_failures 'expect successful response with all active integrations' do
             expect(response).to have_gitlab_http_status(:ok)
-            expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false)
+            expect(json_response).to be_an Array
+            expect(json_response.count).to eq(1)
+            expect(json_response.first['slug']).to eq('prometheus')
+            expect(response).to match_response_schema('public_api/v4/integrations')
           end
         end
-      end
 
-      describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do
-        it "fetches the integration and returns the correct fields" do
-          get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user)
+        Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES.map(&:dasherize).each do |integration_name|
+          it "returns 400 when trying to access a project-only integration" do
+            get api("/groups/#{group.id}/integrations/#{integration_name}", user)
 
-          expect(response).to have_gitlab_http_status(:ok)
-          assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration)
+            expect(response).to have_gitlab_http_status(:bad_request)
+            expect(json_response["message"]).to eq("400 Integration not available")
+          end
         end
       end
 
-      describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do
-        it "does not disable the integration" do
-          expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) }
-            .not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true)
+      where(:integration) do
+        # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI.
+        unavailable_integration_names = [
+          Integrations::GitlabSlackApplication.to_param,
+          Integrations::Zentao.to_param,
+          Integrations::Prometheus.to_param
+        ]
 
-          expect(response).to have_gitlab_http_status(:unprocessable_entity)
-          expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API')
-        end
+        names = Integration.available_integration_names(include_instance_specific: false, include_project_specific: false)
+        names.reject { |name| unavailable_integration_names.include?(name) }
       end
-    end
 
-    private
+      with_them do
+        integration = params[:integration]
 
-    def assert_correct_response_fields(response_keys, integration)
-      assert_fields_match_integration(response_keys, integration)
-      assert_secret_fields_filtered(response_keys, integration)
-    end
+        describe "PUT /groups/:id/#{endpoint}/#{integration.dasherize}" do
+          it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
+            let(:parent_resource) { group }
+            let(:integrations_map) { group_integrations_map }
+          end
+        end
 
-    def assert_fields_match_integration(response_keys, integration)
-      expect(response_keys).to match_array(integration.api_field_names)
-    end
+        describe "DELETE /groups/:id/#{endpoint}/#{integration.dasherize}" do
+          it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
+            let(:parent_resource) { group }
+            let(:integrations_map) { group_integrations_map }
+          end
+        end
 
-    def assert_secret_fields_filtered(response_keys, integration)
-      expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty?
+        describe "GET /groups/:id/#{endpoint}/#{integration.dasherize}" do
+          it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do
+            let(:parent_resource) { group }
+            let(:integrations_map) { group_integrations_map }
+          end
+        end
+      end
     end
   end
 
diff --git a/spec/services/integrations/update_service_spec.rb b/spec/services/integrations/update_service_spec.rb
index 273264bf394f72f48b2a0dbda96eb241d67bb293..e41af6ca486a5d9b464aeee6f006dd8fd9f9b20f 100644
--- a/spec/services/integrations/update_service_spec.rb
+++ b/spec/services/integrations/update_service_spec.rb
@@ -18,11 +18,21 @@
   end
 
   shared_examples 'success request' do
+    before do
+      allow(PropagateIntegrationWorker).to receive(:perform_async)
+    end
+
     it 'returns a success response' do
       result
 
       expect(result).to be_success
       expect(result.payload).to eq(integration)
+
+      if integration.project_level?
+        expect(PropagateIntegrationWorker).not_to have_received(:perform_async)
+      else
+        expect(PropagateIntegrationWorker).to have_received(:perform_async).with(integration.id)
+      end
     end
   end
 
diff --git a/spec/support/shared_examples/integrations_shared_examples.rb b/spec/support/shared_examples/integrations_shared_examples.rb
index 085679c9136acd127dc29972f2de9dcfb0c1ad10..db58e955208017cd829f91f7dbe7ece9781c43d5 100644
--- a/spec/support/shared_examples/integrations_shared_examples.rb
+++ b/spec/support/shared_examples/integrations_shared_examples.rb
@@ -1,10 +1,16 @@
 # frozen_string_literal: true
 
-RSpec.shared_examples 'set up an integration' do |endpoint:, integration:|
+RSpec.shared_examples 'set up an integration' do |endpoint:, integration:, parent_resource_name:|
   include_context 'with integration'
 
-  let(:integration_attrs) { attributes_for(integration_factory).without(:active, :type) }
-  let(:url) { api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) }
+  let(:integrations_map) { raise NotImplementedError, 'Define `integrations_map` in the calling context' }
+  let(:parent_resource) { raise NotImplementedError, 'Define `parent_resource` in the calling context' }
+
+  let(:integration_attrs) do
+    attributes_for(integration_factory).without(:active, :type)
+  end
+
+  let(:url) { api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user) }
 
   subject(:request) { put url, params: integration_attrs }
 
@@ -14,7 +20,7 @@
     expect(response).to have_gitlab_http_status(:ok)
     expect(json_response['slug']).to eq(dashed_integration)
 
-    current_integration = project.integrations.by_name(integration).first
+    current_integration = parent_resource.integrations.by_name(integration).first
     expect(current_integration).to have_attributes(integration_attrs)
     expect(json_response['properties'].keys).to match_array(current_integration.api_field_names)
 
@@ -32,7 +38,7 @@
       put url, params: flipped_attrs
 
       expect(response).to have_gitlab_http_status(:ok)
-      expect(project.integrations.by_name(integration).first).to have_attributes(flipped_attrs)
+      expect(parent_resource.integrations.by_name(integration).first).to have_attributes(flipped_attrs)
     end
   end
 
@@ -57,24 +63,60 @@
     expect(response).to have_gitlab_http_status(expected_code)
   end
 
-  context 'when an integration is disabled' do
+  context "when updates fails" do
     before do
-      allow(Integration).to receive(:disabled_integration_names).and_return([integration.to_param])
+      allow_next_instance_of(::Integrations::UpdateService) do |instance|
+        allow(instance).to receive(:execute).and_return(
+          instance_double(ServiceResponse, success?: false, message: 'Update failed')
+        )
+      end
     end
 
-    it 'returns bad request' do
-      put url, params: integration_attrs
+    it 'returns 400 with correct message' do
+      request
 
       expect(response).to have_gitlab_http_status(:bad_request)
+      expect(json_response['message']).to eq('Update failed')
     end
   end
 
-  context 'when an integration is disabled at the project-level' do
-    before do
-      allow_next_found_instance_of(Project) do |project|
-        allow(project).to receive(:disabled_integrations).and_return([integration])
+  context 'when the integration does not exist' do
+    let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) }
+    let(:parent_resource) { fresh_parent_resource }
+
+    it "creates #{integration} and returns the correct fields" do
+      initial_count = parent_resource.integrations.by_name(integration).count
+      expect(initial_count).to eq(0)
+
+      request
+
+      current_integration = parent_resource.integrations.by_name(integration).first
+      manual_or_special = current_integration&.manual_activation? ||
+        current_integration.is_a?(::Integrations::Prometheus)
+      delta = manual_or_special ? 1 : 0
+
+      expect(parent_resource.integrations.by_name(integration).count).to eq(initial_count + delta)
+
+      if manual_or_special
+        expect(response).to have_gitlab_http_status(:ok)
+        expect(json_response['slug']).to eq(dashed_integration)
+
+        expect(current_integration).to have_attributes(integration_attrs)
+        expect(json_response['properties'].keys).to match_array(current_integration.api_field_names)
+
+        if current_integration.secret_fields.present?
+          expect(json_response['properties'].keys).not_to include(*current_integration.secret_fields)
+        end
+      else
+        expect(response).to have_gitlab_http_status(:unprocessable_entity)
       end
     end
+  end
+
+  context 'when an integration is disabled' do
+    before do
+      allow(Integration).to receive(:disabled_integration_names).and_return([integration.to_param])
+    end
 
     it 'returns bad request' do
       put url, params: integration_attrs
@@ -82,39 +124,69 @@
       expect(response).to have_gitlab_http_status(:bad_request)
     end
   end
+
+  if parent_resource_name == 'project'
+    context 'when an integration is disabled at the parent_resource-level' do
+      before do
+        allow_next_found_instance_of(parent_resource.class) do |instance|
+          allow(instance).to receive(:disabled_integrations).and_return([integration])
+        end
+      end
+
+      it 'returns bad request' do
+        put url, params: integration_attrs
+
+        expect(response).to have_gitlab_http_status(:bad_request)
+      end
+    end
+  end
 end
 
-RSpec.shared_examples 'disable an integration' do |endpoint:, integration:|
+RSpec.shared_examples 'disable an integration' do |endpoint:, integration:, parent_resource_name:|
   include_context 'with integration'
 
-  subject(:request) { delete api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) }
+  let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) }
+  let(:parent_resource) { fresh_parent_resource }
+
+  subject(:request) do
+    delete api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
+  end
 
   before do
-    project_integrations_map[integration].activate!
+    integrations_map[integration].update_column(:active, true)
   end
 
   it "deletes #{integration}" do
-    request
+    expect do
+      request
+    end.to change {
+      parent_resource.integrations.where(type_new: integration_klass.name, active: true).count
+    }.from(1).to(0)
 
     expect(response).to have_gitlab_http_status(:no_content)
-    project.send(integration_method).reload
-    expect(project.send(integration_method).activated?).to be_falsey
   end
 
   it 'returns not found if integration does not exist' do
-    delete api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user)
+    delete api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
 
     expect(response).to have_gitlab_http_status(:not_found)
     expect(json_response['message']).to eq('404 Integration Not Found')
   end
 end
 
-RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:|
+RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:, parent_resource_name:|
   include_context 'with integration'
 
-  let(:initialized_integration) { project_integrations_map[integration] }
+  let(:initialized_integration) do
+    integrations_map[integration]
+  end
+
+  let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) }
+  let(:parent_resource) { fresh_parent_resource }
 
-  subject(:request) { get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) }
+  subject(:request) do
+    get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
+  end
 
   def deactive_integration!
     return initialized_integration.deactivate! unless initialized_integration.is_a?(::Integrations::Prometheus)
@@ -142,10 +214,10 @@ def activate_integration!
 
       expect(initialized_integration).not_to be_active
       expect(response).to have_gitlab_http_status(:ok)
-      expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
+      expect(json_response['properties'].keys).to match_array(initialized_integration.api_field_names)
 
-      unless integration_instance.secret_fields.empty?
-        expect(json_response['properties'].keys).not_to include(*integration_instance.secret_fields)
+      unless initialized_integration.secret_fields.empty?
+        expect(json_response['properties'].keys).not_to include(*initialized_integration.secret_fields)
       end
     end
   end
@@ -160,40 +232,44 @@ def activate_integration!
 
       expect(initialized_integration).to be_active
       expect(response).to have_gitlab_http_status(:ok)
-      expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
+      expect(json_response['properties'].keys).to match_array(initialized_integration.api_field_names)
 
-      unless integration_instance.secret_fields.empty?
-        expect(json_response['properties'].keys).not_to include(*integration_instance.secret_fields)
+      unless initialized_integration.secret_fields.empty?
+        expect(json_response['properties'].keys).not_to include(*initialized_integration.secret_fields)
       end
     end
   end
 
   it 'returns authentication error when unauthenticated' do
-    get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}")
+    get api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}")
+
     expect(response).to have_gitlab_http_status(:unauthorized)
   end
 
   it "returns not found if integration does not exist" do
-    get api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user)
+    get api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
 
     expect(response).to have_gitlab_http_status(:not_found)
     expect(json_response['message']).to eq('404 Integration Not Found')
   end
 
-  it "returns not found if integration exists but is in `Project#disabled_integrations`" do
-    allow_next_found_instance_of(Project) do |project|
-      allow(project).to receive(:disabled_integrations).and_return([integration])
-    end
+  if parent_resource_name == 'project'
+    it "returns not found if integration exists but is in `#{parent_resource_name}#disabled_integrations`" do
+      allow_next_found_instance_of(parent_resource.class) do |instance|
+        allow(instance).to receive(:disabled_integrations).and_return([integration])
+      end
 
-    request
+      get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user)
 
-    expect(response).to have_gitlab_http_status(:not_found)
-    expect(json_response['message']).to eq('404 Integration Not Found')
+      expect(response).to have_gitlab_http_status(:not_found)
+      expect(json_response['message']).to eq('404 Integration Not Found')
+    end
   end
 
   it "returns error when authenticated but not a project owner" do
-    project.add_developer(user2)
-    get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user2)
+    parent_resource.add_developer(user2)
+
+    get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user2)
 
     expect(response).to have_gitlab_http_status(:forbidden)
   end