diff --git a/changelogs/unreleased/paginate-all-the-things.yml b/changelogs/unreleased/paginate-all-the-things.yml
new file mode 100644
index 0000000000000000000000000000000000000000..52f23ba52a987f4d2994c72b149f036607cff1c6
--- /dev/null
+++ b/changelogs/unreleased/paginate-all-the-things.yml
@@ -0,0 +1,4 @@
+---
+title: 'API: Paginate all endpoints that return an array'
+merge_request: 8606
+author: Robert Schilling
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 84ff72bc36c601f669dc2dd49c35178f652e6f7d..5c1fa6b47a04f35c481581be3362390f66abbee2 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -24,3 +24,4 @@ changes are in V4:
   - `/dockerfiles/:key`
 - Moved `/projects/fork/:id` to `/projects/:id/fork`
 - Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters
+- Return pagination headers for all endpoints that return an array
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 06346ae822a1f22c29fad8013ea223172f1bbf7a..dbb7271ccbd24a6046ecda8b2edead478bc7df49 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,13 +5,22 @@ module API
     version %w(v3 v4), using: :path
 
     version 'v3', using: :path do
+      mount ::API::V3::Boards
+      mount ::API::V3::Branches
       mount ::API::V3::DeployKeys
       mount ::API::V3::Issues
+      mount ::API::V3::Labels
       mount ::API::V3::Members
+      mount ::API::V3::MergeRequestDiffs
       mount ::API::V3::MergeRequests
+      mount ::API::V3::ProjectHooks
       mount ::API::V3::Projects
       mount ::API::V3::ProjectSnippets
+      mount ::API::V3::Repositories
+      mount ::API::V3::SystemHooks
+      mount ::API::V3::Tags
       mount ::API::V3::Templates
+      mount ::API::V3::Users
     end
 
     before { allow_access_with_scope :api }
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 58a4df54bea7541498cc30d3b22cec618ec69cae..2ef327217ea7929796bc6e395aa0c29dfe859a6d 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -28,8 +28,8 @@ module API
           end
           get endpoint do
             if can_read_awardable?
-              awards = paginate(awardable.award_emoji)
-              present awards, with: Entities::AwardEmoji
+              awards = awardable.award_emoji
+              present paginate(awards), with: Entities::AwardEmoji
             else
               not_found!("Award Emoji")
             end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 13752eb49476c8ad6842a71d4fa936e3a72bff7e..f4226e5a89d759235177a299a195957072d73df0 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -1,6 +1,7 @@
 module API
-  # Boards API
   class Boards < Grape::API
+    include PaginationParams
+
     before { authenticate! }
 
     params do
@@ -11,9 +12,12 @@ module API
         detail 'This feature was introduced in 8.13'
         success Entities::Board
       end
+      params do
+        use :pagination
+      end
       get ':id/boards' do
         authorize!(:read_board, user_project)
-        present user_project.boards, with: Entities::Board
+        present paginate(user_project.boards), with: Entities::Board
       end
 
       params do
@@ -40,9 +44,12 @@ module API
           detail 'Does not include `done` list. This feature was introduced in 8.13'
           success Entities::List
         end
+        params do
+          use :pagination
+        end
         get '/lists' do
           authorize!(:read_board, user_project)
-          present board_lists, with: Entities::List
+          present paginate(board_lists), with: Entities::List
         end
 
         desc 'Get a list of a project board' do
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 9331be1f7de1b46d545fab132ff78e397db12004..9d1f5a28ef62f76c84a77362320027949462de53 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -1,8 +1,9 @@
 require 'mime/types'
 
 module API
-  # Projects API
   class Branches < Grape::API
+    include PaginationParams
+
     before { authenticate! }
     before { authorize! :download_code, user_project }
 
@@ -13,10 +14,13 @@ module API
       desc 'Get a project repository branches' do
         success Entities::RepoBranch
       end
+      params do
+        use :pagination
+      end
       get ":id/repository/branches" do
-        branches = user_project.repository.branches.sort_by(&:name)
+        branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
 
-        present branches, with: Entities::RepoBranch, project: user_project
+        present paginate(branches), with: Entities::RepoBranch, project: user_project
       end
 
       desc 'Get a single branch' do
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 3f5183d46a2e39c8946e8de22a0f382b794058f6..982645c2f64fc3cc51d679f6f4a06088fd5f5cd5 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -1,12 +1,17 @@
 module API
   class DeployKeys < Grape::API
+    include PaginationParams
+
     before { authenticate! }
 
+    desc 'Return all deploy keys'
+    params do
+      use :pagination
+    end
     get "deploy_keys" do
       authenticated_as_admin!
 
-      keys = DeployKey.all
-      present keys, with: Entities::SSHKey
+      present paginate(DeployKey.all), with: Entities::SSHKey
     end
 
     params do
@@ -18,8 +23,11 @@ module API
       desc "Get a specific project's deploy keys" do
         success Entities::SSHKey
       end
+      params do
+        use :pagination
+      end
       get ":id/deploy_keys" do
-        present user_project.deploy_keys, with: Entities::SSHKey
+        present paginate(user_project.deploy_keys), with: Entities::SSHKey
       end
 
       desc 'Get single deploy key' do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 2ecdd747c8e598dbe9c6759e82e09cb3d8bbf787..6e16ccd2fd8c86b95f682f1c60ee2bc08c0d84df 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,5 +1,4 @@
 module API
-  # Projects API
   class Files < Grape::API
     helpers do
       def commit_params(attrs)
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 2199eea7e5ffa40b78c86f8aeee35555de4c24b5..0764b58fb4cea5c53f85dc0db4d35854a6a6fe2c 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -2,7 +2,7 @@ module API
   module Helpers
     module Pagination
       def paginate(relation)
-        relation.page(params[:page]).per(params[:per_page].to_i).tap do |data|
+        relation.page(params[:page]).per(params[:per_page]).tap do |data|
           add_pagination_headers(data)
         end
       end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 652786d4e3eba436d881c3d1346138be8ef708ec..d2955af3f95e322beb588e5640d9190d2ceeb1ca 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,6 +1,7 @@
 module API
-  # Labels API
   class Labels < Grape::API
+    include PaginationParams
+    
     before { authenticate! }
 
     params do
@@ -10,8 +11,11 @@ module API
       desc 'Get all labels of the project' do
         success Entities::Label
       end
+      params do
+        use :pagination
+      end
       get ':id/labels' do
-        present available_labels, with: Entities::Label, current_user: current_user, project: user_project
+        present paginate(available_labels), with: Entities::Label, current_user: current_user, project: user_project
       end
 
       desc 'Create a new label' do
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index bc3d69f6904b6de2cbd08f20655f15c1cf2d40b7..4901a7cfea62d9bc5207b1a9908f4344f024fd46 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -1,6 +1,8 @@
 module API
   # MergeRequestDiff API
   class MergeRequestDiffs < Grape::API
+    include PaginationParams
+
     before { authenticate! }
 
     resource :projects do
@@ -12,12 +14,12 @@ module API
       params do
         requires :id, type: String, desc: 'The ID of a project'
         requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+        use :pagination
       end
-
       get ":id/merge_requests/:merge_request_id/versions" do
         merge_request = find_merge_request_with_access(params[:merge_request_id])
 
-        present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
+        present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
       end
 
       desc 'Get a single merge request diff version' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8e09a6f73549418186c4e3b170dfba8427a286c7..bdd764abfebe50898dfd7fd73af02c6249a88ebc 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -119,8 +119,9 @@ module API
       end
       get ':id/merge_requests/:merge_request_id/commits' do
         merge_request = find_merge_request_with_access(params[:merge_request_id])
+        commits = ::Kaminari.paginate_array(merge_request.commits)
 
-        present merge_request.commits, with: Entities::RepoCommit
+        present paginate(commits), with: Entities::RepoCommit
       end
 
       desc 'Show the merge request changes' do
diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb
index 8c1e4381a742402df0606202e0c9637a7b5e60d0..f566eb3ed2b12aaa88d31f859a3626f4cafcc454 100644
--- a/lib/api/pagination_params.rb
+++ b/lib/api/pagination_params.rb
@@ -15,8 +15,8 @@ module API
     included do
       helpers do
         params :pagination do
-          optional :page, type: Integer, desc: 'Current page number'
-          optional :per_page, type: Integer, desc: 'Number of items per page'
+          optional :page, type: Integer, default: 1, desc: 'Current page number'
+          optional :per_page, type: Integer, default: 20, desc: 'Number of items per page'
         end
       end
     end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index cb679e6658a3085a7e61f53fd8555a3f56ae3cc6..f7a28d7ad10284d3dc6fd85bc66ef2038a3f09f0 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -32,9 +32,7 @@ module API
         use :pagination
       end
       get ":id/hooks" do
-        hooks = paginate user_project.hooks
-
-        present hooks, with: Entities::ProjectHook
+        present paginate(user_project.hooks), with: Entities::ProjectHook
       end
 
       desc 'Get a project hook' do
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 4ca6646a6f1e99516bb8dab8e0ea5bd38e187203..bfda6f45b0a8d5697bab88b3d37d1a909aa901ce 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -2,6 +2,8 @@ require 'mime/types'
 
 module API
   class Repositories < Grape::API
+    include PaginationParams
+
     before { authorize! :download_code, user_project }
 
     params do
@@ -24,6 +26,7 @@ module API
         optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
         optional :path, type: String, desc: 'The path of the tree'
         optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+        use :pagination
       end
       get ':id/repository/tree' do
         ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
@@ -33,8 +36,8 @@ module API
         not_found!('Tree') unless commit
 
         tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
-
-        present tree.sorted_entries, with: Entities::RepoTreeObject
+        entries = ::Kaminari.paginate_array(tree.sorted_entries)
+        present paginate(entries), with: Entities::RepoTreeObject
       end
 
       desc 'Get a raw file contents'
@@ -100,10 +103,13 @@ module API
       desc 'Get repository contributors' do
         success Entities::Contributor
       end
