diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 30a35a04339f6a9fcc680e0ff554a32ea0443275..c714c0fa9394c1c75961cf821ea26e391f85b50e 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -66,7 +66,7 @@ class @DropzoneInput
 
       success: (header, response) ->
         child = $(dropzone[0]).children("textarea")
-        $(child).val $(child).val() + formatLink(response.link) + "\n"
+        $(child).val $(child).val() + response.link.markdown + "\n"
         return
 
       error: (temp, errorMessage) ->
@@ -99,11 +99,6 @@ class @DropzoneInput
 
     child = $(dropzone[0]).children("textarea")
 
-    formatLink = (link) ->
-      text = "[#{link.alt}](#{link.url})"
-      text = "!#{text}" if link.is_image
-      text
-
     handlePaste = (event) ->
       pasteEvent = event.originalEvent
       if pasteEvent.clipboardData and pasteEvent.clipboardData.items
@@ -162,7 +157,7 @@ class @DropzoneInput
           closeAlertMessage()
 
         success: (e, textStatus, response) ->
-          insertToTextArea(filename, formatLink(response.responseJSON.link))
+          insertToTextArea(filename, response.responseJSON.link.markdown)
 
         error: (response) ->
           showError(response.responseJSON.message)
@@ -202,8 +197,3 @@ class @DropzoneInput
       e.preventDefault()
       $(@).closest('.gfm-form').find('.div-dropzone').click()
       return
-
-  formatLink: (link) ->
-    text = "[#{link.alt}](#{link.url})"
-    text = "!#{text}" if link.is_image
-    text
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
index 99f22293d0dd0e182f4b936e4e03a371e0c507de..b846a59ed948bd5493340e34f632dd5d4cb6bc1a 100644
--- a/app/services/projects/download_service.rb
+++ b/app/services/projects/download_service.rb
@@ -18,10 +18,15 @@ module Projects
 
       filename = uploader.image? ? uploader.file.basename : uploader.file.filename
 
+      escaped_filename = filename.gsub("]", "\\]")
+      markdown = "[#{escaped_filename}](#{uploader.secure_url})"
+      markdown.prepend("!") if uploader.image?
+
       {
         'alt'       => filename,
         'url'       => uploader.secure_url,
-        'is_image'  => uploader.image?
+        'is_image'  => uploader.image?,
+        'markdown'  => markdown
       }
     end
 
diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb
index 279550d6f4a2e9e484711ad0b65a3e2249a0f5ed..36ccf1cda12e338983a341e9bb14b6e7596c246e 100644
--- a/app/services/projects/upload_service.rb
+++ b/app/services/projects/upload_service.rb
@@ -12,10 +12,15 @@ module Projects
 
       filename = uploader.image? ? uploader.file.basename : uploader.file.filename
 
+      escaped_filename = filename.gsub("]", "\\]")
+      markdown = "[#{escaped_filename}](#{uploader.secure_url})"
+      markdown.prepend("!") if uploader.image?
+
       {
         alt:      filename,
         url:      uploader.secure_url,
-        is_image: uploader.image?
+        is_image: uploader.image?,
+        markdown: markdown
       }
     end
 
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0ca81ffd49ee1b39636a9d9045d2e6d8022523b6..37d74216c1b5a7850fd1ae7d05017e4fb2ab2d23 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -482,6 +482,34 @@ Parameters:
 
 - `id` (required) - The ID of a project
 
+## Uploads
+
+### Upload a file
+
+Uploads a file to the specified project to be used in an issue or merge request description, or a comment.
+
+```
+POST /projects/:id/uploads
+```
+
+Parameters:
+
+- `id` (required) - The ID of the project
+- `file` (required) - The file to be uploaded
+
+```json
+{
+  "alt": "dk",
+  "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
+  "is_image": true,
+  "markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
+}
+```
+
+**Note**: The returned `url` is relative to the project path.
+In Markdown contexts, the link is automatically expanded when the format in `markdown` is used.
+
+
 ## Team members
 
 ### List project team members
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 0781236cf6daa07fd0546f58f8f9620d2c22bf06..8b1390e3289ec8d8db1e55f72b4fd880089cf106 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -269,7 +269,7 @@ module API
       # Remove a forked_from relationship
       #
       # Parameters:
-      # id: (required) - The ID of the project being marked as a fork
+      #   id: (required) - The ID of the project being marked as a fork
       # Example Request:
       #  DELETE /projects/:id/fork
       delete ":id/fork" do
@@ -278,6 +278,16 @@ module API
           user_project.forked_project_link.destroy
         end
       end
+
+      # Upload a file
+      #
+      # Parameters:
+      #   id: (required) - The ID of the project
+      #   file: (required) - The file to be uploaded
+      post ":id/uploads" do
+        ::Projects::UploadService.new(user_project, params[:file]).execute
+      end
+
       # search for projects current_user has access to
       #
       # Parameters:
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 2b252b3288798e7ffe92726f7736829053b0cc5c..2ca21af5bc8de8bdc765c2f97a12ef2ec335398c 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -74,7 +74,7 @@ module Gitlab
 
       def sent_notification
         return nil unless reply_key
-        
+
         SentNotification.for(reply_key)
       end
 
@@ -82,10 +82,7 @@ module Gitlab
         attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)
 
         attachments.each do |link|
-          text = "[#{link[:alt]}](#{link[:url]})"
-          text.prepend("!") if link[:is_image]
-
-          reply << "\n\n#{text}"
+          reply << "\n\n#{link[:markdown]}"
         end
 
         reply
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 403ebeec47417d678a0a617fa6697ec89ba0953a..d5f755f90e5d989c1ad3a683034c04669e702fd0 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -232,9 +232,7 @@ module Gitlab
 
         return nil if res.nil?
 
-        text = "[#{res['alt']}](#{res['url']})"
-        text = "!#{text}" if res['is_image']
-        text
+        text = res['markdown']
       end
 
       def build_attachment_url(rel_url)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index ab2530859eaaf937efc6898eeb7224d40a9bc69a..6f4c336b66ca65e37c6bc79c80f59986993022ea 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -353,6 +353,20 @@ describe API::API, api: true  do
     end
   end
 
+  describe "POST /projects/:id/uploads" do
+    before { project }
+
+    it "uploads the file and returns its info" do
+      post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+
+      expect(response.status).to be(201)
+      expect(json_response['alt']).to eq("dk")
+      expect(json_response['url']).to start_with("/uploads/")
+      expect(json_response['url']).to end_with("/dk.png")
+      expect(json_response['is_image']).to eq(true)
+    end
+  end
+
   describe 'GET /projects/:id' do
     before { project }
     before { project_member }