diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index e7f79a0d359a3b48995cfa051143027128fb2131..8762aad42254e1649afd1b5794452b54624c6922 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -27,6 +27,7 @@ Example response:
 ```json
 [
    {
+      "id": 42,
       "tag_name":"v0.2",
       "description":"## CHANGELOG\r\n\r\n- Escape label and milestone titles to prevent XSS in GFM autocomplete. !2740\r\n- Prevent private snippets from being embeddable.\r\n- Add subresources removal to member destroy service.",
       "name":"Awesome app v0.2 beta",
@@ -93,6 +94,7 @@ Example response:
       }
    },
    {
+      "id": 43,
       "tag_name":"v0.1",
       "description":"## CHANGELOG\r\n\r\n-Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
       "name":"Awesome app v0.1 alpha",
@@ -154,6 +156,9 @@ Example response:
 
 Get a Release for the given tag.
 
+CAUTION: **Warning:**
+This endpoint has been deprecated in Gitlab 11.11 and will be removed in 12.0. Switch to using `GET /projects/:id/releases/:release_id` instead.
+
 ```
 GET /projects/:id/releases/:tag_name
 ```
@@ -173,6 +178,87 @@ Example response:
 
 ```json
 {
+   "id": 42,
+   "tag_name":"v0.1",
+   "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
+   "name":"Awesome app v0.1 alpha",
+   "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
+   "created_at":"2019-01-03T01:55:18.203Z",
+   "author":{
+      "id":1,
+      "name":"Administrator",
+      "username":"root",
+      "state":"active",
+      "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+      "web_url":"http://localhost:3000/root"
+   },
+   "commit":{
+      "id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
+      "short_id":"f8d3d94c",
+      "title":"Initial commit",
+      "created_at":"2019-01-03T01:53:28.000Z",
+      "parent_ids":[
+
+      ],
+      "message":"Initial commit",
+      "author_name":"Administrator",
+      "author_email":"admin@example.com",
+      "authored_date":"2019-01-03T01:53:28.000Z",
+      "committer_name":"Administrator",
+      "committer_email":"admin@example.com",
+      "committed_date":"2019-01-03T01:53:28.000Z"
+   },
+   "assets":{
+      "count":4,
+      "sources":[
+         {
+            "format":"zip",
+            "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+         },
+         {
+            "format":"tar.gz",
+            "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+         },
+         {
+            "format":"tar.bz2",
+            "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+         },
+         {
+            "format":"tar",
+            "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+         }
+      ],
+      "links":[
+
+      ]
+   }
+}
+```
+
+## Get single release
+
+Get a specific release for given id.
+
+```
+GET /projects/:id/releases/:release_id
+```
+
+| Attribute     | Type           | Required | Description                             |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id`          | integer/string | yes      | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
+| `release_id`  | integer/string | yes      | The release id. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/123"
+```
+
+Example response:
+
+```json
+{
+   "id": 42,
    "tag_name":"v0.1",
    "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
    "name":"Awesome app v0.1 alpha",
@@ -260,6 +346,7 @@ Example response:
 
 ```json
 {
+   "id": 42,
    "tag_name":"v0.3",
    "description":"Super nice release",
    "name":"New release",
@@ -346,6 +433,7 @@ Example response:
 
 ```json
 {
+   "id": 42,
    "tag_name":"v0.1",
    "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
    "name":"new name",
@@ -425,6 +513,7 @@ Example response:
 
 ```json
 {
+   "id": 42,
    "tag_name":"v0.1",
    "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
    "name":"new name",
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 3177fec618f916f172abd42295ae3ed32fabfbdc..645e338e736f509b2d64d6b6fe8919caf3c13ff3 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -41,6 +41,7 @@ Parameters:
       "committed_date": "2012-05-28T04:42:42-07:00"
     },
     "release": {
+      "id": 42,
       "tag_name": "1.0.0",
       "description": "Amazing release. Wow"
     },
@@ -133,6 +134,7 @@ Parameters:
     "committed_date": "2012-05-28T04:42:42-07:00"
   },
   "release": {
+    "id": 42,
     "tag_name": "1.0.0",
     "description": "Amazing release. Wow"
   },
@@ -191,6 +193,7 @@ Response:
 
 ```json
 {
+  "id": 42,
   "tag_name": "1.0.0",
   "description": "Amazing release. Wow"
 }
@@ -223,6 +226,7 @@ Response:
 
 ```json
 {
+  "id": 42,
   "tag_name": "1.0.0",
   "description": "Amazing release. Wow"
 }
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index e25401b62604aa2785cf4f6240d0b6518b6dbae8..c493c45a874cb536983fd1cdb5debb4ec7de9855 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1130,6 +1130,7 @@ def self.exposed_attributes
 
     # deprecated old Release representation
     class TagRelease < Grape::Entity
+      expose :id
       expose :tag, as: :tag_name
       expose :description
     end
@@ -1149,6 +1150,7 @@ class Source < Grape::Entity
     end
 
     class Release < Grape::Entity
+      expose :id
       expose :name
       expose :tag, as: :tag_name, if: -> (release, _) { can_download_code?(release.project) }
       expose :description
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 6b17f4317db1272106eb81093fe297440cdca5e5..94f73bb156b647930031f3f93167896e09598589 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -27,16 +27,28 @@ class Releases < Grape::API
       end
 
       desc 'Get a single project release' do
-        detail 'This feature was introduced in GitLab 11.7.'
+        detail 'Finding release by tag name  has been deprecated in Gitlab 11.11 and will be removed in 12.0. Switch to using `GET /projects/:id/releases/:release_id` instead.'
         success Entities::Release
       end
       params do
         requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
       end
       get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
-        authorize_download_code!
-
-        present release, with: Entities::Release, current_user: current_user
+        # Deprecate `:id/releases/:tag_name` in GitLab 11.11.
+        # We want to introduce `:id/releases/:release_id` which is basicaly the same route,
+        # so for the time being we need to make this endpoint support both.
+        # If release with such tag exists behave like the old endpoint,
+        # otherwise act like looking up release by id.
+        # Cleanup once we remove `:id/releases/:tag_name` in GitLab 12.0.
+        if release_by_tag
+          authorize! :download_code, release_by_tag
+          present release_by_tag, with: Entities::Release, current_user: current_user
+        elsif release_by_id
+          authorize! :read_release, release_by_id
+          present release_by_id, with: Entities::Release, current_user: current_user
+        else
+          forbidden!
+        end
       end
 
       desc 'Create a new release' do
@@ -123,10 +135,6 @@ def authorize_read_releases!
         authorize! :read_release, user_project
       end
 
-      def authorize_read_release!
-        authorize! :read_release, release
-      end
-
       def authorize_update_release!
         authorize! :update_release, release
       end
@@ -135,13 +143,17 @@ def authorize_destroy_release!
         authorize! :destroy_release, release
       end
 
-      def authorize_download_code!
-        authorize! :download_code, release
-      end
-
       def release
         @release ||= user_project.releases.find_by_tag(params[:tag])
       end
+
+      def release_by_tag
+        release
+      end
+
+      def release_by_id
+        @release_by_id ||= user_project.releases.find_by_id(params[:tag])
+      end
     end
   end
 end
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
index 6ea0781c1edef1362ac4a5f77ca2790f64267af6..a6ab0f647bd6560cd07d8f47a826e3b248f473e4 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -1,7 +1,8 @@
 {
   "type": "object",
-  "required": ["name", "tag_name", "commit"],
+  "required": ["id", "name", "tag_name", "commit"],
   "properties": {
+    "id": { "type": "integer" },
     "name": { "type": "string" },
     "tag_name": { "type": "string" },
     "description": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
index e78398ad1d5231335451c3a2fb8ff20765c6be33..54cd83cb68c3a546143f452f55b9f13b3fcee9ff 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
@@ -1,7 +1,8 @@
 {
   "type": "object",
-  "required": ["name"],
+  "required": ["id", "name"],
   "properties": {
+    "id": { "type": "integer" },
     "name": { "type": "string" },
     "description": { "type": "string" },
     "description_html": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
index 6612c2a9911b852e1ccbcbac862ab1577990dc2e..635b4323458dbcc4ae8e7b8d52ca31db682305e0 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release/tag_release.json
@@ -1,10 +1,12 @@
 {
   "type": "object",
   "required" : [
+    "id",
     "tag_name",
     "description"
   ],
   "properties" : {
+    "id": { "type": "integer" },
     "tag_name": { "type": ["string", "null"] },
     "description": { "type": "string" }
   },
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 8603fa2a73dafe0098d958bfcd62662b4c23fb94..5efd480868f9c38694a1d5a5b6a9d5ec65d241b3 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -291,6 +291,169 @@
     end
   end
 
+  describe 'GET /projects/:id/releases/:id' do
+    context 'when there is a release' do
+      let!(:release) do
+        create(:release,
+               project: project,
+               tag: 'v0.1',
+               sha: commit.id,
+               author: maintainer,
+               description: 'This is v0.1')
+      end
+
+      it 'returns 200 HTTP status' do
+        get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+        expect(response).to have_gitlab_http_status(:ok)
+      end
+
+      it 'returns a release entry' do
+        get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+        expect(json_response['tag_name']).to eq(release.tag)
+        expect(json_response['description']).to eq('This is v0.1')
+        expect(json_response['author']['name']).to eq(maintainer.name)
+        expect(json_response['commit']['id']).to eq(commit.id)
+        expect(json_response['assets']['count']).to eq(4)
+      end
+
+      it 'matches response schema' do
+        get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+        expect(response).to match_response_schema('public_api/v4/release')
+      end
+
+      it 'contains source information as assets' do
+        get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+        expect(json_response['assets']['sources'].map { |h| h['format'] })
+          .to match_array(release.sources.map(&:format))
+        expect(json_response['assets']['sources'].map { |h| h['url'] })
+          .to match_array(release.sources.map(&:url))
+      end
+
+      context "when release description contains confidential issue's link" do
+        let(:confidential_issue) do
+          create(:issue,
+                 :confidential,
+                 project: project,
+                 title: 'A vulnerability')
+        end
+
+        let!(:release) do
+          create(:release,
+                 project: project,
+                 tag: 'v0.1',
+                 sha: commit.id,
+                 author: maintainer,
+                 description: "This is confidential #{confidential_issue.to_reference}")
+        end
+
+        it "does not expose confidential issue's title" do
+          get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+          expect(json_response['description_html']).to include(confidential_issue.to_reference)
+          expect(json_response['description_html']).not_to include('A vulnerability')
+        end
+      end
+
+      context 'when release has link asset' do
+        let!(:link) do
+          create(:release_link,
+                 release: release,
+                 name: 'release-18.04.dmg',
+                 url: url)
+        end
+
+        let(:url) { 'https://my-external-hosting.example.com/scrambled-url/app.zip' }
+
+        it 'contains link information as assets' do
+          get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+          expect(json_response['assets']['links'].count).to eq(1)
+          expect(json_response['assets']['links'].first['id']).to eq(link.id)
+          expect(json_response['assets']['links'].first['name'])
+            .to eq('release-18.04.dmg')
+          expect(json_response['assets']['links'].first['url'])
+            .to eq('https://my-external-hosting.example.com/scrambled-url/app.zip')
+          expect(json_response['assets']['links'].first['external'])
+            .to be_truthy
+        end
+
+        context 'when link is internal' do
+          let(:url) do
+            "#{project.web_url}/-/jobs/artifacts/v11.6.0-rc4/download?" \
+            "job=rspec-mysql+41%2F50"
+          end
+
+          it 'has external false' do
+            get api("/projects/#{project.id}/releases/#{release.id}", maintainer)
+
+            expect(json_response['assets']['links'].first['external'])
+              .to be_falsy
+          end
+        end
+      end
+
+      context 'when user is a guest' do
+        it 'responds 200 OK' do
+          get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+          expect(response).to have_gitlab_http_status(:ok)
+        end
+
+        it "does not expose tag, commit and source code" do
+          get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+          expect(response).to match_response_schema('public_api/v4/release/release_for_guest')
+          expect(json_response['assets']['count']).to eq(release.links.count)
+        end
+
+        context 'when project is public' do
+          let(:project) { create(:project, :repository, :public) }
+
+          it 'responds 200 OK' do
+            get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+            expect(response).to have_gitlab_http_status(:ok)
+          end
+
+          it "exposes tag and commit" do
+            create(:release,
+                   project: project,
+                   tag: 'v0.1',
+                   author: maintainer,
+                   created_at: 2.days.ago)
+            get api("/projects/#{project.id}/releases/#{release.id}", guest)
+
+            expect(response).to match_response_schema('public_api/v4/release')
+          end
+        end
+      end
+    end
+
+    context 'when user is not a project member' do
+      let!(:release) { create(:release, tag: 'v0.1', project: project) }
+
+      it 'cannot find the project' do
+        get api("/projects/#{project.id}/releases/#{release.id}", non_project_member)
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+
+      context 'when project is public' do
+        let(:project) { create(:project, :repository, :public) }
+
+        it 'allows the request' do
+          get api("/projects/#{project.id}/releases/#{release.id}", non_project_member)
+
+          expect(response).to have_gitlab_http_status(:ok)
+        end
+      end
+    end
+  end
+
   describe 'POST /projects/:id/releases' do
     let(:params) do
       {