+      params do
+        use :pagination
+      end
       get ':id/repository/contributors' do
         begin
-          present user_project.repository.contributors,
-                  with: Entities::Contributor
+          contributors = ::Kaminari.paginate_array(user_project.repository.contributors)
+          present paginate(contributors), with: Entities::Contributor
         rescue
           not_found!
         end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 708ec8cfe70fccc626dfd1dd685049f74b61051e..d038a3fa828321257e31f7d654adeaf4a5cabc78 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,6 +1,7 @@
 module API
-  # Hooks API
   class SystemHooks < Grape::API
+    include PaginationParams
+
     before do
       authenticate!
       authenticated_as_admin!
@@ -10,10 +11,11 @@ module API
       desc 'Get the list of system hooks' do
         success Entities::Hook
       end
+      params do
+        use :pagination
+      end
       get do
-        hooks = SystemHook.all
-
-        present hooks, with: Entities::Hook
+        present paginate(SystemHook.all), with: Entities::Hook
       end
 
       desc 'Create a new system hook' do
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index b6fd8f569a91554c8c31ee6551fbefac136a5365..86759ab882f72f730333d0a7029d4fe5968c60c4 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -1,6 +1,7 @@
 module API
-  # Git Tags API
   class Tags < Grape::API
+    include PaginationParams
+
     before { authorize! :download_code, user_project }
 
     params do
@@ -10,9 +11,12 @@ module API
       desc 'Get a project repository tags' do
         success Entities::RepoTag
       end
+      params do
+        use :pagination
+      end
       get ":id/repository/tags" do
-        present user_project.repository.tags.sort_by(&:name).reverse,
-                with: Entities::RepoTag, project: user_project
+        tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse)
+        present paginate(tags), with: Entities::RepoTag, project: user_project
       end
 
       desc 'Get a single repository tag' do
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 8a2d66efd892ade36ec127160bfe4417b96f66a5..0fc13b35d5bddd6f4608e805724f101a3ea4250b 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,5 +1,7 @@
 module API
   class Templates < Grape::API
+    include PaginationParams
+
     GLOBAL_TEMPLATE_TYPES = {
       gitignores: {
         klass: Gitlab::Template::GitignoreTemplate,
@@ -51,12 +53,14 @@ module API
     end
     params do
       optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+      use :pagination
     end
     get "templates/licenses" do
       options = {
         featured: declared(params).popular.present? ? true : nil
       }
-      present Licensee::License.all(options), with: ::API::Entities::RepoLicense
+      licences = ::Kaminari.paginate_array(Licensee::License.all(options))
+      present paginate(licences), with: Entities::RepoLicense
     end
 
     desc 'Get the text for a specific license' do
@@ -82,8 +86,12 @@ module API
         detail "This feature was introduced in GitLab #{gitlab_version}."
         success Entities::TemplatesList
       end
+      params do
+        use :pagination
+      end
       get "templates/#{template_type}" do
-        present klass.all, with: Entities::TemplatesList
+        templates = ::Kaminari.paginate_array(klass.all)
+        present paginate(templates), with: Entities::TemplatesList
       end
 
       desc 'Get the text for a specific template present in local filesystem' do
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 82ac3886ac3b9feb3e6b8034f4cc41d3fa432219..05538f5a42fbe8134e5538e5c42df3395d62afbe 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -209,6 +209,7 @@ module API
       end
       params do
         requires :id, type: Integer, desc: 'The ID of the user'
+        use :pagination
       end
       get ':id/keys' do
         authenticated_as_admin!
@@ -216,7 +217,7 @@ module API
         user = User.find_by(id: params[:id])
         not_found!('User') unless user
 
-        present user.keys, with: Entities::SSHKey
+        present paginate(user.keys), with: Entities::SSHKey
       end
 
       desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
@@ -266,13 +267,14 @@ module API
       end
       params do
         requires :id, type: Integer, desc: 'The ID of the user'
+        use :pagination
       end
       get ':id/emails' do
         authenticated_as_admin!
         user = User.find_by(id: params[:id])
         not_found!('User') unless user
 
-        present user.emails, with: Entities::Email
+        present paginate(user.emails), with: Entities::Email
       end
 
       desc 'Delete an email address of a specified user. Available only for admins.' do
@@ -373,8 +375,11 @@ module API
       desc "Get the currently authenticated user's SSH keys" do
         success Entities::SSHKey
       end
+      params do
+        use :pagination
+      end
       get "keys" do
-        present current_user.keys, with: Entities::SSHKey
+        present paginate(current_user.keys), with: Entities::SSHKey
       end
 
       desc 'Get a single key owned by currently authenticated user' do
@@ -423,8 +428,11 @@ module API
       desc "Get the currently authenticated user's email addresses" do
         success Entities::Email
       end
+      params do
+        use :pagination
+      end
       get "emails" do
-        present current_user.emails, with: Entities::Email
+        present paginate(current_user.emails), with: Entities::Email
       end
 
       desc 'Get a single email address owned by the currently authenticated user' do
diff --git a/lib/api/v3/boards.rb b/lib/api/v3/boards.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31d708bc2c83a6b3c5cad7024de7ed1ff051f61a
--- /dev/null
+++ b/lib/api/v3/boards.rb
@@ -0,0 +1,51 @@
+module API
+  module V3
+    class Boards < Grape::API
+      before { authenticate! }
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+      end
+      resource :projects do
+        desc 'Get all project boards' do
+          detail 'This feature was introduced in 8.13'
+          success ::API::Entities::Board
+        end
+        get ':id/boards' do
+          authorize!(:read_board, user_project)
+          present user_project.boards, with: ::API::Entities::Board
+        end
+
+        params do
+          requires :board_id, type: Integer, desc: 'The ID of a board'
+        end
+        segment ':id/boards/:board_id' do
+          helpers do
+            def project_board
+              board = user_project.boards.first
+
+              if params[:board_id] == board.id
+                board
+              else
+                not_found!('Board')
+              end
+            end
+
+            def board_lists
+              project_board.lists.destroyable
+            end
+          end
+
+          desc 'Get the lists of a project board' do
+            detail 'Does not include `done` list. This feature was introduced in 8.13'
+            success ::API::Entities::List
+          end
+          get '/lists' do
+            authorize!(:read_board, user_project)
+            present board_lists, with: ::API::Entities::List
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/v3/branches.rb b/lib/api/v3/branches.rb
new file mode 100644
index 0000000000000000000000000000000000000000..733c6b21be5d50124b6831c0b331761b7d2e9a97
--- /dev/null
+++ b/lib/api/v3/branches.rb
@@ -0,0 +1,24 @@
+require 'mime/types'
+
+module API
+  module V3
+    class Branches < Grape::API
+      before { authenticate! }
+      before { authorize! :download_code, user_project }
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+      end
+      resource :projects do
+        desc 'Get a project repository branches' do
+          success ::API::Entities::RepoBranch
+        end
+        get ":id/repository/branches" do
+          branches = user_project.repository.branches.sort_by(&:name)
+
+          present branches, with: ::API::Entities::RepoBranch, project: user_project
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c3261311bf7bcfd7dd6a7bf63118440f28bd98b
--- /dev/null
+++ b/lib/api/v3/labels.rb
@@ -0,0 +1,19 @@
+module API
+  module V3
+    class Labels < Grape::API
+      before { authenticate! }
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+      end
+      resource :projects do
+        desc 'Get all labels of the project' do
+          success ::API::Entities::Label
+        end
+        get ':id/labels' do
+          present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3549ea225eff085e08c0babfe78f21e776cd0561
--- /dev/null
+++ b/lib/api/v3/repositories.rb
@@ -0,0 +1,55 @@
+require 'mime/types'
+
+module API
+  module V3
+    class Repositories < Grape::API
+      before { authorize! :download_code, user_project }
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+      end
+      resource :projects do
+        helpers do
+          def handle_project_member_errors(errors)
+            if errors[:project_access].any?
+              error!(errors[:project_access], 422)
+            end
+            not_found!
+          end
+        end
+
+        desc 'Get a project repository tree' do
+          success ::API::Entities::RepoTreeObject
+        end
+        params do
+          optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
+          optional :path, type: String, desc: 'The path of the tree'
+          optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
+        end
+        get ':id/repository/tree' do
+          ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+          path = params[:path] || nil
+
+          commit = user_project.commit(ref)
+          not_found!('Tree') unless commit
+
+          tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive])
+
+          present tree.sorted_entries, with: ::API::Entities::RepoTreeObject
+        end
+
+        desc 'Get repository contributors' do
+          success ::API::Entities::Contributor
+        end
+        get ':id/repository/contributors' do
+          begin
+            present user_project.repository.contributors,
+                    with: ::API::Entities::Contributor
+          rescue
+            not_found!
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/v3/system_hooks.rb b/lib/api/v3/system_hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..391510b9ee0bab9e69959b3647d03b10d9351cc7
--- /dev/null
+++ b/lib/api/v3/system_hooks.rb
@@ -0,0 +1,19 @@
+module API
+  module V3
+    class SystemHooks < Grape::API
+      before do
+        authenticate!
+        authenticated_as_admin!
+      end
+
+      resource :hooks do
+        desc 'Get the list of system hooks' do
+          success ::API::Entities::Hook
+        end
+        get do
+          present SystemHook.all, with: ::API::Entities::Hook
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/v3/tags.rb b/lib/api/v3/tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..016e3d8693243003861c8aca8309c13be1ceafdc
--- /dev/null
+++ b/lib/api/v3/tags.rb
@@ -0,0 +1,20 @@
+module API
+  module V3
+    class Tags < Grape::API
+      before { authorize! :download_code, user_project }
+
+      params do
+        requires :id, type: String, desc: 'The ID of a project'
+      end
+      resource :projects do
+        desc 'Get a project repository tags' do
+          success ::API::Entities::RepoTag
+        end
+        get ":id/repository/tags" do
+          tags = user_project.repository.tags.sort_by(&:name).reverse
+          present tags, with: ::API::Entities::RepoTag, project: user_project
+        end
+      end
+    end
+  end
+end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ceb139d11b831e51420d44dbcaba1573afcfd58a
--- /dev/null
+++ b/lib/api/v3/users.rb
@@ -0,0 +1,64 @@
+module API
+  module V3
+    class Users < Grape::API
+      include PaginationParams
+
+      before do
+        allow_access_with_scope :read_user if request.get?
+        authenticate!
+      end
+
+      resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
+        desc 'Get the SSH keys of a specified user. Available only for admins.' do
+          success ::API::Entities::SSHKey
+        end
+        params do
+          requires :id, type: Integer, desc: 'The ID of the user'
+          use :pagination
+        end
+        get ':id/keys' do
+          authenticated_as_admin!
+
+          user = User.find_by(id: params[:id])
+          not_found!('User') unless user
+
+          present paginate(user.keys), with: ::API::Entities::SSHKey
+        end
+
+        desc 'Get the emails addresses of a specified user. Available only for admins.' do
+          success ::API::Entities::Email
+        end
+        params do
+          requires :id, type: Integer, desc: 'The ID of the user'
+          use :pagination
+        end
+        get ':id/emails' do
+          authenticated_as_admin!
+          user = User.find_by(id: params[:id])
+          not_found!('User') unless user
+
+          present user.emails, with: ::API::Entities::Email
+        end
+      end
+
+      resource :user do
+        desc "Get the currently authenticated user's SSH keys" do
+          success ::API::Entities::SSHKey
+        end
+        params do
+          use :pagination
+        end
+        get "keys" do
+          present current_user.keys, with: ::API::Entities::SSHKey
+        end
+
+        desc "Get the currently authenticated user's email addresses" do
+          success ::API::Entities::Email
+        end
+        get "emails" do
+          present current_user.emails, with: ::API::Entities::Email
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index e487297748b1dbfe30c08e79e9ce0f705255a112..919c98d6437a05239948718637746eb8e74140a8 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -48,6 +48,7 @@ describe API::AccessRequests, api: true  do
           get api("/#{source_type.pluralize}/#{source.id}/access_requests", master)
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.size).to eq(1)
         end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index c8e8f31cc1f4390167c892c2d9cbdd517506cd55..6cc1ef315db895646741cb4dccb5daa1b201a5f0 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -34,6 +34,7 @@ describe API::AwardEmoji, api: true  do
         get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['name']).to eq(downvote.name)
       end
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index c14c3cb1ce708594497a6830067e843d257f97c1..71df534ebe15e7e9d90b6412209694d05ea7ce47 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -55,6 +55,7 @@ describe API::Boards, api: true  do
         get api(base_url, user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['id']).to eq(board.id)
