diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go
index ce1ecb1d43..b585ad15e3 100644
--- a/integrations/api_repo_test.go
+++ b/integrations/api_repo_test.go
@@ -405,6 +405,27 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) {
 	})
 }
 
+// mirror-sync must fail with "400 (Bad Request)" when an attempt is made to
+// sync a non-mirror repository.
+func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) {
+	defer prepareTestEnv(t)()
+
+	session := loginUser(t, "user2")
+	token := getTokenForLoggedInUser(t, session)
+
+	var repo api.Repository
+	req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1")
+	resp := MakeRequest(t, req, http.StatusOK)
+	DecodeJSON(t, resp, &repo)
+	assert.EqualValues(t, false, repo.Mirror)
+
+	req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync?token=%s", token)
+	resp = session.MakeRequest(t, req, http.StatusBadRequest)
+	errRespJSON := map[string]string{}
+	DecodeJSON(t, resp, &errRespJSON)
+	assert.Equal(t, "Repository is not a mirror", errRespJSON["message"])
+}
+
 func TestAPIOrgRepoCreate(t *testing.T) {
 	testCases := []struct {
 		ctxUserID         int64
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index c9ac3e8292..d7facd24d9 100644
--- a/routers/api/v1/repo/mirror.go
+++ b/routers/api/v1/repo/mirror.go
@@ -5,8 +5,10 @@
 package repo
 
 import (
+	"errors"
 	"net/http"
 
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/setting"
@@ -48,6 +50,15 @@ func MirrorSync(ctx *context.APIContext) {
 		return
 	}
 
+	if _, err := repo_model.GetMirrorByRepoID(repo.ID); err != nil {
+		if errors.Is(err, repo_model.ErrMirrorNotExist) {
+			ctx.Error(http.StatusBadRequest, "MirrorSync", "Repository is not a mirror")
+			return
+		}
+		ctx.Error(http.StatusInternalServerError, "MirrorSync", err)
+		return
+	}
+
 	mirror_service.StartToMirror(repo.ID)
 
 	ctx.Status(http.StatusOK)