diff --git a/integrations/api_issue_reaction_test.go b/integrations/api_issue_reaction_test.go
new file mode 100644
index 0000000000..f3fcf3946f
--- /dev/null
+++ b/integrations/api_issue_reaction_test.go
@@ -0,0 +1,145 @@
+// Copyright 2019 The Gitea 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 integrations
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"code.gitea.io/gitea/models"
+	api "code.gitea.io/gitea/modules/structs"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAPIIssuesReactions(t *testing.T) {
+	defer prepareTestEnv(t)()
+
+	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
+	_ = issue.LoadRepo()
+	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User)
+
+	session := loginUser(t, owner.Name)
+	token := getTokenForLoggedInUser(t, session)
+
+	user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
+	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions?token=%s",
+		owner.Name, issue.Repo.Name, issue.Index, token)
+
+	//Try to add not allowed reaction
+	req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
+		Reaction: "wrong",
+	})
+	resp := session.MakeRequest(t, req, http.StatusForbidden)
+
+	//Delete not allowed reaction
+	req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
+		Reaction: "zzz",
+	})
+	resp = session.MakeRequest(t, req, http.StatusOK)
+
+	//Add allowed reaction
+	req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
+		Reaction: "rocket",
+	})
+	resp = session.MakeRequest(t, req, http.StatusCreated)
+	var apiNewReaction api.ReactionResponse
+	DecodeJSON(t, resp, &apiNewReaction)
+
+	//Add existing reaction
+	resp = session.MakeRequest(t, req, http.StatusForbidden)
+
+	//Get end result of reaction list of issue #1
+	req = NewRequestf(t, "GET", urlStr)
+	resp = session.MakeRequest(t, req, http.StatusOK)
+	var apiReactions []*api.ReactionResponse
+	DecodeJSON(t, resp, &apiReactions)
+	expectResponse := make(map[int]api.ReactionResponse)
+	expectResponse[0] = api.ReactionResponse{
+		User:     user1.APIFormat(),
+		Reaction: "zzz",
+		Created:  time.Unix(1573248002, 0),
+	}
+	expectResponse[1] = api.ReactionResponse{
+		User:     user2.APIFormat(),
+		Reaction: "eyes",
+		Created:  time.Unix(1573248003, 0),
+	}
+	expectResponse[2] = apiNewReaction
+	assert.Len(t, apiReactions, 3)
+	for i, r := range apiReactions {
+		assert.Equal(t, expectResponse[i].Reaction, r.Reaction)
+		assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix())
+		assert.Equal(t, expectResponse[i].User.ID, r.User.ID)
+	}
+}
+
+func TestAPICommentReactions(t *testing.T) {
+	defer prepareTestEnv(t)()
+
+	comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment)
+	_ = comment.LoadIssue()
+	issue := comment.Issue
+	_ = issue.LoadRepo()
+	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User)
+
+	session := loginUser(t, owner.Name)
+	token := getTokenForLoggedInUser(t, session)
+
+	user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
+	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s",
+		owner.Name, issue.Repo.Name, comment.ID, token)
+
+	//Try to add not allowed reaction
+	req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
+		Reaction: "wrong",
+	})
+	resp := session.MakeRequest(t, req, http.StatusForbidden)
+
+	//Delete none existing reaction
+	req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
+		Reaction: "eyes",
+	})
+	resp = session.MakeRequest(t, req, http.StatusOK)
+
+	//Add allowed reaction
+	req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
+		Reaction: "+1",
+	})
+	resp = session.MakeRequest(t, req, http.StatusCreated)
+	var apiNewReaction api.ReactionResponse
+	DecodeJSON(t, resp, &apiNewReaction)
+
+	//Add existing reaction
+	resp = session.MakeRequest(t, req, http.StatusForbidden)
+
+	//Get end result of reaction list of issue #1
+	req = NewRequestf(t, "GET", urlStr)
+	resp = session.MakeRequest(t, req, http.StatusOK)
+	var apiReactions []*api.ReactionResponse
+	DecodeJSON(t, resp, &apiReactions)
+	expectResponse := make(map[int]api.ReactionResponse)
+	expectResponse[0] = api.ReactionResponse{
+		User:     user2.APIFormat(),
+		Reaction: "laugh",
+		Created:  time.Unix(1573248004, 0),
+	}
+	expectResponse[1] = api.ReactionResponse{
+		User:     user1.APIFormat(),
+		Reaction: "laugh",
+		Created:  time.Unix(1573248005, 0),
+	}
+	expectResponse[2] = apiNewReaction
+	assert.Len(t, apiReactions, 3)
+	for i, r := range apiReactions {
+		assert.Equal(t, expectResponse[i].Reaction, r.Reaction)
+		assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix())
+		assert.Equal(t, expectResponse[i].User.ID, r.User.ID)
+	}
+}
diff --git a/models/error.go b/models/error.go
index 313c36354d..16be512139 100644
--- a/models/error.go
+++ b/models/error.go
@@ -1121,6 +1121,21 @@ func (err ErrNewIssueInsert) Error() string {
 	return err.OriginalError.Error()
 }
 
