diff --git a/doc/api/group_wikis.md b/doc/api/group_wikis.md
new file mode 100644
index 0000000000000000000000000000000000000000..62094ffc940dd4235db98cb5e2cb82a977193656
--- /dev/null
+++ b/doc/api/group_wikis.md
@@ -0,0 +1,196 @@
+# Wikis API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212199) in GitLab 13.2.
+
+Available only in APIv4.
+
+## List wiki pages
+
+List all wiki pages for a given group.
+
+```plaintext
+GET /groups/:id/wikis
+```
+
+| Attribute      | Type           | Required | Description                                                                   |
+| ---------      | -------        | -------- | ---------------------                                                         |
+| `id`           | integer/string | yes      | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `with_content` | boolean        | no       | Include pages' content                                                        |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis?with_content=1"
+```
+
+Example response:
+
+```json
+[
+  {
+    "content" : "Here is an instruction how to deploy this project.",
+    "format" : "markdown",
+    "slug" : "deploy",
+    "title" : "deploy"
+  },
+  {
+    "content" : "Our development process is described here.",
+    "format" : "markdown",
+    "slug" : "development",
+    "title" : "development"
+  },{
+    "content" : "*  [Deploy](deploy)\n*  [Development](development)",
+    "format" : "markdown",
+    "slug" : "home",
+    "title" : "home"
+  }
+]
+```
+
+## Get a wiki page
+
+Get a wiki page for a given group.
+
+```plaintext
+GET /groups/:id/wikis/:slug
+```
+
+| Attribute | Type           | Required | Description                                                                   |
+| --------- | -------        | -------- | ---------------------                                                         |
+| `id`      | integer/string | yes      | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `slug`    | string         | yes      | The slug (a unique string) of the wiki page                                   |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis/home"
+```
+
+Example response:
+
+```json
+{
+  "content" : "home page",
+  "format" : "markdown",
+  "slug" : "home",
+  "title" : "home"
+}
+```
+
+## Create a new wiki page
+
+Create a new wiki page for the given repository with the given title, slug, and content.
+
+```plaintext
+POST /projects/:id/wikis
+```
+
+| Attribute     | Type           | Required | Description                                                                                            |
+| ------------- | -------        | -------- | ----------------------------                                                                           |
+| `id`          | integer/string | yes      | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding)                          |
+| `content`     | string         | yes      | The content of the wiki page                                                                           |
+| `title`       | string         | yes      | The title of the wiki page                                                                             |
+| `format`      | string         | no       | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
+
+```shell
+curl --data "format=rdoc&title=Hello&content=Hello world" \
+     --header "PRIVATE-TOKEN: <your_access_token>" \
+     "https://gitlab.example.com/api/v4/groups/1/wikis"
+```
+
+Example response:
+
+```json
+{
+  "content" : "Hello world",
+  "format" : "markdown",
+  "slug" : "Hello",
+  "title" : "Hello"
+}
+```
+
+## Edit an existing wiki page
+
+Update an existing wiki page. At least one parameter is required to update the wiki page.
+
+```plaintext
+PUT /groups/:id/wikis/:slug
+```
+
+| Attribute       | Type           | Required                          | Description                                                                                            |
+| --------------- | -------        | --------------------------------- | -------------------------------                                                                        |
+| `id`            | integer/string | yes                               | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding)                          |
+| `content`       | string         | yes if `title` is not provided    | The content of the wiki page                                                                           |
+| `title`         | string         | yes if `content` is not provided  | The title of the wiki page                                                                             |
+| `format`        | string         | no                                | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
+| `slug`          | string         | yes                               | The slug (a unique identifier) of the wiki page                                                        |
+
+```shell
+curl --request PUT --data "format=rdoc&content=documentation&title=Docs" \
+     --header "PRIVATE-TOKEN: <your_access_token>" \
+     "https://gitlab.example.com/api/v4/groups/1/wikis/foo"
+```
+
+Example response:
+
+```json
+{
+  "content" : "documentation",
+  "format" : "markdown",
+  "slug" : "Docs",
+  "title" : "Docs"
+}
+```
+
+## Delete a wiki page
+
+Delete a wiki page with a given slug.
+
+```plaintext
+DELETE /groups/:id/wikis/:slug
+```
+
+| Attribute | Type           | Required | Description                                                                   |
+| --------- | -------        | -------- | ---------------------                                                         |
+| `id`      | integer/string | yes      | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `slug`    | string         | yes      | The slug (a unique identifier) of the wiki page                               |
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis/foo"
+```
+
+On success the HTTP status code is `204` and no JSON response is expected.
+
+## Upload an attachment to the wiki repository
+
+Upload a file to the attachment folder inside the wiki's repository. The
+attachment folder is the `uploads` folder.
+
+```plaintext
+POST /groups/:id/wikis/attachments
+```
+
+| Attribute     | Type           | Required | Description                                                                   |
+| ------------- | -------        | -------- | ----------------------------                                                  |
+| `id`          | integer/string | yes      | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `file`        | string         | yes      | The attachment to be uploaded                                                 |
+| `branch`      | string         | no       | The name of the branch. Defaults to the wiki repository default branch        |
+
+To upload a file from your filesystem, use the `--form` argument. This causes
+cURL to post data using the header `Content-Type: multipart/form-data`.
+The `file=` parameter must point to a file on your filesystem and be preceded
+by `@`. For example:
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "file=@dk.png" "https://gitlab.example.com/api/v4/groups/1/wikis/attachments"
+```
+
+Example response:
+
+```json
+{
+  "file_name" : "dk.png",
+  "file_path" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
+  "branch" : "master",
+  "link" : {
+    "url" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
+    "markdown" : "![dk](uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png)"
+  }
+}
+```
diff --git a/ee/changelogs/unreleased/ajk-group-wiki-attachments.yml b/ee/changelogs/unreleased/ajk-group-wiki-attachments.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e1652625e271520d80837aa0cb12e19752222a4d
--- /dev/null
+++ b/ee/changelogs/unreleased/ajk-group-wiki-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Add group wiki REST API
+merge_request: 34232
+author:
+type: added
diff --git a/ee/lib/ee/api/helpers/wikis_helpers.rb b/ee/lib/ee/api/helpers/wikis_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f563cf1b68b4e25cd2762bbb5c4ff8cbc0c7abef
--- /dev/null
+++ b/ee/lib/ee/api/helpers/wikis_helpers.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module EE
+  module API
+    module Helpers
+      module WikisHelpers
+        extend ActiveSupport::Concern
+        extend ::Gitlab::Utils::Override
+
+        class_methods do
+          def wiki_resource_kinds
+            [:groups, *super]
+          end
+        end
+
+        override :find_container
+        def find_container(kind)
+          return user_group if kind == :groups
+
+          super
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/requests/api/wikis_spec.rb b/ee/spec/requests/api/wikis_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c144c0be9e2698362d97c70b36c3fe006a3c0161
--- /dev/null
+++ b/ee/spec/requests/api/wikis_spec.rb
@@ -0,0 +1,701 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# For every API endpoint we test 3 states of wikis:
+# - disabled
+# - enabled only for team members
+# - enabled for everyone who has access
+# Every state is tested for 3 user roles:
+# - guest
+# - developer
+# - maintainer
+# because they are 3 edge cases of using wiki pages.
+
+describe API::Wikis do
+  include WorkhorseHelpers
+
+  let_it_be(:user) { create(:user) }
+  let(:group) { create(:group, :internal, :wiki_repo) }
+  let(:wiki) { create(:group_wiki, container: group, user: user) }
+  let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
+  let(:expected_keys_with_content) { %w(content format slug title) }
+  let(:expected_keys_without_content) { %w(format slug title) }
+
+  shared_examples_for 'wiki API 404 Group Not Found' do
+    include_examples 'wiki API 404 Not Found', 'Group'
+  end
+
+  describe 'GET /groups/:id/wikis' do
+    let(:url) { "/groups/#{group.id}/wikis" }
+
+    context 'when group wiki is disabled' do
+      before do
+        stub_feature_flags(group_wiki: false)
+      end
+
+      context 'when user is guest' do
+        before do
+          get api(url)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+    end
+
+    # Skipped pending https://gitlab.com/gitlab-org/gitlab/-/issues/208412
+    xcontext 'when wiki is available only for team members' do
+      let(:group) { create(:group, :wiki_repo, :wiki_private) }
+
+      context 'when user is guest' do
+        before do
+          get api(url)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wikis API returns list of wiki pages'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wikis API returns list of wiki pages'
+      end
+    end
+
+    context 'when wiki is available for everyone with access' do
+      context 'when user is guest' do
+        before do
+          get api(url)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wikis API returns list of wiki pages'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wikis API returns list of wiki pages'
+      end
+    end
+  end
+
+  describe 'GET /groups/:id/wikis/:slug' do
+    let(:page) { create(:wiki_page, wiki: wiki) }
+    let(:url) { "/groups/#{group.id}/wikis/#{page.slug}" }
+
+    context 'when wiki is disabled' do
+      before do
+        stub_feature_flags(group_wiki: false)
+      end
+
+      context 'when user is guest' do
+        before do
+          get api(url)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+    end
+
+    # Skipped pending https://gitlab.com/gitlab-org/gitlab/-/issues/208412
+    xcontext 'when wiki is available only for team members' do
+      let(:group) { create(:group, :wiki_repo, :wiki_private) }
+
+      context 'when user is guest' do
+        before do
+          get api(url)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+          get api(url, user)
+        end
+
+        include_examples 'wikis API returns wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wikis API returns wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+    end
+
+    context 'when wiki is available for everyone with access' do
+      context 'when user is guest' do
+        before do
+          get api(url)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wikis API returns wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          get api(url, user)
+        end
+
+        include_examples 'wikis API returns wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+    end
+  end
+
+  describe 'POST /groups/:id/wikis' do
+    let(:payload) { { title: 'title', content: 'content' } }
+    let(:url) { "/groups/#{group.id}/wikis" }
+
+    context 'when wiki is disabled' do
+      before do
+        stub_feature_flags(group_wiki: false)
+      end
+
+      context 'when user is guest' do
+        before do
+          post(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+          post(api(url, user), params: payload)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+          post(api(url, user), params: payload)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+    end
+
+    xcontext 'when wiki is available only for team members' do
+      let(:group) { create(:group, :wiki_private, :wiki_repo) }
+
+      context 'when user is guest' do
+        before do
+          post(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wikis API creates wiki page'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wikis API creates wiki page'
+      end
+    end
+
+    context 'when wiki is available for everyone with access' do
+      context 'when user is guest' do
+        before do
+          post(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wikis API creates wiki page'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wikis API creates wiki page'
+      end
+    end
+  end
+
+  describe 'PUT /group/:id/wikis/:slug' do
+    let(:page) { create(:wiki_page, wiki: wiki) }
+    let(:payload) { { title: 'new title', content: 'new content' } }
+    let(:url) { "/groups/#{group.id}/wikis/#{page.slug}" }
+
+    context 'when wiki is disabled' do
+      before do
+        stub_feature_flags(group_wiki: false)
+      end
+
+      context 'when user is guest' do
+        before do
+          put(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          put(api(url, user), params: payload)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          put(api(url, user), params: payload)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+    end
+
+    xcontext 'when wiki is available only for team members' do
+      let(:group) { create(:group, :wiki_private, :wiki_repo) }
+
+      context 'when user is guest' do
+        before do
+          put(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wikis API updates wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          before do
+            put(api(url, user), params: payload)
+          end
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wikis API updates wiki page'
+
+        context 'when page is not existing' do
+          let(:url) { "/group/#{group.id}/wikis/unknown" }
+
+          before do
+            put(api(url, user), params: payload)
+          end
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+    end
+
+    context 'when wiki is available for everyone with access' do
+      context 'when user is guest' do
+        before do
+          put(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wikis API updates wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          before do
+            put(api(url, user), params: payload)
+          end
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wikis API updates wiki page'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          before do
+            put(api(url, user), params: payload)
+          end
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+    end
+
+    context 'when user is owner of parent group' do
+      let(:namespace) { create(:group).tap { |g| g.add_owner(user) } }
+      let(:group) { create(:group, :wiki_repo, parent: namespace) }
+
+      include_examples 'wikis API updates wiki page'
+    end
+  end
+
+  describe 'DELETE /groups/:id/wikis/:slug' do
+    let(:page) { create(:wiki_page, wiki: wiki) }
+    let(:url) { "/groups/#{group.id}/wikis/#{page.slug}" }
+
+    context 'when wiki is disabled' do
+      before do
+        stub_feature_flags(group_wiki: false)
+      end
+
+      context 'when user is guest' do
+        before do
+          delete(api(url))
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          delete(api(url, user))
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          delete(api(url, user))
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+    end
+
+    # Skipped pending https://gitlab.com/gitlab-org/gitlab/-/issues/208412
+    xcontext 'when wiki is available only for team members' do
+      let(:group) { create(:group, :wiki_repo, :wiki_private) }
+
+      context 'when user is guest' do
+        before do
+          delete(api(url))
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          delete(api(url, user))
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          delete(api(url, user))
+        end
+
+        include_examples 'wiki API 204 No Content'
+      end
+    end
+
+    context 'when wiki is available for everyone with access' do
+      context 'when user is guest' do
+        before do
+          delete(api(url))
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+
+          delete(api(url, user))
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+
+          delete(api(url, user))
+        end
+
+        include_examples 'wiki API 204 No Content'
+
+        context 'when page does not exist' do
+          let(:url) { "/groups/#{group.id}/wikis/unknown" }
+
+          include_examples 'wiki API 404 Wiki Page Not Found'
+        end
+      end
+    end
+
+    context 'when user is owner of parent group' do
+      let(:namespace) { create(:group).tap { |g| g.add_owner(user) } }
+      let(:group) { create(:group, :wiki_repo, parent: namespace) }
+
+      before do
+        delete(api(url, user))
+      end
+
+      include_examples 'wiki API 204 No Content'
+    end
+  end
+
+  describe 'POST /groups/:id/wikis/attachments' do
+    let(:payload) { { file: fixture_file_upload('spec/fixtures/dk.png') } }
+    let(:url) { "/groups/#{group.id}/wikis/attachments" }
+    let(:file_path) { "#{Wikis::CreateAttachmentService::ATTACHMENT_PATH}/fixed_hex/dk.png" }
+    let(:result_hash) do
+      {
+        file_name: 'dk.png',
+        file_path: file_path,
+        branch: 'master',
+        link: {
+          url: file_path,
+          markdown: "![dk](#{file_path})"
+        }
+      }
+    end
+
+    context 'when wiki is disabled' do
+      before do
+        stub_feature_flags(group_wiki: false)
+      end
+
+      context 'when user is guest' do
+        before do
+          post(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+          post(api(url, user), params: payload)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+          post(api(url, user), params: payload)
+        end
+
+        include_examples 'wiki API 403 Forbidden'
+      end
+    end
+
+    # Skipped pending https://gitlab.com/gitlab-org/gitlab/-/issues/208412
+    xcontext 'when wiki is available only for team members' do
+      let(:group) { create(:group, :wiki_private, :wiki_repo) }
+
+      context 'when user is guest' do
+        before do
+          post(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wiki API uploads wiki attachment'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wiki API uploads wiki attachment'
+      end
+    end
+
+    context 'when wiki is available for everyone with access' do
+      context 'when user is guest' do
+        before do
+          post(api(url), params: payload)
+        end
+
+        include_examples 'wiki API 404 Group Not Found'
+      end
+
+      context 'when user is developer' do
+        before do
+          group.add_developer(user)
+        end
+
+        include_examples 'wiki API uploads wiki attachment'
+      end
+
+      context 'when user is maintainer' do
+        before do
+          group.add_maintainer(user)
+        end
+
+        include_examples 'wiki API uploads wiki attachment'
+      end
+    end
+  end
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b1bbc3cf0fde371ad68397c8f685c205a0b71498..d8569dea4f1d1d5168bbc36bfef1c8c1bdab6ba4 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -79,12 +79,6 @@ def user_project
       @project ||= find_project!(params[:id])
     end
 
-    def wiki_page
-      page = ProjectWiki.new(user_project, current_user).find_page(params[:slug])
-
-      page || not_found!('Wiki Page')
-    end
-
     def available_labels_for(label_parent, include_ancestor_groups: true)
       search_params = { include_ancestor_groups: include_ancestor_groups }
 
diff --git a/lib/api/helpers/wikis_helpers.rb b/lib/api/helpers/wikis_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49da1e317abf839e06c1f98cc6a3084fa2a06967
--- /dev/null
+++ b/lib/api/helpers/wikis_helpers.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module API
+  module Helpers
+    module WikisHelpers
+      def self.wiki_resource_kinds
+        [:projects]
+      end
+
+      def find_container(kind)
+        return user_project if kind == :projects
+
+        raise "Unknown wiki container #{kind}"
+      end
+
+      def wiki_page
+        Wiki.for_container(container, current_user).find_page(params[:slug]) || not_found!('Wiki Page')
+      end
+
+      def commit_params(attrs)
+        base_params = { branch_name: attrs[:branch] }
+        file_details = case attrs[:file]
+                       when Hash # legacy format: TODO remove when we drop support for non accelerated uploads
+                         { file_name: attrs[:file][:filename], file_content: attrs[:file][:tempfile].read }
+                       else
+                         { file_name: attrs[:file].original_filename, file_content: attrs[:file].read }
+                       end
+
+        base_params.merge(file_details)
+      end
+    end
+  end
+end
+
+API::Helpers::WikisHelpers.prepend_if_ee('EE::API::Helpers::WikisHelpers')
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index c1bf3a6492326b16f3ea5b6ac41d1a383fa2ac35..006dc257fe1dae186e96c47ec6441667edb15a71 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -2,24 +2,10 @@
 
 module API
   class Wikis < Grape::API
+    helpers ::API::Helpers::WikisHelpers
+
     helpers do
-      def commit_params(attrs)
-        # In order to avoid service disruption this can work with an old workhorse without the acceleration
-        # the first branch of this if must be removed when we drop support for non accelerated uploads
-        if attrs[:file].is_a?(Hash)
-          {
-            file_name: attrs[:file][:filename],
-            file_content: attrs[:file][:tempfile].read,
-            branch_name: attrs[:branch]
-          }
-        else
-          {
-            file_name: attrs[:file].original_filename,
-            file_content: attrs[:file].read,
-            branch_name: attrs[:branch]
-          }
-        end
-      end
+      attr_reader :container
 
       params :common_wiki_page_params do
         optional :format,
@@ -32,108 +18,118 @@ def commit_params(attrs)
 
     WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
 
-    resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do
-      desc 'Get a list of wiki pages' do
-        success Entities::WikiPageBasic
-      end
-      params do
-        optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
-      end
-      get ':id/wikis' do
-        authorize! :read_wiki, user_project
-
-        entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
+    ::API::Helpers::WikisHelpers.wiki_resource_kinds.each do |container_resource|
+      resource container_resource, requirements: WIKI_ENDPOINT_REQUIREMENTS do
+        after_validation do
+          @container = Gitlab::Lazy.new { find_container(container_resource) }
+        end
 
-        present user_project.wiki.list_pages(load_content: params[:with_content]), with: entity
-      end
+        desc 'Get a list of wiki pages' do
+          success Entities::WikiPageBasic
+        end
+        params do
+          optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
+        end
+        get ':id/wikis' do
+          authorize! :read_wiki, container
 
-      desc 'Get a wiki page' do
-        success Entities::WikiPage
-      end
-      params do
-        requires :slug, type: String, desc: 'The slug of a wiki page'
-      end
-      get ':id/wikis/:slug' do
-        authorize! :read_wiki, user_project
+          entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
 
-        present wiki_page, with: Entities::WikiPage
-      end
+          present container.wiki.list_pages(load_content: params[:with_content]), with: entity
+        end
 
-      desc 'Create a wiki page' do
-        success Entities::WikiPage
-      end
-      params do
-        requires :title, type: String, desc: 'Title of a wiki page'
-        requires :content, type: String, desc: 'Content of a wiki page'
-        use :common_wiki_page_params
-      end
-      post ':id/wikis' do
-        authorize! :create_wiki, user_project
+        desc 'Get a wiki page' do
+          success Entities::WikiPage
+        end
+        params do
+          requires :slug, type: String, desc: 'The slug of a wiki page'
+        end
+        get ':id/wikis/:slug' do
+          authorize! :read_wiki, container
 
-        page = WikiPages::CreateService.new(container: user_project, current_user: current_user, params: params).execute
+          present wiki_page, with: Entities::WikiPage
+        end
 
-        if page.valid?
-          present page, with: Entities::WikiPage
-        else
-          render_validation_error!(page)
+        desc 'Create a wiki page' do
+          success Entities::WikiPage
         end
-      end
+        params do
+          requires :title, type: String, desc: 'Title of a wiki page'
+          requires :content, type: String, desc: 'Content of a wiki page'
+          use :common_wiki_page_params
+        end
+        post ':id/wikis' do
+          authorize! :create_wiki, container
 
-      desc 'Update a wiki page' do
-        success Entities::WikiPage
-      end
-      params do
-        optional :title, type: String, desc: 'Title of a wiki page'
-        optional :content, type: String, desc: 'Content of a wiki page'
-        use :common_wiki_page_params
-        at_least_one_of :content, :title, :format
-      end
-      put ':id/wikis/:slug' do
-        authorize! :create_wiki, user_project
+          page = WikiPages::CreateService.new(container: container, current_user: current_user, params: params).execute
 
-        page = WikiPages::UpdateService.new(container: user_project, current_user: current_user, params: params).execute(wiki_page)
+          if page.valid?
+            present page, with: Entities::WikiPage
+          else
+            render_validation_error!(page)
+          end
+        end
 
-        if page.valid?
-          present page, with: Entities::WikiPage
-        else
-          render_validation_error!(page)
+        desc 'Update a wiki page' do
+          success Entities::WikiPage
+        end
+        params do
+          optional :title, type: String, desc: 'Title of a wiki page'
+          optional :content, type: String, desc: 'Content of a wiki page'
+          use :common_wiki_page_params
+          at_least_one_of :content, :title, :format
+        end
+        put ':id/wikis/:slug' do
+          authorize! :create_wiki, container
+
+          page = WikiPages::UpdateService
+            .new(container: container, current_user: current_user, params: params)
+            .execute(wiki_page)
+
+          if page.valid?
+            present page, with: Entities::WikiPage
+          else
+            render_validation_error!(page)
+          end
         end
-      end
 
-      desc 'Delete a wiki page'
-      params do
-        requires :slug, type: String, desc: 'The slug of a wiki page'
-      end
-      delete ':id/wikis/:slug' do
-        authorize! :admin_wiki, user_project
+        desc 'Delete a wiki page'
+        params do
+          requires :slug, type: String, desc: 'The slug of a wiki page'
+        end
+        delete ':id/wikis/:slug' do
+          authorize! :admin_wiki, container
 
-        WikiPages::DestroyService.new(container: user_project, current_user: current_user).execute(wiki_page)
+          WikiPages::DestroyService
+            .new(container: container, current_user: current_user)
+            .execute(wiki_page)
 
-        no_content!
-      end
+          no_content!
+        end
 
-      desc 'Upload an attachment to the wiki repository' do
-        detail 'This feature was introduced in GitLab 11.3.'
-        success Entities::WikiAttachment
-      end
-      params do
-        requires :file, types: [::API::Validations::Types::SafeFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
-        optional :branch, type: String, desc: 'The name of the branch'
-      end
-      post ":id/wikis/attachments" do
-        authorize! :create_wiki, user_project
-
-        result = ::Wikis::CreateAttachmentService.new(
-          container: user_project,
-          current_user: current_user,
-          params: commit_params(declared_params(include_missing: false))
-        ).execute
-
-        if result[:status] == :success
-          status(201)
-          present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
-        else
-          render_api_error!(result[:message], 400)
+        desc 'Upload an attachment to the wiki repository' do
+          detail 'This feature was introduced in GitLab 11.3.'
+          success Entities::WikiAttachment
+        end
+        params do
+          requires :file, types: [::API::Validations::Types::SafeFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
+          optional :branch, type: String, desc: 'The name of the branch'
+        end
+        post ":id/wikis/attachments" do
+          authorize! :create_wiki, container
+
+          result = ::Wikis::CreateAttachmentService.new(
+            container: container,
+            current_user: current_user,
+            params: commit_params(declared_params(include_missing: false))
+          ).execute
+
+          if result[:status] == :success
+            status(201)
+            present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
+          else
+            render_api_error!(result[:message], 400)
+          end
         end
       end
     end
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index ea22e4421278c9ed108e100f69cfa7bb80390832..f271f8aa853bd58d4980b59335900301d3a08c58 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -21,178 +21,10 @@
   let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
   let(:expected_keys_with_content) { %w(content format slug title) }
   let(:expected_keys_without_content) { %w(format slug title) }
+  let(:wiki) { project_wiki }
 
-  shared_examples_for 'returns list of wiki pages' do
-    context 'when wiki has pages' do
-      let!(:pages) do
-        [create(:wiki_page, wiki: project_wiki, title: 'page1', content: 'content of page1'),
-         create(:wiki_page, wiki: project_wiki, title: 'page2.with.dot', content: 'content of page2')]
-      end
-
-      it 'returns the list of wiki pages without content' do
-        get api(url, user)
-
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response.size).to eq(2)
-
-        json_response.each_with_index do |page, index|
-          expect(page.keys).to match_array(expected_keys_without_content)
-          expect(page['slug']).to eq(pages[index].slug)
-          expect(page['title']).to eq(pages[index].title)
-        end
-      end
-
-      it 'returns the list of wiki pages with content' do
-        get api(url, user), params: { with_content: 1 }
-
-        expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response.size).to eq(2)
-
-        json_response.each_with_index do |page, index|
-          expect(page.keys).to match_array(expected_keys_with_content)
-          expect(page['content']).to eq(pages[index].content)
-          expect(page['slug']).to eq(pages[index].slug)
-          expect(page['title']).to eq(pages[index].title)
-        end
-      end
-    end
-
-    it 'return the empty list of wiki pages' do
-      get api(url, user)
-
-      expect(response).to have_gitlab_http_status(:ok)
-      expect(json_response.size).to eq(0)
-    end
-  end
-
-  shared_examples_for 'returns wiki page' do
-    it 'returns the wiki page' do
-      expect(response).to have_gitlab_http_status(:ok)
-      expect(json_response.size).to eq(4)
-      expect(json_response.keys).to match_array(expected_keys_with_content)
-      expect(json_response['content']).to eq(page.content)
-      expect(json_response['slug']).to eq(page.slug)
-      expect(json_response['title']).to eq(page.title)
-    end
-  end
-
-  shared_examples_for 'creates wiki page' do
-    it 'creates the wiki page' do
-      post(api(url, user), params: payload)
-
-      expect(response).to have_gitlab_http_status(:created)
-      expect(json_response.size).to eq(4)
-      expect(json_response.keys).to match_array(expected_keys_with_content)
-      expect(json_response['content']).to eq(payload[:content])
-      expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
-      expect(json_response['title']).to eq(payload[:title])
-      expect(json_response['rdoc']).to eq(payload[:rdoc])
-    end
-
-    [:title, :content].each do |part|
-      it "responds with validation error on empty #{part}" do
-        payload.delete(part)
-
-        post(api(url, user), params: payload)
-
-        expect(response).to have_gitlab_http_status(:bad_request)
-        expect(json_response.size).to eq(1)
-        expect(json_response['error']).to eq("#{part} is missing")
-      end
-    end
-  end
-
-  shared_examples_for 'updates wiki page' do
-    it 'updates the wiki page' do
-      put(api(url, user), params: payload)
-
-      expect(response).to have_gitlab_http_status(:ok)
-      expect(json_response.size).to eq(4)
-      expect(json_response.keys).to match_array(expected_keys_with_content)
-      expect(json_response['content']).to eq(payload[:content])
-      expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
-      expect(json_response['title']).to eq(payload[:title])
-    end
-
-    [:title, :content, :format].each do |part|
-      it "updates with wiki with missing #{part}" do
-        payload.delete(part)
-
-        put(api(url, user), params: payload)
-
-        expect(response).to have_gitlab_http_status(:ok)
-      end
-    end
-  end
-
-  shared_examples_for '403 Forbidden' do
-    it 'returns 403 Forbidden' do
-      expect(response).to have_gitlab_http_status(:forbidden)
-      expect(json_response.size).to eq(1)
-      expect(json_response['message']).to eq('403 Forbidden')
-    end
-  end
-
-  shared_examples_for '404 Wiki Page Not Found' do
-    it 'returns 404 Wiki Page Not Found' do
-      expect(response).to have_gitlab_http_status(:not_found)
-      expect(json_response.size).to eq(1)
-      expect(json_response['message']).to eq('404 Wiki Page Not Found')
-    end
-  end
-
-  shared_examples_for '404 Project Not Found' do
-    it 'returns 404 Project Not Found' do
-      expect(response).to have_gitlab_http_status(:not_found)
-      expect(json_response.size).to eq(1)
-      expect(json_response['message']).to eq('404 Project Not Found')
-    end
-  end
-
-  shared_examples_for '204 No Content' do
-    it 'returns 204 No Content' do
-      expect(response).to have_gitlab_http_status(:no_content)
-    end
-  end
-
-  shared_examples_for 'uploads wiki attachment' do
-    it 'pushes attachment to the wiki repository' do
-      allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
-      workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
-
-      expect(response).to have_gitlab_http_status(:created)
-      expect(json_response).to eq result_hash.deep_stringify_keys
-    end
-
-    it 'responds with validation error on empty file' do
-      payload.delete(:file)
-
-      post(api(url, user), params: payload)
-
-      expect(response).to have_gitlab_http_status(:bad_request)
-      expect(json_response.size).to eq(1)
-      expect(json_response['error']).to eq('file is missing')
-    end
-
-    it 'responds with validation error on invalid temp file' do
-      payload[:file] = { tempfile: '/etc/hosts' }
-
-      post(api(url, user), params: payload)
-
-      expect(response).to have_gitlab_http_status(:bad_request)
-      expect(json_response.size).to eq(1)
-      expect(json_response['error']).to eq('file is invalid')
-    end
-
-    it 'is backward compatible with regular multipart uploads' do
-      allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
-      post(api(url, user), params: payload)
-
-      expect(response).to have_gitlab_http_status(:created)
-      expect(json_response).to eq result_hash.deep_stringify_keys
-    end
+  shared_examples_for 'wiki API 404 Project Not Found' do
+    include_examples 'wiki API 404 Not Found', 'Project'
   end
 
   describe 'GET /projects/:id/wikis' do
@@ -206,7 +38,7 @@
           get api(url)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -216,7 +48,7 @@
           get api(url, user)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -226,7 +58,7 @@
           get api(url, user)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
     end
 
@@ -238,7 +70,7 @@
           get api(url)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -246,7 +78,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'returns list of wiki pages'
+        include_examples 'wikis API returns list of wiki pages'
       end
 
       context 'when user is maintainer' do
@@ -254,7 +86,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'returns list of wiki pages'
+        include_examples 'wikis API returns list of wiki pages'
       end
     end
 
@@ -266,7 +98,7 @@
           get api(url)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -274,7 +106,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'returns list of wiki pages'
+        include_examples 'wikis API returns list of wiki pages'
       end
 
       context 'when user is maintainer' do
@@ -282,7 +114,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'returns list of wiki pages'
+        include_examples 'wikis API returns list of wiki pages'
       end
     end
   end
@@ -299,7 +131,7 @@
           get api(url)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -309,7 +141,7 @@
           get api(url, user)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -319,7 +151,7 @@
           get api(url, user)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
     end
 
@@ -331,7 +163,7 @@
           get api(url)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -340,12 +172,12 @@
           get api(url, user)
         end
 
-        include_examples 'returns wiki page'
+        include_examples 'wikis API returns wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
 
@@ -356,12 +188,12 @@
           get api(url, user)
         end
 
-        include_examples 'returns wiki page'
+        include_examples 'wikis API returns wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
     end
@@ -374,7 +206,7 @@
           get api(url)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -384,12 +216,12 @@
           get api(url, user)
         end
 
-        include_examples 'returns wiki page'
+        include_examples 'wikis API returns wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
 
@@ -400,12 +232,12 @@
           get api(url, user)
         end
 
-        include_examples 'returns wiki page'
+        include_examples 'wikis API returns wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
     end
@@ -423,7 +255,7 @@
           post(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -432,7 +264,7 @@
           post(api(url, user), params: payload)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -441,7 +273,7 @@
           post(api(url, user), params: payload)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
     end
 
@@ -453,7 +285,7 @@
           post(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -461,7 +293,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'creates wiki page'
+        include_examples 'wikis API creates wiki page'
       end
 
       context 'when user is maintainer' do
@@ -469,7 +301,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'creates wiki page'
+        include_examples 'wikis API creates wiki page'
       end
     end
 
@@ -481,7 +313,7 @@
           post(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -489,7 +321,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'creates wiki page'
+        include_examples 'wikis API creates wiki page'
       end
 
       context 'when user is maintainer' do
@@ -497,7 +329,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'creates wiki page'
+        include_examples 'wikis API creates wiki page'
       end
     end
   end
@@ -515,7 +347,7 @@
           put(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -525,7 +357,7 @@
           put(api(url, user), params: payload)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -535,7 +367,7 @@
           put(api(url, user), params: payload)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
     end
 
@@ -547,7 +379,7 @@
           put(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -555,7 +387,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'updates wiki page'
+        include_examples 'wikis API updates wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -564,7 +396,7 @@
             put(api(url, user), params: payload)
           end
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
 
@@ -573,7 +405,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'updates wiki page'
+        include_examples 'wikis API updates wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -582,7 +414,7 @@
             put(api(url, user), params: payload)
           end
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
     end
@@ -595,7 +427,7 @@
           put(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -603,7 +435,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'updates wiki page'
+        include_examples 'wikis API updates wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -612,7 +444,7 @@
             put(api(url, user), params: payload)
           end
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
 
@@ -621,7 +453,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'updates wiki page'
+        include_examples 'wikis API updates wiki page'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -630,7 +462,7 @@
             put(api(url, user), params: payload)
           end
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
     end
@@ -638,7 +470,7 @@
     context 'when wiki belongs to a group project' do
       let(:project) { create(:project, :wiki_repo, namespace: group) }
 
-      include_examples 'updates wiki page'
+      include_examples 'wikis API updates wiki page'
     end
   end
 
@@ -654,7 +486,7 @@
           delete(api(url))
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -664,7 +496,7 @@
           delete(api(url, user))
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -674,7 +506,7 @@
           delete(api(url, user))
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
     end
 
@@ -686,7 +518,7 @@
           delete(api(url))
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -696,7 +528,7 @@
           delete(api(url, user))
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -706,7 +538,7 @@
           delete(api(url, user))
         end
 
-        include_examples '204 No Content'
+        include_examples 'wiki API 204 No Content'
       end
     end
 
@@ -718,7 +550,7 @@
           delete(api(url))
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -728,7 +560,7 @@
           delete(api(url, user))
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -738,12 +570,12 @@
           delete(api(url, user))
         end
 
-        include_examples '204 No Content'
+        include_examples 'wiki API 204 No Content'
 
         context 'when page is not existing' do
           let(:url) { "/projects/#{project.id}/wikis/unknown" }
 
-          include_examples '404 Wiki Page Not Found'
+          include_examples 'wiki API 404 Wiki Page Not Found'
         end
       end
     end
@@ -755,7 +587,7 @@
         delete(api(url, user))
       end
 
-      include_examples '204 No Content'
+      include_examples 'wiki API 204 No Content'
     end
   end
 
@@ -783,7 +615,7 @@
           post(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -792,7 +624,7 @@
           post(api(url, user), params: payload)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
 
       context 'when user is maintainer' do
@@ -801,7 +633,7 @@
           post(api(url, user), params: payload)
         end
 
-        include_examples '403 Forbidden'
+        include_examples 'wiki API 403 Forbidden'
       end
     end
 
@@ -813,7 +645,7 @@
           post(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -821,7 +653,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'uploads wiki attachment'
+        include_examples 'wiki API uploads wiki attachment'
       end
 
       context 'when user is maintainer' do
@@ -829,7 +661,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'uploads wiki attachment'
+        include_examples 'wiki API uploads wiki attachment'
       end
     end
 
@@ -841,7 +673,7 @@
           post(api(url), params: payload)
         end
 
-        include_examples '404 Project Not Found'
+        include_examples 'wiki API 404 Project Not Found'
       end
 
       context 'when user is developer' do
@@ -849,7 +681,7 @@
           project.add_developer(user)
         end
 
-        include_examples 'uploads wiki attachment'
+        include_examples 'wiki API uploads wiki attachment'
       end
 
       context 'when user is maintainer' do
@@ -857,7 +689,7 @@
           project.add_maintainer(user)
         end
 
-        include_examples 'uploads wiki attachment'
+        include_examples 'wiki API uploads wiki attachment'
       end
     end
   end
diff --git a/spec/support/shared_examples/lib/wikis_api_examples.rb b/spec/support/shared_examples/lib/wikis_api_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2e4c667d37e3a5a36f1aab87e389408aed40c306
--- /dev/null
+++ b/spec/support/shared_examples/lib/wikis_api_examples.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'wikis API returns list of wiki pages' do
+  context 'when wiki has pages' do
+    let!(:pages) do
+      [create(:wiki_page, wiki: wiki, title: 'page1', content: 'content of page1'),
+       create(:wiki_page, wiki: wiki, title: 'page2.with.dot', content: 'content of page2')]
+    end
+
+    it 'returns the list of wiki pages without content' do
+      get api(url, user)
+
+      expect(response).to have_gitlab_http_status(:ok)
+      expect(json_response.size).to eq(2)
+
+      json_response.each_with_index do |page, index|
+        expect(page.keys).to match_array(expected_keys_without_content)
+        expect(page['slug']).to eq(pages[index].slug)
+        expect(page['title']).to eq(pages[index].title)
+      end
+    end
+
+    it 'returns the list of wiki pages with content' do
+      get api(url, user), params: { with_content: 1 }
+
+      expect(response).to have_gitlab_http_status(:ok)
+      expect(json_response.size).to eq(2)
+
+      json_response.each_with_index do |page, index|
+        expect(page.keys).to match_array(expected_keys_with_content)
+        expect(page['content']).to eq(pages[index].content)
+        expect(page['slug']).to eq(pages[index].slug)
+        expect(page['title']).to eq(pages[index].title)
+      end
+    end
+  end
+
+  it 'return the empty list of wiki pages' do
+    get api(url, user)
+
+    expect(response).to have_gitlab_http_status(:ok)
+    expect(json_response.size).to eq(0)
+  end
+end
+
+RSpec.shared_examples_for 'wikis API returns wiki page' do
+  it 'returns the wiki page' do
+    expect(response).to have_gitlab_http_status(:ok)
+    expect(json_response.size).to eq(4)
+    expect(json_response.keys).to match_array(expected_keys_with_content)
+    expect(json_response['content']).to eq(page.content)
+    expect(json_response['slug']).to eq(page.slug)
+    expect(json_response['title']).to eq(page.title)
+  end
+end
+
+RSpec.shared_examples_for 'wikis API creates wiki page' do
+  it 'creates the wiki page' do
+    post(api(url, user), params: payload)
+
+    expect(response).to have_gitlab_http_status(:created)
+    expect(json_response.size).to eq(4)
+    expect(json_response.keys).to match_array(expected_keys_with_content)
+    expect(json_response['content']).to eq(payload[:content])
+    expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+    expect(json_response['title']).to eq(payload[:title])
+    expect(json_response['rdoc']).to eq(payload[:rdoc])
+  end
+
+  [:title, :content].each do |part|
+    it "responds with validation error on empty #{part}" do
+      payload.delete(part)
+
+      post(api(url, user), params: payload)
+
+      expect(response).to have_gitlab_http_status(:bad_request)
+      expect(json_response.size).to eq(1)
+      expect(json_response['error']).to eq("#{part} is missing")
+    end
+  end
+end
+
+RSpec.shared_examples_for 'wikis API updates wiki page' do
+  it 'updates the wiki page' do
+    put(api(url, user), params: payload)
+
+    expect(response).to have_gitlab_http_status(:ok)
+    expect(json_response.size).to eq(4)
+    expect(json_response.keys).to match_array(expected_keys_with_content)
+    expect(json_response['content']).to eq(payload[:content])
+    expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
+    expect(json_response['title']).to eq(payload[:title])
+  end
+
+  [:title, :content, :format].each do |part|
+    it "updates with wiki with missing #{part}" do
+      payload.delete(part)
+
+      put(api(url, user), params: payload)
+
+      expect(response).to have_gitlab_http_status(:ok)
+    end
+  end
+end
+
+RSpec.shared_examples_for 'wiki API 403 Forbidden' do
+  it 'returns 403 Forbidden' do
+    expect(response).to have_gitlab_http_status(:forbidden)
+    expect(json_response.size).to eq(1)
+    expect(json_response['message']).to eq('403 Forbidden')
+  end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Wiki Page Not Found' do
+  it 'returns 404 Wiki Page Not Found' do
+    expect(response).to have_gitlab_http_status(:not_found)
+    expect(json_response.size).to eq(1)
+    expect(json_response['message']).to eq('404 Wiki Page Not Found')
+  end
+end
+
+RSpec.shared_examples_for 'wiki API 404 Not Found' do |what|
+  it "returns 404 #{what} Not Found" do
+    expect(response).to have_gitlab_http_status(:not_found)
+    expect(json_response.size).to eq(1)
+    expect(json_response['message']).to eq("404 #{what} Not Found")
+  end
+end
+
+RSpec.shared_examples_for 'wiki API 204 No Content' do
+  it 'returns 204 No Content' do
+    expect(response).to have_gitlab_http_status(:no_content)
+  end
+end
+
+RSpec.shared_examples_for 'wiki API uploads wiki attachment' do
+  it 'pushes attachment to the wiki repository' do
+    allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+    workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
+
+    expect(response).to have_gitlab_http_status(:created)
+    expect(json_response).to eq result_hash.deep_stringify_keys
+  end
+
+  it 'responds with validation error on empty file' do
+    payload.delete(:file)
+
+    post(api(url, user), params: payload)
+
+    expect(response).to have_gitlab_http_status(:bad_request)
+    expect(json_response.size).to eq(1)
+    expect(json_response['error']).to eq('file is missing')
+  end
+
+  it 'responds with validation error on invalid temp file' do
+    payload[:file] = { tempfile: '/etc/hosts' }
+
+    post(api(url, user), params: payload)
+
+    expect(response).to have_gitlab_http_status(:bad_request)
+    expect(json_response.size).to eq(1)
+    expect(json_response['error']).to eq('file is invalid')
+  end
+
+  it 'is backward compatible with regular multipart uploads' do
+    allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+    post(api(url, user), params: payload)
+
+    expect(response).to have_gitlab_http_status(:created)
+    expect(json_response).to eq result_hash.deep_stringify_keys
+  end
+end