diff --git a/modules/structs/org.go b/modules/structs/org.go
index c0a545ac1c..f93b3b6493 100644
--- a/modules/structs/org.go
+++ b/modules/structs/org.go
@@ -57,3 +57,12 @@ type EditOrgOption struct {
 	Visibility                string `json:"visibility" binding:"In(,public,limited,private)"`
 	RepoAdminChangeTeamAccess *bool  `json:"repo_admin_change_team_access"`
 }
+
+// RenameOrgOption options when renaming an organization
+type RenameOrgOption struct {
+	// New username for this org. This name cannot be in use yet by any other user.
+	//
+	// required: true
+	// unique: true
+	NewName string `json:"new_name" binding:"Required"`
+}
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 21cb2f9ccd..53eee72631 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -477,26 +477,16 @@ func RenameUser(ctx *context.APIContext) {
 		return
 	}
 
-	oldName := ctx.ContextUser.Name
 	newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
 
-	// Check if user name has been changed
+	// Check if username has been changed
 	if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
-		switch {
-		case user_model.IsErrUserAlreadyExist(err):
-			ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
-		case db.IsErrNameReserved(err):
-			ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_reserved", newName))
-		case db.IsErrNamePatternNotAllowed(err):
-			ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_pattern_not_allowed", newName))
-		case db.IsErrNameCharsNotAllowed(err):
-			ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_chars_not_allowed", newName))
-		default:
+		if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
+			ctx.Error(http.StatusUnprocessableEntity, "", err)
+		} else {
 			ctx.ServerError("ChangeUserName", err)
 		}
 		return
 	}
-
-	log.Trace("User name changed: %s -> %s", oldName, newName)
 	ctx.Status(http.StatusNoContent)
 }
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index b1a42a85e6..438db4ae71 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1530,6 +1530,7 @@ func Routes() *web.Router {
 			m.Combo("").Get(org.Get).
 				Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
 				Delete(reqToken(), reqOrgOwnership(), org.Delete)
+			m.Post("/rename", reqToken(), reqOrgOwnership(), bind(api.RenameOrgOption{}), org.Rename)
 			m.Combo("/repos").Get(user.ListOrgRepos).
 				Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
 			m.Group("/members", func() {
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index d65f922434..2fcba0bf1a 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -315,6 +315,44 @@ func Get(ctx *context.APIContext) {
 	ctx.JSON(http.StatusOK, org)
 }
 
+func Rename(ctx *context.APIContext) {
+	// swagger:operation POST /orgs/{org}/rename organization renameOrg
+	// ---
+	// summary: Rename an organization
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: org
+	//   in: path
+	//   description: existing org name
+	//   type: string
+	//   required: true
+	// - name: body
+	//   in: body
+	//   required: true
+	//   schema:
+	//     "$ref": "#/definitions/RenameOrgOption"
+	// responses:
+	//   "204":
+	//     "$ref": "#/responses/empty"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	//   "422":
+	//     "$ref": "#/responses/validationError"
+
+	form := web.GetForm(ctx).(*api.RenameOrgOption)
+	orgUser := ctx.Org.Organization.AsUser()
+	if err := user_service.RenameUser(ctx, orgUser, form.NewName); err != nil {
+		if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
+			ctx.Error(http.StatusUnprocessableEntity, "RenameOrg", err)
+		} else {
+			ctx.ServerError("RenameOrg", err)
+		}
+		return
+	}
+	ctx.Status(http.StatusNoContent)
+}
+
 // Edit change an organization's information
 func Edit(ctx *context.APIContext) {
 	// swagger:operation PATCH /orgs/{org} organization orgEdit
diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
index 125605d98f..353d6de89b 100644
--- a/routers/api/v1/swagger/options.go
+++ b/routers/api/v1/swagger/options.go
@@ -208,6 +208,9 @@ type swaggerParameterBodies struct {
 	// in:body
 	CreateVariableOption api.CreateVariableOption
 
+	// in:body
+	RenameOrgOption api.RenameOrgOption
+
 	// in:body
 	UpdateVariableOption api.UpdateVariableOption
 }
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 8082fc594a..c58b21062d 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -2991,6 +2991,46 @@
         }
       }
     },