@@ -72,6 +73,7 @@ describe API::Boards, api: true  do
       get api(base_url, user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
       expect(json_response.first['label']['name']).to eq(dev_label.title)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 3e66236f6aef380fee172f00b5b017f76b75b5c6..2e6db0f43c6ecd8252d2c0cb467c9e56124f185e 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -17,8 +17,10 @@ describe API::Branches, api: true  do
     it "returns an array of project branches" do
       project.repository.expire_all_method_caches
 
-      get api("/projects/#{project.id}/repository/branches", user)
+      get api("/projects/#{project.id}/repository/branches", user), per_page: 100
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       branch_names = json_response.map { |x| x['name'] }
       expect(branch_names).to match_array(project.repository.branch_names)
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 7c9078b28642cd1d44b9675368ffc7152659c40a..921d8714173a2f6636959c70d3f49d9190eb3e02 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -25,6 +25,7 @@ describe API::BroadcastMessages, api: true do
       get api('/broadcast_messages', admin)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_kind_of(Array)
       expect(json_response.first.keys)
         .to match_array(%w(id message starts_at ends_at color font active))
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 834c4e52693f133c340069f0b031d41052fbde17..38aef7f276767278f933c725b8ed26154f495657 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -22,6 +22,7 @@ describe API::Builds, api: true do
     context 'authorized user' do
       it 'returns project builds' do
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
       end
 
@@ -97,6 +98,7 @@ describe API::Builds, api: true do
 
           it 'returns project jobs for specific commit' do
             expect(response).to have_http_status(200)
+            expect(response).to include_pagination_headers
             expect(json_response).to be_an Array
             expect(json_response.size).to eq 2
           end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index eb53fd718721d6c91e9ff3633a709a80d6b17a7f..1e4cb61168183ef1341def5a9cdb8b23b394f64b 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -54,7 +54,7 @@ describe API::CommitStatuses, api: true do
 
           it 'returns all commit statuses' do
             expect(response).to have_http_status(200)
-
+            expect(response).to include_pagination_headers
             expect(json_response).to be_an Array
             expect(statuses_id).to contain_exactly(status1.id, status2.id,
                                                    status3.id, status4.id,
@@ -67,7 +67,7 @@ describe API::CommitStatuses, api: true do
 
           it 'returns latest commit statuses for specific ref' do
             expect(response).to have_http_status(200)
-
+            expect(response).to include_pagination_headers
             expect(json_response).to be_an Array
             expect(statuses_id).to contain_exactly(status3.id, status5.id)
           end
@@ -78,7 +78,7 @@ describe API::CommitStatuses, api: true do
 
           it 'return latest commit statuses for specific name' do
             expect(response).to have_http_status(200)
-
+            expect(response).to include_pagination_headers
             expect(json_response).to be_an Array
             expect(statuses_id).to contain_exactly(status4.id, status5.id)
           end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 3eef10c06983052d085a2a13101d2ff370854fc5..9fa007332f0aa4b713fc514cc12a0d83abd9b89d 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -456,6 +456,7 @@ describe API::Commits, api: true  do
       it 'returns merge_request comments' do
         get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(2)
         expect(json_response.first['note']).to eq('a comment on a commit')
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 766234d7104d0cd2f99451df9e979603e16fa90f..67039bb037e02a3a064fcebfff9de75a536d6428 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -35,6 +35,7 @@ describe API::DeployKeys, api: true  do
         get api('/deploy_keys', admin)
 
         expect(response.status).to eq(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
       end
@@ -48,6 +49,7 @@ describe API::DeployKeys, api: true  do
       get api("/projects/#{project.id}/deploy_keys", admin)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.first['title']).to eq(deploy_key.title)
     end
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 31e3cfa1b2ff7f0c273f514058a6c61fb7d95be9..5c4ce39f70c43c6421fc2bb1cbca0ebf0167675b 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -22,6 +22,7 @@ describe API::Deployments, api: true  do
         get api("/projects/#{project.id}/deployments", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.size).to eq(1)
         expect(json_response.first['iid']).to eq(deployment.iid)
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 8168b6137668917fb5f248a684b12a5bc91d4903..b0ee196666e6d609048f9ba79a1604dbdcff707d 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -22,6 +22,7 @@ describe API::Environments, api: true  do
         get api("/projects/#{project.id}/environments", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.size).to eq(1)
         expect(json_response.first['name']).to eq(environment.name)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index ccd7898586ca859342d560788f89b77f898ddfaf..a59112579e5b198c967d2537f26d183a7de61a6a 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -33,6 +33,7 @@ describe API::Groups, api: true  do
         get api("/groups", user1)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response)
@@ -43,6 +44,7 @@ describe API::Groups, api: true  do
         get api("/groups", user1), statistics: true
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first).not_to include 'statistics'
       end
@@ -53,6 +55,7 @@ describe API::Groups, api: true  do
         get api("/groups", admin)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(2)
       end
@@ -61,6 +64,7 @@ describe API::Groups, api: true  do
         get api("/groups", admin)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first).not_to include('statistics')
       end
@@ -78,6 +82,7 @@ describe API::Groups, api: true  do
         get api("/groups", admin), statistics: true
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response)
           .to satisfy_one { |group| group['statistics'] == attributes }
@@ -89,6 +94,7 @@ describe API::Groups, api: true  do
         get api("/groups", admin), skip_groups: [group2.id]
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
       end
@@ -103,6 +109,7 @@ describe API::Groups, api: true  do
         get api("/groups", user1), all_available: true
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_groups).to contain_exactly(public_group.name, group1.name)
       end
@@ -120,6 +127,7 @@ describe API::Groups, api: true  do
         get api("/groups", user1)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_groups).to eq([group3.name, group1.name])
       end
@@ -128,6 +136,7 @@ describe API::Groups, api: true  do
         get api("/groups", user1), sort: "desc"
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_groups).to eq([group1.name, group3.name])
       end
@@ -136,6 +145,7 @@ describe API::Groups, api: true  do
         get api("/groups", user1), order_by: "path"
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_groups).to eq([group1.name, group3.name])
       end
@@ -156,6 +166,7 @@ describe API::Groups, api: true  do
         get api('/groups/owned', user2)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['name']).to eq(group2.name)
       end
@@ -290,6 +301,7 @@ describe API::Groups, api: true  do
         get api("/groups/#{group1.id}/projects", user1)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response.length).to eq(2)
         project_names = json_response.map { |proj| proj['name' ] }
         expect(project_names).to match_array([project1.name, project3.name])
@@ -300,6 +312,7 @@ describe API::Groups, api: true  do
         get api("/groups/#{group1.id}/projects", user1), simple: true
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response.length).to eq(2)
         project_names = json_response.map { |proj| proj['name' ] }
         expect(project_names).to match_array([project1.name, project3.name])
@@ -312,6 +325,7 @@ describe API::Groups, api: true  do
         get api("/groups/#{group1.id}/projects", user1), visibility: 'public'
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an(Array)
         expect(json_response.length).to eq(1)
         expect(json_response.first['name']).to eq(public_project.name)
@@ -335,6 +349,7 @@ describe API::Groups, api: true  do
         get api("/groups/#{group1.id}/projects", user3)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response.length).to eq(1)
         expect(json_response.first['name']).to eq(project3.name)
       end
@@ -365,6 +380,7 @@ describe API::Groups, api: true  do
         get api("/groups/#{group2.id}/projects", admin)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response.length).to eq(1)
         expect(json_response.first['name']).to eq(project2.name)
       end
