diff --git a/CHANGELOG b/CHANGELOG
index 19782969b85dbd7e75562c745a7347039ce2d9d5..5e115a8bb17bfbd729581da25a5987da235a9f5f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@ v 8.10.0 (unreleased)
   - Fix pagination when sorting by columns with lots of ties (like priority)
   - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
   - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
+  - Add API endpoint for a group issues !4520 (mahcsig)
 
 v 8.9.1
   - Fix merge requests project settings help link anchor
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 0bc82ef9edb9afaacfe354bbc1f5509929b4ff3c..708fc691f679673b703de2598e68380c45bcb9f0 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -28,7 +28,7 @@ GET /issues?labels=foo,bar&state=opened
 | Attribute | Type | Required | Description |
 | --------- | ---- | -------- | ----------- |
 | `state`   | string  | no    | Return all issues or just those that are `opened` or `closed`|
-| `labels`  | string  | no    | Comma-separated list of label names |
+| `labels`  | string  | no    | Comma-separated list of label names, issues with any of the labels will be returned |
 | `order_by`| string  | no    | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
 | `sort`    | string  | no    | Return requests sorted in `asc` or `desc` order. Default is `desc`  |
 
@@ -83,6 +83,82 @@ Example response:
 ]
 ```
 
+## List group issues
+
+Get a list of a group's issues.
+
+```
+GET /groups/:id/issues
+GET /groups/:id/issues?state=opened
+GET /groups/:id/issues?state=closed
+GET /groups/:id/issues?labels=foo
+GET /groups/:id/issues?labels=foo,bar
+GET /groups/:id/issues?labels=foo,bar&state=opened
+GET /groups/:id/issues?milestone=1.0.0
+GET /groups/:id/issues?milestone=1.0.0&state=opened
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id`      | integer | yes   | The ID of a group |
+| `state`   | string  | no    | Return all issues or just those that are `opened` or `closed`|
+| `labels`  | string  | no    | Comma-separated list of label names, issues must have all labels to be returned |
+| `milestone` | string| no    | The milestone title |
+| `order_by`| string  | no    | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort`    | string  | no    | Return requests sorted in `asc` or `desc` order. Default is `desc`  |
+
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues
+```
+
+Example response:
+
+```json
+[
+   {
+      "project_id" : 4,
+      "milestone" : {
+         "due_date" : null,
+         "project_id" : 4,
+         "state" : "closed",
+         "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+         "iid" : 3,
+         "id" : 11,
+         "title" : "v3.0",
+         "created_at" : "2016-01-04T15:31:39.788Z",
+         "updated_at" : "2016-01-04T15:31:39.788Z"
+      },
+      "author" : {
+         "state" : "active",
+         "web_url" : "https://gitlab.example.com/u/root",
+         "avatar_url" : null,
+         "username" : "root",
+         "id" : 1,
+         "name" : "Administrator"
+      },
+      "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+      "state" : "closed",
+      "iid" : 1,
+      "assignee" : {
+         "avatar_url" : null,
+         "web_url" : "https://gitlab.example.com/u/lennie",
+         "state" : "active",
+         "username" : "lennie",
+         "id" : 9,
+         "name" : "Dr. Luella Kovacek"
+      },
+      "labels" : [],
+      "id" : 41,
+      "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+      "updated_at" : "2016-01-04T15:31:46.176Z",
+      "created_at" : "2016-01-04T15:31:46.176Z",
+      "subscribed" : false,
+      "user_notes_count": 1
+   }
+]
+```
+
 ## List project issues
 
 Get a list of a project's issues.
@@ -104,7 +180,7 @@ GET /projects/:id/issues?iid=42
 | `id`      | integer | yes   | The ID of a project |
 | `iid`     | integer | no    | Return the issue having the given `iid` |
 | `state`   | string  | no    | Return all issues or just those that are `opened` or `closed`|
-| `labels`  | string  | no    | Comma-separated list of label names |
+| `labels`  | string  | no    | Comma-separated list of label names, issues with any of the labels will be returned |
 | `milestone` | string| no    | The milestone title |
 | `order_by`| string  | no    | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
 | `sort`    | string  | no    | Return requests sorted in `asc` or `desc` order. Default is `desc`  |
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 4c43257c48a75dffb7ea70f999d59e91669a23b5..8a03a41e9c592345685ce44e96375a82923387f6 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -59,6 +59,41 @@ module API
       end
     end
 
+    resource :groups do
+      # Get a list of group issues
+      #
+      # Parameters:
+      #   id (required) - The ID of a group
+      #   state (optional) - Return "opened" or "closed" issues
+      #   labels (optional) - Comma-separated list of label names
+      #   milestone (optional) - Milestone title
+      #   order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+      #   sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+      #
+      # Example Requests:
+      #   GET /groups/:id/issues
+      #   GET /groups/:id/issues?state=opened
+      #   GET /groups/:id/issues?state=closed
+      #   GET /groups/:id/issues?labels=foo
+      #   GET /groups/:id/issues?labels=foo,bar
+      #   GET /groups/:id/issues?labels=foo,bar&state=opened
+      #   GET /groups/:id/issues?milestone=1.0.0
+      #   GET /groups/:id/issues?milestone=1.0.0&state=closed
+      get ":id/issues" do
+        group = find_group(params[:id])
+
+        params[:state] ||= 'opened'
+        params[:group_id] = group.id
+        params[:milestone_title] = params.delete(:milestone)
+        params[:label_name] = params.delete(:labels)
+        params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort]
+
+        issues = IssuesFinder.new(current_user, params).execute
+
+        present paginate(issues), with: Entities::Issue, current_user: current_user
+      end
+    end
+
     resource :projects do
       # Get a list of project issues
       #
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 59e557c5b2a21d3c25359462fc947c7408bffc6e..edec53dad89fa8ec24cbe7c697fa2daac20e58f4 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -136,6 +136,148 @@ describe API::API, api: true  do
     end
   end
 
+  describe "GET /groups/:id/issues" do
+    let!(:group)            { create(:group) }
+    let!(:group_project)    { create(:project, :public, creator_id: user.id, namespace: group) }
+    let!(:group_closed_issue) do
+      create :closed_issue,
+             author: user,
+             assignee: user,
+             project: group_project,
+             state: :closed,
+             milestone: group_milestone
+    end
+    let!(:group_confidential_issue) do
+      create :issue,
+             :confidential,
+             project: group_project,
+             author: author,
+             assignee: assignee
+    end
+    let!(:group_issue) do
+      create :issue,
+             author: user,
+             assignee: user,
+             project: group_project,
+             milestone: group_milestone
+    end
+    let!(:group_label) do
+      create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
+    end
+    let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) }
+    let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) }
+    let!(:group_empty_milestone) do
+      create(:milestone, title: '4.0.0', project: group_project)
+    end
+    let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }
+
+    before do
+      group_project.team << [user, :reporter]
+    end
+    let(:base_url) { "/groups/#{group.id}/issues" }
+
+    it 'returns group issues without confidential issues for non project members' do
+      get api(base_url, non_member)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(1)
+      expect(json_response.first['title']).to eq(group_issue.title)
+    end
+
+    it 'returns group confidential issues for author' do
+      get api(base_url, author)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(2)
+    end
+
+    it 'returns group confidential issues for assignee' do
+      get api(base_url, assignee)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(2)
+    end
+
+    it 'returns group issues with confidential issues for project members' do
+      get api(base_url, user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(2)
+    end
+
+    it 'returns group confidential issues for admin' do
+      get api(base_url, admin)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(2)
+    end
+
+    it 'returns an array of labeled group issues' do
+      get api("#{base_url}?labels=#{group_label.title}", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(1)
+      expect(json_response.first['labels']).to eq([group_label.title])
+    end
+
+    it 'returns an array of labeled group issues where all labels match' do
+      get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(0)
+    end
+
+    it 'returns an empty array if no group issue matches labels' do
+      get api("#{base_url}?labels=foo,bar", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(0)
+    end
+
+    it 'returns an empty array if no issue matches milestone' do
+      get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(0)
+    end
+
+    it 'returns an empty array if milestone does not exist' do
+      get api("#{base_url}?milestone=foo", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(0)
+    end
+
+    it 'returns an array of issues in given milestone' do
+      get api("#{base_url}?milestone=#{group_milestone.title}", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(1)
+      expect(json_response.first['id']).to eq(group_issue.id)
+    end
+
+    it 'returns an array of issues matching state in milestone' do
+      get api("#{base_url}?milestone=#{group_milestone.title}"\
+              '&state=closed', user)
+
+      expect(response.status).to eq(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(1)
+      expect(json_response.first['id']).to eq(group_closed_issue.id)
+    end
+  end
+
   describe "GET /projects/:id/issues" do
     let(:base_url) { "/projects/#{project.id}" }
     let(:title) { milestone.title }