diff --git a/models/user.go b/models/user.go
index 0b6063fbc9..e8c5c91680 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1179,3 +1179,18 @@ func UnfollowUser(userID, followID int64) (err error) {
 	}
 	return sess.Commit()
 }
+
+// GetStarredRepos returns the repos starred by a particular user
+func GetStarredRepos(userID int64, private bool) ([]*Repository, error) {
+	sess := x.Where("star.uid=?", userID).
+		Join("LEFT", "star", "`repository`.id=`star`.repo_id")
+	if !private {
+		sess = sess.And("is_private=?", false)
+	}
+	repos := make([]*Repository, 0, 10)
+	err := sess.Find(&repos)
+	if err != nil {
+		return nil, err
+	}
+	return repos, nil
+}
diff --git a/modules/context/api.go b/modules/context/api.go
index 197fbd6df1..0da1823797 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"strings"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -70,3 +71,33 @@ func APIContexter() macaron.Handler {
 		c.Map(ctx)
 	}
 }
+
+// ExtractOwnerAndRepo returns a handler that populates the `Repo.Owner` and
+// `Repo.Repository` fields of an APIContext
+func ExtractOwnerAndRepo() macaron.Handler {
+	return func(ctx *APIContext) {
+		owner, err := models.GetUserByName(ctx.Params(":username"))
+		if err != nil {
+			if models.IsErrUserNotExist(err) {
+				ctx.Error(422, "", err)
+			} else {
+				ctx.Error(500, "GetUserByName", err)
+			}
+			return
+		}
+
+		repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
+		if err != nil {
+			if models.IsErrRepoNotExist(err) {
+				ctx.Status(404)
+			} else {
+				ctx.Error(500, "GetRepositoryByName", err)
+			}
+			return
+		}
+		ctx.Repo.Owner = owner
+		ctx.Data["Owner"] = owner
+		ctx.Repo.Repository = repo
+		ctx.Data["Repository"] = repo
+	}
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 08fc2c75e6..95bdfd072e 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -200,6 +200,8 @@ func RegisterRoutes(m *macaron.Macaron) {
 					m.Get("", user.ListFollowing)
 					m.Get("/:target", user.CheckFollowing)
 				})
+
+				m.Get("/starred", user.GetStarredRepos)
 			})
 		}, reqToken())
 
@@ -221,6 +223,15 @@ func RegisterRoutes(m *macaron.Macaron) {
 				m.Combo("/:id").Get(user.GetPublicKey).
 					Delete(user.DeletePublicKey)
 			})
+
+			m.Group("/starred", func() {
+				m.Get("", user.GetMyStarredRepos)
+				m.Group("/:username/:reponame", func() {
+					m.Get("", user.IsStarring)
+					m.Put("", user.Star)
+					m.Delete("", user.Unstar)
+				}, context.ExtractOwnerAndRepo())
+			})
 		}, reqToken())
 
 		// Repositories
@@ -234,7 +245,8 @@ func RegisterRoutes(m *macaron.Macaron) {
 
 		m.Group("/repos", func() {
 			m.Post("/migrate", bind(auth.MigrateRepoForm{}), repo.Migrate)
-			m.Combo("/:username/:reponame").Get(repo.Get).
+			m.Combo("/:username/:reponame", context.ExtractOwnerAndRepo()).
+				Get(repo.Get).
 				Delete(repo.Delete)
 
 			m.Group("/:username/:reponame", func() {
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 02eb82d0fd..4353fb74ea 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -238,46 +238,16 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
 	ctx.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
 }
 
-func parseOwnerAndRepo(ctx *context.APIContext) (*models.User, *models.Repository) {
-	owner, err := models.GetUserByName(ctx.Params(":username"))
-	if err != nil {
-		if models.IsErrUserNotExist(err) {
-			ctx.Error(422, "", err)
-		} else {
-			ctx.Error(500, "GetUserByName", err)
-		}
-		return nil, nil
-	}
-
-	repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
-	if err != nil {
-		if models.IsErrRepoNotExist(err) {
-			ctx.Status(404)
-		} else {
-			ctx.Error(500, "GetRepositoryByName", err)
-		}
-		return nil, nil
-	}
-
-	return owner, repo
-}
-
 // https://github.com/gogits/go-gogs-client/wiki/Repositories#get
 func Get(ctx *context.APIContext) {
-	_, repo := parseOwnerAndRepo(ctx)
-	if ctx.Written() {
-		return
-	}
-
+	repo := ctx.Repo.Repository
 	ctx.JSON(200, repo.APIFormat(&api.Permission{true, true, true}))
 }
 
 // https://github.com/gogits/go-gogs-client/wiki/Repositories#delete
 func Delete(ctx *context.APIContext) {
-	owner, repo := parseOwnerAndRepo(ctx)
-	if ctx.Written() {
-		return
-	}
+	owner := ctx.Repo.Owner
+	repo := ctx.Repo.Repository
 
 	if owner.IsOrganization() && !owner.IsOwnedBy(ctx.User.ID) {
 		ctx.Error(403, "", "Given user is not owner of organization.")
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
new file mode 100644
index 0000000000..c856ecefdd
--- /dev/null
+++ b/routers/api/v1/user/star.go
@@ -0,0 +1,76 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	api "code.gitea.io/sdk/gitea"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/context"
+)
+
+// getStarredRepos returns the repos that the user with the specified userID has
+// starred
+func getStarredRepos(userID int64, private bool) ([]*api.Repository, error) {
+	starredRepos, err := models.GetStarredRepos(userID, private)
+	if err != nil {
+		return nil, err
+	}
+	repos := make([]*api.Repository, len(starredRepos))
+	for i, starred := range starredRepos {
+		repos[i] = starred.APIFormat(&api.Permission{true, true, true})
+	}
+	return repos, nil
+}
+
+// GetStarredRepos returns the repos that the user specified by the APIContext
+// has starred
+func GetStarredRepos(ctx *context.APIContext) {
+	user := GetUserByParams(ctx)
+	private := user.ID == ctx.User.ID
+	repos, err := getStarredRepos(user.ID, private)
+	if err != nil {
+		ctx.Error(500, "getStarredRepos", err)
+	}
+	ctx.JSON(200, &repos)
+}
+
+// GetMyStarredRepos returns the repos that the authenticated user has starred
+func GetMyStarredRepos(ctx *context.APIContext) {
+	repos, err := getStarredRepos(ctx.User.ID, true)
+	if err != nil {
+		ctx.Error(500, "getStarredRepos", err)
+	}
+	ctx.JSON(200, &repos)
+}
+
+// IsStarring returns whether the authenticated is starring the repo
+func IsStarring(ctx *context.APIContext) {
+	if models.IsStaring(ctx.User.ID, ctx.Repo.Repository.ID) {
+		ctx.Status(204)
+	} else {
+		ctx.Status(404)
+	}
+}
+
+// Star the repo specified in the APIContext, as the authenticated user
+func Star(ctx *context.APIContext) {
+	err := models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true)
+	if err != nil {
+		ctx.Error(500, "StarRepo", err)
+		return
+	}
+	ctx.Status(204)
+}
+
+// Unstar the repo specified in the APIContext, as the authenticated user
+func Unstar(ctx *context.APIContext) {
+	err := models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false)
+	if err != nil {
+		ctx.Error(500, "StarRepo", err)
+		return
+	}
+	ctx.Status(204)
+}