+    "/orgs/{org}/rename": {
+      "post": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "organization"
+        ],
+        "summary": "Rename an organization",
+        "operationId": "renameOrg",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "existing org name",
+            "name": "org",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "body",
+            "in": "body",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/RenameOrgOption"
+            }
+          }
+        ],
+        "responses": {
+          "204": {
+            "$ref": "#/responses/empty"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          },
+          "422": {
+            "$ref": "#/responses/validationError"
+          }
+        }
+      }
+    },
     "/orgs/{org}/repos": {
       "get": {
         "produces": [
@@ -24207,6 +24247,22 @@
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "RenameOrgOption": {
+      "description": "RenameOrgOption options when renaming an organization",
+      "type": "object",
+      "required": [
+        "new_name"
+      ],
+      "properties": {
+        "new_name": {
+          "description": "New username for this org. This name cannot be in use yet by any other user.",
+          "type": "string",
+          "uniqueItems": true,
+          "x-go-name": "NewName"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "RenameUserOption": {
       "description": "RenameUserOption options when renaming a user",
       "type": "object",
diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go
index fff121490c..d766b1e8be 100644
--- a/tests/integration/api_org_test.go
+++ b/tests/integration/api_org_test.go
@@ -6,7 +6,6 @@ package integration
 import (
 	"fmt"
 	"net/http"
-	"net/url"
 	"strings"
 	"testing"
 
@@ -19,46 +18,52 @@ import (
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/tests"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestAPIOrgCreate(t *testing.T) {
-	onGiteaRun(t, func(*testing.T, *url.URL) {
-		token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization)
+func TestAPIOrgCreateRename(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+	token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization)
 
-		org := api.CreateOrgOption{
-			UserName:    "user1_org",
-			FullName:    "User1's organization",
-			Description: "This organization created by user1",
-			Website:     "https://try.gitea.io",
-			Location:    "Shanghai",
-			Visibility:  "limited",
-		}
-		req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).
-			AddTokenAuth(token)
-		resp := MakeRequest(t, req, http.StatusCreated)
+	org := api.CreateOrgOption{
+		UserName:    "user1_org",
+		FullName:    "User1's organization",
+		Description: "This organization created by user1",
+		Website:     "https://try.gitea.io",
+		Location:    "Shanghai",
+		Visibility:  "limited",
+	}
+	req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token)
+	resp := MakeRequest(t, req, http.StatusCreated)
 
-		var apiOrg api.Organization
-		DecodeJSON(t, resp, &apiOrg)
+	var apiOrg api.Organization
+	DecodeJSON(t, resp, &apiOrg)
 
-		assert.Equal(t, org.UserName, apiOrg.Name)
-		assert.Equal(t, org.FullName, apiOrg.FullName)
-		assert.Equal(t, org.Description, apiOrg.Description)
-		assert.Equal(t, org.Website, apiOrg.Website)
-		assert.Equal(t, org.Location, apiOrg.Location)
-		assert.Equal(t, org.Visibility, apiOrg.Visibility)
+	assert.Equal(t, org.UserName, apiOrg.Name)
+	assert.Equal(t, org.FullName, apiOrg.FullName)
+	assert.Equal(t, org.Description, apiOrg.Description)
+	assert.Equal(t, org.Website, apiOrg.Website)
+	assert.Equal(t, org.Location, apiOrg.Location)
+	assert.Equal(t, org.Visibility, apiOrg.Visibility)
 
-		unittest.AssertExistsAndLoadBean(t, &user_model.User{
-			Name:      org.UserName,
-			LowerName: strings.ToLower(org.UserName),
-			FullName:  org.FullName,
-		})
+	unittest.AssertExistsAndLoadBean(t, &user_model.User{
+		Name:      org.UserName,
+		LowerName: strings.ToLower(org.UserName),
+		FullName:  org.FullName,
+	})
 
+	// check org name
+	req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName).AddTokenAuth(token)
+	resp = MakeRequest(t, req, http.StatusOK)
+	DecodeJSON(t, resp, &apiOrg)
+	assert.EqualValues(t, org.UserName, apiOrg.Name)
+
+	t.Run("CheckPermission", func(t *testing.T) {
 		// Check owner team permission
 		ownerTeam, _ := org_model.GetOwnerTeam(db.DefaultContext, apiOrg.ID)
-
 		for _, ut := range unit_model.AllRepoUnitTypes {
 			up := perm.AccessModeOwner
 			if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
@@ -71,25 +76,10 @@ func TestAPIOrgCreate(t *testing.T) {
 				AccessMode: up,
 			})
 		}
+	})
 
-		req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName).
-			AddTokenAuth(token)
-		resp = MakeRequest(t, req, http.StatusOK)
-		DecodeJSON(t, resp, &apiOrg)
-		assert.EqualValues(t, org.UserName, apiOrg.Name)
-
-		req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName).
-			AddTokenAuth(token)
-		resp = MakeRequest(t, req, http.StatusOK)
-
-		var repos []*api.Repository
-		DecodeJSON(t, resp, &repos)
-		for _, repo := range repos {
-			assert.False(t, repo.Private)
-		}
-
-		req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName).
-			AddTokenAuth(token)
+	t.Run("CheckMembers", func(t *testing.T) {
+		req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName).AddTokenAuth(token)
 		resp = MakeRequest(t, req, http.StatusOK)
 
 		// user1 on this org is public
@@ -98,76 +88,89 @@ func TestAPIOrgCreate(t *testing.T) {
 		assert.Len(t, users, 1)
 		assert.EqualValues(t, "user1", users[0].UserName)
 	})
+
+	t.Run("RenameOrg", func(t *testing.T) {
+		req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/user1_org/rename", &api.RenameOrgOption{
+			NewName: "renamed_org",
+		}).AddTokenAuth(token)
+		MakeRequest(t, req, http.StatusNoContent)
+		unittest.AssertExistsAndLoadBean(t, &org_model.Organization{Name: "renamed_org"})
+		org.UserName = "renamed_org" // update the variable so the following tests could still use it
+	})
+
+	t.Run("ListRepos", func(t *testing.T) {
+		// FIXME: this test is wrong, there is no repository at all, so the for-loop is empty
+		req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName).AddTokenAuth(token)
+		resp = MakeRequest(t, req, http.StatusOK)
+		var repos []*api.Repository
+		DecodeJSON(t, resp, &repos)
+		for _, repo := range repos {
+			assert.False(t, repo.Private)
+		}
+	})
 }
 
 func TestAPIOrgEdit(t *testing.T) {
-	onGiteaRun(t, func(*testing.T, *url.URL) {
-		session := loginUser(t, "user1")
+	defer tests.PrepareTestEnv(t)()
+	session := loginUser(t, "user1")
 
-		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
-		org := api.EditOrgOption{
-			FullName:    "Org3 organization new full name",
-			Description: "A new description",
-			Website:     "https://try.gitea.io/new",
-			Location:    "Beijing",
-			Visibility:  "private",
-		}
-		req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).
-			AddTokenAuth(token)
-		resp := MakeRequest(t, req, http.StatusOK)
+	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
+	org := api.EditOrgOption{
+		FullName:    "Org3 organization new full name",
+		Description: "A new description",
+		Website:     "https://try.gitea.io/new",
+		Location:    "Beijing",
+		Visibility:  "private",
+	}
+	req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).
+		AddTokenAuth(token)
+	resp := MakeRequest(t, req, http.StatusOK)
 
-		var apiOrg api.Organization
-		DecodeJSON(t, resp, &apiOrg)
+	var apiOrg api.Organization
+	DecodeJSON(t, resp, &apiOrg)
 
-		assert.Equal(t, "org3", apiOrg.Name)
-		assert.Equal(t, org.FullName, apiOrg.FullName)
-		assert.Equal(t, org.Description, apiOrg.Description)
-		assert.Equal(t, org.Website, apiOrg.Website)
-		assert.Equal(t, org.Location, apiOrg.Location)
-		assert.Equal(t, org.Visibility, apiOrg.Visibility)
-	})
+	assert.Equal(t, "org3", apiOrg.Name)
+	assert.Equal(t, org.FullName, apiOrg.FullName)
+	assert.Equal(t, org.Description, apiOrg.Description)
+	assert.Equal(t, org.Website, apiOrg.Website)
+	assert.Equal(t, org.Location, apiOrg.Location)
+	assert.Equal(t, org.Visibility, apiOrg.Visibility)
 }
 
 func TestAPIOrgEditBadVisibility(t *testing.T) {
-	onGiteaRun(t, func(*testing.T, *url.URL) {
-		session := loginUser(t, "user1")
+	defer tests.PrepareTestEnv(t)()
+	session := loginUser(t, "user1")
 
-		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
-		org := api.EditOrgOption{
-			FullName:    "Org3 organization new full name",
-			Description: "A new description",
-			Website:     "https://try.gitea.io/new",
-			Location:    "Beijing",
-			Visibility:  "badvisibility",
-		}
-		req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).
-			AddTokenAuth(token)
-		MakeRequest(t, req, http.StatusUnprocessableEntity)
-	})
+	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
+	org := api.EditOrgOption{
+		FullName:    "Org3 organization new full name",
+		Description: "A new description",
+		Website:     "https://try.gitea.io/new",
+		Location:    "Beijing",
+		Visibility:  "badvisibility",
+	}
+	req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).
+		AddTokenAuth(token)
+	MakeRequest(t, req, http.StatusUnprocessableEntity)
 }
 
 func TestAPIOrgDeny(t *testing.T) {
-	onGiteaRun(t, func(*testing.T, *url.URL) {
-		setting.Service.RequireSignInView = true
-		defer func() {
-			setting.Service.RequireSignInView = false
-		}()
+	defer tests.PrepareTestEnv(t)()
+	defer test.MockVariableValue(&setting.Service.RequireSignInView, true)()
 
-		orgName := "user1_org"
-		req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName)
-		MakeRequest(t, req, http.StatusNotFound)
+	orgName := "user1_org"
+	req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName)
+	MakeRequest(t, req, http.StatusNotFound)
 
-		req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName)
-		MakeRequest(t, req, http.StatusNotFound)
+	req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName)
+	MakeRequest(t, req, http.StatusNotFound)
 