+// ErrForbiddenIssueReaction is used when a forbidden reaction was try to created
+type ErrForbiddenIssueReaction struct {
+	Reaction string
+}
+
+// IsErrForbiddenIssueReaction checks if an error is a ErrForbiddenIssueReaction.
+func IsErrForbiddenIssueReaction(err error) bool {
+	_, ok := err.(ErrForbiddenIssueReaction)
+	return ok
+}
+
+func (err ErrForbiddenIssueReaction) Error() string {
+	return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction)
+}
+
 // __________      .__  .__ __________                                     __
 // \______   \__ __|  | |  |\______   \ ____  ________ __   ____   _______/  |_
 //  |     ___/  |  \  | |  | |       _// __ \/ ____/  |  \_/ __ \ /  ___/\   __\
diff --git a/models/fixtures/reaction.yml b/models/fixtures/reaction.yml
index ca780a73aa..4925935fe6 100644
--- a/models/fixtures/reaction.yml
+++ b/models/fixtures/reaction.yml
@@ -1 +1,39 @@
-[] # empty
+-
+  id: 1 #issue reaction
+  type: zzz # not allowed reaction (added before allowed reaction list has changed)
+  issue_id: 1
+  comment_id: 0
+  user_id: 2
+  created_unix: 1573248001
+
+-
+  id: 2 #issue reaction
+  type: zzz # not allowed reaction (added before allowed reaction list has changed)
+  issue_id: 1
+  comment_id: 0
+  user_id: 1
+  created_unix: 1573248002
+
+-
+  id: 3 #issue reaction
+  type: eyes # allowed reaction
+  issue_id: 1
+  comment_id: 0
+  user_id: 2
+  created_unix: 1573248003
+
+-
+  id: 4 #comment reaction
+  type: laugh # allowed reaction
+  issue_id: 1
+  comment_id: 2
+  user_id: 2
+  created_unix: 1573248004
+
+-
+  id: 5 #comment reaction
+  type: laugh # allowed reaction
+  issue_id: 1
+  comment_id: 2
+  user_id: 1
+  created_unix: 1573248005
diff --git a/models/issue_reaction.go b/models/issue_reaction.go
index 4596d32d06..b4f332a089 100644
--- a/models/issue_reaction.go
+++ b/models/issue_reaction.go
@@ -33,16 +33,38 @@ type FindReactionsOptions struct {
 }
 
 func (opts *FindReactionsOptions) toConds() builder.Cond {
+	//If Issue ID is set add to Query
 	var cond = builder.NewCond()
 	if opts.IssueID > 0 {
 		cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueID})
 	}
+	//If CommentID is > 0 add to Query
+	//If it is 0 Query ignore CommentID to select
+	//If it is -1 it explicit search of Issue Reactions where CommentID = 0
 	if opts.CommentID > 0 {
 		cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
+	} else if opts.CommentID == -1 {
+		cond = cond.And(builder.Eq{"reaction.comment_id": 0})
 	}
+
 	return cond
 }
 
