diff --git a/api.go b/api.go
index 1a034f49f18551f79d5e92c4cf57f0a6fd1bda09..77bcd1a8ae21922f4b26918da91629243e2844ad 100644
--- a/api.go
+++ b/api.go
@@ -5,40 +5,14 @@ import (
 	log "github.com/sirupsen/logrus"
 	"io/ioutil"
 	"net/http"
+	"random.chars.jp/git/image-board/api"
 	"random.chars.jp/git/image-board/store"
 	"strconv"
 	"strings"
 )
 
-type UserPayload struct {
-	Username   string `json:"username"`
-	ID         string `json:"id"`
-	Privileged bool   `json:"privileged"`
-}
-
-type UserCreatePayload struct {
-	Username   string `json:"username"`
-	Password   string `json:"password"`
-	Privileged bool   `json:"privileged"`
-}
-
-type UserUpdatePayload struct {
-	Username string `json:"username"`
-}
-
-type TagUpdatePayload struct {
-	Type string `json:"type"`
-}
-
-type ImageUpdatePayload struct {
-	Source string `json:"source"`
-}
-
-var unauthorized = gin.H{"error": "not authorized"}
-var denied = gin.H{"error": "permission denied"}
-
 func registerAPI() {
-	router.GET("/api", func(context *gin.Context) {
+	router.GET(api.Base, func(context *gin.Context) {
 		context.JSON(http.StatusOK, store.Info{
 			Revision:       instance.Revision,
 			Compat:         instance.Compat,
@@ -49,99 +23,110 @@ func registerAPI() {
 		})
 	})
 
-	router.GET("/api/single_user", func(context *gin.Context) {
+	router.GET(api.SingleUser, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.SingleUser)
 	})
 
-	router.GET("/api/user", func(context *gin.Context) {
+	router.GET(api.User, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.Users())
 	})
 
-	router.PUT("/api/user", func(context *gin.Context) {
+	router.PUT(api.User, func(context *gin.Context) {
 		user, ok := getUser(context)
 		if !instance.Register {
 			if !ok {
-				context.JSON(http.StatusForbidden, gin.H{
-					"error": "user registration disallowed",
-				})
+				context.JSON(http.StatusForbidden, api.Error{Error: "user registration daisallowed"})
 				return
 			}
 
 			if !user.Privileged {
-				context.JSON(http.StatusForbidden, denied)
+				context.JSON(http.StatusForbidden, api.Denied)
 				return
 			}
 		}
 
-		var payload UserCreatePayload
+		var payload api.UserCreatePayload
 		if err := context.ShouldBindJSON(&payload); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": err.Error(),
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 		if !user.Privileged && payload.Privileged {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 
 		context.JSON(http.StatusOK, instance.UserAdd(payload.Username, payload.Privileged, payload.Password))
 	})
 
-	router.GET("/api/user/this", func(context *gin.Context) {
+	router.GET(api.UserThis, func(context *gin.Context) {
 		info, ok := getUser(context)
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
-		context.JSON(http.StatusOK, UserPayload{
+		context.JSON(http.StatusOK, api.UserPayload{
 			Username:   info.Username,
 			ID:         info.Snowflake,
 			Privileged: info.Privileged,
 		})
 	})
 
-	router.GET("/api/user/:flake", func(context *gin.Context) {
+	router.GET(api.UserField, func(context *gin.Context) {
 		info := instance.User(context.Param("flake"))
-		context.JSON(http.StatusOK, UserPayload{
+		context.JSON(http.StatusOK, api.UserPayload{
 			Username:   info.Username,
 			ID:         info.Snowflake,
 			Privileged: info.Privileged,
 		})
 	})
 
-	router.PATCH("/api/user/:flake", func(context *gin.Context) {
+	router.PATCH(api.UserField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		if !info.Privileged && (info.Snowflake != flake) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 
-		var payload UserUpdatePayload
+		var payload api.UserUpdatePayload
 		if err := context.ShouldBindJSON(&payload); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": err.Error(),
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 
 		instance.UserUsernameUpdate(flake, payload.Username)
 	})
 
-	router.DELETE("/api/user/:flake/password", func(context *gin.Context) {
+	router.DELETE(api.UserField, func(context *gin.Context) {
+		info, ok := getUser(context)
+
+		// Require sign in
+		if !ok {
+			context.JSON(http.StatusForbidden, api.Unauthorized)
+			return
+		}
+
+		flake := context.Param("flake")
+		if !info.Privileged && (info.Snowflake != flake) {
+			context.JSON(http.StatusForbidden, api.Denied)
+			return
+		}
+		instance.UserDestroy(flake)
+	})
+
+	router.DELETE(api.UserPassword, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require privileged
 		if !ok || !info.Privileged {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
@@ -150,34 +135,30 @@ func registerAPI() {
 		instance.UserSecretRegen(flake)
 	})
 
-	router.PUT("/api/user/:flake/password", func(context *gin.Context) {
+	router.PUT(api.UserPassword, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		if !info.Privileged && (info.Snowflake != flake) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 		}
 
 		var newPass string
 		if payload, err := context.GetRawData(); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": err.Error(),
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		} else {
 			newPass = string(payload)
 		}
 
 		if newPass == "" {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": "empty passwords are not allowed",
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: "empty passwords are not allowed"})
 			return
 		}
 
@@ -187,9 +168,9 @@ func registerAPI() {
 		})
 	})
 
-	router.GET("/api/username/:name", func(context *gin.Context) {
+	router.GET(api.UsernameField, func(context *gin.Context) {
 		info := instance.UserUsername(context.Param("name"))
-		payload := UserPayload{
+		payload := api.UserPayload{
 			Username:   info.Username,
 			ID:         info.Snowflake,
 			Privileged: info.Privileged,
@@ -197,13 +178,11 @@ func registerAPI() {
 		context.JSON(http.StatusOK, payload)
 	})
 
-	router.POST("/api/username/:name/auth", func(context *gin.Context) {
+	router.POST(api.UsernameAuth, func(context *gin.Context) {
 		var password string
 
 		if payload, err := context.GetRawData(); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": err.Error(),
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		} else {
 			password = string(payload)
@@ -213,148 +192,72 @@ func registerAPI() {
 		if instance.UserUsernamePasswordValidate(username, password) {
 			context.String(http.StatusOK, instance.UserUsername(username).Secret)
 		} else {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 		}
 	})
 
-	router.DELETE("/api/user/:flake", func(context *gin.Context) {
+	router.GET(api.UserSecret, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		if !info.Privileged && (info.Snowflake != flake) {
-			context.JSON(http.StatusForbidden, denied)
-			return
-		}
-		instance.UserDestroy(flake)
-	})
-
-	router.GET("/api/user/:flake/secret", func(context *gin.Context) {
-		info, ok := getUser(context)
-
-		// Require sign in
-		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
-			return
-		}
-
-		flake := context.Param("flake")
-		if !info.Privileged && (info.Snowflake != flake) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		context.String(http.StatusOK, instance.User(flake).Secret)
 	})
 
-	router.PUT("/api/user/:flake/secret", func(context *gin.Context) {
+	router.PUT(api.UserSecret, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		if !info.Privileged && (info.Snowflake != flake) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		context.String(http.StatusOK, instance.UserSecretRegen(flake))
 	})
 
-	router.GET("/api/user/:flake/image", func(context *gin.Context) {
+	router.GET(api.UserImage, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.UserImages(context.Param("flake")))
 	})
 
-	router.GET("/api/search/:tags", func(context *gin.Context) {
+	router.GET(api.SearchField, func(context *gin.Context) {
 		tagsPayload := context.Param("tags")
 		tags := strings.Split(tagsPayload, "!")
 		context.JSON(http.StatusOK, instance.ImageSearch(tags))
 	})
 
-	router.GET("/api/image/snowflake", func(context *gin.Context) {
+	router.GET(api.Image, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require privileged
 		if !ok || !info.Privileged {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		context.JSON(http.StatusOK, instance.ImageSnowflakes())
 	})
 
-	router.GET("/api/image/snowflake/:flake", func(context *gin.Context) {
-		flake := context.Param("flake")
-		if _, err := strconv.Atoi(flake); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": "invalid snowflake",
-			})
-			return
-		}
-		context.JSON(http.StatusOK, instance.ImageSnowflake(flake))
-	})
-
-	router.GET("/api/image/snowflake/:flake/file", func(context *gin.Context) {
-		flake := context.Param("flake")
-		if _, err := strconv.Atoi(flake); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": "invalid snowflake",
-			})
-			return
-		}
-		image, data := instance.ImageData(instance.ImageSnowflakeHash(flake), false)
-		if image.Hash == "" {
-			context.JSON(http.StatusNotFound, gin.H{
-				"error": "not found",
-			})
-			return
-		}
-		context.Data(http.StatusOK, "image/"+image.Type, data)
-	})
-
-	router.GET("/api/image/snowflake/:flake/preview", func(context *gin.Context) {
-		flake := context.Param("flake")
-		if _, err := strconv.Atoi(flake); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": "invalid snowflake",
-			})
-			return
-		}
-		image, data := instance.ImageData(instance.ImageSnowflakeHash(flake), true)
-		if image.Hash == "" {
-			context.JSON(http.StatusNotFound, gin.H{
-				"error": "not found",
-			})
-			return
-		}
-		context.Data(http.StatusOK, "image/jpeg", data)
-	})
-
-	router.GET("/api/image", func(context *gin.Context) {
-		info, ok := getUser(context)
-
-		// Require privileged
-		if !ok || !info.Privileged {
-			context.JSON(http.StatusForbidden, unauthorized)
-			return
-		}
-
-		context.JSON(http.StatusOK, instance.Images())
-	})
-
-	router.POST("/api/image", func(context *gin.Context) {
+	router.POST(api.Image, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
@@ -377,264 +280,254 @@ func registerAPI() {
 		}
 		image := instance.ImageAdd(data, info.Snowflake)
 		if image.Hash == "" {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": "invalid image",
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: "invalid image"})
 			return
 		}
 		context.JSON(http.StatusOK, image)
 	})
 
-	router.GET("/api/image/:flake", func(context *gin.Context) {
+	router.GET(api.ImageField, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.ImageSnowflake(context.Param("flake")))
 	})
 
-	router.PATCH("/api/image/:flake", func(context *gin.Context) {
+	router.PATCH(api.ImageField, func(context *gin.Context) {
 		user, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		info := instance.ImageSnowflake(context.Param("flake"))
 		if !user.Privileged && (info.User != user.Snowflake) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 
-		var payload ImageUpdatePayload
+		var payload api.ImageUpdatePayload
 		if err := context.ShouldBindJSON(&payload); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": err.Error(),
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 
 		instance.ImageSource(info.Hash, payload.Source)
 	})
 
-	router.DELETE("/api/image/:flake", func(context *gin.Context) {
+	router.DELETE(api.ImageField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		image := instance.ImageSnowflake(flake)
 		if !info.Privileged && (info.Snowflake != image.User) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		instance.ImageDestroy(image.Hash)
 	})
 
-	router.GET("/api/image/:flake/file", func(context *gin.Context) {
+	router.GET(api.ImageFile, func(context *gin.Context) {
 		flake := context.Param("flake")
 		image, data := instance.ImageData(instance.ImageSnowflakeHash(flake), false)
 		if image.Snowflake == flake {
-			context.JSON(http.StatusNotFound, gin.H{
-				"error": "not found",
-			})
+			context.JSON(http.StatusNotFound, api.Error{Error: "not found"})
 			return
 		}
 		context.Data(http.StatusOK, "image/"+image.Type, data)
 	})
 
-	router.GET("/api/image/:flake/preview", func(context *gin.Context) {
+	router.GET(api.ImagePreview, func(context *gin.Context) {
 		flake := context.Param("flake")
 		image, data := instance.ImageData(instance.ImageSnowflakeHash(flake), true)
 		if image.Snowflake == flake {
-			context.JSON(http.StatusNotFound, gin.H{
-				"error": "not found",
-			})
+			context.JSON(http.StatusNotFound, api.Error{Error: "not found"})
 			return
 		}
 		context.Data(http.StatusOK, "image/jpeg", data)
 	})
 
-	router.GET("/api/image/:flake/tag", func(context *gin.Context) {
+	router.GET(api.ImageTag, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.ImageTags(context.Param("flake")))
 	})
 
-	router.PUT("/api/image/:flake/tag/:tag", func(context *gin.Context) {
+	router.PUT(api.ImageTagField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		if !info.Privileged && (info.Snowflake != instance.ImageSnowflake(flake).User) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		instance.ImageTagAdd(flake, context.Param("tag"))
 	})
 
-	router.DELETE("/api/image/:flake/tag/:tag", func(context *gin.Context) {
+	router.DELETE(api.ImageTagField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		flake := context.Param("flake")
 		if !info.Privileged && (info.Snowflake != instance.ImageSnowflake(flake).User) {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		instance.ImageTagRemove(flake, context.Param("tag"))
 	})
 
-	router.GET("/api/tag", func(context *gin.Context) {
+	router.GET(api.Tag, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.Tags())
 	})
 
-	router.GET("/api/tag/:tag", func(context *gin.Context) {
+	router.GET(api.TagField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		if !info.Privileged {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		context.JSON(http.StatusOK, instance.Tag(context.Param("tag")))
 	})
 
-	router.PUT("/api/tag/:tag", func(context *gin.Context) {
+	router.PUT(api.TagField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		if !info.Privileged {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		context.JSON(http.StatusOK, instance.TagCreate(context.Param("tag")))
 	})
 
-	router.DELETE("/api/tag/:tag", func(context *gin.Context) {
+	router.DELETE(api.TagField, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		if !info.Privileged {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 		instance.TagDestroy(context.Param("tag"))
 	})
 
-	router.GET("/api/tag/:tag/page", func(context *gin.Context) {
+	router.GET(api.TagPage, func(context *gin.Context) {
 		tag := context.Param("tag")
 		if !instance.MatchName(tag) {
-			context.JSON(http.StatusBadRequest, denied)
+			context.JSON(http.StatusBadRequest, api.Denied)
 			return
 		}
 		context.String(http.StatusOK, strconv.Itoa(instance.PageTotal("tag_"+tag)))
 	})
 
-	router.GET("/api/tag/:tag/page/:entry", func(context *gin.Context) {
+	router.GET(api.TagPageField, func(context *gin.Context) {
 		tag := context.Param("tag")
 		if !instance.MatchName(tag) {
-			context.JSON(http.StatusBadRequest, denied)
+			context.JSON(http.StatusBadRequest, api.Denied)
 			return
 		}
 
 		param := context.Param("entry")
 		entry, err := strconv.Atoi(param)
 		if err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 		context.JSON(http.StatusOK, instance.Page("tag_"+tag, entry))
 	})
 
-	router.GET("/api/tag/:tag/page/:entry/image", func(context *gin.Context) {
+	router.GET(api.TagPageImage, func(context *gin.Context) {
 		tag := context.Param("tag")
 		if !instance.MatchName(tag) {
-			context.JSON(http.StatusBadRequest, denied)
+			context.JSON(http.StatusBadRequest, api.Denied)
 			return
 		}
 
 		param := context.Param("entry")
 		entry, err := strconv.Atoi(param)
 		if err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 		context.JSON(http.StatusOK, instance.PageImages("tag_"+tag, entry))
 	})
 
-	router.GET("/api/tag/:tag/info", func(context *gin.Context) {
+	router.GET(api.TagInfo, func(context *gin.Context) {
 		context.JSON(http.StatusOK, instance.TagInfo(context.Param("tag")))
 	})
 
-	router.PATCH("/api/tag/:tag/info", func(context *gin.Context) {
+	router.PATCH(api.TagInfo, func(context *gin.Context) {
 		info, ok := getUser(context)
 
 		// Require sign in
 		if !ok {
-			context.JSON(http.StatusForbidden, unauthorized)
+			context.JSON(http.StatusForbidden, api.Unauthorized)
 			return
 		}
 
 		if !info.Privileged {
-			context.JSON(http.StatusForbidden, denied)
+			context.JSON(http.StatusForbidden, api.Denied)
 			return
 		}
 
-		var payload TagUpdatePayload
+		var payload api.TagUpdatePayload
 		if err := context.ShouldBindJSON(&payload); err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{
-				"error": err.Error(),
-			})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 		instance.TagType(context.Param("tag"), payload.Type)
 	})
 
-	router.GET("/api/page", func(context *gin.Context) {
+	router.GET(api.Page, func(context *gin.Context) {
 		context.String(http.StatusOK, strconv.Itoa(instance.PageTotal(store.ImageRootPageVariant)))
 	})
 
-	router.GET("/api/page/:entry", func(context *gin.Context) {
+	router.GET(api.PageField, func(context *gin.Context) {
 		param := context.Param("entry")
 		entry, err := strconv.Atoi(param)
 		if err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 		context.JSON(http.StatusOK, instance.Page(store.ImageRootPageVariant, entry))
 	})
 
-	router.GET("/api/page/:entry/image", func(context *gin.Context) {
+	router.GET(api.PageImage, func(context *gin.Context) {
 		param := context.Param("entry")
 		entry, err := strconv.Atoi(param)
 		if err != nil {
-			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			context.JSON(http.StatusBadRequest, api.Error{Error: err.Error()})
 			return
 		}
 		context.JSON(http.StatusOK, instance.PageImages(store.ImageRootPageVariant, entry))
diff --git a/api/errors.go b/api/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..7da482d9f68730737d1da23455e351a7222cd904
--- /dev/null
+++ b/api/errors.go
@@ -0,0 +1,8 @@
+package api
+
+type Error struct {
+	Error string `json:"error"`
+}
+
+var Unauthorized = Error{"not authorized"}
+var Denied = Error{"permission denied"}
diff --git a/api/paths.go b/api/paths.go
new file mode 100644
index 0000000000000000000000000000000000000000..5738d9269a9e6ad08d59297372ee7510ed6a57b0
--- /dev/null
+++ b/api/paths.go
@@ -0,0 +1,32 @@
+package api
+
+const (
+	Base          = "/api"
+	SingleUser    = Base + "/single_user"
+	Image         = Base + "/image"
+	ImageField    = Image + "/:flake"
+	ImageFile     = ImageField + "/file"
+	ImagePreview  = ImageField + "/preview"
+	ImageTag      = ImageField + "/tag"
+	ImageTagField = ImageTag + "/:tag"
+	Tag           = Base + "/tag"
+	TagField      = Tag + "/:tag"
+	TagInfo       = TagField + "/info"
+	TagPage       = TagField + "/page"
+	TagPageField  = TagPage + "/:entry"
+	TagPageImage  = TagPageField + "/image"
+	Page          = Base + "/page"
+	PageField     = Page + "/:entry"
+	PageImage     = PageField + "/image"
+	Search        = Base + "/search"
+	SearchField   = Search + "/:tags"
+	User          = Base + "/user"
+	UserThis      = User + "/this"
+	UserField     = User + "/:flake"
+	UserSecret    = UserField + "/secret"
+	UserImage     = UserField + "/image"
+	UserPassword  = UserField + "/password"
+	Username      = Base + "/username"
+	UsernameField = Username + "/:name"
+	UsernameAuth  = UsernameField + "/auth"
+)
diff --git a/api/types.go b/api/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..b0ea0155f2db158af24b7d540ac4ef939ed46a21
--- /dev/null
+++ b/api/types.go
@@ -0,0 +1,25 @@
+package api
+
+type UserPayload struct {
+	Username   string `json:"username"`
+	ID         string `json:"id"`
+	Privileged bool   `json:"privileged"`
+}
+
+type UserCreatePayload struct {
+	Username   string `json:"username"`
+	Password   string `json:"password"`
+	Privileged bool   `json:"privileged"`
+}
+
+type UserUpdatePayload struct {
+	Username string `json:"username"`
+}
+
+type TagUpdatePayload struct {
+	Type string `json:"type"`
+}
+
+type ImageUpdatePayload struct {
+	Source string `json:"source"`
+}
diff --git a/store/image.go b/store/image.go
index 323fa45865aaeb884c115c25b1ede8821e06cdaa..771efb83e2873950549f393d24c7ddb7033e95bc 100644
--- a/store/image.go
+++ b/store/image.go
@@ -27,7 +27,7 @@ type Image struct {
 	CommentaryTranslation string `json:"commentary_translation"`
 }
 
-// Images returns a list of images.
+// Images returns a slice of image hashes.
 func (s *Store) Images() []string {
 	var images []string
 	if entries, err := os.ReadDir(s.ImagesDir()); err != nil {
@@ -305,7 +305,7 @@ func (s *Store) ImageSource(hash, source string) {
 	log.Infof("Image hash %s source set to %s.", hash, source)
 }
 
-// ImageSnowflakes returns all snowflakes.
+// ImageSnowflakes returns a slice of image snowflakes.
 func (s *Store) ImageSnowflakes() []string {
 	var snowflakes []string
 	if entries, err := os.ReadDir(s.ImagesSnowflakeDir()); err != nil {