@@ -381,6 +397,7 @@ describe API::Groups, api: true  do
         get api("/groups/#{group1.path}/projects", admin)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         project_names = json_response.map { |proj| proj['name' ] }
         expect(project_names).to match_array([project1.name, project3.name])
       end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index cca00df95919271bc044eabb657f9dc0689bccbc..74ac7955cb8f89d97987bf935c878ee79cec7d65 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -68,7 +68,9 @@ describe API::Issues, api: true  do
     context "when authenticated" do
       it "returns an array of issues" do
         get api("/issues", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['title']).to eq(issue.title)
         expect(json_response.last).to have_key('web_url')
@@ -76,7 +78,9 @@ describe API::Issues, api: true  do
 
       it 'returns an array of closed issues' do
         get api('/issues?state=closed', user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['id']).to eq(closed_issue.id)
@@ -84,7 +88,9 @@ describe API::Issues, api: true  do
 
       it 'returns an array of opened issues' do
         get api('/issues?state=opened', user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['id']).to eq(issue.id)
@@ -92,7 +98,9 @@ describe API::Issues, api: true  do
 
       it 'returns an array of all issues' do
         get api('/issues?state=all', user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(2)
         expect(json_response.first['id']).to eq(issue.id)
@@ -101,7 +109,9 @@ describe API::Issues, api: true  do
 
       it 'returns an array of labeled issues' do
         get api("/issues?labels=#{label.title}", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['labels']).to eq([label.title])
@@ -111,6 +121,7 @@ describe API::Issues, api: true  do
         get api("/issues?labels=#{label.title},foo,bar", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['labels']).to eq([label.title])
@@ -118,14 +129,18 @@ describe API::Issues, api: true  do
 
       it 'returns an empty array if no issue matches labels' do
         get api('/issues?labels=foo,bar', user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(0)
       end
 
       it 'returns an array of labeled issues matching given state' do
         get api("/issues?labels=#{label.title}&state=opened", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['labels']).to eq([label.title])
@@ -134,7 +149,9 @@ describe API::Issues, api: true  do
 
       it 'returns an empty array if no issue matches labels and state filters' do
         get api("/issues?labels=#{label.title}&state=closed", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(0)
       end
@@ -143,6 +160,7 @@ describe API::Issues, api: true  do
         get api("/issues?milestone=#{empty_milestone.title}", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(0)
       end
@@ -151,6 +169,7 @@ describe API::Issues, api: true  do
         get api("/issues?milestone=foo", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(0)
       end
@@ -159,6 +178,7 @@ describe API::Issues, api: true  do
         get api("/issues?milestone=#{milestone.title}", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(2)
         expect(json_response.first['id']).to eq(issue.id)
@@ -170,6 +190,7 @@ describe API::Issues, api: true  do
                 '&state=closed', user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['id']).to eq(closed_issue.id)
@@ -179,6 +200,7 @@ describe API::Issues, api: true  do
         get api("/issues?milestone=#{no_milestone_title}", author)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['id']).to eq(confidential_issue.id)
@@ -186,36 +208,40 @@ describe API::Issues, api: true  do
 
       it 'sorts by created_at descending by default' do
         get api('/issues', user)
-        response_dates = json_response.map { |issue| issue['created_at'] }
 
+        response_dates = json_response.map { |issue| issue['created_at'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_dates).to eq(response_dates.sort.reverse)
       end
 
       it 'sorts ascending when requested' do
         get api('/issues?sort=asc', user)
-        response_dates = json_response.map { |issue| issue['created_at'] }
 
+        response_dates = json_response.map { |issue| issue['created_at'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_dates).to eq(response_dates.sort)
       end
 
       it 'sorts by updated_at descending when requested' do
         get api('/issues?order_by=updated_at', user)
-        response_dates = json_response.map { |issue| issue['updated_at'] }
 
+        response_dates = json_response.map { |issue| issue['updated_at'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_dates).to eq(response_dates.sort.reverse)
       end
 
       it 'sorts by updated_at ascending when requested' do
         get api('/issues?order_by=updated_at&sort=asc', user)
-        response_dates = json_response.map { |issue| issue['updated_at'] }
 
+        response_dates = json_response.map { |issue| issue['updated_at'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(response_dates).to eq(response_dates.sort)
       end
@@ -269,6 +295,7 @@ describe API::Issues, api: true  do
       get api(base_url, non_member)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['title']).to eq(group_issue.title)
@@ -278,6 +305,7 @@ describe API::Issues, api: true  do
       get api(base_url, author)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
     end
@@ -286,6 +314,7 @@ describe API::Issues, api: true  do
       get api(base_url, assignee)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
     end
@@ -294,6 +323,7 @@ describe API::Issues, api: true  do
       get api(base_url, user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
     end
@@ -302,6 +332,7 @@ describe API::Issues, api: true  do
       get api(base_url, admin)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
     end
@@ -310,6 +341,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?labels=#{group_label.title}", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['labels']).to eq([group_label.title])
@@ -319,6 +351,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?labels=#{group_label.title},foo,bar", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(0)
     end
@@ -327,6 +360,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?labels=foo,bar", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(0)
     end
@@ -335,6 +369,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(0)
     end
@@ -343,6 +378,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?milestone=foo", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(0)
     end
@@ -351,6 +387,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?milestone=#{group_milestone.title}", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(group_issue.id)
@@ -361,6 +398,7 @@ describe API::Issues, api: true  do
               '&state=closed', user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       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)
@@ -370,6 +408,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}?milestone=#{no_milestone_title}", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(group_confidential_issue.id)
@@ -377,36 +416,40 @@ describe API::Issues, api: true  do
 
     it 'sorts by created_at descending by default' do
       get api(base_url, user)
-      response_dates = json_response.map { |issue| issue['created_at'] }
 
+      response_dates = json_response.map { |issue| issue['created_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort.reverse)
     end
 
     it 'sorts ascending when requested' do
       get api("#{base_url}?sort=asc", user)
-      response_dates = json_response.map { |issue| issue['created_at'] }
 
+      response_dates = json_response.map { |issue| issue['created_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort)
     end
 
     it 'sorts by updated_at descending when requested' do
       get api("#{base_url}?order_by=updated_at", user)
-      response_dates = json_response.map { |issue| issue['updated_at'] }
 
+      response_dates = json_response.map { |issue| issue['updated_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort.reverse)
     end
 
     it 'sorts by updated_at ascending when requested' do
       get api("#{base_url}?order_by=updated_at&sort=asc", user)
-      response_dates = json_response.map { |issue| issue['updated_at'] }
 
+      response_dates = json_response.map { |issue| issue['updated_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort)
     end
@@ -430,12 +473,17 @@ describe API::Issues, api: true  do
 
       get api("/projects/#{restricted_project.id}/issues", non_member)
 
+      expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response).to eq([])
     end
 
     it 'returns project issues without confidential issues for non project members' do
       get api("#{base_url}/issues", non_member)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
       expect(json_response.first['title']).to eq(issue.title)
@@ -443,7 +491,9 @@ describe API::Issues, api: true  do
 
     it 'returns project issues without confidential issues for project members with guest role' do
       get api("#{base_url}/issues", guest)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
       expect(json_response.first['title']).to eq(issue.title)
@@ -451,7 +501,9 @@ describe API::Issues, api: true  do
 
     it 'returns project confidential issues for author' do
       get api("#{base_url}/issues", author)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(3)
       expect(json_response.first['title']).to eq(issue.title)
@@ -459,7 +511,9 @@ describe API::Issues, api: true  do
 
     it 'returns project confidential issues for assignee' do
       get api("#{base_url}/issues", assignee)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(3)
       expect(json_response.first['title']).to eq(issue.title)
@@ -467,7 +521,9 @@ describe API::Issues, api: true  do
 
     it 'returns project issues with confidential issues for project members' do
       get api("#{base_url}/issues", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(3)
       expect(json_response.first['title']).to eq(issue.title)
@@ -475,7 +531,9 @@ describe API::Issues, api: true  do
 
     it 'returns project confidential issues for admin' do
       get api("#{base_url}/issues", admin)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(3)
       expect(json_response.first['title']).to eq(issue.title)
@@ -483,7 +541,9 @@ describe API::Issues, api: true  do
 
     it 'returns an array of labeled project issues' do
       get api("#{base_url}/issues?labels=#{label.title}", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['labels']).to eq([label.title])
@@ -493,6 +553,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['labels']).to eq([label.title])
@@ -500,21 +561,27 @@ describe API::Issues, api: true  do
 
     it 'returns an empty array if no project issue matches labels' do
       get api("#{base_url}/issues?labels=foo,bar", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       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}/issues?milestone=#{empty_milestone.title}", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       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}/issues?milestone=foo", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(0)
     end
@@ -523,6 +590,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}/issues?milestone=#{milestone.title}", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
       expect(json_response.first['id']).to eq(issue.id)
@@ -530,9 +598,10 @@ describe API::Issues, api: true  do
     end
 
     it 'returns an array of issues matching state in milestone' do
-      get api("#{base_url}/issues?milestone=#{milestone.title}"\
-              '&state=closed', user)
+      get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(closed_issue.id)
@@ -542,6 +611,7 @@ describe API::Issues, api: true  do
       get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(confidential_issue.id)
@@ -549,36 +619,40 @@ describe API::Issues, api: true  do
 
     it 'sorts by created_at descending by default' do
       get api("#{base_url}/issues", user)
-      response_dates = json_response.map { |issue| issue['created_at'] }
 
+      response_dates = json_response.map { |issue| issue['created_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort.reverse)
     end
 
     it 'sorts ascending when requested' do
       get api("#{base_url}/issues?sort=asc", user)
-      response_dates = json_response.map { |issue| issue['created_at'] }
 
+      response_dates = json_response.map { |issue| issue['created_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort)
     end
 
     it 'sorts by updated_at descending when requested' do
       get api("#{base_url}/issues?order_by=updated_at", user)
-      response_dates = json_response.map { |issue| issue['updated_at'] }
 
+      response_dates = json_response.map { |issue| issue['updated_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort.reverse)
     end
 
     it 'sorts by updated_at ascending when requested' do
       get api("#{base_url}/issues?order_by=updated_at&sort=asc", user)
-      response_dates = json_response.map { |issue| issue['updated_at'] }
 
+      response_dates = json_response.map { |issue| issue['updated_at'] }
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(response_dates).to eq(response_dates.sort)
     end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index a8cd787f3983807a3bb82eca6122ff8cc7ffbe1d..5d7a76cf3beb5cc7a4cd95507e31d92e423a6968 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -30,6 +30,7 @@ describe API::Labels, api: true  do
       get api("/projects/#{project.id}/labels", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.size).to eq(3)
       expect(json_response.first.keys).to match_array expected_keys
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 3e9bcfd1a6014296de42b8e1a4ace0e451d77a2b..31166b50033d850542d1734c004a75638ee3daed 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -34,9 +34,12 @@ describe API::Members, api: true  do
         context "when authenticated as a #{type}" do
           it 'returns 200' do
             user = public_send(type)
+
             get api("/#{source_type.pluralize}/#{source.id}/members", user)
 
             expect(response).to have_http_status(200)
+            expect(response).to include_pagination_headers
+            expect(json_response).to be_an Array
             expect(json_response.size).to eq(2)
             expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
           end
@@ -49,6 +52,8 @@ describe API::Members, api: true  do
         get api("/#{source_type.pluralize}/#{source.id}/members", developer)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
+        expect(json_response).to be_an Array
         expect(json_response.size).to eq(2)
         expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
       end
@@ -57,6 +62,8 @@ describe API::Members, api: true  do
         get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
+        expect(json_response).to be_an Array
         expect(json_response.count).to eq(1)
         expect(json_response.first['username']).to eq(master.username)
       end
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index e1887138aab527ced9ea697c37f414994f11ebaf..1d02e827183393734cf2814e4d3656a56bf71427 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -19,6 +19,8 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true  do
       merge_request_diff = merge_request.merge_request_diffs.first
 
       expect(response.status).to eq 200
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.size).to eq(merge_request.merge_request_diffs.size)
       expect(json_response.first['id']).to eq(merge_request_diff.id)
       expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index ff10e79e4174fb286ad696041686330e0a0c1acc..f4dee4a4ca1dffb5be9418fa4dcd0ea2ac87c070 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -27,7 +27,9 @@ describe API::MergeRequests, api: true  do
     context "when authenticated" do
       it "returns an array of all merge_requests" do
         get api("/projects/#{project.id}/merge_requests", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(3)
         expect(json_response.last['title']).to eq(merge_request.title)
@@ -43,7 +45,9 @@ describe API::MergeRequests, api: true  do
 
       it "returns an array of all merge_requests" do
         get api("/projects/#{project.id}/merge_requests?state", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(3)
         expect(json_response.last['title']).to eq(merge_request.title)
@@ -51,7 +55,9 @@ describe API::MergeRequests, api: true  do
 
       it "returns an array of open merge_requests" do
         get api("/projects/#{project.id}/merge_requests?state=opened", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.last['title']).to eq(merge_request.title)
@@ -59,7 +65,9 @@ describe API::MergeRequests, api: true  do
 
       it "returns an array of closed merge_requests" do
         get api("/projects/#{project.id}/merge_requests?state=closed", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['title']).to eq(merge_request_closed.title)
@@ -67,7 +75,9 @@ describe API::MergeRequests, api: true  do
 
       it "returns an array of merged merge_requests" do
         get api("/projects/#{project.id}/merge_requests?state=merged", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(1)
         expect(json_response.first['title']).to eq(merge_request_merged.title)
@@ -91,7 +101,9 @@ describe API::MergeRequests, api: true  do
 
         it "returns an array of merge_requests in ascending order" do
           get api("/projects/#{project.id}/merge_requests?sort=asc", user)
+
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(3)
           response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -100,7 +112,9 @@ describe API::MergeRequests, api: true  do
 
         it "returns an array of merge_requests in descending order" do
           get api("/projects/#{project.id}/merge_requests?sort=desc", user)
+
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(3)
           response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -109,7 +123,9 @@ describe API::MergeRequests, api: true  do
 
         it "returns an array of merge_requests ordered by updated_at" do
           get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
+
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(3)
           response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
@@ -118,7 +134,9 @@ describe API::MergeRequests, api: true  do
 
         it "returns an array of merge_requests ordered by created_at" do
           get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
+
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(3)
           response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
@@ -191,6 +209,8 @@ describe API::MergeRequests, api: true  do
       commit = merge_request.commits.first
 
       expect(response.status).to eq 200
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.size).to eq(merge_request.commits.size)
       expect(json_response.first['id']).to eq(commit.id)
       expect(json_response.first['title']).to eq(commit.title)
@@ -205,6 +225,7 @@ describe API::MergeRequests, api: true  do
   describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
     it 'returns the change information of the merge_request' do
       get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
+
       expect(response.status).to eq 200
       expect(json_response['changes'].size).to eq(merge_request.diffs.size)
     end
@@ -572,7 +593,9 @@ describe API::MergeRequests, api: true  do
 
     it "returns merge_request comments ordered by created_at" do
       get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(2)
       expect(json_response.first['note']).to eq("a comment on a MR")
@@ -594,7 +617,9 @@ describe API::MergeRequests, api: true  do
       end
 
       get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(issue.id)
@@ -602,7 +627,9 @@ describe API::MergeRequests, api: true  do
 
     it 'returns an empty array when there are no issues to be closed' do
       get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(0)
     end
@@ -616,6 +643,7 @@ describe API::MergeRequests, api: true  do
       get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['title']).to eq(issue.title)
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 7bb208721a19d15fa7b4a210a5774c99f1551269..418bf5a507cfa122efbcb387c94dc195a0a7a6fc 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -14,6 +14,7 @@ describe API::Milestones, api: true  do
       get api("/projects/#{project.id}/milestones", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.first['title']).to eq(milestone.title)
     end
@@ -28,6 +29,7 @@ describe API::Milestones, api: true  do
       get api("/projects/#{project.id}/milestones?state=active", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(milestone.id)
@@ -37,25 +39,18 @@ describe API::Milestones, api: true  do
       get api("/projects/#{project.id}/milestones?state=closed", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.length).to eq(1)
       expect(json_response.first['id']).to eq(closed_milestone.id)
     end
-  end
-
-  describe 'GET /projects/:id/milestones/:milestone_id' do
-    it 'returns a project milestone by id' do
-      get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
-
-      expect(response).to have_http_status(200)
-      expect(json_response['title']).to eq(milestone.title)
-      expect(json_response['iid']).to eq(milestone.iid)
-    end
 
     it 'returns a project milestone by iid' do
       get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user)
 
       expect(response.status).to eq 200
+      expect(response).to include_pagination_headers
+      expect(json_response.size).to eq(1)
       expect(json_response.size).to eq(1)
       expect(json_response.first['title']).to eq closed_milestone.title
       expect(json_response.first['id']).to eq closed_milestone.id
@@ -70,6 +65,26 @@ describe API::Milestones, api: true  do
       expect(json_response.first['id']).to eq milestone.id
     end
 
+    it 'returns a project milestone by iid array' do
+      get api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid]
+
+      expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response.size).to eq(2)
+      expect(json_response.first['title']).to eq milestone.title
+      expect(json_response.first['id']).to eq milestone.id
+    end
+  end
+
+  describe 'GET /projects/:id/milestones/:milestone_id' do
+    it 'returns a project milestone by id' do
+      get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response['title']).to eq(milestone.title)
+      expect(json_response['iid']).to eq(milestone.iid)
+    end
+
     it 'returns 401 error if user not authenticated' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}")
 
@@ -177,6 +192,7 @@ describe API::Milestones, api: true  do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.first['milestone']['title']).to eq(milestone.title)
     end
@@ -202,6 +218,7 @@ describe API::Milestones, api: true  do
         get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.size).to eq(2)
         expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
@@ -214,6 +231,7 @@ describe API::Milestones, api: true  do
         get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.size).to eq(1)
         expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
@@ -223,6 +241,7 @@ describe API::Milestones, api: true  do
         get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.size).to eq(1)
         expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index a945d56f529ee1b94a0c31b1e4821fb877515c8f..da8fa06d0af95ea7bade62b960ec18c2b9a72969 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -18,17 +18,19 @@ describe API::Namespaces, api: true  do
     context "when authenticated as admin" do
       it "admin: returns an array of all namespaces" do
         get api("/namespaces", admin)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
-
         expect(json_response.length).to eq(Namespace.count)
       end
 
       it "admin: returns an array of matched namespaces" do
         get api("/namespaces?search=#{group2.name}", admin)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
-
         expect(json_response.length).to eq(1)
         expect(json_response.last['path']).to eq(group2.path)
         expect(json_response.last['full_path']).to eq(group2.full_path)
@@ -38,17 +40,19 @@ describe API::Namespaces, api: true  do
     context "when authenticated as a regular user" do
       it "user: returns an array of namespaces" do
         get api("/namespaces", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
-
         expect(json_response.length).to eq(1)
       end
 
       it "admin: returns an array of matched namespaces" do
         get api("/namespaces?search=#{user.username}", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
-
         expect(json_response.length).to eq(1)
       end
     end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 0353ebea9e59b3c518ea17bcf01a98e24c0f3aa1..0d3040519bca388a7d938344d522b168b31d961e 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -41,6 +41,7 @@ describe API::Notes, api: true  do
         get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['body']).to eq(issue_note.note)
       end
@@ -56,6 +57,7 @@ describe API::Notes, api: true  do
           get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response).to be_empty
         end
@@ -75,6 +77,7 @@ describe API::Notes, api: true  do
             get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
 
             expect(response).to have_http_status(200)
+            expect(response).to include_pagination_headers
             expect(json_response).to be_an Array
             expect(json_response.first['body']).to eq(cross_reference_note.note)
           end
@@ -87,6 +90,7 @@ describe API::Notes, api: true  do
         get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['body']).to eq(snippet_note.note)
       end
@@ -109,6 +113,7 @@ describe API::Notes, api: true  do
         get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['body']).to eq(merge_request_note.note)
       end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index f4973d7108859a33a8823a5434593ade1d666601..20c76bd2c057611373e297336070957223941543 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -25,6 +25,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
         expect(response).to have_http_status(200)
 
         expect(json_response).to be_an Array
+        expect(response).to include_pagination_headers
         expect(json_response.count).to eq(1)
         expect(json_response.first['url']).to eq("http://example.com")
         expect(json_response.first['issues_events']).to eq(true)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index eea76c7bb942c844bd7c9b5be9f723a6bcd78a82..f56876bcf54223976f6c9944d5be7f387fb5e3dd 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -16,9 +16,11 @@ describe API::ProjectSnippets, api: true do
       internal_snippet = create(:project_snippet, :internal, project: project)
       private_snippet = create(:project_snippet, :private, project: project)
 
-      get api("/projects/#{project.id}/snippets/", user)
+      get api("/projects/#{project.id}/snippets", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.size).to eq(3)
       expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
       expect(json_response.last).to have_key('web_url')
@@ -28,7 +30,10 @@ describe API::ProjectSnippets, api: true do
       create(:project_snippet, :private, project: project)
 
       get api("/projects/#{project.id}/snippets/", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.size).to eq(0)
     end
   end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 741815a780e105c456a8f6e4757f2c7765bfb15b..db70b63917e1133547ec005ffdb4754a22464bb0 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -76,6 +76,7 @@ describe API::Projects, api: true  do
         get api('/projects', user)
 
         expect(response.status).to eq 200
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first.keys).to include('tag_list')
       end
@@ -84,6 +85,7 @@ describe API::Projects, api: true  do
         get api('/projects', user)
 
         expect(response.status).to eq 200
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first.keys).to include('open_issues_count')
       end
@@ -94,6 +96,7 @@ describe API::Projects, api: true  do
         get api('/projects', user)
 
         expect(response.status).to eq 200
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
       end
@@ -102,6 +105,7 @@ describe API::Projects, api: true  do
         get api('/projects', user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first).not_to include('statistics')
       end
@@ -110,6 +114,7 @@ describe API::Projects, api: true  do
         get api('/projects', user), statistics: true
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first).to include 'statistics'
       end
@@ -121,6 +126,7 @@ describe API::Projects, api: true  do
           get api('/projects?simple=true', user)
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.first.keys).to match_array expected_keys
         end
@@ -131,6 +137,7 @@ describe API::Projects, api: true  do
           get api('/projects', user), { search: project.name }
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(1)
         end
@@ -141,6 +148,7 @@ describe API::Projects, api: true  do
           get api('/projects', user), { visibility: 'private' }
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
         end
@@ -151,6 +159,7 @@ describe API::Projects, api: true  do
           get api('/projects', user), { visibility: 'internal' }
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
         end
@@ -159,6 +168,7 @@ describe API::Projects, api: true  do
           get api('/projects', user), { visibility: 'public' }
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
         end
@@ -169,6 +179,7 @@ describe API::Projects, api: true  do
           get api('/projects', user), { order_by: 'id', sort: 'desc' }
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.first['id']).to eq(project3.id)
         end
@@ -179,6 +190,7 @@ describe API::Projects, api: true  do
           get api('/projects', user4), owned: true
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.first['name']).to eq(project4.name)
           expect(json_response.first['owner']['username']).to eq(user4.username)
@@ -197,6 +209,7 @@ describe API::Projects, api: true  do
           get api('/projects', user3), starred: true
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
         end
@@ -223,6 +236,7 @@ describe API::Projects, api: true  do
           get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.size).to eq(1)
           expect(json_response.first['id']).to eq(project5.id)
@@ -644,9 +658,10 @@ describe API::Projects, api: true  do
         get api("/projects/#{project.id}/events", current_user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
+        expect(json_response).to be_an Array
 
         first_event = json_response.first
-
         expect(first_event['action_name']).to eq('commented on')
         expect(first_event['note']['body']).to eq('What an awesome day!')
 
@@ -699,11 +714,11 @@ describe API::Projects, api: true  do
         get api("/projects/#{project.id}/users", current_user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.size).to eq(1)
 
         first_user = json_response.first
-
         expect(first_user['username']).to eq(member.username)
         expect(first_user['name']).to eq(member.name)
         expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
@@ -746,7 +761,9 @@ describe API::Projects, api: true  do
 
     it 'returns an array of project snippets' do
       get api("/projects/#{project.id}/snippets", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.first['title']).to eq(snippet.title)
     end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index c61208e395c4a0dbe38d3305b0af2c1a034d044c..7652606a4910cb514a5e2311202f23756cfaf732 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -19,10 +19,10 @@ describe API::Repositories, api: true  do
         get api(route, current_user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
+        expect(json_response).to be_an Array
 
         first_commit = json_response.first
-
-        expect(json_response).to be_an Array
         expect(first_commit['name']).to eq('bar')
         expect(first_commit['type']).to eq('tree')
         expect(first_commit['mode']).to eq('040000')
@@ -49,6 +49,7 @@ describe API::Repositories, api: true  do
 
           expect(response.status).to eq(200)
           expect(json_response).to be_an Array
+          expect(response).to include_pagination_headers
           expect(json_response[4]['name']).to eq('html')
           expect(json_response[4]['path']).to eq('files/html')
           expect(json_response[4]['type']).to eq('tree')
@@ -380,10 +381,10 @@ describe API::Repositories, api: true  do
         get api(route, current_user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
 
         first_contributor = json_response.first
-
         expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
         expect(first_contributor['name']).to eq('tiagonbotelho')
         expect(first_contributor['commits']).to eq(1)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index daef9062e7dd25d10b2c91d43a3b63957715c0af..103d6755888b15c874b7a3cab4c94dfedd87f3ec 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -37,18 +37,20 @@ describe API::Runners, api: true  do
     context 'authorized user' do
       it 'returns user available runners' do
         get api('/runners', user)
-        shared = json_response.any?{ |r| r['is_shared'] }
 
+        shared = json_response.any?{ |r| r['is_shared'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(shared).to be_falsey
       end
 
       it 'filters runners by scope' do
         get api('/runners?scope=active', user)
-        shared = json_response.any?{ |r| r['is_shared'] }
 
+        shared = json_response.any?{ |r| r['is_shared'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(shared).to be_falsey
       end
@@ -73,9 +75,10 @@ describe API::Runners, api: true  do
       context 'with admin privileges' do
         it 'returns all runners' do
           get api('/runners/all', admin)
-          shared = json_response.any?{ |r| r['is_shared'] }
 
+          shared = json_response.any?{ |r| r['is_shared'] }
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(shared).to be_truthy
         end
@@ -91,9 +94,10 @@ describe API::Runners, api: true  do
 
       it 'filters runners by scope' do
         get api('/runners/all?scope=specific', admin)
-        shared = json_response.any?{ |r| r['is_shared'] }
 
+        shared = json_response.any?{ |r| r['is_shared'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(shared).to be_falsey
       end
@@ -342,9 +346,10 @@ describe API::Runners, api: true  do
     context 'authorized user with master privileges' do
       it "returns project's runners" do
         get api("/projects/#{project.id}/runners", user)
-        shared = json_response.any?{ |r| r['is_shared'] }
 
+        shared = json_response.any?{ |r| r['is_shared'] }
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(shared).to be_truthy
       end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index 6b9a739b4390b94703e744cd4ea608f9af07a171..1ef92930b3c8e02947ed7284ed11a7961c339380 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -13,6 +13,8 @@ describe API::Snippets, api: true do
       get api("/snippets/", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
         public_snippet.id,
         internal_snippet.id,
@@ -25,7 +27,10 @@ describe API::Snippets, api: true do
       create(:personal_snippet, :private)
 
       get api("/snippets/", user)
+
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.size).to eq(0)
     end
   end
@@ -43,6 +48,8 @@ describe API::Snippets, api: true do
       get api("/snippets/public", user)
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
+      expect(json_response).to be_an Array
       expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
         public_snippet.id,
         public_snippet_other.id)
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index b3e5afdadb1599c8d1eb8ea6ab18735fea3f8b74..b59da632c0037e27f898b8ec132e1e471cb4b81a 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -31,6 +31,7 @@ describe API::SystemHooks, api: true  do
         get api("/hooks", admin)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['url']).to eq(hook.url)
         expect(json_response.first['push_events']).to be true
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 898d2b27e5c31322ef1212c5ce06ffc01e58cab6..8a4f078182f34ad295dc37ddb1956b983d9a362b 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -20,10 +20,9 @@ describe API::Tags, api: true  do
         get api("/projects/#{project.id}/repository/tags", current_user)
 
         expect(response).to have_http_status(200)
-
-        first_tag = json_response.first
-
-        expect(first_tag['name']).to eq(tag_name)
+        expect(response).to include_pagination_headers
+        expect(json_response).to be_an Array
+        expect(json_response.first['name']).to eq(tag_name)
       end
     end
 
@@ -43,7 +42,9 @@ describe API::Tags, api: true  do
     context 'without releases' do
       it "returns an array of project tags" do
         get api("/projects/#{project.id}/repository/tags", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['name']).to eq(tag_name)
       end
@@ -59,6 +60,7 @@ describe API::Tags, api: true  do
         get api("/projects/#{project.id}/repository/tags", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['name']).to eq(tag_name)
         expect(json_response.first['message']).to eq('Version 1.1.0')
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index c0a8c0832bbc1b9d889c29c817d8e4994a74dd6d..8506e8fccdebe590ddf6abfe6601e5eab62b389c 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -22,6 +22,7 @@ describe API::Templates, api: true  do
       get api('/templates/gitignores')
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.size).to be > 15
     end
@@ -32,6 +33,7 @@ describe API::Templates, api: true  do
       get api('/templates/gitlab_ci_ymls')
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.first['name']).not_to be_nil
     end
@@ -69,6 +71,7 @@ describe API::Templates, api: true  do
       get api('/templates/licenses')
 
       expect(response).to have_http_status(200)
+      expect(response).to include_pagination_headers
       expect(json_response).to be_an Array
       expect(json_response.size).to eq(15)
       expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
@@ -80,6 +83,7 @@ describe API::Templates, api: true  do
           get api('/templates/licenses?popular=1')
 
           expect(response).to have_http_status(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.size).to eq(3)
           expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 56dc017ce54ba8c9e17e39717e75024f2ac3fb43..2069d2a7c75a0393142e363b6692fa33fb04f148 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -33,6 +33,7 @@ describe API::Todos, api: true do
         get api('/todos', john_doe)
 
         expect(response.status).to eq(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.length).to eq(3)
         expect(json_response[0]['id']).to eq(pending_3.id)
@@ -52,6 +53,7 @@ describe API::Todos, api: true do
           get api('/todos', john_doe), { author_id: author_2.id }
 
           expect(response.status).to eq(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(2)
         end
@@ -64,6 +66,7 @@ describe API::Todos, api: true do
           get api('/todos', john_doe), { type: 'MergeRequest' }
 
           expect(response.status).to eq(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(1)
         end
@@ -74,6 +77,7 @@ describe API::Todos, api: true do
           get api('/todos', john_doe), { state: 'done' }
 
           expect(response.status).to eq(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(1)
         end
@@ -84,6 +88,7 @@ describe API::Todos, api: true do
           get api('/todos', john_doe), { project_id: project_2.id }
 
           expect(response.status).to eq(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(1)
         end
@@ -94,6 +99,7 @@ describe API::Todos, api: true do
           get api('/todos', john_doe), { action: 'mentioned' }
 
           expect(response.status).to eq(200)
+          expect(response).to include_pagination_headers
           expect(json_response).to be_an Array
           expect(json_response.length).to eq(1)
         end
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 84104aa66ee94cd1a38a5bbf6319283f4038342a..92dfc2aa277308ec87d470e4f842cc0f47ac0350 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -100,6 +100,7 @@ describe API::Triggers do
         get api("/projects/#{project.id}/triggers", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_a(Array)
         expect(json_response[0]).to have_key('token')
       end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5958012672ed4db6a0951802bc45461fabfb5a78..7ece22f19349f5778287a364877cb3026652e6c7 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -40,7 +40,9 @@ describe API::Users, api: true  do
 
       it "returns an array of users" do
         get api("/users", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         username = user.username
         expect(json_response.detect do |user|
@@ -55,13 +57,16 @@ describe API::Users, api: true  do
         get api("/users?blocked=true", user)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response).to all(include('state' => /(blocked|ldap_blocked)/))
       end
 
       it "returns one user" do
         get api("/users?username=#{omniauth_user.username}", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['username']).to eq(omniauth_user.username)
       end
@@ -70,7 +75,9 @@ describe API::Users, api: true  do
     context "when admin" do
       it "returns an array of users" do
         get api("/users", admin)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first.keys).to include 'email'
         expect(json_response.first.keys).to include 'organization'
@@ -87,6 +94,7 @@ describe API::Users, api: true  do
         get api("/users?external=true", admin)
 
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response).to all(include('external' => true))
       end
@@ -507,8 +515,11 @@ describe API::Users, api: true  do
       it 'returns array of ssh keys' do
         user.keys << key
         user.save
+
         get api("/users/#{user.id}/keys", admin)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['title']).to eq(key.title)
       end
@@ -595,8 +606,11 @@ describe API::Users, api: true  do
       it 'returns array of emails' do
         user.emails << email
         user.save
+
         get api("/users/#{user.id}/emails", admin)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first['email']).to eq(email.email)
       end
@@ -774,8 +788,11 @@ describe API::Users, api: true  do
       it "returns array of ssh keys" do
         user.keys << key
         user.save
+
         get api("/user/keys", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first["title"]).to eq(key.title)
       end
@@ -891,8 +908,11 @@ describe API::Users, api: true  do
       it "returns array of emails" do
         user.emails << email
         user.save
+
         get api("/user/emails", user)
+
         expect(response).to have_http_status(200)
+        expect(response).to include_pagination_headers
         expect(json_response).to be_an Array
         expect(json_response.first["email"]).to eq(email.email)
       end
diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8aaf3be4f876553ba9c65312ee5bd583dce25f3a
--- /dev/null
+++ b/spec/requests/api/v3/boards_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe API::V3::Boards, api: true  do
+  include ApiHelpers
+
+  let(:user)        { create(:user) }
+  let(:guest)       { create(:user) }
+  let!(:project)    { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) }
+
+  let!(:dev_label) do
+    create(:label, title: 'Development', color: '#FFAABB', project: project)
+  end
+
+  let!(:test_label) do
+    create(:label, title: 'Testing', color: '#FFAACC', project: project)
+  end
+
+  let!(:dev_list) do
+    create(:list, label: dev_label, position: 1)
+  end
+
+  let!(:test_list) do
+    create(:list, label: test_label, position: 2)
+  end
+
+  let!(:board) do
+    create(:board, project: project, lists: [dev_list, test_list])
+  end
+
+  before do
+    project.team << [user, :reporter]
+    project.team << [guest, :guest]
+  end
+
+  describe "GET /projects/:id/boards" do
+    let(:base_url) { "/projects/#{project.id}/boards" }
+
+    context "when unauthenticated" do
+      it "returns authentication error" do
+        get v3_api(base_url)
+
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context "when authenticated" do
+      it "returns the project issue board" do
+        get v3_api(base_url, user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.length).to eq(1)
+        expect(json_response.first['id']).to eq(board.id)
+        expect(json_response.first['lists']).to be_an Array
+        expect(json_response.first['lists'].length).to eq(2)
+        expect(json_response.first['lists'].last).to have_key('position')
+      end
+    end
+  end
+
+  describe "GET /projects/:id/boards/:board_id/lists" do
+    let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" }
+
+    it 'returns issue board lists' do
+      get v3_api(base_url, user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(json_response.length).to eq(2)
+      expect(json_response.first['label']['name']).to eq(dev_label.title)
+    end
+
+    it 'returns 404 if board not found' do
+      get v3_api("/projects/#{project.id}/boards/22343/lists", user)
+
+      expect(response).to have_http_status(404)
+    end
+  end
+end
diff --git a/spec/requests/api/v3/branches_spec.rb b/spec/requests/api/v3/branches_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0e4c6bc3bc63c6eebbabb49a221b84c2081978c5
--- /dev/null
+++ b/spec/requests/api/v3/branches_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Branches, api: true  do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let!(:project) { create(:project, :repository, creator: user) }
+  let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+  describe "GET /projects/:id/repository/branches" do
+    it "returns an array of project branches" do
+      project.repository.expire_all_method_caches
+
+      get v3_api("/projects/#{project.id}/repository/branches", user), per_page: 100
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      branch_names = json_response.map { |x| x['name'] }
+      expect(branch_names).to match_array(project.repository.branch_names)
+    end
+  end
+end
diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18e2c0d40c8b1c7a121ef1e76931c316f28b4ed6
--- /dev/null
+++ b/spec/requests/api/v3/labels_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe API::V3::Labels, api: true  do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
+  let!(:label1) { create(:label, title: 'label1', project: project) }
+  let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
+
+  before do
+    project.team << [user, :master]
+  end
+
+  describe 'GET /projects/:id/labels' do
+    it 'returns all available labels to the project' do
+      group = create(:group)
+      group_label = create(:group_label, title: 'feature', group: group)
+      project.update(group: group)
+      create(:labeled_issue, project: project, labels: [group_label], author: user)
+      create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
+      create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+
+      expected_keys = [
+        'id', 'name', 'color', 'description',
+        'open_issues_count', 'closed_issues_count', 'open_merge_requests_count',
+        'subscribed', 'priority'
+      ]
+
+      get v3_api("/projects/#{project.id}/labels", user)
+
+      expect(response).to have_http_status(200)
+      expect(json_response).to be_an Array
+      expect(json_response.size).to eq(3)
+      expect(json_response.first.keys).to match_array expected_keys
+      expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+
+      label1_response = json_response.find { |l| l['name'] == label1.title }
+      group_label_response = json_response.find { |l| l['name'] == group_label.title }
+      priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
+
+      expect(label1_response['open_issues_count']).to eq(0)
+      expect(label1_response['closed_issues_count']).to eq(1)
+      expect(label1_response['open_merge_requests_count']).to eq(0)
+      expect(label1_response['name']).to eq(label1.name)
+      expect(label1_response['color']).to be_present
+      expect(label1_response['description']).to be_nil
+      expect(label1_response['priority']).to be_nil
+      expect(label1_response['subscribed']).to be_falsey
+
+      expect(group_label_response['open_issues_count']).to eq(1)
+      expect(group_label_response['closed_issues_count']).to eq(0)
+      expect(group_label_response['open_merge_requests_count']).to eq(0)
+      expect(group_label_response['name']).to eq(group_label.name)
+      expect(group_label_response['color']).to be_present
+      expect(group_label_response['description']).to be_nil
+      expect(group_label_response['priority']).to be_nil
+      expect(group_label_response['subscribed']).to be_falsey
+
+      expect(priority_label_response['open_issues_count']).to eq(0)
+      expect(priority_label_response['closed_issues_count']).to eq(0)
+      expect(priority_label_response['open_merge_requests_count']).to eq(1)
+      expect(priority_label_response['name']).to eq(priority_label.name)
+      expect(priority_label_response['color']).to be_present
+      expect(priority_label_response['description']).to be_nil
+      expect(priority_label_response['priority']).to eq(3)
+      expect(priority_label_response['subscribed']).to be_falsey
+    end
+  end
+end
diff --git a/spec/requests/api/v3/repositories_spec.rb b/spec/requests/api/v3/repositories_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c696721c1c9177e8f6919606f03f246a5955219b
--- /dev/null
+++ b/spec/requests/api/v3/repositories_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Repositories, api: true  do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
+  let!(:project) { create(:project, :repository, creator: user) }
+  let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+  describe "GET /projects/:id/repository/tree" do
+    let(:route) { "/projects/#{project.id}/repository/tree" }
+
+    shared_examples_for 'repository tree' do
+      it 'returns the repository tree' do
+        get v3_api(route, current_user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+
+        first_commit = json_response.first
+        expect(first_commit['name']).to eq('bar')
+        expect(first_commit['type']).to eq('tree')
+        expect(first_commit['mode']).to eq('040000')
+      end
+
+      context 'when ref does not exist' do
+        it_behaves_like '404 response' do
+          let(:request) { get v3_api("#{route}?ref_name=foo", current_user) }
+          let(:message) { '404 Tree Not Found' }
+        end
+      end
+
+      context 'when repository is disabled' do
+        include_context 'disabled repository'
+
+        it_behaves_like '403 response' do
+          let(:request) { get v3_api(route, current_user) }
+        end
+      end
+
+      context 'with recursive=1' do
+        it 'returns recursive project paths tree' do
+          get v3_api("#{route}?recursive=1", current_user)
+
+          expect(response.status).to eq(200)
+          expect(json_response).to be_an Array
+          expect(json_response[4]['name']).to eq('html')
+          expect(json_response[4]['path']).to eq('files/html')
+          expect(json_response[4]['type']).to eq('tree')
+          expect(json_response[4]['mode']).to eq('040000')
+        end
+
+        context 'when repository is disabled' do
+          include_context 'disabled repository'
+
+          it_behaves_like '403 response' do
+            let(:request) { get v3_api(route, current_user) }
+          end
+        end
+
+        context 'when ref does not exist' do
+          it_behaves_like '404 response' do
+            let(:request) { get v3_api("#{route}?recursive=1&ref_name=foo", current_user) }
+            let(:message) { '404 Tree Not Found' }
+          end
+        end
+      end
+    end
+
+    context 'when unauthenticated', 'and project is public' do
+      it_behaves_like 'repository tree' do
+        let(:project) { create(:project, :public, :repository) }
+        let(:current_user) { nil }
+      end
+    end
+
+    context 'when unauthenticated', 'and project is private' do
+      it_behaves_like '404 response' do
+        let(:request) { get v3_api(route) }
+        let(:message) { '404 Project Not Found' }
+      end
+    end
+
+    context 'when authenticated', 'as a developer' do
+      it_behaves_like 'repository tree' do
+        let(:current_user) { user }
+      end
+    end
+
+    context 'when authenticated', 'as a guest' do
+      it_behaves_like '403 response' do
+        let(:request) { get v3_api(route, guest) }
+      end
+    end
+  end
+
+  describe 'GET /projects/:id/repository/contributors' do
+    let(:route) { "/projects/#{project.id}/repository/contributors" }
+
+    shared_examples_for 'repository contributors' do
+      it 'returns valid data' do
+        get v3_api(route, current_user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+
+        first_contributor = json_response.first
+        expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
+        expect(first_contributor['name']).to eq('tiagonbotelho')
+        expect(first_contributor['commits']).to eq(1)
+        expect(first_contributor['additions']).to eq(0)
+        expect(first_contributor['deletions']).to eq(0)
+      end
+    end
+
+    context 'when unauthenticated', 'and project is public' do
+      it_behaves_like 'repository contributors' do
+        let(:project) { create(:project, :public, :repository) }
+        let(:current_user) { nil }
+      end
+    end
+
+    context 'when unauthenticated', 'and project is private' do
+      it_behaves_like '404 response' do
+        let(:request) { get v3_api(route) }
+        let(:message) { '404 Project Not Found' }
+      end
+    end
+
+    context 'when authenticated', 'as a developer' do
+      it_behaves_like 'repository contributors' do
+        let(:current_user) { user }
+      end
+    end
+
+    context 'when authenticated', 'as a guest' do
+      it_behaves_like '403 response' do
+        let(:request) { get v3_api(route, guest) }
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/v3/system_hooks_spec.rb b/spec/requests/api/v3/system_hooks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..da58efb6ebf0de1a9fcab1dc89c98e4d0a26cc49
--- /dev/null
+++ b/spec/requests/api/v3/system_hooks_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe API::V3::SystemHooks, api: true  do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let(:admin) { create(:admin) }
+  let!(:hook) { create(:system_hook, url: "http://example.com") }
+
+  before { stub_request(:post, hook.url) }
+
+  describe "GET /hooks" do
+    context "when no user" do
+      it "returns authentication error" do
+        get v3_api("/hooks")
+
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context "when not an admin" do
+      it "returns forbidden error" do
+        get v3_api("/hooks", user)
+
+        expect(response).to have_http_status(403)
+      end
+    end
+
+    context "when authenticated as admin" do
+      it "returns an array of hooks" do
+        get v3_api("/hooks", admin)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['url']).to eq(hook.url)
+        expect(json_response.first['push_events']).to be true
+        expect(json_response.first['tag_push_events']).to be false
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/v3/tags_spec.rb b/spec/requests/api/v3/tags_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6722789d928aac30d97f6be3adf4dfc432eeab08
--- /dev/null
+++ b/spec/requests/api/v3/tags_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+require 'mime/types'
+
+describe API::V3::Tags, api: true  do
+  include ApiHelpers
+  include RepoHelpers
+
+  let(:user) { create(:user) }
+  let(:user2) { create(:user) }
+  let!(:project) { create(:project, :repository, creator: user) }
+  let!(:master) { create(:project_member, :master, user: user, project: project) }
+
+  describe "GET /projects/:id/repository/tags" do
+    let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+    let(:description) { 'Awesome release!' }
+
+    shared_examples_for 'repository tags' do
+      it 'returns the repository tags' do
+        get v3_api("/projects/#{project.id}/repository/tags", current_user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['name']).to eq(tag_name)
+      end
+    end
+
+    context 'when unauthenticated' do
+      it_behaves_like 'repository tags' do
+        let(:project) { create(:project, :public, :repository) }
+        let(:current_user) { nil }
+      end
+    end
+
+    context 'when authenticated' do
+      it_behaves_like 'repository tags' do
+        let(:current_user) { user }
+      end
+    end
+
+    context 'without releases' do
+      it "returns an array of project tags" do
+        get v3_api("/projects/#{project.id}/repository/tags", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['name']).to eq(tag_name)
+      end
+    end
+
+    context 'with releases' do
+      before do
+        release = project.releases.find_or_initialize_by(tag: tag_name)
+        release.update_attributes(description: description)
+      end
+
+      it "returns an array of project tags with release info" do
+        get v3_api("/projects/#{project.id}/repository/tags", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['name']).to eq(tag_name)
+        expect(json_response.first['message']).to eq('Version 1.1.0')
+        expect(json_response.first['release']['description']).to eq(description)
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7022f87bc51e3bbc4f1ed4e56cbdafa771069eee
--- /dev/null
+++ b/spec/requests/api/v3/users_spec.rb
@@ -0,0 +1,120 @@
+require 'spec_helper'
+
+describe API::V3::Users, api: true  do
+  include ApiHelpers
+
+  let(:user)  { create(:user) }
+  let(:admin) { create(:admin) }
+  let(:key)   { create(:key, user: user) }
+  let(:email)   { create(:email, user: user) }
+
+  describe 'GET /user/:id/keys' do
+    before { admin }
+
+    context 'when unauthenticated' do
+      it 'returns authentication error' do
+        get v3_api("/users/#{user.id}/keys")
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context 'when authenticated' do
+      it 'returns 404 for non-existing user' do
+        get v3_api('/users/999999/keys', admin)
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 User Not Found')
+      end
+
+      it 'returns array of ssh keys' do
+        user.keys << key
+        user.save
+
+        get v3_api("/users/#{user.id}/keys", admin)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['title']).to eq(key.title)
+      end
+    end
+  end
+
+  describe 'GET /user/:id/emails' do
+    before { admin }
+
+    context 'when unauthenticated' do
+      it 'returns authentication error' do
+        get v3_api("/users/#{user.id}/emails")
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context 'when authenticated' do
+      it 'returns 404 for non-existing user' do
+        get v3_api('/users/999999/emails', admin)
+        expect(response).to have_http_status(404)
+        expect(json_response['message']).to eq('404 User Not Found')
+      end
+
+      it 'returns array of emails' do
+        user.emails << email
+        user.save
+
+        get v3_api("/users/#{user.id}/emails", admin)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first['email']).to eq(email.email)
+      end
+
+      it "returns a 404 for invalid ID" do
+        put v3_api("/users/ASDF/emails", admin)
+
+        expect(response).to have_http_status(404)
+      end
+    end
+  end
+
+  describe "GET /user/keys" do
+    context "when unauthenticated" do
+      it "returns authentication error" do
+        get v3_api("/user/keys")
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context "when authenticated" do
+      it "returns array of ssh keys" do
+        user.keys << key
+        user.save
+
+        get v3_api("/user/keys", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first["title"]).to eq(key.title)
+      end
+    end
+  end
+
+  describe "GET /user/emails" do
+    context "when unauthenticated" do
+      it "returns authentication error" do
+        get v3_api("/user/emails")
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context "when authenticated" do
+      it "returns array of emails" do
+        user.emails << email
+        user.save
+
+        get v3_api("/user/emails", user)
+
+        expect(response).to have_http_status(200)
+        expect(json_response).to be_an Array
+        expect(json_response.first["email"]).to eq(email.email)
+      end
+    end
+  end
+end
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
new file mode 100644
index 0000000000000000000000000000000000000000..60f5e8239a735fc0a372ea7942b03152e5c75a97
--- /dev/null
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :include_pagination_headers do |expected|
+  match do |actual|
+    expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
+  end
+end