+// FindCommentReactions returns a ReactionList of all reactions from an comment
+func FindCommentReactions(comment *Comment) (ReactionList, error) {
+	return findReactions(x, FindReactionsOptions{
+		IssueID:   comment.IssueID,
+		CommentID: comment.ID})
+}
+
+// FindIssueReactions returns a ReactionList of all reactions from an issue
+func FindIssueReactions(issue *Issue) (ReactionList, error) {
+	return findReactions(x, FindReactionsOptions{
+		IssueID:   issue.ID,
+		CommentID: -1,
+	})
+}
+
 func findReactions(e Engine, opts FindReactionsOptions) ([]*Reaction, error) {
 	reactions := make([]*Reaction, 0, 10)
 	sess := e.Where(opts.toConds())
@@ -77,6 +99,10 @@ type ReactionOptions struct {
 
 // CreateReaction creates reaction for issue or comment.
 func CreateReaction(opts *ReactionOptions) (reaction *Reaction, err error) {
+	if !setting.UI.ReactionsMap[opts.Type] {
+		return nil, ErrForbiddenIssueReaction{opts.Type}
+	}
+
 	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
@@ -160,6 +186,19 @@ func DeleteCommentReaction(doer *User, issue *Issue, comment *Comment, content s
 	})
 }
 
+// LoadUser load user of reaction
+func (r *Reaction) LoadUser() (*User, error) {
+	if r.User != nil {
+		return r.User, nil
+	}
+	user, err := getUserByID(x, r.UserID)
+	if err != nil {
+		return nil, err
+	}
+	r.User = user
+	return user, nil
+}
+
 // ReactionList represents list of reactions
 type ReactionList []*Reaction
 
diff --git a/models/issue_reaction_test.go b/models/issue_reaction_test.go
index bbd8cf29fe..1189b389e9 100644
--- a/models/issue_reaction_test.go
+++ b/models/issue_reaction_test.go
@@ -81,22 +81,22 @@ func TestIssueReactionCount(t *testing.T) {
 	user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
 	ghost := NewGhostUser()
 
-	issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
+	issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
 
-	addReaction(t, user1, issue1, nil, "heart")
-	addReaction(t, user2, issue1, nil, "heart")
-	addReaction(t, user3, issue1, nil, "heart")
-	addReaction(t, user3, issue1, nil, "+1")
-	addReaction(t, user4, issue1, nil, "+1")
-	addReaction(t, user4, issue1, nil, "heart")
-	addReaction(t, ghost, issue1, nil, "-1")
+	addReaction(t, user1, issue, nil, "heart")
+	addReaction(t, user2, issue, nil, "heart")
+	addReaction(t, user3, issue, nil, "heart")
+	addReaction(t, user3, issue, nil, "+1")
+	addReaction(t, user4, issue, nil, "+1")
+	addReaction(t, user4, issue, nil, "heart")
+	addReaction(t, ghost, issue, nil, "-1")
 
-	err := issue1.loadReactions(x)
+	err := issue.loadReactions(x)
 	assert.NoError(t, err)
 
-	assert.Len(t, issue1.Reactions, 7)
+	assert.Len(t, issue.Reactions, 7)
 
-	reactions := issue1.Reactions.GroupByType()
+	reactions := issue.Reactions.GroupByType()
 	assert.Len(t, reactions["heart"], 4)
 	assert.Equal(t, 2, reactions["heart"].GetMoreUserCount())
 	assert.Equal(t, user1.DisplayName()+", "+user2.DisplayName(), reactions["heart"].GetFirstUsers())
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index c08621df58..f55833a0e0 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -171,6 +171,7 @@ var (
 		DefaultTheme          string
 		Themes                []string
 		Reactions             []string
+		ReactionsMap          map[string]bool
 		SearchRepoDescription bool
 		UseServiceWorker      bool
 
@@ -985,6 +986,11 @@ func NewContext() {
 	U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimRight(AppURL, "/"))
 
 	zip.Verbose = false
+
+	UI.ReactionsMap = make(map[string]bool)
+	for _, reaction := range UI.Reactions {
+		UI.ReactionsMap[reaction] = true
+	}
 }
 
 func loadInternalToken(sec *ini.Section) string {
diff --git a/modules/structs/issue_reaction.go b/modules/structs/issue_reaction.go
new file mode 100644
index 0000000000..9d71740052
--- /dev/null
+++ b/modules/structs/issue_reaction.go
@@ -0,0 +1,22 @@
+// Copyright 2019 The Gitea 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 structs
+
+import (
+	"time"
+)
+
+// EditReactionOption contain the reaction type
+type EditReactionOption struct {
+	Reaction string `json:"content"`
+}
+
+// ReactionResponse contain one reaction
+type ReactionResponse struct {
+	User     *User  `json:"user"`
+	Reaction string `json:"content"`
+	// swagger:strfmt date-time
+	Created time.Time `json:"created_at"`
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 47c9c95c7f..cd5fc1f3eb 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -657,21 +657,25 @@ func RegisterRoutes(m *macaron.Macaron) {
 						Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
 					m.Group("/comments", func() {
 						m.Get("", repo.ListRepoIssueComments)
-						m.Combo("/:id", reqToken()).
-							Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
-							Delete(repo.DeleteIssueComment)
+						m.Group("/:id", func() {
+							m.Combo("", reqToken()).
+								Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
+								Delete(repo.DeleteIssueComment)
+							m.Combo("/reactions", reqToken()).
+								Get(repo.GetIssueCommentReactions).
+								Post(bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
+								Delete(bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
+						})
 					})
 					m.Group("/:index", func() {
 						m.Combo("").Get(repo.GetIssue).
 							Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
-
 						m.Group("/comments", func() {
 							m.Combo("").Get(repo.ListIssueComments).
 								Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
 							m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
 								Delete(repo.DeleteIssueCommentDeprecated)
 						})
-
 						m.Group("/labels", func() {
 							m.Combo("").Get(repo.ListIssueLabels).
 								Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
@@ -679,12 +683,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 								Delete(reqToken(), repo.ClearIssueLabels)
 							m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
 						})
-
 						m.Group("/times", func() {
 							m.Combo("").Get(repo.ListTrackedTimes).
 								Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
 						})
-
 						m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
 						m.Group("/stopwatch", func() {
 							m.Post("/start", reqToken(), repo.StartIssueStopwatch)
@@ -695,6 +697,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 							m.Put("/:user", reqToken(), repo.AddIssueSubscription)
 							m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
 						})
+						m.Combo("/reactions", reqToken()).
+							Get(repo.GetIssueReactions).
+							Post(bind(api.EditReactionOption{}), repo.PostIssueReaction).
+							Delete(bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
 					})
 				}, mustEnableIssuesOrPulls)
 				m.Group("/labels", func() {
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
new file mode 100644
index 0000000000..56e12ccdcc
--- /dev/null
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -0,0 +1,394 @@
+// Copyright 2019 The Gitea 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 repo
+
+import (
+	"errors"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/context"
+	api "code.gitea.io/gitea/modules/structs"
+)
+
+// GetIssueCommentReactions list reactions of a issue comment
+func GetIssueCommentReactions(ctx *context.APIContext) {
+	// swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions
+	// ---
+	// summary: Get a list reactions of a issue comment
+	// consumes:
+	// - application/json
+	// 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: id
+	//   in: path
+	//   description: id of the comment to edit
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/ReactionResponseList"
+	comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
+	if err != nil {
+		if models.IsErrCommentNotExist(err) {
+			ctx.NotFound(err)
+		} else {
+			ctx.Error(500, "GetCommentByID", err)
+		}
+		return
+	}
+
+	if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+		ctx.Error(403, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
+		return
+	}
+
+	reactions, err := models.FindCommentReactions(comment)
+	if err != nil {
+		ctx.Error(500, "FindIssueReactions", err)
+		return
+	}
+	_, err = reactions.LoadUsers()
+	if err != nil {
+		ctx.Error(500, "ReactionList.LoadUsers()", err)
+		return
+	}
+
+	var result []api.ReactionResponse
+	for _, r := range reactions {
+		result = append(result, api.ReactionResponse{
+			User:     r.User.APIFormat(),
+			Reaction: r.Type,
+			Created:  r.CreatedUnix.AsTime(),
+		})
+	}
+
+	ctx.JSON(200, result)
+}
+
+// PostIssueCommentReaction add a reaction to a comment of a issue
+func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
+	// swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
+	// ---
+	// summary: Add a reaction to a comment of a issue comment
+	// consumes:
+	// - application/json
+	// 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: id
+	//   in: path
+	//   description: id of the comment to edit
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// - name: content
+	//   in: body
+	//   schema:
+	//     "$ref": "#/definitions/EditReactionOption"
+	// responses:
+	//   "201":
+	//     "$ref": "#/responses/ReactionResponse"
+	changeIssueCommentReaction(ctx, form, true)
+}
+
+// DeleteIssueCommentReaction list reactions of a issue comment
+func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
+	// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
+	// ---
+	// summary: Remove a reaction from a comment of a issue comment
+	// consumes:
+	// - application/json
+	// 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: id
+	//   in: path
+	//   description: id of the comment to edit
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// - name: content
+	//   in: body
+	//   schema:
+	//     "$ref": "#/definitions/EditReactionOption"
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/empty"
+	changeIssueCommentReaction(ctx, form, false)
+}
+
+func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
+	comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
+	if err != nil {
+		if models.IsErrCommentNotExist(err) {
+			ctx.NotFound(err)
+		} else {
+			ctx.Error(500, "GetCommentByID", err)
+		}
+		return
+	}
+
+	err = comment.LoadIssue()
+	if err != nil {
+		ctx.Error(500, "comment.LoadIssue() failed", err)
+	}
+
+	if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+		ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
+		return
+	}
+
+	if isCreateType {
+		// PostIssueCommentReaction part
+		reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
+		if err != nil {
+			if models.IsErrForbiddenIssueReaction(err) {
+				ctx.Error(403, err.Error(), err)
+			} else {
+				ctx.Error(500, "CreateCommentReaction", err)
+			}
+			return
+		}
+		_, err = reaction.LoadUser()
+		if err != nil {
+			ctx.Error(500, "Reaction.LoadUser()", err)
+			return
+		}
+
+		ctx.JSON(201, api.ReactionResponse{
+			User:     reaction.User.APIFormat(),
+			Reaction: reaction.Type,
+			Created:  reaction.CreatedUnix.AsTime(),
+		})
+	} else {
+		// DeleteIssueCommentReaction part
+		err = models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
+		if err != nil {
+			ctx.Error(500, "DeleteCommentReaction", err)
+			return
+		}
+		ctx.Status(200)
+	}
+}
+
+// GetIssueReactions list reactions of a issue comment
+func GetIssueReactions(ctx *context.APIContext) {
+	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/reactions issue issueGetIssueReactions
+	// ---
+	// summary: Get a list reactions of a issue
+	// consumes:
+	// - application/json
+	// 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 issue
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/ReactionResponseList"
+	issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+	if err != nil {
+		if models.IsErrIssueNotExist(err) {
+			ctx.NotFound()
+		} else {
+			ctx.Error(500, "GetIssueByIndex", err)
+		}
+		return
+	}
+
+	if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+		ctx.Error(403, "GetIssueReactions", errors.New("no permission to get reactions"))
+		return
+	}
+
+	reactions, err := models.FindIssueReactions(issue)
+	if err != nil {
+		ctx.Error(500, "FindIssueReactions", err)
+		return
+	}
+	_, err = reactions.LoadUsers()
+	if err != nil {
+		ctx.Error(500, "ReactionList.LoadUsers()", err)
+		return
+	}
+
+	var result []api.ReactionResponse
+	for _, r := range reactions {
+		result = append(result, api.ReactionResponse{
+			User:     r.User.APIFormat(),
+			Reaction: r.Type,
+			Created:  r.CreatedUnix.AsTime(),
+		})
+	}
+
+	ctx.JSON(200, result)
+}
+
+// PostIssueReaction add a reaction to a comment of a issue
+func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
+	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
+	// ---
+	// summary: Add a reaction to a comment of a issue
+	// consumes:
+	// - application/json
+	// 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 issue
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// - name: content
+	//   in: body
+	//   schema:
+	//     "$ref": "#/definitions/EditReactionOption"
+	// responses:
+	//   "201":
+	//     "$ref": "#/responses/ReactionResponse"
+	changeIssueReaction(ctx, form, true)
+}
+
+// DeleteIssueReaction list reactions of a issue comment
+func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
+	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
+	// ---
+	// summary: Remove a reaction from a comment of a issue
+	// consumes:
+	// - application/json
+	// 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 issue
+	//   type: integer
+	//   format: int64
+	//   required: true
+	// - name: content
+	//   in: body
+	//   schema:
+	//     "$ref": "#/definitions/EditReactionOption"
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/empty"
+	changeIssueReaction(ctx, form, false)
+}
+
+func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
+	issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+	if err != nil {
+		if models.IsErrIssueNotExist(err) {
+			ctx.NotFound()
+		} else {
+			ctx.Error(500, "GetIssueByIndex", err)
+		}
+		return
+	}
+
+	if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+		ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
+		return
+	}
+
+	if isCreateType {
+		// PostIssueReaction part
+		reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction)
+		if err != nil {
+			if models.IsErrForbiddenIssueReaction(err) {
+				ctx.Error(403, err.Error(), err)
+			} else {
+				ctx.Error(500, "CreateCommentReaction", err)
+			}
+			return
+		}
+		_, err = reaction.LoadUser()
+		if err != nil {
+			ctx.Error(500, "Reaction.LoadUser()", err)
+			return
+		}
+
+		ctx.JSON(201, api.ReactionResponse{
+			User:     reaction.User.APIFormat(),
+			Reaction: reaction.Type,
+			Created:  reaction.CreatedUnix.AsTime(),
+		})
+	} else {
+		// DeleteIssueReaction part
+		err = models.DeleteIssueReaction(ctx.User, issue, form.Reaction)
+		if err != nil {
+			ctx.Error(500, "DeleteIssueReaction", err)
+			return
+		}
+		ctx.Status(200)
+	}
+}
diff --git a/routers/api/v1/swagger/issue.go b/routers/api/v1/swagger/issue.go
index c06186bf6c..a78c2982fd 100644
--- a/routers/api/v1/swagger/issue.go
+++ b/routers/api/v1/swagger/issue.go
@@ -84,3 +84,24 @@ type swaggerIssueDeadline struct {
 	// in:body
 	Body api.IssueDeadline `json:"body"`
 }
