diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go
index 1e443362f6..c909e96f06 100644
--- a/integrations/api_repo_test.go
+++ b/integrations/api_repo_test.go
@@ -498,6 +498,85 @@ func TestAPIRepoTransfer(t *testing.T) {
 	_ = models.DeleteRepository(user, repo.OwnerID, repo.ID)
 }
 
+func transfer(t *testing.T) *repo_model.Repository {
+	//create repo to move
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	session := loginUser(t, user.Name)
+	token := getTokenForLoggedInUser(t, session)
+	repoName := "moveME"
+	apiRepo := new(api.Repository)
+	req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/repos?token=%s", token), &api.CreateRepoOption{
+		Name:        repoName,
+		Description: "repo move around",
+		Private:     false,
+		Readme:      "Default",
+		AutoInit:    true,
+	})
+
+	resp := session.MakeRequest(t, req, http.StatusCreated)
+	DecodeJSON(t, resp, apiRepo)
+
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}).(*repo_model.Repository)
+	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer?token=%s", repo.OwnerName, repo.Name, token), &api.TransferRepoOption{
+		NewOwner: "user4",
+	})
+	session.MakeRequest(t, req, http.StatusCreated)
+
+	return repo
+}
+
+func TestAPIAcceptTransfer(t *testing.T) {
+	defer prepareTestEnv(t)()
+
+	repo := transfer(t)
+
+	// try to accept with not authorized user
+	session := loginUser(t, "user2")
+	token := getTokenForLoggedInUser(t, session)
+	req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token))
+	session.MakeRequest(t, req, http.StatusForbidden)
+
+	// try to accept repo that's not marked as transferred
+	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", "user2", "repo1", token))
+	session.MakeRequest(t, req, http.StatusNotFound)
+
+	// accept transfer
+	session = loginUser(t, "user4")
+	token = getTokenForLoggedInUser(t, session)
+
+	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", repo.OwnerName, repo.Name, token))
+	resp := session.MakeRequest(t, req, http.StatusAccepted)
+	apiRepo := new(api.Repository)
+	DecodeJSON(t, resp, apiRepo)
+	assert.Equal(t, "user4", apiRepo.Owner.UserName)
+}
+
+func TestAPIRejectTransfer(t *testing.T) {
+	defer prepareTestEnv(t)()
+
+	repo := transfer(t)
+
+	// try to reject with not authorized user
+	session := loginUser(t, "user2")
+	token := getTokenForLoggedInUser(t, session)
+	req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token))
+	session.MakeRequest(t, req, http.StatusForbidden)
+
+	// try to reject repo that's not marked as transferred
+	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", "user2", "repo1", token))
+	session.MakeRequest(t, req, http.StatusNotFound)
+
+	// reject transfer
+	session = loginUser(t, "user4")
+	token = getTokenForLoggedInUser(t, session)
+
+	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token))
+	resp := session.MakeRequest(t, req, http.StatusOK)
+	apiRepo := new(api.Repository)
+	DecodeJSON(t, resp, apiRepo)
+	assert.Equal(t, "user2", apiRepo.Owner.UserName)
+}
+
 func TestAPIGenerateRepo(t *testing.T) {
 	defer prepareTestEnv(t)()
 
diff --git a/modules/convert/repository.go b/modules/convert/repository.go
index 9859ea2674..725b04e2ca 100644
--- a/modules/convert/repository.go
+++ b/modules/convert/repository.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/perm"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
+	"code.gitea.io/gitea/modules/log"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
@@ -106,6 +107,20 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 		}
 	}
 
+	var transfer *api.RepoTransfer
+	if repo.Status == repo_model.RepositoryPendingTransfer {
+		t, err := models.GetPendingRepositoryTransfer(repo)
+		if err != nil && !models.IsErrNoPendingTransfer(err) {
+			log.Warn("GetPendingRepositoryTransfer: %v", err)
+		} else {
+			if err := t.LoadAttributes(); err != nil {
+				log.Warn("LoadAttributes of RepoTransfer: %v", err)
+			} else {
+				transfer = ToRepoTransfer(t)
+			}
+		}
+	}
+
 	return &api.Repository{
 		ID:                        repo.ID,
 		Owner:                     ToUserWithAccessMode(repo.Owner, mode),
@@ -151,5 +166,20 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 		AvatarURL:                 repo.AvatarLink(),
 		Internal:                  !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
 		MirrorInterval:            mirrorInterval,
+		RepoTransfer:              transfer,
+	}
+}
+
+// ToRepoTransfer convert a models.RepoTransfer to a structs.RepeTransfer
+func ToRepoTransfer(t *models.RepoTransfer) *api.RepoTransfer {
+	var teams []*api.Team
+	for _, v := range t.Teams {
+		teams = append(teams, ToTeam(v))
+	}
+
+	return &api.RepoTransfer{
+		Doer:      ToUser(t.Doer, nil),
+		Recipient: ToUser(t.Recipient, nil),
+		Teams:     teams,
 	}
 }
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index b1a3781d05..38d80db704 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -93,6 +93,7 @@ type Repository struct {
 	AvatarURL                 string           `json:"avatar_url"`
 	Internal                  bool             `json:"internal"`
 	MirrorInterval            string           `json:"mirror_interval"`
+	RepoTransfer              *RepoTransfer    `json:"repo_transfer"`
 }
 
 // CreateRepoOption options when creating repository
