diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go
index 42a62aa..637fc7f 100644
--- a/pkg/service/submissions.go
+++ b/pkg/service/submissions.go
@@ -552,6 +552,56 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
 	return nil
 }
 
+// ActionSubmissionRetryValidate invokes actionSubmissionRetryValidate operation.
+//
+// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
+//
+// POST /submissions/{SubmissionID}/status/retry-validate
+func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params api.ActionSubmissionRetryValidateParams) error {
+	userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
+	if !ok {
+		return ErrUserInfo
+	}
+
+	has_role, err := userInfo.HasRoleSubmissionReview()
+	if err != nil {
+		return err
+	}
+	// check if caller has required role
+	if !has_role {
+		return ErrPermissionDeniedNeedRoleSubmissionReview
+	}
+
+	// transaction
+	smap := datastore.Optional()
+	smap.Add("status_id", model.StatusValidating)
+	submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusAccepted}, smap)
+	if err != nil {
+		return err
+	}
+
+	validate_request := model.ValidateRequest{
+		SubmissionID:     submission.ID,
+		ModelID:          submission.AssetID,
+		ModelVersion:     submission.AssetVersion,
+		ValidatedModelID: nil,
+	}
+
+	// sentinel values because we're not using rust
+	if submission.ValidatedAssetID != 0 {
+		validate_request.ValidatedModelID = &submission.ValidatedAssetID
+	}
+
+	j, err := json.Marshal(validate_request)
+	if err != nil {
+		return err
+	}
+
+	svc.Nats.Publish("maptest.submissions.validate", []byte(j))
+
+	return nil
+}
+
 // ActionSubmissionAccepted implements actionSubmissionAccepted operation.
 //
 // Role SubmissionReview changes status from Validating -> Accepted.