+
+// EditReactionOption
+// swagger:response EditReactionOption
+type swaggerEditReactionOption struct {
+	// in:body
+	Body api.EditReactionOption `json:"body"`
+}
+
+// ReactionResponse
+// swagger:response ReactionResponse
+type swaggerReactionResponse struct {
+	// in:body
+	Body api.ReactionResponse `json:"body"`
+}
+
+// ReactionResponseList
+// swagger:response ReactionResponseList
+type swaggerReactionResponseList struct {
+	// in:body
+	Body []api.ReactionResponse `json:"body"`
+}
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index c79ea02e86..5d5aaca253 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -1463,14 +1463,12 @@ func ChangeIssueReaction(ctx *context.Context, form auth.ReactionForm) {
 
 	switch ctx.Params(":action") {
 	case "react":
-		if !util.IsStringInSlice(form.Content, setting.UI.Reactions) {
-			err := fmt.Errorf("ChangeIssueReaction: '%s' is not an allowed reaction", form.Content)
-			ctx.ServerError(err.Error(), err)
-			return
-		}
-
 		reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Content)
 		if err != nil {
+			if models.IsErrForbiddenIssueReaction(err) {
+				ctx.ServerError("ChangeIssueReaction", err)
+				return
+			}
 			log.Info("CreateIssueReaction: %s", err)
 			break
 		}
@@ -1564,14 +1562,12 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) {
 
 	switch ctx.Params(":action") {
 	case "react":
-		if !util.IsStringInSlice(form.Content, setting.UI.Reactions) {
-			err := fmt.Errorf("ChangeIssueReaction: '%s' is not an allowed reaction", form.Content)
-			ctx.ServerError(err.Error(), err)
-			return
-		}
-
 		reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Content)
 		if err != nil {
+			if models.IsErrForbiddenIssueReaction(err) {
+				ctx.ServerError("ChangeIssueReaction", err)
+				return
+			}
 			log.Info("CreateCommentReaction: %s", err)
 			break
 		}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 1d8cd36858..9c8db28817 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -3016,6 +3016,148 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/issues/comments/{id}/reactions": {
+      "get": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Get a list reactions of a issue comment",
+        "operationId": "issueGetCommentReactions",
+        "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": "id of the comment to edit",
+            "name": "id",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/ReactionResponseList"
+          }
+        }
+      },
+      "post": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Add a reaction to a comment of a issue comment",
+        "operationId": "issuePostCommentReaction",
+        "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": "id of the comment to edit",
+            "name": "id",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "content",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/EditReactionOption"
+            }
+          }
+        ],
+        "responses": {
+          "201": {
+            "$ref": "#/responses/ReactionResponse"
+          }
+        }
+      },
+      "delete": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Remove a reaction from a comment of a issue comment",
+        "operationId": "issueDeleteCommentReaction",
+        "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": "id of the comment to edit",
+            "name": "id",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "content",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/EditReactionOption"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/empty"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/issues/{id}/times": {
       "get": {
         "produces": [
@@ -3688,6 +3830,148 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/issues/{index}/reactions": {
+      "get": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Get a list reactions of a issue",
+        "operationId": "issueGetIssueReactions",
+        "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 issue",
+            "name": "index",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/ReactionResponseList"
+          }
+        }
+      },
+      "post": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Add a reaction to a comment of a issue",
+        "operationId": "issuePostIssueReaction",
+        "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 issue",
+            "name": "index",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "content",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/EditReactionOption"
+            }
+          }
+        ],
+        "responses": {
+          "201": {
+            "$ref": "#/responses/ReactionResponse"
+          }
+        }
+      },
+      "delete": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Remove a reaction from a comment of a issue",
+        "operationId": "issueDeleteIssueReaction",
+        "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 issue",
+            "name": "index",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "content",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/EditReactionOption"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/empty"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/issues/{index}/stopwatch/start": {
       "post": {
         "consumes": [
@@ -8721,6 +9005,17 @@
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "EditReactionOption": {
+      "description": "EditReactionOption contain the reaction type",
+      "type": "object",
+      "properties": {
+        "content": {
+          "type": "string",
+          "x-go-name": "Reaction"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "EditReleaseOption": {
       "description": "EditReleaseOption options when editing a release",
       "type": "object",
@@ -10095,6 +10390,25 @@
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "ReactionResponse": {
+      "description": "ReactionResponse contain one reaction",
+      "type": "object",
+      "properties": {
+        "content": {
+          "type": "string",
+          "x-go-name": "Reaction"
+        },
+        "created_at": {
+          "type": "string",
+          "format": "date-time",
+          "x-go-name": "Created"
+        },
+        "user": {
+          "$ref": "#/definitions/User"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "Reference": {
       "type": "object",
       "title": "Reference represents a Git reference.",
@@ -10960,6 +11274,12 @@
         }
       }
     },
+    "EditReactionOption": {
+      "description": "EditReactionOption",
+      "schema": {
+        "$ref": "#/definitions/EditReactionOption"
+      }
+    },
     "EmailList": {
       "description": "EmailList",
       "schema": {
@@ -11146,6 +11466,21 @@
         }
       }
     },
+    "ReactionResponse": {
+      "description": "ReactionResponse",
+      "schema": {
+        "$ref": "#/definitions/ReactionResponse"
+      }
+    },
+    "ReactionResponseList": {
+      "description": "ReactionResponseList",
+      "schema": {
+        "type": "array",
+        "items": {
+          "$ref": "#/definitions/ReactionResponse"
+        }
+      }
+    },
     "Reference": {
       "description": "Reference",
       "schema": {