diff --git a/internal/route/api/v1/api.go b/internal/route/api/v1/api.go
index 844012b68..e2ed95a19 100644
--- a/internal/route/api/v1/api.go
+++ b/internal/route/api/v1/api.go
@@ -189,7 +189,8 @@ func RegisterRoutes(m *macaron.Macaron) {
 					accessTokensHandler := user.NewAccessTokensHandler(user.NewAccessTokensStore())
 					m.Combo("").
 						Get(accessTokensHandler.List()).
-						Post(bind(api.CreateAccessTokenOption{}), accessTokensHandler.Create())
+						Post(bind(api.CreateAccessTokenOption{}), accessTokensHandler.Create()).
+						Delete(bind(api.DeleteAccessTokenOption{}), accessTokensHandler.Delete())
 				}, reqBasicAuth())
 			})
 		})
diff --git a/internal/route/api/v1/user/access_tokens.go b/internal/route/api/v1/user/access_tokens.go
index b8c3062a9..05461d12b 100644
--- a/internal/route/api/v1/user/access_tokens.go
+++ b/internal/route/api/v1/user/access_tokens.go
@@ -59,6 +59,37 @@ func (h *AccessTokensHandler) Create() macaron.Handler {
 	}
 }
 
+// Deletes the provided token identified by SHA1
+// This prevents anyone from deleting everything in list(), as the identifiers in list() are not the actual SHA1.
+func (h *AccessTokensHandler) Delete() macaron.Handler {
+	//TODO make the latter arg api.DeleteAccessTokenOption
+	return func(c *context.APIContext, form api.DeleteAccessTokenOption) {
+		// We need the ID of the token to delete it.
+		existing, err := h.store.GetBySHA1(c.Req.Context(), form.Sha1);
+		if err != nil {
+			if database.IsErrAccessTokenNotExist(err) {
+				c.ErrorStatus(http.StatusUnprocessableEntity, err)
+			} else {
+				c.Error(err, "list tokens in delete prep")
+			}
+			return
+		}
+
+		// We need the User ID and the Token ID to delete it.
+		err = h.store.DeleteAccessToken(c.Req.Context(), c.User.ID, existing.ID)
+		if err != nil {
+			// Always possible that we race, TOCTOU
+			if database.IsErrAccessTokenNotExist(err) {
+				c.ErrorStatus(http.StatusUnprocessableEntity, err)
+			} else {
+				c.Error(err, "delete access token")
+			}
+			return
+		}
+		c.JSON(http.StatusOK, &api.AccessToken{Name: existing.Name})
+	}
+}
+
 // AccessTokensStore is the data layer carrier for user access tokens API
 // endpoints. This interface is meant to abstract away and limit the exposure of
 // the underlying data layer to the handler through a thin-wrapper.
@@ -69,6 +100,10 @@ type AccessTokensStore interface {
 	CreateAccessToken(ctx gocontext.Context, userID int64, name string) (*database.AccessToken, error)
 	// ListAccessTokens returns all access tokens belongs to given user.
 	ListAccessTokens(ctx gocontext.Context, userID int64) ([]*database.AccessToken, error)
+	// Get a token by SHA1 (used to find a tok to delete it)
+	GetBySHA1(ctx gocontext.Context, Sha1 string) (*database.AccessToken, error)
+	// Delete a given token
+	DeleteAccessToken(ctx gocontext.Context, userID int64, tokenID int64) error
 }
 
 type accessTokensStore struct{}
@@ -86,3 +121,12 @@ func (*accessTokensStore) CreateAccessToken(ctx gocontext.Context, userID int64,
 func (*accessTokensStore) ListAccessTokens(ctx gocontext.Context, userID int64) ([]*database.AccessToken, error) {
 	return database.Handle.AccessTokens().List(ctx, userID)
 }
+
+// Note: the possibility, though remote, of SHA1 collissions could be made far less likely via providing a user as well.
+func (*accessTokensStore) GetBySHA1(ctx gocontext.Context, Sha1 string) (*database.AccessToken, error) {
+	return database.Handle.AccessTokens().GetBySHA1(ctx, Sha1)
+}
+
+func (*accessTokensStore) DeleteAccessToken(ctx gocontext.Context, userID int64, tokenID int64) error {
+	return database.Handle.AccessTokens().DeleteByID(ctx, userID, tokenID)
+}