diff --git a/.github/workflows/oci-conformance-action.yml b/.github/workflows/oci-conformance-action.yml
index 371e35fc..ec2dc4ae 100644
--- a/.github/workflows/oci-conformance-action.yml
+++ b/.github/workflows/oci-conformance-action.yml
@@ -36,8 +36,6 @@ jobs:
           echo "SERVER_URL=http://${IP}:8080" >> $GITHUB_ENV
     - uses: actions/checkout@v3
       with:
-        # TODO: change to upstream once the foloowing PR is merged:
-        # https://github.com/opencontainers/distribution-spec/pull/436
         repository: opencontainers/distribution-spec
         ref: main
         path: distribution-spec
diff --git a/errors/errors.go b/errors/errors.go
index 3e4c65e9..b3cf4bfd 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -1,16 +1,57 @@
 package errors
 
-import "errors"
+import (
+	"errors"
+)
+
+type Error struct {
+	err     error
+	details map[string]string
+}
+
+func (e *Error) Error() string {
+	return e.err.Error()
+}
+
+func (e *Error) Is(target error) bool {
+	return errors.Is(e.err, target)
+}
+
+func (e *Error) AddDetail(key, value string) *Error {
+	e.details[key] = value
+
+	return e
+}
+
+func (e *Error) GetDetails() map[string]string {
+	return e.details
+}
+
+func NewError(err error) *Error {
+	return &Error{
+		err:     err,
+		details: GetDetails(err), // preserve details if chained error
+	}
+}
+
+func GetDetails(err error) map[string]string {
+	var internalErr *Error
+	details := make(map[string]string)
+
+	if errors.As(err, &internalErr) {
+		details = internalErr.GetDetails()
+	}
+
+	return details
+}
 
 var (
 	ErrBadConfig                      = errors.New("config: invalid config")
 	ErrCliBadConfig                   = errors.New("cli: bad config")
 	ErrRepoNotFound                   = errors.New("repository: not found")
-	ErrRepoIsNotDir                   = errors.New("repository: not a directory")
 	ErrRepoBadVersion                 = errors.New("repository: unsupported layout version")
 	ErrManifestNotFound               = errors.New("manifest: not found")
 	ErrBadManifest                    = errors.New("manifest: invalid contents")
-	ErrBadIndex                       = errors.New("index: invalid contents")
 	ErrUploadNotFound                 = errors.New("uploads: not found")
 	ErrBadUploadRange                 = errors.New("uploads: bad range")
 	ErrBlobNotFound                   = errors.New("blob: not found")
diff --git a/pkg/api/authn.go b/pkg/api/authn.go
index 33d8fac4..8e7bb6b9 100644
--- a/pkg/api/authn.go
+++ b/pkg/api/authn.go
@@ -36,7 +36,7 @@ import (
 	"zotregistry.io/zot/pkg/api/config"
 	"zotregistry.io/zot/pkg/api/constants"
 	apiErr "zotregistry.io/zot/pkg/api/errors"
-	"zotregistry.io/zot/pkg/common"
+	zcommon "zotregistry.io/zot/pkg/common"
 	"zotregistry.io/zot/pkg/log"
 	localCtx "zotregistry.io/zot/pkg/requestcontext"
 	storageConstants "zotregistry.io/zot/pkg/storage/constants"
@@ -463,7 +463,7 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
 			if err != nil {
 				ctlr.Log.Error().Err(err).Msg("issue parsing Authorization header")
 				response.Header().Set("Content-Type", "application/json")
-				common.WriteJSON(response, http.StatusInternalServerError, apiErr.NewErrorList(apiErr.NewError(apiErr.UNSUPPORTED)))
+				zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewError(apiErr.UNSUPPORTED))
 
 				return
 			}
@@ -472,8 +472,7 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
 				response.Header().Set("Content-Type", "application/json")
 				response.Header().Set("WWW-Authenticate", permissions.WWWAuthenticateHeader)
 
-				common.WriteJSON(response, http.StatusUnauthorized,
-					apiErr.NewErrorList(apiErr.NewError(apiErr.UNAUTHORIZED)))
+				zcommon.WriteJSON(response, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
 
 				return
 			}
@@ -591,7 +590,7 @@ func getRelyingPartyArgs(cfg *config.Config, provider string) (
 
 	scopes := cfg.HTTP.Auth.OpenID.Providers[provider].Scopes
 	// openid scope must be the first one in list
-	if !common.Contains(scopes, oidc.ScopeOpenID) && config.IsOpenIDSupported(provider) {
+	if !zcommon.Contains(scopes, oidc.ScopeOpenID) && config.IsOpenIDSupported(provider) {
 		scopes = append([]string{oidc.ScopeOpenID}, scopes...)
 	}
 
@@ -663,7 +662,7 @@ func authFail(w http.ResponseWriter, r *http.Request, realm string, delay int) {
 	}
 
 	w.Header().Set("Content-Type", "application/json")
-	common.WriteJSON(w, http.StatusUnauthorized, apiErr.NewErrorList(apiErr.NewError(apiErr.UNAUTHORIZED)))
+	zcommon.WriteJSON(w, http.StatusUnauthorized, apiErr.NewError(apiErr.UNAUTHORIZED))
 }
 
 func isAuthorizationHeaderEmpty(request *http.Request) bool {
diff --git a/pkg/api/errors/errors.go b/pkg/api/errors/errors.go
index d218e71d..cfc4adb7 100644
--- a/pkg/api/errors/errors.go
+++ b/pkg/api/errors/errors.go
@@ -5,16 +5,10 @@ import (
 )
 
 type Error struct {
-	Code        string      `json:"code"`
-	Message     string      `json:"message"`
-	Description string      `json:"description"`
-	Detail      interface{} `json:"detail,omitempty"`
-}
-
-func (e Error) WithMessage(msg string) Error {
-	e.Message = msg
-
-	return e
+	Code        string            `json:"code"`
+	Message     string            `json:"message"`
+	Description string            `json:"-"`
+	Detail      map[string]string `json:"detail"`
 }
 
 type ErrorList struct {
@@ -66,7 +60,7 @@ func (e ErrorCode) String() string {
 	return errMap[e]
 }
 
-func NewError(code ErrorCode, detail ...interface{}) Error {
+func NewError(code ErrorCode) *Error {
 	errMap := map[ErrorCode]Error{
 		BLOB_UNKNOWN: {
 			Message: "blob unknown to registry",
@@ -77,12 +71,12 @@ func NewError(code ErrorCode, detail ...interface{}) Error {
 
 		BLOB_UPLOAD_INVALID: {
 			Message:     "blob upload invalid",
-			Description: `The blob upload encountered an error and can no longer proceed.`,
+			Description: "The blob upload encountered an error and can no longer proceed.",
 		},
 
 		BLOB_UPLOAD_UNKNOWN: {
 			Message:     "blob upload unknown to registry",
-			Description: `If a blob upload has been cancelled or was never started, this error code MAY be returned.`,
+			Description: "If a blob upload has been cancelled or was never started, this error code MAY be returned.",
 		},
 
 		DIGEST_INVALID: {
@@ -94,71 +88,68 @@ func NewError(code ErrorCode, detail ...interface{}) Error {
 		},
 
 		MANIFEST_BLOB_UNKNOWN: {
-			Message: "blob unknown to registry",
-			Description: `This error MAY be returned when a manifest blob is unknown
-			to the registry.`,
+			Message:     "blob unknown to registry",
+			Description: "This error MAY be returned when a manifest blob is unknown to the registry.",
 		},
 
 		MANIFEST_INVALID: {
 			Message: "manifest invalid",
-			Description: `During upload, manifests undergo several checks ensuring
-			validity. If those checks fail, this error MAY be returned, unless a more
-			specific error is included. The detail will contain information the failed
-			validation.`,
+			Description: "During upload, manifests undergo several checks ensuring " +
+				"validity. If those checks fail, this error MAY be returned, unless a more " +
+				"specific error is included. The detail will contain information the failed validation.",
 		},
 
 		MANIFEST_UNKNOWN: {
 			Message: "manifest unknown",
-			Description: `This error is returned when the manifest, identified by name
-			and tag is unknown to the repository.`,
+			Description: "This error is returned when the manifest, identified by name " +
+				"and tag is unknown to the repository.",
 		},
 
 		MANIFEST_UNVERIFIED: {
 			Message: "manifest failed signature verification",
-			Description: `During manifest upload, if the manifest fails signature
-			verification, this error will be returned.`,
+			Description: "During manifest upload, if the manifest fails signature " +
+				"verification, this error will be returned.",
 		},
 
 		NAME_INVALID: {
 			Message: "invalid repository name",
-			Description: `Invalid repository name encountered either during manifest
-			validation or any API operation.`,
+			Description: "Invalid repository name encountered either during manifest " +
+				"validation or any API operation.",
 		},
 
 		NAME_UNKNOWN: {
 			Message:     "repository name not known to registry",
-			Description: `This is returned if the name used during an operation is unknown to the registry.`,
+			Description: "This is returned if the name used during an operation is unknown to the registry.",
 		},
 
 		SIZE_INVALID: {
 			Message: "provided length did not match content length",
-			Description: "When a layer is uploaded, the provided size will be checked against the uploaded " +
-				"content. If they do not match, this error will be returned.",
+			Description: "When a layer is uploaded, the provided size will be checked against " +
+				"the uploaded content. If they do not match, this error will be returned.",
 		},
 
 		TAG_INVALID: {
 			Message: "manifest tag did not match URI",
-			Description: `During a manifest upload, if the tag in the manifest does
-			not match the uri tag, this error will be returned.`,
+			Description: "During a manifest upload, if the tag in the manifest does " +
+				"not match the uri tag, this error will be returned.",
 		},
 
 		UNAUTHORIZED: {
 			Message: "authentication required",
-			Description: `The access controller was unable to authenticate the client.
-			Often this will be accompanied by a Www-Authenticate HTTP response header
-			indicating how to authenticate.`,
+			Description: "The access controller was unable to authenticate the client." +
+				"Often this will be accompanied by a Www-Authenticate HTTP response header " +
+				"indicating how to authenticate.",
 		},
 
 		DENIED: {
-			Message: "requested access to the resource is denied",
-			Description: `The access controller denied access for the operation on a
-			resource.`,
+			Message:     "requested access to the resource is denied",
+			Description: "The access controller denied access for the operation on a resource.",
 		},
 
 		UNSUPPORTED: {
 			Message: "The operation is unsupported.",
-			Description: `The operation was unsupported due to a missing
-			implementation or invalid set of parameters.`,
+			Description: "The operation was unsupported due to a missing " +
+				"implementation or invalid set of parameters.",
 		},
 
 		INVALID_INDEX: {
@@ -173,19 +164,24 @@ func NewError(code ErrorCode, detail ...interface{}) Error {
 	}
 
 	err.Code = code.String()
-	err.Detail = detail
+	err.Detail = map[string]string{
+		"description": err.Description,
+	}
+
+	return &err
+}
+
+func (err *Error) AddDetail(m map[string]string) *Error {
+	for k, v := range m {
+		err.Detail[k] = v
+	}
 
 	return err
 }
 
-func NewErrorList(errors ...Error) ErrorList {
-	errList := make([]*Error, 0)
-	err := Error{}
-
-	for _, e := range errors {
-		err = e
-		errList = append(errList, &err)
-	}
+func NewErrorList(errors ...*Error) ErrorList {
+	var errList []*Error
+	errList = append(errList, errors...)
 
 	return ErrorList{errList}
 }
diff --git a/pkg/api/errors/errors_test.go b/pkg/api/errors/errors_test.go
index 0a4744e3..d62a3055 100644
--- a/pkg/api/errors/errors_test.go
+++ b/pkg/api/errors/errors_test.go
@@ -10,6 +10,6 @@ import (
 
 func TestUnknownCodeError(t *testing.T) {
 	Convey("Retrieve a new error with unknown code", t, func() {
-		So(func() { _ = apiErr.NewError(123456789, nil) }, ShouldPanic)
+		So(func() { _ = apiErr.NewError(123456789) }, ShouldPanic)
 	})
 }
diff --git a/pkg/api/routes.go b/pkg/api/routes.go
index 417cf3dd..396bcc9a 100644
--- a/pkg/api/routes.go
+++ b/pkg/api/routes.go
@@ -334,8 +334,8 @@ func (rh *RouteHandler) ListTags(response http.ResponseWriter, request *http.Req
 
 	tags, err := imgStore.GetImageTags(name)
 	if err != nil {
-		zcommon.WriteJSON(response, http.StatusNotFound,
-			apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+		e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(map[string]string{"name": name})
+		zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 
 		return
 	}
@@ -422,25 +422,27 @@ func (rh *RouteHandler) CheckManifest(response http.ResponseWriter, request *htt
 
 	reference, ok := vars["reference"]
 	if !ok || reference == "" {
-		zcommon.WriteJSON(response,
-			http.StatusNotFound,
-			apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
+		e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"reference": reference})
+		zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 
 		return
 	}
 
 	content, digest, mediaType, err := getImageManifest(rh, imgStore, name, reference) //nolint:contextcheck
 	if err != nil {
+		details := zerr.GetDetails(err)
+		details["reference"] = reference
+
 		if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"reference": reference})))
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrManifestNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
+			e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
-			zcommon.WriteJSON(response, http.StatusInternalServerError,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
+			e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewErrorList(e))
 		}
 
 		return
@@ -490,24 +492,27 @@ func (rh *RouteHandler) GetManifest(response http.ResponseWriter, request *http.
 
 	reference, ok := vars["reference"]
 	if !ok || reference == "" {
-		zcommon.WriteJSON(response,
-			http.StatusNotFound,
-			apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
+		err := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(map[string]string{"reference": reference})
+		zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(err))
 
 		return
 	}
 
 	content, digest, mediaType, err := getImageManifest(rh, imgStore, name, reference) //nolint: contextcheck
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoBadVersion) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrManifestNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -655,18 +660,16 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
 
 	reference, ok := vars["reference"]
 	if !ok || reference == "" {
-		zcommon.WriteJSON(response,
-			http.StatusNotFound,
-			apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
+		err := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"reference": reference})
+		zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(err))
 
 		return
 	}
 
 	mediaType := request.Header.Get("Content-Type")
 	if !storageCommon.IsSupportedMediaType(mediaType) {
-		// response.WriteHeader(http.StatusUnsupportedMediaType)
-		zcommon.WriteJSON(response, http.StatusUnsupportedMediaType,
-			apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"mediaType": mediaType})))
+		err := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(map[string]string{"mediaType": mediaType})
+		zcommon.WriteJSON(response, http.StatusUnsupportedMediaType, apiErr.NewErrorList(err))
 
 		return
 	}
@@ -683,25 +686,31 @@ func (rh *RouteHandler) UpdateManifest(response http.ResponseWriter, request *ht
 
 	digest, subjectDigest, err := imgStore.PutImageManifest(name, reference, mediaType, body)
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrManifestNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBadManifest) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBlobNotFound) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN, map[string]string{"blob": digest.String()})))
+			details["blob"] = digest.String()
+			e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoBadVersion) {
-			zcommon.WriteJSON(response, http.StatusInternalServerError,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.INVALID_INDEX, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.INVALID_INDEX).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusInternalServerError, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrImageLintAnnotations) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(
-					apiErr.MANIFEST_INVALID, map[string]string{"reference": reference}).WithMessage(err.Error())))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else {
 			// could be syscall.EMFILE (Err:0x18 too many opened files), etc
 			rh.c.Log.Error().Err(err).Msg("unexpected error: performing cleanup")
@@ -782,15 +791,19 @@ func (rh *RouteHandler) DeleteManifest(response http.ResponseWriter, request *ht
 
 	manifestBlob, manifestDigest, mediaType, err := imgStore.GetImageManifest(name, reference)
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrManifestNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBadManifest) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.UNSUPPORTED, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.UNSUPPORTED).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -800,19 +813,24 @@ func (rh *RouteHandler) DeleteManifest(response http.ResponseWriter, request *ht
 	}
 
 	err = imgStore.DeleteImageManifest(name, reference, detectCollision)