@@ -336,3 +337,10 @@ var (
 		CodebaseService,
 	}
 )
+
+// RepoTransfer represents a pending repo transfer
+type RepoTransfer struct {
+	Doer      *User   `json:"doer"`
+	Recipient *User   `json:"recipient"`
+	Teams     []*Team `json:"teams"`
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index af5ab96d05..c587907d4b 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -736,6 +736,8 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
 					Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
 				m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
 				m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
+				m.Post("/transfer/accept", reqToken(), repo.AcceptTransfer)
+				m.Post("/transfer/reject", reqToken(), repo.RejectTransfer)
 				m.Combo("/notifications").
 					Get(reqToken(), notify.ListRepoNotifications).
 					Put(reqToken(), notify.ReadRepoNotifications)
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index dd7730b42c..a997444f49 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -127,3 +127,105 @@ func Transfer(ctx *context.APIContext) {
 	log.Trace("Repository transferred: %s -> %s", ctx.Repo.Repository.FullName(), newOwner.Name)
 	ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx.Repo.Repository, perm.AccessModeAdmin))
 }
+
+// AcceptTransfer accept a repo transfer
+func AcceptTransfer(ctx *context.APIContext) {
+	// swagger:operation POST /repos/{owner}/{repo}/transfer/accept repository acceptRepoTransfer
+	// ---
+	// summary: Accept a repo transfer
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repo to transfer
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repo to transfer
+	//   type: string
+	//   required: true
+	// responses:
+	//   "202":
+	//     "$ref": "#/responses/Repository"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	err := acceptOrRejectRepoTransfer(ctx, true)
+	if ctx.Written() {
+		return
+	}
+	if err != nil {
+		ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
+		return
+	}
+
+	ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx.Repo.Repository, ctx.Repo.AccessMode))
+}
+
+// RejectTransfer reject a repo transfer
+func RejectTransfer(ctx *context.APIContext) {
+	// swagger:operation POST /repos/{owner}/{repo}/transfer/reject repository rejectRepoTransfer
+	// ---
+	// summary: Reject a repo transfer
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repo to transfer
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repo to transfer
+	//   type: string
+	//   required: true
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/Repository"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	//   "404":
+	//     "$ref": "#/responses/notFound"
+
+	err := acceptOrRejectRepoTransfer(ctx, false)
+	if ctx.Written() {
+		return
+	}
+	if err != nil {
+		ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, convert.ToRepo(ctx.Repo.Repository, ctx.Repo.AccessMode))
+}
+
+func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
+	repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
+	if err != nil {
+		if models.IsErrNoPendingTransfer(err) {
+			ctx.NotFound()
+			return nil
+		}
+		return err
+	}
+
+	if err := repoTransfer.LoadAttributes(); err != nil {
+		return err
+	}
+
+	if !repoTransfer.CanUserAcceptTransfer(ctx.User) {
+		ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
+		return fmt.Errorf("user does not have permissions to do this")
+	}
+
+	if accept {
+		return repo_service.TransferOwnership(repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
+	}
+
+	return models.CancelRepositoryTransfer(ctx.Repo.Repository)
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index b016ad22a2..96dd262301 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -9895,6 +9895,84 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/transfer/accept": {
+      "post": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Accept a repo transfer",
+        "operationId": "acceptRepoTransfer",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo to transfer",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo to transfer",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "202": {
+            "$ref": "#/responses/Repository"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          }
+        }
+      }
+    },
+    "/repos/{owner}/{repo}/transfer/reject": {
+      "post": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Reject a repo transfer",
+        "operationId": "rejectRepoTransfer",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo to transfer",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo to transfer",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/Repository"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/wiki/new": {
       "post": {
         "consumes": [
@@ -16890,6 +16968,26 @@
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "RepoTransfer": {
+      "description": "RepoTransfer represents a pending repo transfer",
+      "type": "object",
+      "properties": {
+        "doer": {
+          "$ref": "#/definitions/User"
+        },
+        "recipient": {
+          "$ref": "#/definitions/User"
+        },
+        "teams": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/Team"
+          },
+          "x-go-name": "Teams"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "Repository": {
       "description": "Repository represents a repository",
       "type": "object",
@@ -17042,6 +17140,9 @@
           "format": "int64",
           "x-go-name": "Releases"
         },
+        "repo_transfer": {
+          "$ref": "#/definitions/RepoTransfer"
+        },
         "size": {
           "type": "integer",
           "format": "int64",