-		req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName)
-		MakeRequest(t, req, http.StatusNotFound)
-	})
+	req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName)
+	MakeRequest(t, req, http.StatusNotFound)
 }
 
 func TestAPIGetAll(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
-
 	token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization)
 
 	// accessing with a token will return all orgs
@@ -192,37 +195,36 @@ func TestAPIGetAll(t *testing.T) {
 }
 
 func TestAPIOrgSearchEmptyTeam(t *testing.T) {
-	onGiteaRun(t, func(*testing.T, *url.URL) {
-		token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization)
-		orgName := "org_with_empty_team"
+	defer tests.PrepareTestEnv(t)()
+	token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization)
+	orgName := "org_with_empty_team"
 
-		// create org
-		req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{
-			UserName: orgName,
-		}).AddTokenAuth(token)
-		MakeRequest(t, req, http.StatusCreated)
+	// create org
+	req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{
+		UserName: orgName,
+	}).AddTokenAuth(token)
+	MakeRequest(t, req, http.StatusCreated)
 
-		// create team with no member
-		req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &api.CreateTeamOption{
-			Name:                    "Empty",
-			IncludesAllRepositories: true,
-			Permission:              "read",
-			Units:                   []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"},
-		}).AddTokenAuth(token)
-		MakeRequest(t, req, http.StatusCreated)
+	// create team with no member
+	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &api.CreateTeamOption{
+		Name:                    "Empty",
+		IncludesAllRepositories: true,
+		Permission:              "read",
+		Units:                   []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"},
+	}).AddTokenAuth(token)
+	MakeRequest(t, req, http.StatusCreated)
 
-		// case-insensitive search for teams that have no members
-		req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")).
-			AddTokenAuth(token)
-		resp := MakeRequest(t, req, http.StatusOK)
-		data := struct {
-			Ok   bool
-			Data []*api.Team
-		}{}
-		DecodeJSON(t, resp, &data)
-		assert.True(t, data.Ok)
-		if assert.Len(t, data.Data, 1) {
-			assert.EqualValues(t, "Empty", data.Data[0].Name)
-		}
-	})
+	// case-insensitive search for teams that have no members
+	req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")).
+		AddTokenAuth(token)
+	resp := MakeRequest(t, req, http.StatusOK)
+	data := struct {
+		Ok   bool
+		Data []*api.Team
+	}{}
+	DecodeJSON(t, resp, &data)
+	assert.True(t, data.Ok)
+	if assert.Len(t, data.Data, 1) {
+		assert.EqualValues(t, "Empty", data.Data[0].Name)
+	}
 }