-	if err != nil {
+	if err != nil { //nolint: dupl
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrManifestNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrManifestConflict) {
-			zcommon.WriteJSON(response, http.StatusConflict,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.MANIFEST_INVALID, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.MANIFEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusConflict, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBadManifest) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.UNSUPPORTED, map[string]string{"reference": reference})))
+			details["reference"] = reference
+			e := apiErr.NewError(apiErr.UNSUPPORTED).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -868,17 +886,19 @@ func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Re
 
 	ok, blen, err := imgStore.CheckBlob(name, digest)
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response,
-				http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBlobNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN,
-					map[string]string{"digest": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -888,8 +908,8 @@ func (rh *RouteHandler) CheckBlob(response http.ResponseWriter, request *http.Re
 	}
 
 	if !ok {
-		zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN,
-			map[string]string{"digest": digest.String()})))
+		e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(map[string]string{"digest": digest.String()})
+		zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 
 		return
 	}
@@ -1018,18 +1038,19 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
 	}
 
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response,
-				http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response,
-				http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBlobNotFound) {
-			zcommon.WriteJSON(response,
-				http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN, map[string]string{"digest": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -1087,22 +1108,23 @@ func (rh *RouteHandler) DeleteBlob(response http.ResponseWriter, request *http.R
 
 	err = imgStore.DeleteBlob(name, digest)
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response,
-				http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response,
-				http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(map[string]string{"name": name})
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBlobNotFound) {
-			zcommon.WriteJSON(response,
-				http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UNKNOWN, map[string]string{".String()": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.BLOB_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBlobReferenced) {
-			zcommon.WriteJSON(response,
-				http.StatusMethodNotAllowed,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.DENIED, map[string]string{".String()": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.DENIED).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusMethodNotAllowed, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -1154,9 +1176,11 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
 		if err != nil {
 			upload, err := imgStore.NewBlobUpload(name)
 			if err != nil {
+				details := zerr.GetDetails(err)
 				if errors.Is(err, zerr.ErrRepoNotFound) {
-					zcommon.WriteJSON(response, http.StatusNotFound,
-						apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+					details["name"] = name
+					e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+					zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 				} else {
 					rh.c.Log.Error().Err(err).Msg("unexpected error")
 					response.WriteHeader(http.StatusInternalServerError)
@@ -1211,8 +1235,15 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
 		contentLength, err := strconv.ParseInt(request.Header.Get("Content-Length"), 10, 64)
 		if err != nil || contentLength <= 0 {
 			rh.c.Log.Warn().Str("actual", request.Header.Get("Content-Length")).Msg("invalid content length")
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"digest": digest.String()})))
+			details := map[string]string{"digest": digest.String()}
+
+			if err != nil {
+				details["conversion error"] = err.Error()
+			} else {
+				details["Content-Length"] = request.Header.Get("Content-Length")
+			}
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 
 			return
 		}
@@ -1241,9 +1272,11 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
 
 	upload, err := imgStore.NewBlobUpload(name)
 	if err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -1291,18 +1324,20 @@ func (rh *RouteHandler) GetBlobUpload(response http.ResponseWriter, request *htt
 
 	size, err := imgStore.GetBlobUpload(name, sessionID)
 	if err != nil {
-		if errors.Is(err, zerr.ErrBadUploadRange) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
-		} else if errors.Is(err, zerr.ErrBadBlobDigest) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
+		details := zerr.GetDetails(err)
+		//nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
+		if errors.Is(err, zerr.ErrBadUploadRange) || errors.Is(err, zerr.ErrBadBlobDigest) {
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrUploadNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
@@ -1380,16 +1415,20 @@ func (rh *RouteHandler) PatchBlobUpload(response http.ResponseWriter, request *h
 		clen, err = imgStore.PutBlobChunk(name, sessionID, from, to, request.Body)
 	}
 
-	if err != nil {
+	if err != nil { //nolint: dupl
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrBadUploadRange) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusRequestedRangeNotSatisfiable,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusRequestedRangeNotSatisfiable, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrUploadNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			// could be io.ErrUnexpectedEOF, syscall.EMFILE (Err:0x18 too many opened files), etc
 			rh.c.Log.Error().Err(err).Msg("unexpected error: removing .uploads/ files")
@@ -1500,16 +1539,20 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
 		}
 
 		_, err = imgStore.PutBlobChunk(name, sessionID, from, to, request.Body)
-		if err != nil {
+		if err != nil { //nolint:dupl
+			details := zerr.GetDetails(err)
 			if errors.Is(err, zerr.ErrBadUploadRange) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-				zcommon.WriteJSON(response, http.StatusBadRequest,
-					apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
+				details["session_id"] = sessionID
+				e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
+				zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 			} else if errors.Is(err, zerr.ErrRepoNotFound) {
-				zcommon.WriteJSON(response, http.StatusNotFound,
-					apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+				details["name"] = name
+				e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+				zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 			} else if errors.Is(err, zerr.ErrUploadNotFound) {
-				zcommon.WriteJSON(response, http.StatusNotFound,
-					apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
+				details["session_id"] = sessionID
+				e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
+				zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 			} else {
 				// could be io.ErrUnexpectedEOF, syscall.EMFILE (Err:0x18 too many opened files), etc
 				rh.c.Log.Error().Err(err).Msg("unexpected error: removing .uploads/ files")
@@ -1528,18 +1571,23 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
 finish:
 	// blob chunks already transferred, just finish
 	if err := imgStore.FinishBlobUpload(name, sessionID, request.Body, digest); err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrBadBlobDigest) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.DIGEST_INVALID, map[string]string{"digest": digest.String()})))
+			details["digest"] = digest.String()
+			e := apiErr.NewError(apiErr.DIGEST_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrBadUploadRange) {
-			zcommon.WriteJSON(response, http.StatusBadRequest,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_INVALID).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusBadRequest, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrRepoNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrUploadNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			// could be io.ErrUnexpectedEOF, syscall.EMFILE (Err:0x18 too many opened files), etc
 			rh.c.Log.Error().Err(err).Msg("unexpected error: removing .uploads/ files")
@@ -1591,12 +1639,15 @@ func (rh *RouteHandler) DeleteBlobUpload(response http.ResponseWriter, request *
 	}
 
 	if err := imgStore.DeleteBlobUpload(name, sessionID); err != nil {
+		details := zerr.GetDetails(err)
 		if errors.Is(err, zerr.ErrRepoNotFound) { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.NAME_UNKNOWN, map[string]string{"name": name})))
+			details["name"] = name
+			e := apiErr.NewError(apiErr.NAME_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else if errors.Is(err, zerr.ErrUploadNotFound) {
-			zcommon.WriteJSON(response, http.StatusNotFound,
-				apiErr.NewErrorList(apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
+			details["session_id"] = sessionID
+			e := apiErr.NewError(apiErr.BLOB_UPLOAD_UNKNOWN).AddDetail(details)
+			zcommon.WriteJSON(response, http.StatusNotFound, apiErr.NewErrorList(e))
 		} else {
 			rh.c.Log.Error().Err(err).Msg("unexpected error")
 			response.WriteHeader(http.StatusInternalServerError)
diff --git a/pkg/extensions/extensions_lint_disabled.go b/pkg/extensions/extensions_lint_disabled.go
index d2d803d1..50b993f7 100644
--- a/pkg/extensions/extensions_lint_disabled.go
+++ b/pkg/extensions/extensions_lint_disabled.go
@@ -10,7 +10,7 @@ import (
 )
 
 func GetLinter(config *config.Config, log log.Logger) *lint.Linter {
-	log.Warn().Msg("lint extension is disabled because given zot binary doesn't" +
+	log.Warn().Msg("lint extension is disabled because given zot binary doesn't " +
 		"include this feature please build a binary that does so")
 
 	return nil
diff --git a/pkg/extensions/lint/lint.go b/pkg/extensions/lint/lint.go
index e093ef30..9150fa2b 100644
--- a/pkg/extensions/lint/lint.go
+++ b/pkg/extensions/lint/lint.go
@@ -105,7 +105,7 @@ func (linter *Linter) CheckMandatoryAnnotations(repo string, manifestDigest godi
 			string(manifestDigest), string(configDigest), missingAnnotations)
 		linter.log.Error().Msg(msg)
 
-		return false, fmt.Errorf("%s: %w", msg, zerr.ErrImageLintAnnotations)
+		return false, zerr.NewError(zerr.ErrImageLintAnnotations).AddDetail("missingAnnotations", msg)
 	}
 
 	return true, nil
diff --git a/pkg/storage/common/common.go b/pkg/storage/common/common.go
index ad5643ef..cdb44ece 100644
--- a/pkg/storage/common/common.go
+++ b/pkg/storage/common/common.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"errors"
+	"fmt"
 	"io"
 	"math/rand"
 	"path"
@@ -27,8 +28,7 @@ import (
 
 const (
 	manifestWithEmptyLayersErrMsg = "layers: Array must have at least 1 items"
-
-	cosignSignatureTagSuffix = "sig"
+	cosignSignatureTagSuffix      = "sig"
 )
 
 func GetTagsByIndex(index ispec.Index) []string {
@@ -86,7 +86,7 @@ func ValidateManifest(imgStore storageTypes.ImageStore, repo, reference, mediaTy
 		if err := ValidateManifestSchema(body); err != nil {
 			log.Error().Err(err).Msg("OCIv1 image manifest schema validation failed")
 
-			return "", zerr.ErrBadManifest
+			return "", zerr.NewError(zerr.ErrBadManifest).AddDetail("jsonSchemaValidation", err.Error())
 		}
 
 		if err := json.Unmarshal(body, &manifest); err != nil {
@@ -125,7 +125,7 @@ func ValidateManifest(imgStore storageTypes.ImageStore, repo, reference, mediaTy
 		if err := ValidateImageIndexSchema(body); err != nil {
 			log.Error().Err(err).Msg("OCIv1 image index manifest schema validation failed")
 
-			return "", err
+			return "", zerr.NewError(zerr.ErrBadManifest).AddDetail("jsonSchemaValidation", err.Error())
 		}
 
 		var indexManifest ispec.Index
@@ -219,8 +219,10 @@ func CheckIfIndexNeedsUpdate(index *ispec.Index, desc *ispec.Descriptor,
 				log.Error().Err(err).
 					Str("old mediaType", manifest.MediaType).
 					Str("new mediaType", desc.MediaType).Msg("cannot change media-type")
+				reason := fmt.Sprintf("changing manifest media-type from \"%s\" to \"%s\" is disallowed",
+					manifest.MediaType, desc.MediaType)
 
-				return false, "", err
+				return false, "", zerr.NewError(err).AddDetail("reason", reason)
 			}
 
 			oldDesc := *desc
@@ -780,7 +782,7 @@ func IsNonDistributable(mediaType string) bool {
 func ValidateManifestSchema(buf []byte) error {
 	if err := schema.ValidatorMediaTypeManifest.Validate(bytes.NewBuffer(buf)); err != nil {
 		if !IsEmptyLayersError(err) {
-			return zerr.ErrBadManifest
+			return err
 		}
 	}
 
@@ -789,7 +791,7 @@ func ValidateManifestSchema(buf []byte) error {
 
 func ValidateImageIndexSchema(buf []byte) error {
 	if err := schema.ValidatorMediaTypeImageIndex.Validate(bytes.NewBuffer(buf)); err != nil {
-		return zerr.ErrBadManifest
+		return err
 	}
 
 	return nil
diff --git a/pkg/storage/common/common_test.go b/pkg/storage/common/common_test.go
index a303ef16..315d9640 100644
--- a/pkg/storage/common/common_test.go
+++ b/pkg/storage/common/common_test.go
@@ -3,6 +3,7 @@ package storage_test
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"os"
 	"testing"
 
@@ -12,7 +13,7 @@ import (
 	"github.com/rs/zerolog"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"zotregistry.io/zot/errors"
+	zerr "zotregistry.io/zot/errors"
 	"zotregistry.io/zot/pkg/extensions/monitoring"
 	"zotregistry.io/zot/pkg/log"
 	"zotregistry.io/zot/pkg/storage"
@@ -74,6 +75,10 @@ func TestValidateManifest(t *testing.T) {
 
 			_, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body)
 			So(err, ShouldNotBeNil)
+			var internalErr *zerr.Error
+			So(errors.As(err, &internalErr), ShouldBeTrue)
+			So(internalErr.GetDetails(), ShouldContainKey, "jsonSchemaValidation")
+			So(internalErr.GetDetails()["jsonSchemaValidation"], ShouldEqual, "[schemaVersion: Must be less than or equal to 2]")
 		})
 
 		Convey("manifest with non-distributable layers", func() {
@@ -169,7 +174,7 @@ func TestGetReferrersErrors(t *testing.T) {
 					return indexBuf, nil
 				},
 				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
-					return []byte{}, errors.ErrBlobNotFound
+					return []byte{}, zerr.ErrBlobNotFound
 				},
 			}
 
@@ -188,7 +193,7 @@ func TestGetReferrersErrors(t *testing.T) {
 					return indexBuf, nil
 				},
 				GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
-					return []byte{}, errors.ErrBadBlob
+					return []byte{}, zerr.ErrBadBlob
 				},
 			}
 
@@ -363,7 +368,7 @@ func TestGetImageIndexErrors(t *testing.T) {
 	Convey("Trigger GetBlobContent error", t, func(c C) {
 		imgStore := &mocks.MockedImageStore{
 			GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
-				return []byte{}, errors.ErrBlobNotFound
+				return []byte{}, zerr.ErrBlobNotFound
 			},
 		}