diff --git a/integrations/pull_update_test.go b/integrations/pull_update_test.go
index 484390001c..2dc966316e 100644
--- a/integrations/pull_update_test.go
+++ b/integrations/pull_update_test.go
@@ -5,7 +5,7 @@
 package integrations
 
 import (
-	"fmt"
+	"net/http"
 	"net/url"
 	"testing"
 	"time"
@@ -19,7 +19,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestPullUpdate(t *testing.T) {
+func TestAPIPullUpdate(t *testing.T) {
 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
 		//Create PR to test
 		user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
@@ -31,17 +31,19 @@ func TestPullUpdate(t *testing.T) {
 		assert.NoError(t, err)
 		assert.EqualValues(t, 1, diffCount.Behind)
 		assert.EqualValues(t, 1, diffCount.Ahead)
+		assert.NoError(t, pr.LoadBaseRepo())
+		assert.NoError(t, pr.LoadIssue())
 
-		message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
-		err = pull_service.Update(pr, user, message)
-		assert.NoError(t, err)
+		session := loginUser(t, "user2")
+		token := getTokenForLoggedInUser(t, session)
+		req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index)
+		session.MakeRequest(t, req, http.StatusOK)
 
 		//Test GetDiverging after update
 		diffCount, err = pull_service.GetDiverging(pr)
 		assert.NoError(t, err)
 		assert.EqualValues(t, 0, diffCount.Behind)
 		assert.EqualValues(t, 2, diffCount.Ahead)
-
 	})
 }
 
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index f67eebacc4..506e6a3ec0 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -806,6 +806,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 							Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
 						m.Get(".diff", repo.DownloadPullDiff)
 						m.Get(".patch", repo.DownloadPullPatch)
+						m.Post("/update", reqToken(), repo.UpdatePullRequest)
 						m.Combo("/merge").Get(repo.IsPullRequestMerged).
 							Post(reqToken(), mustNotBeArchived, bind(auth.MergePullRequestForm{}), repo.MergePullRequest)
 						m.Group("/reviews", func() {
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index 5acbb9e297..5fc0cd8cfb 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -968,3 +968,99 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 
 	return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
 }
+
+// UpdatePullRequest merge PR's baseBranch into headBranch
+func UpdatePullRequest(ctx *context.APIContext) {
+	// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/update repository repoUpdatePullRequest
+	// ---
+	// summary: Merge PR's baseBranch into headBranch
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repo
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repo
+	//   type: string
+	//   required: true
+	// - name: index
+	//   in: path
+	//   description: index of the pull request to get
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/empty"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+	//   "409":
+	//     "$ref": "#/responses/error"
+	//   "422":
+	//     "$ref": "#/responses/validationError"
+
+	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+	if err != nil {
+		if models.IsErrPullRequestNotExist(err) {
+			ctx.NotFound()
+		} else {
+			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
+		}
+		return
+	}
+
+	if pr.HasMerged {
+		ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err)
+		return
+	}
+
+	if err = pr.LoadIssue(); err != nil {
+		ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
+		return
+	}
+
+	if pr.Issue.IsClosed {
+		ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err)
+		return
+	}
+
+	if err = pr.LoadBaseRepo(); err != nil {
+		ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
+		return
+	}
+	if err = pr.LoadHeadRepo(); err != nil {
+		ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
+		return
+	}
+
+	allowedUpdate, err := pull_service.IsUserAllowedToUpdate(pr, ctx.User)
+	if err != nil {
+		ctx.Error(http.StatusInternalServerError, "IsUserAllowedToMerge", err)
+		return
+	}
+
+	if !allowedUpdate {
+		ctx.Status(http.StatusForbidden)
+		return
+	}
+
+	// default merge commit message
+	message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
+
+	if err = pull_service.Update(pr, ctx.User, message); err != nil {
+		if models.IsErrMergeConflicts(err) {
+			ctx.Error(http.StatusConflict, "Update", "merge failed because of conflict")
+			return
+		}
+		ctx.Error(http.StatusInternalServerError, "pull_service.Update", err)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 60326db03a..cfe30a1a19 100644
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -666,7 +666,7 @@ func ViewPullFiles(ctx *context.Context) {
 	ctx.HTML(200, tplPullFiles)
 }
 
-// UpdatePullRequest merge master into PR
+// UpdatePullRequest merge PR's baseBranch into headBranch
 func UpdatePullRequest(ctx *context.Context) {
 	issue := checkPullInfo(ctx)
 	if ctx.Written() {
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index c601809a75..d5e5c86cd8 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -7285,6 +7285,59 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/pulls/{index}/update": {
+      "post": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Merge PR's baseBranch into headBranch",
+        "operationId": "repoUpdatePullRequest",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "integer",
+            "format": "int64",
+            "description": "index of the pull request to get",
+            "name": "index",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/empty"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          },
+          "409": {
+            "$ref": "#/responses/error"
+          },
+          "422": {
+            "$ref": "#/responses/validationError"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/raw/{filepath}": {
       "get": {
         "produces": [