diff --git a/openapi-internal.yaml b/openapi-internal.yaml
index c47f030..6a74bd4 100644
--- a/openapi-internal.yaml
+++ b/openapi-internal.yaml
@@ -9,6 +9,31 @@ tags:
   - name: Submissions
     description: Submission operations
 paths:
+  /mapfixes:
+    post:
+      summary: Create a mapfix
+      operationId: createMapfix
+      tags:
+        - Mapfixes
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/MapfixCreate'
+      responses:
+        "201":
+          description: Successful response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Id"
+        default:
+          description: General Error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
   /mapfixes/{MapfixID}/validated-model:
     post:
       summary: Update validated model
@@ -96,6 +121,55 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/Error"
+  /operations/{OperationID}/failed:
+    post:
+      summary: (Internal endpoint) Fail an operation and write a StatusMessage
+      operationId: actionOperationFailed
+      tags:
+        - Operations
+      parameters:
+        - $ref: '#/components/parameters/OperationID'
+        - name: StatusMessage
+          in: query
+          required: true
+          schema:
+            type: string
+            minLength: 0
+            maxLength: 4096
+      responses:
+        "204":
+          description: Successful response
+        default:
+          description: General Error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+  /submissions:
+    post:
+      summary: Create a new submission
+      operationId: createSubmission
+      tags:
+        - Submissions
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/SubmissionCreate'
+      responses:
+        "201":
+          description: Successful response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Id"
+        default:
+          description: General Error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
   /submissions/{SubmissionID}/validated-model:
     post:
       summary: Update validated model
@@ -358,6 +432,14 @@ components:
       schema:
         type: integer
         format: int64
+    OperationID:
+      name: OperationID
+      in: path
+      required: true
+      description: The unique identifier for a long-running operation.
+      schema:
+        type: integer
+        format: int32
     SubmissionID:
       name: SubmissionID
       in: path
@@ -400,6 +482,74 @@ components:
         ID:
           type: integer
           format: int64
+    MapfixCreate:
+      required:
+      - OperationID
+      - AssetOwner
+      - DisplayName
+      - Creator
+      - GameID
+      - AssetID
+      - AssetVersion
+      - TargetAssetID
+      type: object
+      properties:
+        OperationID:
+          type: integer
+          format: int32
+        AssetOwner:
+          type: integer
+          format: int64
+        DisplayName:
+          type: string
+          maxLength: 128
+        Creator:
+          type: string
+          maxLength: 128
+        GameID:
+          type: integer
+          format: int32
+        AssetID:
+          type: integer
+          format: int64
+        AssetVersion:
+          type: integer
+          format: int64
+        TargetAssetID:
+          type: integer
+          format: int64
+    SubmissionCreate:
+      required:
+      - OperationID
+      - AssetOwner
+      - DisplayName
+      - Creator
+      - GameID
+      - AssetID
+      - AssetVersion
+      type: object
+      properties:
+        OperationID:
+          type: integer
+          format: int32
+        AssetOwner:
+          type: integer
+          format: int64
+        DisplayName:
+          type: string
+          maxLength: 128
+        Creator:
+          type: string
+          maxLength: 128
+        GameID:
+          type: integer
+          format: int32
+        AssetID:
+          type: integer
+          format: int64
+        AssetVersion:
+          type: integer
+          format: int64
     Script:
       required:
       - ID
diff --git a/openapi.yaml b/openapi.yaml
index bb8bebc..3970378 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -10,6 +10,8 @@ tags:
     description: Mapfix operations
   - name: Maps
     description: Map queries
+  - name: Operations
+    description: Long-running operations
   - name: Session
     description: Session queries
   - name: Submissions
@@ -191,7 +193,7 @@ paths:
               schema:
                 $ref: "#/components/schemas/Error"
     post:
-      summary: Create new mapfix
+      summary: Trigger the validator to create a mapfix
       operationId: createMapfix
       tags:
         - Mapfixes
@@ -200,14 +202,14 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/MapfixCreate'
+              $ref: '#/components/schemas/MapfixTriggerCreate'
       responses:
         "201":
           description: Successful response
           content:
             application/json:
               schema:
-                $ref: "#/components/schemas/Id"
+                $ref: "#/components/schemas/OperationID"
         default:
           description: General Error
           content:
@@ -435,6 +437,27 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/Error"
+  /operations/{OperationID}:
+    get:
+      summary: Retrieve operation with ID
+      operationId: getOperation
+      tags:
+        - Operations
+      parameters:
+        - $ref: '#/components/parameters/OperationID'
+      responses:
+        "200":
+          description: Successful response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Operation"
+        default:
+          description: General Error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
   /submissions:
     get:
       summary: Get list of submissions
@@ -481,7 +504,7 @@ paths:
               schema:
                 $ref: "#/components/schemas/Error"
     post:
-      summary: Create new submission
+      summary: Trigger the validator to create a new submission
       operationId: createSubmission
       tags:
         - Submissions
@@ -490,14 +513,14 @@ paths:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/SubmissionCreate'
+              $ref: '#/components/schemas/SubmissionTriggerCreate'
       responses:
         "201":
           description: Successful response
           content:
             application/json:
               schema:
-                $ref: "#/components/schemas/Id"
+                $ref: "#/components/schemas/OperationID"
         default:
           description: General Error
           content:
@@ -1033,6 +1056,14 @@ components:
       schema:
         type: integer
         format: int64
+    OperationID:
+      name: OperationID
+      in: path
+      required: true
+      description: The unique identifier for a long-running operation.
+      schema:
+        type: integer
+        format: int32
     SubmissionID:
       name: SubmissionID
       in: path
@@ -1083,6 +1114,14 @@ components:
         ID:
           type: integer
           format: int64
+    OperationID:
+      required:
+      - OperationID
+      type: object
+      properties:
+        OperationID:
+          type: integer
+          format: int32
     Roles:
       required:
       - Roles
@@ -1186,34 +1225,46 @@ components:
         StatusMessage:
           type: string
           maxLength: 256
-    MapfixCreate:
+    MapfixTriggerCreate:
       required:
-      - DisplayName
-      - Creator
-      - GameID
       - AssetID
-      - AssetVersion
       - TargetAssetID
       type: object
       properties:
-        DisplayName:
-          type: string
-          maxLength: 128
-        Creator:
-          type: string
-          maxLength: 128
-        GameID:
-          type: integer
-          format: int32
         AssetID:
           type: integer
           format: int64
-        AssetVersion:
-          type: integer
-          format: int64
         TargetAssetID:
           type: integer
           format: int64
+    Operation:
+      required:
+      - OperationID
+      - Date
+      - Owner
+      - Status
+      - StatusMessage
+      - Path
+      type: object
+      properties:
+        OperationID:
+          type: integer
+          format: int32
+        Date:
+          type: integer
+          format: int64
+        Owner:
+          type: integer
+          format: int64
+        Status:
+          type: integer
+          format: int32
+        StatusMessage:
+          type: string
+          maxLength: 256
+        Path:
+          type: string
+          maxLength: 128
     Submission:
       required:
       - ID
@@ -1277,30 +1328,14 @@ components:
         StatusMessage:
           type: string
           maxLength: 256
-    SubmissionCreate:
+    SubmissionTriggerCreate:
       required:
-      - DisplayName
-      - Creator
-      - GameID
       - AssetID
-      - AssetVersion
       type: object
       properties:
-        DisplayName:
-          type: string
-          maxLength: 128
-        Creator:
-          type: string
-          maxLength: 128
-        GameID:
-          type: integer
-          format: int32
         AssetID:
           type: integer
           format: int64
-        AssetVersion:
-          type: integer
-          format: int64
     ReleaseInfo:
       required:
       - SubmissionID
diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go
index 714161e..3fac351 100644
--- a/pkg/api/oas_client_gen.go
+++ b/pkg/api/oas_client_gen.go
@@ -139,10 +139,10 @@ type Invoker interface {
 	ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
 	// CreateMapfix invokes createMapfix operation.
 	//
-	// Create new mapfix.
+	// Trigger the validator to create a mapfix.
 	//
 	// POST /mapfixes
-	CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error)
+	CreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (*OperationID, error)
 	// CreateScript invokes createScript operation.
 	//
 	// Create a new script.
@@ -157,10 +157,10 @@ type Invoker interface {
 	CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error)
 	// CreateSubmission invokes createSubmission operation.
 	//
-	// Create new submission.
+	// Trigger the validator to create a new submission.
 	//
 	// POST /submissions
-	CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error)
+	CreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error)
 	// DeleteScript invokes deleteScript operation.
 	//
 	// Delete the specified script by ID.
@@ -185,6 +185,12 @@ type Invoker interface {
 	//
 	// GET /mapfixes/{MapfixID}
 	GetMapfix(ctx context.Context, params GetMapfixParams) (*Mapfix, error)
+	// GetOperation invokes getOperation operation.
+	//
+	// Retrieve operation with ID.
+	//
+	// GET /operations/{OperationID}
+	GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error)
 	// GetScript invokes getScript operation.
 	//
 	// Get the specified script by ID.
@@ -2578,15 +2584,15 @@ func (c *Client) sendActionSubmissionValidated(ctx context.Context, params Actio
 
 // CreateMapfix invokes createMapfix operation.
 //
-// Create new mapfix.
+// Trigger the validator to create a mapfix.
 //
 // POST /mapfixes
-func (c *Client) CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) {
+func (c *Client) CreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (*OperationID, error) {
 	res, err := c.sendCreateMapfix(ctx, request)
 	return res, err
 }
 
-func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (res *ID, err error) {
+func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (res *OperationID, err error) {
 	otelAttrs := []attribute.KeyValue{
 		otelogen.OperationID("createMapfix"),
 		semconv.HTTPRequestMethodKey.String("POST"),
@@ -2902,15 +2908,15 @@ func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPoli
 
 // CreateSubmission invokes createSubmission operation.
 //
-// Create new submission.
+// Trigger the validator to create a new submission.
 //
 // POST /submissions
-func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) {
+func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) {
 	res, err := c.sendCreateSubmission(ctx, request)
 	return res, err
 }
 
-func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCreate) (res *ID, err error) {
+func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (res *OperationID, err error) {
 	otelAttrs := []attribute.KeyValue{
 		otelogen.OperationID("createSubmission"),
 		semconv.HTTPRequestMethodKey.String("POST"),
@@ -3434,6 +3440,129 @@ func (c *Client) sendGetMapfix(ctx context.Context, params GetMapfixParams) (res
 	return result, nil
 }
 
+// GetOperation invokes getOperation operation.
+//
+// Retrieve operation with ID.
+//
+// GET /operations/{OperationID}
+func (c *Client) GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error) {
+	res, err := c.sendGetOperation(ctx, params)
+	return res, err
+}
+
+func (c *Client) sendGetOperation(ctx context.Context, params GetOperationParams) (res *Operation, err error) {
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("getOperation"),
+		semconv.HTTPRequestMethodKey.String("GET"),
+		semconv.HTTPRouteKey.String("/operations/{OperationID}"),
+	}
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		elapsedDuration := time.Since(startTime)
+		c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
+	}()
+
+	// Increment request counter.
+	c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+
+	// Start a span for this request.
+	ctx, span := c.cfg.Tracer.Start(ctx, GetOperationOperation,
+		trace.WithAttributes(otelAttrs...),
+		clientSpanKind,
+	)
+	// Track stage for error reporting.
+	var stage string
+	defer func() {
+		if err != nil {
+			span.RecordError(err)
+			span.SetStatus(codes.Error, stage)
+			c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+		}
+		span.End()
+	}()
+
+	stage = "BuildURL"
+	u := uri.Clone(c.requestURL(ctx))
+	var pathParts [2]string
+	pathParts[0] = "/operations/"
+	{
+		// Encode "OperationID" parameter.
+		e := uri.NewPathEncoder(uri.PathEncoderConfig{
+			Param:   "OperationID",
+			Style:   uri.PathStyleSimple,
+			Explode: false,
+		})
+		if err := func() error {
+			return e.EncodeValue(conv.Int32ToString(params.OperationID))
+		}(); err != nil {
+			return res, errors.Wrap(err, "encode path")
+		}
+		encoded, err := e.Result()
+		if err != nil {
+			return res, errors.Wrap(err, "encode path")
+		}
+		pathParts[1] = encoded
+	}
+	uri.AddPathParts(u, pathParts[:]...)
+
+	stage = "EncodeRequest"
+	r, err := ht.NewRequest(ctx, "GET", u)
+	if err != nil {
+		return res, errors.Wrap(err, "create request")
+	}
+
+	{
+		type bitset = [1]uint8
+		var satisfied bitset
+		{
+			stage = "Security:CookieAuth"
+			switch err := c.securityCookieAuth(ctx, GetOperationOperation, r); {
+			case err == nil: // if NO error
+				satisfied[0] |= 1 << 0
+			case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
+				// Skip this security.
+			default:
+				return res, errors.Wrap(err, "security \"CookieAuth\"")
+			}
+		}
+
+		if ok := func() bool {
+		nextRequirement:
+			for _, requirement := range []bitset{
+				{0b00000001},
+			} {
+				for i, mask := range requirement {
+					if satisfied[i]&mask != mask {
+						continue nextRequirement
+					}
+				}
+				return true
+			}
+			return false
+		}(); !ok {
+			return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied
+		}
+	}
+
+	stage = "SendRequest"
+	resp, err := c.cfg.Client.Do(r)
+	if err != nil {
+		return res, errors.Wrap(err, "do request")
+	}
+	defer resp.Body.Close()
+
+	stage = "DecodeResponse"
+	result, err := decodeGetOperationResponse(resp)
+	if err != nil {
+		return res, errors.Wrap(err, "decode response")
+	}
+
+	return result, nil
+}
+
 // GetScript invokes getScript operation.
 //
 // Get the specified script by ID.
diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go
index 95e1799..d667263 100644
--- a/pkg/api/oas_handlers_gen.go
+++ b/pkg/api/oas_handlers_gen.go
@@ -3542,7 +3542,7 @@ func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEsca
 
 // handleCreateMapfixRequest handles createMapfix operation.
 //
-// Create new mapfix.
+// Trigger the validator to create a mapfix.
 //
 // POST /mapfixes
 func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -3676,12 +3676,12 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h
 		}
 	}()
 
-	var response *ID
+	var response *OperationID
 	if m := s.cfg.Middleware; m != nil {
 		mreq := middleware.Request{
 			Context:          ctx,
 			OperationName:    CreateMapfixOperation,
-			OperationSummary: "Create new mapfix",
+			OperationSummary: "Trigger the validator to create a mapfix",
 			OperationID:      "createMapfix",
 			Body:             request,
 			Params:           middleware.Parameters{},
@@ -3689,9 +3689,9 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h
 		}
 
 		type (
-			Request  = *MapfixCreate
+			Request  = *MapfixTriggerCreate
 			Params   = struct{}
-			Response = *ID
+			Response = *OperationID
 		)
 		response, err = middleware.HookMiddleware[
 			Request,
@@ -4127,7 +4127,7 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo
 
 // handleCreateSubmissionRequest handles createSubmission operation.
 //
-// Create new submission.
+// Trigger the validator to create a new submission.
 //
 // POST /submissions
 func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
@@ -4261,12 +4261,12 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool,
 		}
 	}()
 
-	var response *ID
+	var response *OperationID
 	if m := s.cfg.Middleware; m != nil {
 		mreq := middleware.Request{
 			Context:          ctx,
 			OperationName:    CreateSubmissionOperation,
-			OperationSummary: "Create new submission",
+			OperationSummary: "Trigger the validator to create a new submission",
 			OperationID:      "createSubmission",
 			Body:             request,
 			Params:           middleware.Parameters{},
@@ -4274,9 +4274,9 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool,
 		}
 
 		type (
-			Request  = *SubmissionCreate
+			Request  = *SubmissionTriggerCreate
 			Params   = struct{}
-			Response = *ID
+			Response = *OperationID
 		)
 		response, err = middleware.HookMiddleware[
 			Request,
@@ -5008,6 +5008,201 @@ func (s *Server) handleGetMapfixRequest(args [1]string, argsEscaped bool, w http
 	}
 }
 
+// handleGetOperationRequest handles getOperation operation.
+//
+// Retrieve operation with ID.
+//
+// GET /operations/{OperationID}
+func (s *Server) handleGetOperationRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
+	statusWriter := &codeRecorder{ResponseWriter: w}
+	w = statusWriter
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("getOperation"),
+		semconv.HTTPRequestMethodKey.String("GET"),
+		semconv.HTTPRouteKey.String("/operations/{OperationID}"),
+	}
+
+	// Start a span for this request.
+	ctx, span := s.cfg.Tracer.Start(r.Context(), GetOperationOperation,
+		trace.WithAttributes(otelAttrs...),
+		serverSpanKind,
+	)
+	defer span.End()
+
+	// Add Labeler to context.
+	labeler := &Labeler{attrs: otelAttrs}
+	ctx = contextWithLabeler(ctx, labeler)
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		elapsedDuration := time.Since(startTime)
+
+		attrSet := labeler.AttributeSet()
+		attrs := attrSet.ToSlice()
+		code := statusWriter.status
+		if code != 0 {
+			codeAttr := semconv.HTTPResponseStatusCode(code)
+			attrs = append(attrs, codeAttr)
+			span.SetAttributes(codeAttr)
+		}
+		attrOpt := metric.WithAttributes(attrs...)
+
+		// Increment request counter.
+		s.requests.Add(ctx, 1, attrOpt)
+
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
+	}()
+
+	var (
+		recordError = func(stage string, err error) {
+			span.RecordError(err)
+
+			// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
+			// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
+			// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
+			// max redirects exceeded), in which case status MUST be set to Error.
+			code := statusWriter.status
+			if code >= 100 && code < 500 {
+				span.SetStatus(codes.Error, stage)
+			}
+
+			attrSet := labeler.AttributeSet()
+			attrs := attrSet.ToSlice()
+			if code != 0 {
+				attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
+			}
+
+			s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
+		}
+		err          error
+		opErrContext = ogenerrors.OperationContext{
+			Name: GetOperationOperation,
+			ID:   "getOperation",
+		}
+	)
+	{
+		type bitset = [1]uint8
+		var satisfied bitset
+		{
+			sctx, ok, err := s.securityCookieAuth(ctx, GetOperationOperation, r)
+			if err != nil {
+				err = &ogenerrors.SecurityError{
+					OperationContext: opErrContext,
+					Security:         "CookieAuth",
+					Err:              err,
+				}
+				if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil {
+					defer recordError("Security:CookieAuth", err)
+				}
+				return
+			}
+			if ok {
+				satisfied[0] |= 1 << 0
+				ctx = sctx
+			}
+		}
+
+		if ok := func() bool {
+		nextRequirement:
+			for _, requirement := range []bitset{
+				{0b00000001},
+			} {
+				for i, mask := range requirement {
+					if satisfied[i]&mask != mask {
+						continue nextRequirement
+					}
+				}
+				return true
+			}
+			return false
+		}(); !ok {
+			err = &ogenerrors.SecurityError{
+				OperationContext: opErrContext,
+				Err:              ogenerrors.ErrSecurityRequirementIsNotSatisfied,
+			}
+			if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil {
+				defer recordError("Security", err)
+			}
+			return
+		}
+	}
+	params, err := decodeGetOperationParams(args, argsEscaped, r)
+	if err != nil {
+		err = &ogenerrors.DecodeParamsError{
+			OperationContext: opErrContext,
+			Err:              err,
+		}
+		defer recordError("DecodeParams", err)
+		s.cfg.ErrorHandler(ctx, w, r, err)
+		return
+	}
+
+	var response *Operation
+	if m := s.cfg.Middleware; m != nil {
+		mreq := middleware.Request{
+			Context:          ctx,
+			OperationName:    GetOperationOperation,
+			OperationSummary: "Retrieve operation with ID",
+			OperationID:      "getOperation",
+			Body:             nil,
+			Params: middleware.Parameters{
+				{
+					Name: "OperationID",
+					In:   "path",
+				}: params.OperationID,
+			},
+			Raw: r,
+		}
+
+		type (
+			Request  = struct{}
+			Params   = GetOperationParams
+			Response = *Operation
+		)
+		response, err = middleware.HookMiddleware[
+			Request,
+			Params,
+			Response,
+		](
+			m,
+			mreq,
+			unpackGetOperationParams,
+			func(ctx context.Context, request Request, params Params) (response Response, err error) {
+				response, err = s.h.GetOperation(ctx, params)
+				return response, err
+			},
+		)
+	} else {
+		response, err = s.h.GetOperation(ctx, params)
+	}
+	if err != nil {
+		if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
+			if err := encodeErrorResponse(errRes, w, span); err != nil {
+				defer recordError("Internal", err)
+			}
+			return
+		}
+		if errors.Is(err, ht.ErrNotImplemented) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+			return
+		}
+		if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
+			defer recordError("Internal", err)
+		}
+		return
+	}
+
+	if err := encodeGetOperationResponse(response, w, span); err != nil {
+		defer recordError("EncodeResponse", err)
+		if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+		}
+		return
+	}
+}
+
 // handleGetScriptRequest handles getScript operation.
 //
 // Get the specified script by ID.
diff --git a/pkg/api/oas_json_gen.go b/pkg/api/oas_json_gen.go
index 9061475..6833f15 100644
--- a/pkg/api/oas_json_gen.go
+++ b/pkg/api/oas_json_gen.go
@@ -688,96 +688,40 @@ func (s *Mapfix) UnmarshalJSON(data []byte) error {
 }
 
 // Encode implements json.Marshaler.
-func (s *MapfixCreate) Encode(e *jx.Encoder) {
+func (s *MapfixTriggerCreate) Encode(e *jx.Encoder) {
 	e.ObjStart()
 	s.encodeFields(e)
 	e.ObjEnd()
 }
 
 // encodeFields encodes fields.
-func (s *MapfixCreate) encodeFields(e *jx.Encoder) {
-	{
-		e.FieldStart("DisplayName")
-		e.Str(s.DisplayName)
-	}
-	{
-		e.FieldStart("Creator")
-		e.Str(s.Creator)
-	}
-	{
-		e.FieldStart("GameID")
-		e.Int32(s.GameID)
-	}
+func (s *MapfixTriggerCreate) encodeFields(e *jx.Encoder) {
 	{
 		e.FieldStart("AssetID")
 		e.Int64(s.AssetID)
 	}
-	{
-		e.FieldStart("AssetVersion")
-		e.Int64(s.AssetVersion)
-	}
 	{
 		e.FieldStart("TargetAssetID")
 		e.Int64(s.TargetAssetID)
 	}
 }
 
-var jsonFieldsNameOfMapfixCreate = [6]string{
-	0: "DisplayName",
-	1: "Creator",
-	2: "GameID",
-	3: "AssetID",
-	4: "AssetVersion",
-	5: "TargetAssetID",
+var jsonFieldsNameOfMapfixTriggerCreate = [2]string{
+	0: "AssetID",
+	1: "TargetAssetID",
 }
 
-// Decode decodes MapfixCreate from json.
-func (s *MapfixCreate) Decode(d *jx.Decoder) error {
+// Decode decodes MapfixTriggerCreate from json.
+func (s *MapfixTriggerCreate) Decode(d *jx.Decoder) error {
 	if s == nil {
-		return errors.New("invalid: unable to decode MapfixCreate to nil")
+		return errors.New("invalid: unable to decode MapfixTriggerCreate to nil")
 	}
 	var requiredBitSet [1]uint8
 
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch string(k) {
-		case "DisplayName":
-			requiredBitSet[0] |= 1 << 0
-			if err := func() error {
-				v, err := d.Str()
-				s.DisplayName = string(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"DisplayName\"")
-			}
-		case "Creator":
-			requiredBitSet[0] |= 1 << 1
-			if err := func() error {
-				v, err := d.Str()
-				s.Creator = string(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"Creator\"")
-			}
-		case "GameID":
-			requiredBitSet[0] |= 1 << 2
-			if err := func() error {
-				v, err := d.Int32()
-				s.GameID = int32(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"GameID\"")
-			}
 		case "AssetID":
-			requiredBitSet[0] |= 1 << 3
+			requiredBitSet[0] |= 1 << 0
 			if err := func() error {
 				v, err := d.Int64()
 				s.AssetID = int64(v)
@@ -788,20 +732,8 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
 			}(); err != nil {
 				return errors.Wrap(err, "decode field \"AssetID\"")
 			}
-		case "AssetVersion":
-			requiredBitSet[0] |= 1 << 4
-			if err := func() error {
-				v, err := d.Int64()
-				s.AssetVersion = int64(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"AssetVersion\"")
-			}
 		case "TargetAssetID":
-			requiredBitSet[0] |= 1 << 5
+			requiredBitSet[0] |= 1 << 1
 			if err := func() error {
 				v, err := d.Int64()
 				s.TargetAssetID = int64(v)
@@ -817,12 +749,12 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
 		}
 		return nil
 	}); err != nil {
-		return errors.Wrap(err, "decode MapfixCreate")
+		return errors.Wrap(err, "decode MapfixTriggerCreate")
 	}
 	// Validate required fields.
 	var failures []validate.FieldError
 	for i, mask := range [1]uint8{
-		0b00111111,
+		0b00000011,
 	} {
 		if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
 			// Mask only required fields and check equality to mask using XOR.
@@ -834,8 +766,8 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
 				bitIdx := bits.TrailingZeros8(result)
 				fieldIdx := i*8 + bitIdx
 				var name string
-				if fieldIdx < len(jsonFieldsNameOfMapfixCreate) {
-					name = jsonFieldsNameOfMapfixCreate[fieldIdx]
+				if fieldIdx < len(jsonFieldsNameOfMapfixTriggerCreate) {
+					name = jsonFieldsNameOfMapfixTriggerCreate[fieldIdx]
 				} else {
 					name = strconv.Itoa(fieldIdx)
 				}
@@ -856,14 +788,291 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
 }
 
 // MarshalJSON implements stdjson.Marshaler.
-func (s *MapfixCreate) MarshalJSON() ([]byte, error) {
+func (s *MapfixTriggerCreate) MarshalJSON() ([]byte, error) {
 	e := jx.Encoder{}
 	s.Encode(&e)
 	return e.Bytes(), nil
 }
 
 // UnmarshalJSON implements stdjson.Unmarshaler.
-func (s *MapfixCreate) UnmarshalJSON(data []byte) error {
+func (s *MapfixTriggerCreate) UnmarshalJSON(data []byte) error {
+	d := jx.DecodeBytes(data)
+	return s.Decode(d)
+}
+
+// Encode implements json.Marshaler.
+func (s *Operation) Encode(e *jx.Encoder) {
+	e.ObjStart()
+	s.encodeFields(e)
+	e.ObjEnd()
+}
+
+// encodeFields encodes fields.
+func (s *Operation) encodeFields(e *jx.Encoder) {
+	{
+		e.FieldStart("OperationID")
+		e.Int32(s.OperationID)
+	}
+	{
+		e.FieldStart("Date")
+		e.Int64(s.Date)
+	}
+	{
+		e.FieldStart("Owner")
+		e.Int64(s.Owner)
+	}
+	{
+		e.FieldStart("Status")
+		e.Int32(s.Status)
+	}
+	{
+		e.FieldStart("StatusMessage")
+		e.Str(s.StatusMessage)
+	}
+	{
+		e.FieldStart("Path")
+		e.Str(s.Path)
+	}
+}
+
+var jsonFieldsNameOfOperation = [6]string{
+	0: "OperationID",
+	1: "Date",
+	2: "Owner",
+	3: "Status",
+	4: "StatusMessage",
+	5: "Path",
+}
+
+// Decode decodes Operation from json.
+func (s *Operation) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode Operation to nil")
+	}
+	var requiredBitSet [1]uint8
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		case "OperationID":
+			requiredBitSet[0] |= 1 << 0
+			if err := func() error {
+				v, err := d.Int32()
+				s.OperationID = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"OperationID\"")
+			}
+		case "Date":
+			requiredBitSet[0] |= 1 << 1
+			if err := func() error {
+				v, err := d.Int64()
+				s.Date = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"Date\"")
+			}
+		case "Owner":
+			requiredBitSet[0] |= 1 << 2
+			if err := func() error {
+				v, err := d.Int64()
+				s.Owner = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"Owner\"")
+			}
+		case "Status":
+			requiredBitSet[0] |= 1 << 3
+			if err := func() error {
+				v, err := d.Int32()
+				s.Status = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"Status\"")
+			}
+		case "StatusMessage":
+			requiredBitSet[0] |= 1 << 4
+			if err := func() error {
+				v, err := d.Str()
+				s.StatusMessage = string(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"StatusMessage\"")
+			}
+		case "Path":
+			requiredBitSet[0] |= 1 << 5
+			if err := func() error {
+				v, err := d.Str()
+				s.Path = string(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"Path\"")
+			}
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode Operation")
+	}
+	// Validate required fields.
+	var failures []validate.FieldError
+	for i, mask := range [1]uint8{
+		0b00111111,
+	} {
+		if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
+			// Mask only required fields and check equality to mask using XOR.
+			//
+			// If XOR result is not zero, result is not equal to expected, so some fields are missed.
+			// Bits of fields which would be set are actually bits of missed fields.
+			missed := bits.OnesCount8(result)
+			for bitN := 0; bitN < missed; bitN++ {
+				bitIdx := bits.TrailingZeros8(result)
+				fieldIdx := i*8 + bitIdx
+				var name string
+				if fieldIdx < len(jsonFieldsNameOfOperation) {
+					name = jsonFieldsNameOfOperation[fieldIdx]
+				} else {
+					name = strconv.Itoa(fieldIdx)
+				}
+				failures = append(failures, validate.FieldError{
+					Name:  name,
+					Error: validate.ErrFieldRequired,
+				})
+				// Reset bit.
+				result &^= 1 << bitIdx
+			}
+		}
+	}
+	if len(failures) > 0 {
+		return &validate.Error{Fields: failures}
+	}
+
+	return nil
+}
+
+// MarshalJSON implements stdjson.Marshaler.
+func (s *Operation) MarshalJSON() ([]byte, error) {
+	e := jx.Encoder{}
+	s.Encode(&e)
+	return e.Bytes(), nil
+}
+
+// UnmarshalJSON implements stdjson.Unmarshaler.
+func (s *Operation) UnmarshalJSON(data []byte) error {
+	d := jx.DecodeBytes(data)
+	return s.Decode(d)
+}
+
+// Encode implements json.Marshaler.
+func (s *OperationID) Encode(e *jx.Encoder) {
+	e.ObjStart()
+	s.encodeFields(e)
+	e.ObjEnd()
+}
+
+// encodeFields encodes fields.
+func (s *OperationID) encodeFields(e *jx.Encoder) {
+	{
+		e.FieldStart("OperationID")
+		e.Int32(s.OperationID)
+	}
+}
+
+var jsonFieldsNameOfOperationID = [1]string{
+	0: "OperationID",
+}
+
+// Decode decodes OperationID from json.
+func (s *OperationID) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode OperationID to nil")
+	}
+	var requiredBitSet [1]uint8
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		case "OperationID":
+			requiredBitSet[0] |= 1 << 0
+			if err := func() error {
+				v, err := d.Int32()
+				s.OperationID = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"OperationID\"")
+			}
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode OperationID")
+	}
+	// Validate required fields.
+	var failures []validate.FieldError
+	for i, mask := range [1]uint8{
+		0b00000001,
+	} {
+		if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
+			// Mask only required fields and check equality to mask using XOR.
+			//
+			// If XOR result is not zero, result is not equal to expected, so some fields are missed.
+			// Bits of fields which would be set are actually bits of missed fields.
+			missed := bits.OnesCount8(result)
+			for bitN := 0; bitN < missed; bitN++ {
+				bitIdx := bits.TrailingZeros8(result)
+				fieldIdx := i*8 + bitIdx
+				var name string
+				if fieldIdx < len(jsonFieldsNameOfOperationID) {
+					name = jsonFieldsNameOfOperationID[fieldIdx]
+				} else {
+					name = strconv.Itoa(fieldIdx)
+				}
+				failures = append(failures, validate.FieldError{
+					Name:  name,
+					Error: validate.ErrFieldRequired,
+				})
+				// Reset bit.
+				result &^= 1 << bitIdx
+			}
+		}
+	}
+	if len(failures) > 0 {
+		return &validate.Error{Fields: failures}
+	}
+
+	return nil
+}
+
+// MarshalJSON implements stdjson.Marshaler.
+func (s *OperationID) MarshalJSON() ([]byte, error) {
+	e := jx.Encoder{}
+	s.Encode(&e)
+	return e.Bytes(), nil
+}
+
+// UnmarshalJSON implements stdjson.Unmarshaler.
+func (s *OperationID) UnmarshalJSON(data []byte) error {
 	d := jx.DecodeBytes(data)
 	return s.Decode(d)
 }
@@ -2434,91 +2643,35 @@ func (s *Submission) UnmarshalJSON(data []byte) error {
 }
 
 // Encode implements json.Marshaler.
-func (s *SubmissionCreate) Encode(e *jx.Encoder) {
+func (s *SubmissionTriggerCreate) Encode(e *jx.Encoder) {
 	e.ObjStart()
 	s.encodeFields(e)
 	e.ObjEnd()
 }
 
 // encodeFields encodes fields.
-func (s *SubmissionCreate) encodeFields(e *jx.Encoder) {
-	{
-		e.FieldStart("DisplayName")
-		e.Str(s.DisplayName)
-	}
-	{
-		e.FieldStart("Creator")
-		e.Str(s.Creator)
-	}
-	{
-		e.FieldStart("GameID")
-		e.Int32(s.GameID)
-	}
+func (s *SubmissionTriggerCreate) encodeFields(e *jx.Encoder) {
 	{
 		e.FieldStart("AssetID")
 		e.Int64(s.AssetID)
 	}
-	{
-		e.FieldStart("AssetVersion")
-		e.Int64(s.AssetVersion)
-	}
 }
 
-var jsonFieldsNameOfSubmissionCreate = [5]string{
-	0: "DisplayName",
-	1: "Creator",
-	2: "GameID",
-	3: "AssetID",
-	4: "AssetVersion",
+var jsonFieldsNameOfSubmissionTriggerCreate = [1]string{
+	0: "AssetID",
 }
 
-// Decode decodes SubmissionCreate from json.
-func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
+// Decode decodes SubmissionTriggerCreate from json.
+func (s *SubmissionTriggerCreate) Decode(d *jx.Decoder) error {
 	if s == nil {
-		return errors.New("invalid: unable to decode SubmissionCreate to nil")
+		return errors.New("invalid: unable to decode SubmissionTriggerCreate to nil")
 	}
 	var requiredBitSet [1]uint8
 
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch string(k) {
-		case "DisplayName":
-			requiredBitSet[0] |= 1 << 0
-			if err := func() error {
-				v, err := d.Str()
-				s.DisplayName = string(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"DisplayName\"")
-			}
-		case "Creator":
-			requiredBitSet[0] |= 1 << 1
-			if err := func() error {
-				v, err := d.Str()
-				s.Creator = string(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"Creator\"")
-			}
-		case "GameID":
-			requiredBitSet[0] |= 1 << 2
-			if err := func() error {
-				v, err := d.Int32()
-				s.GameID = int32(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"GameID\"")
-			}
 		case "AssetID":
-			requiredBitSet[0] |= 1 << 3
+			requiredBitSet[0] |= 1 << 0
 			if err := func() error {
 				v, err := d.Int64()
 				s.AssetID = int64(v)
@@ -2529,29 +2682,17 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
 			}(); err != nil {
 				return errors.Wrap(err, "decode field \"AssetID\"")
 			}
-		case "AssetVersion":
-			requiredBitSet[0] |= 1 << 4
-			if err := func() error {
-				v, err := d.Int64()
-				s.AssetVersion = int64(v)
-				if err != nil {
-					return err
-				}
-				return nil
-			}(); err != nil {
-				return errors.Wrap(err, "decode field \"AssetVersion\"")
-			}
 		default:
 			return d.Skip()
 		}
 		return nil
 	}); err != nil {
-		return errors.Wrap(err, "decode SubmissionCreate")
+		return errors.Wrap(err, "decode SubmissionTriggerCreate")
 	}
 	// Validate required fields.
 	var failures []validate.FieldError
 	for i, mask := range [1]uint8{
-		0b00011111,
+		0b00000001,
 	} {
 		if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
 			// Mask only required fields and check equality to mask using XOR.
@@ -2563,8 +2704,8 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
 				bitIdx := bits.TrailingZeros8(result)
 				fieldIdx := i*8 + bitIdx
 				var name string
-				if fieldIdx < len(jsonFieldsNameOfSubmissionCreate) {
-					name = jsonFieldsNameOfSubmissionCreate[fieldIdx]
+				if fieldIdx < len(jsonFieldsNameOfSubmissionTriggerCreate) {
+					name = jsonFieldsNameOfSubmissionTriggerCreate[fieldIdx]
 				} else {
 					name = strconv.Itoa(fieldIdx)
 				}
@@ -2585,14 +2726,14 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
 }
 
 // MarshalJSON implements stdjson.Marshaler.
-func (s *SubmissionCreate) MarshalJSON() ([]byte, error) {
+func (s *SubmissionTriggerCreate) MarshalJSON() ([]byte, error) {
 	e := jx.Encoder{}
 	s.Encode(&e)
 	return e.Bytes(), nil
 }
 
 // UnmarshalJSON implements stdjson.Unmarshaler.
-func (s *SubmissionCreate) UnmarshalJSON(data []byte) error {
+func (s *SubmissionTriggerCreate) UnmarshalJSON(data []byte) error {
 	d := jx.DecodeBytes(data)
 	return s.Decode(d)
 }
diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go
index f2235e9..9806eb1 100644
--- a/pkg/api/oas_operations_gen.go
+++ b/pkg/api/oas_operations_gen.go
@@ -32,6 +32,7 @@ const (
 	DeleteScriptPolicyOperation              OperationName = "DeleteScriptPolicy"
 	GetMapOperation                          OperationName = "GetMap"
 	GetMapfixOperation                       OperationName = "GetMapfix"
+	GetOperationOperation                    OperationName = "GetOperation"
 	GetScriptOperation                       OperationName = "GetScript"
 	GetScriptPolicyOperation                 OperationName = "GetScriptPolicy"
 	GetSubmissionOperation                   OperationName = "GetSubmission"
diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go
index 6ea7cb4..b2cb6ee 100644
--- a/pkg/api/oas_parameters_gen.go
+++ b/pkg/api/oas_parameters_gen.go
@@ -1467,6 +1467,72 @@ func decodeGetMapfixParams(args [1]string, argsEscaped bool, r *http.Request) (p
 	return params, nil
 }
 
+// GetOperationParams is parameters of getOperation operation.
+type GetOperationParams struct {
+	// The unique identifier for a long-running operation.
+	OperationID int32
+}
+
+func unpackGetOperationParams(packed middleware.Parameters) (params GetOperationParams) {
+	{
+		key := middleware.ParameterKey{
+			Name: "OperationID",
+			In:   "path",
+		}
+		params.OperationID = packed[key].(int32)
+	}
+	return params
+}
+
+func decodeGetOperationParams(args [1]string, argsEscaped bool, r *http.Request) (params GetOperationParams, _ error) {
+	// Decode path: OperationID.
+	if err := func() error {
+		param := args[0]
+		if argsEscaped {
+			unescaped, err := url.PathUnescape(args[0])
+			if err != nil {
+				return errors.Wrap(err, "unescape path")
+			}
+			param = unescaped
+		}
+		if len(param) > 0 {
+			d := uri.NewPathDecoder(uri.PathDecoderConfig{
+				Param:   "OperationID",
+				Value:   param,
+				Style:   uri.PathStyleSimple,
+				Explode: false,
+			})
+
+			if err := func() error {
+				val, err := d.DecodeValue()
+				if err != nil {
+					return err
+				}
+
+				c, err := conv.ToInt32(val)
+				if err != nil {
+					return err
+				}
+
+				params.OperationID = c
+				return nil
+			}(); err != nil {
+				return err
+			}
+		} else {
+			return validate.ErrFieldRequired
+		}
+		return nil
+	}(); err != nil {
+		return params, &ogenerrors.DecodeParamError{
+			Name: "OperationID",
+			In:   "path",
+			Err:  err,
+		}
+	}
+	return params, nil
+}
+
 // GetScriptParams is parameters of getScript operation.
 type GetScriptParams struct {
 	// The unique identifier for a script.
diff --git a/pkg/api/oas_request_decoders_gen.go b/pkg/api/oas_request_decoders_gen.go
index da76dc0..1fa2247 100644
--- a/pkg/api/oas_request_decoders_gen.go
+++ b/pkg/api/oas_request_decoders_gen.go
@@ -16,7 +16,7 @@ import (
 )
 
 func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
-	req *MapfixCreate,
+	req *MapfixTriggerCreate,
 	close func() error,
 	rerr error,
 ) {
@@ -55,7 +55,7 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
 
 		d := jx.DecodeBytes(buf)
 
-		var request MapfixCreate
+		var request MapfixTriggerCreate
 		if err := func() error {
 			if err := request.Decode(d); err != nil {
 				return err
@@ -72,14 +72,6 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
 			}
 			return req, close, err
 		}
-		if err := func() error {
-			if err := request.Validate(); err != nil {
-				return err
-			}
-			return nil
-		}(); err != nil {
-			return req, close, errors.Wrap(err, "validate")
-		}
 		return &request, close, nil
 	default:
 		return req, close, validate.InvalidContentType(ct)
@@ -221,7 +213,7 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
 }
 
 func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
-	req *SubmissionCreate,
+	req *SubmissionTriggerCreate,
 	close func() error,
 	rerr error,
 ) {
@@ -260,7 +252,7 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
 
 		d := jx.DecodeBytes(buf)
 
-		var request SubmissionCreate
+		var request SubmissionTriggerCreate
 		if err := func() error {
 			if err := request.Decode(d); err != nil {
 				return err
@@ -277,14 +269,6 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
 			}
 			return req, close, err
 		}
-		if err := func() error {
-			if err := request.Validate(); err != nil {
-				return err
-			}
-			return nil
-		}(); err != nil {
-			return req, close, errors.Wrap(err, "validate")
-		}
 		return &request, close, nil
 	default:
 		return req, close, validate.InvalidContentType(ct)
diff --git a/pkg/api/oas_request_encoders_gen.go b/pkg/api/oas_request_encoders_gen.go
index b8686b0..52f1046 100644
--- a/pkg/api/oas_request_encoders_gen.go
+++ b/pkg/api/oas_request_encoders_gen.go
@@ -12,7 +12,7 @@ import (
 )
 
 func encodeCreateMapfixRequest(
-	req *MapfixCreate,
+	req *MapfixTriggerCreate,
 	r *http.Request,
 ) error {
 	const contentType = "application/json"
@@ -54,7 +54,7 @@ func encodeCreateScriptPolicyRequest(
 }
 
 func encodeCreateSubmissionRequest(
-	req *SubmissionCreate,
+	req *SubmissionTriggerCreate,
 	r *http.Request,
 ) error {
 	const contentType = "application/json"
diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go
index 6a6b258..197eacd 100644
--- a/pkg/api/oas_response_decoders_gen.go
+++ b/pkg/api/oas_response_decoders_gen.go
@@ -933,7 +933,7 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu
 	return res, errors.Wrap(defRes, "error")
 }
 
-func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) {
+func decodeCreateMapfixResponse(resp *http.Response) (res *OperationID, _ error) {
 	switch resp.StatusCode {
 	case 201:
 		// Code 201.
@@ -949,7 +949,7 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) {
 			}
 			d := jx.DecodeBytes(buf)
 
-			var response ID
+			var response OperationID
 			if err := func() error {
 				if err := response.Decode(d); err != nil {
 					return err
@@ -1182,7 +1182,7 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) {
 	return res, errors.Wrap(defRes, "error")
 }
 
-func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) {
+func decodeCreateSubmissionResponse(resp *http.Response) (res *OperationID, _ error) {
 	switch resp.StatusCode {
 	case 201:
 		// Code 201.
@@ -1198,7 +1198,7 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) {
 			}
 			d := jx.DecodeBytes(buf)
 
-			var response ID
+			var response OperationID
 			if err := func() error {
 				if err := response.Decode(d); err != nil {
 					return err
@@ -1551,6 +1551,98 @@ func decodeGetMapfixResponse(resp *http.Response) (res *Mapfix, _ error) {
 	return res, errors.Wrap(defRes, "error")
 }
 
+func decodeGetOperationResponse(resp *http.Response) (res *Operation, _ error) {
+	switch resp.StatusCode {
+	case 200:
+		// Code 200.
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response Operation
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			// Validate response.
+			if err := func() error {
+				if err := response.Validate(); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return res, errors.Wrap(err, "validate")
+			}
+			return &response, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}
+	// Convenient error response.
+	defRes, err := func() (res *ErrorStatusCode, err error) {
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response Error
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			return &ErrorStatusCode{
+				StatusCode: resp.StatusCode,
+				Response:   response,
+			}, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}()
+	if err != nil {
+		return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
+	}
+	return res, errors.Wrap(defRes, "error")
+}
+
 func decodeGetScriptResponse(resp *http.Response) (res *Script, _ error) {
 	switch resp.StatusCode {
 	case 200:
diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go
index ba6c549..58f61b3 100644
--- a/pkg/api/oas_response_encoders_gen.go
+++ b/pkg/api/oas_response_encoders_gen.go
@@ -139,7 +139,7 @@ func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidated
 	return nil
 }
 
-func encodeCreateMapfixResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
+func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 	w.WriteHeader(201)
 	span.SetStatus(codes.Ok, http.StatusText(201))
@@ -181,7 +181,7 @@ func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span
 	return nil
 }
 
-func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
+func encodeCreateSubmissionResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error {
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 	w.WriteHeader(201)
 	span.SetStatus(codes.Ok, http.StatusText(201))
@@ -237,6 +237,20 @@ func encodeGetMapfixResponse(response *Mapfix, w http.ResponseWriter, span trace
 	return nil
 }
 
+func encodeGetOperationResponse(response *Operation, w http.ResponseWriter, span trace.Span) error {
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	w.WriteHeader(200)
+	span.SetStatus(codes.Ok, http.StatusText(200))
+
+	e := new(jx.Encoder)
+	response.Encode(e)
+	if _, err := e.WriteTo(w); err != nil {
+		return errors.Wrap(err, "write")
+	}
+
+	return nil
+}
+
 func encodeGetScriptResponse(response *Script, w http.ResponseWriter, span trace.Span) error {
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 	w.WriteHeader(200)
diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go
index 6ebc1ef..414d7e4 100644
--- a/pkg/api/oas_router_gen.go
+++ b/pkg/api/oas_router_gen.go
@@ -494,6 +494,37 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 				}
 
+			case 'o': // Prefix: "operations/"
+
+				if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" {
+					elem = elem[l:]
+				} else {
+					break
+				}
+
+				// Param: "OperationID"
+				// Leaf parameter, slashes are prohibited
+				idx := strings.IndexByte(elem, '/')
+				if idx >= 0 {
+					break
+				}
+				args[0] = elem
+				elem = ""
+
+				if len(elem) == 0 {
+					// Leaf node.
+					switch r.Method {
+					case "GET":
+						s.handleGetOperationRequest([1]string{
+							args[0],
+						}, elemIsEscaped, w, r)
+					default:
+						s.notAllowed(w, r, "GET")
+					}
+
+					return
+				}
+
 			case 'r': // Prefix: "release-submissions"
 
 				if l := len("release-submissions"); len(elem) >= l && elem[0:l] == "release-submissions" {
@@ -1233,7 +1264,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 							return r, true
 						case "POST":
 							r.name = CreateMapfixOperation
-							r.summary = "Create new mapfix"
+							r.summary = "Trigger the validator to create a mapfix"
 							r.operationID = "createMapfix"
 							r.pathPattern = "/mapfixes"
 							r.args = args
@@ -1674,6 +1705,39 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 
 				}
 
+			case 'o': // Prefix: "operations/"
+
+				if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" {
+					elem = elem[l:]
+				} else {
+					break
+				}
+
+				// Param: "OperationID"
+				// Leaf parameter, slashes are prohibited
+				idx := strings.IndexByte(elem, '/')
+				if idx >= 0 {
+					break
+				}
+				args[0] = elem
+				elem = ""
+
+				if len(elem) == 0 {
+					// Leaf node.
+					switch method {
+					case "GET":
+						r.name = GetOperationOperation
+						r.summary = "Retrieve operation with ID"
+						r.operationID = "getOperation"
+						r.pathPattern = "/operations/{OperationID}"
+						r.args = args
+						r.count = 1
+						return r, true
+					default:
+						return
+					}
+				}
+
 			case 'r': // Prefix: "release-submissions"
 
 				if l := len("release-submissions"); len(elem) >= l && elem[0:l] == "release-submissions" {
@@ -1994,7 +2058,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 							return r, true
 						case "POST":
 							r.name = CreateSubmissionOperation
-							r.summary = "Create new submission"
+							r.summary = "Trigger the validator to create a new submission"
 							r.operationID = "createSubmission"
 							r.pathPattern = "/submissions"
 							r.args = args
diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go
index 4c368c2..5141d86 100644
--- a/pkg/api/oas_schemas_gen.go
+++ b/pkg/api/oas_schemas_gen.go
@@ -359,74 +359,115 @@ func (s *Mapfix) SetStatusMessage(val string) {
 	s.StatusMessage = val
 }
 
-// Ref: #/components/schemas/MapfixCreate
-type MapfixCreate struct {
-	DisplayName   string `json:"DisplayName"`
-	Creator       string `json:"Creator"`
-	GameID        int32  `json:"GameID"`
-	AssetID       int64  `json:"AssetID"`
-	AssetVersion  int64  `json:"AssetVersion"`
-	TargetAssetID int64  `json:"TargetAssetID"`
-}
-
-// GetDisplayName returns the value of DisplayName.
-func (s *MapfixCreate) GetDisplayName() string {
-	return s.DisplayName
-}
-
-// GetCreator returns the value of Creator.
-func (s *MapfixCreate) GetCreator() string {
-	return s.Creator
-}
-
-// GetGameID returns the value of GameID.
-func (s *MapfixCreate) GetGameID() int32 {
-	return s.GameID
+// Ref: #/components/schemas/MapfixTriggerCreate
+type MapfixTriggerCreate struct {
+	AssetID       int64 `json:"AssetID"`
+	TargetAssetID int64 `json:"TargetAssetID"`
 }
 
 // GetAssetID returns the value of AssetID.
-func (s *MapfixCreate) GetAssetID() int64 {
+func (s *MapfixTriggerCreate) GetAssetID() int64 {
 	return s.AssetID
 }
 
-// GetAssetVersion returns the value of AssetVersion.
-func (s *MapfixCreate) GetAssetVersion() int64 {
-	return s.AssetVersion
-}
-
 // GetTargetAssetID returns the value of TargetAssetID.
-func (s *MapfixCreate) GetTargetAssetID() int64 {
+func (s *MapfixTriggerCreate) GetTargetAssetID() int64 {
 	return s.TargetAssetID
 }
 
-// SetDisplayName sets the value of DisplayName.
-func (s *MapfixCreate) SetDisplayName(val string) {
-	s.DisplayName = val
-}
-
-// SetCreator sets the value of Creator.
-func (s *MapfixCreate) SetCreator(val string) {
-	s.Creator = val
-}
-
-// SetGameID sets the value of GameID.
-func (s *MapfixCreate) SetGameID(val int32) {
-	s.GameID = val
-}
-
 // SetAssetID sets the value of AssetID.
-func (s *MapfixCreate) SetAssetID(val int64) {
+func (s *MapfixTriggerCreate) SetAssetID(val int64) {
 	s.AssetID = val
 }
 
-// SetAssetVersion sets the value of AssetVersion.
-func (s *MapfixCreate) SetAssetVersion(val int64) {
-	s.AssetVersion = val
+// SetTargetAssetID sets the value of TargetAssetID.
+func (s *MapfixTriggerCreate) SetTargetAssetID(val int64) {
+	s.TargetAssetID = val
 }
 
-// SetTargetAssetID sets the value of TargetAssetID.
-func (s *MapfixCreate) SetTargetAssetID(val int64) {
-	s.TargetAssetID = val
+// Ref: #/components/schemas/Operation
+type Operation struct {
+	OperationID   int32  `json:"OperationID"`
+	Date          int64  `json:"Date"`
+	Owner         int64  `json:"Owner"`
+	Status        int32  `json:"Status"`
+	StatusMessage string `json:"StatusMessage"`
+	Path          string `json:"Path"`
+}
+
+// GetOperationID returns the value of OperationID.
+func (s *Operation) GetOperationID() int32 {
+	return s.OperationID
+}
+
+// GetDate returns the value of Date.
+func (s *Operation) GetDate() int64 {
+	return s.Date
+}
+
+// GetOwner returns the value of Owner.
+func (s *Operation) GetOwner() int64 {
+	return s.Owner
+}
+
+// GetStatus returns the value of Status.
+func (s *Operation) GetStatus() int32 {
+	return s.Status
+}
+
+// GetStatusMessage returns the value of StatusMessage.
+func (s *Operation) GetStatusMessage() string {
+	return s.StatusMessage
+}
+
+// GetPath returns the value of Path.
+func (s *Operation) GetPath() string {
+	return s.Path
+}
+
+// SetOperationID sets the value of OperationID.
+func (s *Operation) SetOperationID(val int32) {
+	s.OperationID = val
+}
+
+// SetDate sets the value of Date.
+func (s *Operation) SetDate(val int64) {
+	s.Date = val
+}
+
+// SetOwner sets the value of Owner.
+func (s *Operation) SetOwner(val int64) {
+	s.Owner = val
+}
+
+// SetStatus sets the value of Status.
+func (s *Operation) SetStatus(val int32) {
+	s.Status = val
+}
+
+// SetStatusMessage sets the value of StatusMessage.
+func (s *Operation) SetStatusMessage(val string) {
+	s.StatusMessage = val
+}
+
+// SetPath sets the value of Path.
+func (s *Operation) SetPath(val string) {
+	s.Path = val
+}
+
+// Ref: #/components/schemas/OperationID
+type OperationID struct {
+	OperationID int32 `json:"OperationID"`
+}
+
+// GetOperationID returns the value of OperationID.
+func (s *OperationID) GetOperationID() int32 {
+	return s.OperationID
+}
+
+// SetOperationID sets the value of OperationID.
+func (s *OperationID) SetOperationID(val int32) {
+	s.OperationID = val
 }
 
 // NewOptInt32 returns new OptInt32 with value set to v.
@@ -1096,65 +1137,21 @@ func (s *Submission) SetStatusMessage(val string) {
 	s.StatusMessage = val
 }
 
-// Ref: #/components/schemas/SubmissionCreate
-type SubmissionCreate struct {
-	DisplayName  string `json:"DisplayName"`
-	Creator      string `json:"Creator"`
-	GameID       int32  `json:"GameID"`
-	AssetID      int64  `json:"AssetID"`
-	AssetVersion int64  `json:"AssetVersion"`
-}
-
-// GetDisplayName returns the value of DisplayName.
-func (s *SubmissionCreate) GetDisplayName() string {
-	return s.DisplayName
-}
-
-// GetCreator returns the value of Creator.
-func (s *SubmissionCreate) GetCreator() string {
-	return s.Creator
-}
-
-// GetGameID returns the value of GameID.
-func (s *SubmissionCreate) GetGameID() int32 {
-	return s.GameID
+// Ref: #/components/schemas/SubmissionTriggerCreate
+type SubmissionTriggerCreate struct {
+	AssetID int64 `json:"AssetID"`
 }
 
 // GetAssetID returns the value of AssetID.
-func (s *SubmissionCreate) GetAssetID() int64 {
+func (s *SubmissionTriggerCreate) GetAssetID() int64 {
 	return s.AssetID
 }
 
-// GetAssetVersion returns the value of AssetVersion.
-func (s *SubmissionCreate) GetAssetVersion() int64 {
-	return s.AssetVersion
-}
-
-// SetDisplayName sets the value of DisplayName.
-func (s *SubmissionCreate) SetDisplayName(val string) {
-	s.DisplayName = val
-}
-
-// SetCreator sets the value of Creator.
-func (s *SubmissionCreate) SetCreator(val string) {
-	s.Creator = val
-}
-
-// SetGameID sets the value of GameID.
-func (s *SubmissionCreate) SetGameID(val int32) {
-	s.GameID = val
-}
-
 // SetAssetID sets the value of AssetID.
-func (s *SubmissionCreate) SetAssetID(val int64) {
+func (s *SubmissionTriggerCreate) SetAssetID(val int64) {
 	s.AssetID = val
 }
 
-// SetAssetVersion sets the value of AssetVersion.
-func (s *SubmissionCreate) SetAssetVersion(val int64) {
-	s.AssetVersion = val
-}
-
 // UpdateMapfixModelNoContent is response for UpdateMapfixModel operation.
 type UpdateMapfixModelNoContent struct{}
 
diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go
index 9f294eb..b06f259 100644
--- a/pkg/api/oas_server_gen.go
+++ b/pkg/api/oas_server_gen.go
@@ -118,10 +118,10 @@ type Handler interface {
 	ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
 	// CreateMapfix implements createMapfix operation.
 	//
-	// Create new mapfix.
+	// Trigger the validator to create a mapfix.
 	//
 	// POST /mapfixes
-	CreateMapfix(ctx context.Context, req *MapfixCreate) (*ID, error)
+	CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (*OperationID, error)
 	// CreateScript implements createScript operation.
 	//
 	// Create a new script.
@@ -136,10 +136,10 @@ type Handler interface {
 	CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error)
 	// CreateSubmission implements createSubmission operation.
 	//
-	// Create new submission.
+	// Trigger the validator to create a new submission.
 	//
 	// POST /submissions
-	CreateSubmission(ctx context.Context, req *SubmissionCreate) (*ID, error)
+	CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error)
 	// DeleteScript implements deleteScript operation.
 	//
 	// Delete the specified script by ID.
@@ -164,6 +164,12 @@ type Handler interface {
 	//
 	// GET /mapfixes/{MapfixID}
 	GetMapfix(ctx context.Context, params GetMapfixParams) (*Mapfix, error)
+	// GetOperation implements getOperation operation.
+	//
+	// Retrieve operation with ID.
+	//
+	// GET /operations/{OperationID}
+	GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error)
 	// GetScript implements getScript operation.
 	//
 	// Get the specified script by ID.
diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go
index 738f398..3e92782 100644
--- a/pkg/api/oas_unimplemented_gen.go
+++ b/pkg/api/oas_unimplemented_gen.go
@@ -177,10 +177,10 @@ func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, param
 
 // CreateMapfix implements createMapfix operation.
 //
-// Create new mapfix.
+// Trigger the validator to create a mapfix.
 //
 // POST /mapfixes
-func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) (r *ID, _ error) {
+func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (r *OperationID, _ error) {
 	return r, ht.ErrNotImplemented
 }
 
@@ -204,10 +204,10 @@ func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptP
 
 // CreateSubmission implements createSubmission operation.
 //
-// Create new submission.
+// Trigger the validator to create a new submission.
 //
 // POST /submissions
-func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *ID, _ error) {
+func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (r *OperationID, _ error) {
 	return r, ht.ErrNotImplemented
 }
 
@@ -247,6 +247,15 @@ func (UnimplementedHandler) GetMapfix(ctx context.Context, params GetMapfixParam
 	return r, ht.ErrNotImplemented
 }
 
+// GetOperation implements getOperation operation.
+//
+// Retrieve operation with ID.
+//
+// GET /operations/{OperationID}
+func (UnimplementedHandler) GetOperation(ctx context.Context, params GetOperationParams) (r *Operation, _ error) {
+	return r, ht.ErrNotImplemented
+}
+
 // GetScript implements getScript operation.
 //
 // Get the specified script by ID.
diff --git a/pkg/api/oas_validators_gen.go b/pkg/api/oas_validators_gen.go
index 4dc04e6..006fce8 100644
--- a/pkg/api/oas_validators_gen.go
+++ b/pkg/api/oas_validators_gen.go
@@ -127,7 +127,7 @@ func (s *Mapfix) Validate() error {
 	return nil
 }
 
-func (s *MapfixCreate) Validate() error {
+func (s *Operation) Validate() error {
 	if s == nil {
 		return validate.ErrNilPointer
 	}
@@ -137,18 +137,18 @@ func (s *MapfixCreate) Validate() error {
 		if err := (validate.String{
 			MinLength:    0,
 			MinLengthSet: false,
-			MaxLength:    128,
+			MaxLength:    256,
 			MaxLengthSet: true,
 			Email:        false,
 			Hostname:     false,
 			Regex:        nil,
-		}).Validate(string(s.DisplayName)); err != nil {
+		}).Validate(string(s.StatusMessage)); err != nil {
 			return errors.Wrap(err, "string")
 		}
 		return nil
 	}(); err != nil {
 		failures = append(failures, validate.FieldError{
-			Name:  "DisplayName",
+			Name:  "StatusMessage",
 			Error: err,
 		})
 	}
@@ -161,13 +161,13 @@ func (s *MapfixCreate) Validate() error {
 			Email:        false,
 			Hostname:     false,
 			Regex:        nil,
-		}).Validate(string(s.Creator)); err != nil {
+		}).Validate(string(s.Path)); err != nil {
 			return errors.Wrap(err, "string")
 		}
 		return nil
 	}(); err != nil {
 		failures = append(failures, validate.FieldError{
-			Name:  "Creator",
+			Name:  "Path",
 			Error: err,
 		})
 	}
@@ -460,56 +460,6 @@ func (s *Submission) Validate() error {
 	return nil
 }
 
-func (s *SubmissionCreate) Validate() error {
-	if s == nil {
-		return validate.ErrNilPointer
-	}
-
-	var failures []validate.FieldError
-	if err := func() error {
-		if err := (validate.String{
-			MinLength:    0,
-			MinLengthSet: false,
-			MaxLength:    128,
-			MaxLengthSet: true,
-			Email:        false,
-			Hostname:     false,
-			Regex:        nil,
-		}).Validate(string(s.DisplayName)); err != nil {
-			return errors.Wrap(err, "string")
-		}
-		return nil
-	}(); err != nil {
-		failures = append(failures, validate.FieldError{
-			Name:  "DisplayName",
-			Error: err,
-		})
-	}
-	if err := func() error {
-		if err := (validate.String{
-			MinLength:    0,
-			MinLengthSet: false,
-			MaxLength:    128,
-			MaxLengthSet: true,
-			Email:        false,
-			Hostname:     false,
-			Regex:        nil,
-		}).Validate(string(s.Creator)); err != nil {
-			return errors.Wrap(err, "string")
-		}
-		return nil
-	}(); err != nil {
-		failures = append(failures, validate.FieldError{
-			Name:  "Creator",
-			Error: err,
-		})
-	}
-	if len(failures) > 0 {
-		return &validate.Error{Fields: failures}
-	}
-	return nil
-}
-
 func (s *User) Validate() error {
 	if s == nil {
 		return validate.ErrNilPointer
diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go
index 1cc3f48..82eda8f 100644
--- a/pkg/datastore/datastore.go
+++ b/pkg/datastore/datastore.go
@@ -23,6 +23,7 @@ const (
 
 type Datastore interface {
 	Mapfixes() Mapfixes
+	Operations() Operations
 	Submissions() Submissions
 	Scripts() Scripts
 	ScriptPolicy() ScriptPolicy
@@ -39,6 +40,13 @@ type Mapfixes interface {
 	List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Mapfix, error)
 }
 
+type Operations interface {
+	Get(ctx context.Context, id int32) (model.Operation, error)
+	Create(ctx context.Context, smap model.Operation) (model.Operation, error)
+	Update(ctx context.Context, id int32, values OptionalMap) error
+	Delete(ctx context.Context, id int32) error
+}
+
 type Submissions interface {
 	Get(ctx context.Context, id int64) (model.Submission, error)
 	GetList(ctx context.Context, id []int64) ([]model.Submission, error)
diff --git a/pkg/datastore/gormstore/db.go b/pkg/datastore/gormstore/db.go
index 01d001c..e4d0ed3 100644
--- a/pkg/datastore/gormstore/db.go
+++ b/pkg/datastore/gormstore/db.go
@@ -32,6 +32,7 @@ func New(ctx *cli.Context) (datastore.Datastore, error) {
 	if ctx.Bool("migrate") {
 		if err := db.AutoMigrate(
 			&model.Mapfix{},
+			&model.Operation{},
 			&model.Submission{},
 			&model.Script{},
 			&model.ScriptPolicy{},
diff --git a/pkg/datastore/gormstore/gormstore.go b/pkg/datastore/gormstore/gormstore.go
index da16895..7d88b12 100644
--- a/pkg/datastore/gormstore/gormstore.go
+++ b/pkg/datastore/gormstore/gormstore.go
@@ -13,6 +13,10 @@ func (g Gormstore) Mapfixes() datastore.Mapfixes {
 	return &Mapfixes{db: g.db}
 }
 
+func (g Gormstore) Operations() datastore.Operations {
+	return &Operations{db: g.db}
+}
+
 func (g Gormstore) Submissions() datastore.Submissions {
 	return &Submissions{db: g.db}
 }
diff --git a/pkg/datastore/gormstore/operations.go b/pkg/datastore/gormstore/operations.go
new file mode 100644
index 0000000..bf49c86
--- /dev/null
+++ b/pkg/datastore/gormstore/operations.go
@@ -0,0 +1,55 @@
+package gormstore
+
+import (
+	"context"
+	"errors"
+
+	"git.itzana.me/strafesnet/maps-service/pkg/datastore"
+	"git.itzana.me/strafesnet/maps-service/pkg/model"
+	"gorm.io/gorm"
+)
+
+type Operations struct {
+	db *gorm.DB
+}
+
+func (env *Operations) Get(ctx context.Context, id int32) (model.Operation, error) {
+	var operation model.Operation
+	if err := env.db.First(&operation, id).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return operation, datastore.ErrNotExist
+		}
+		return operation, err
+	}
+	return operation, nil
+}
+
+func (env *Operations) Create(ctx context.Context, smap model.Operation) (model.Operation, error) {
+	if err := env.db.Create(&smap).Error; err != nil {
+		return smap, err
+	}
+
+	return smap, nil
+}
+
+func (env *Operations) Update(ctx context.Context, id int32, values datastore.OptionalMap) error {
+	if err := env.db.Model(&model.Operation{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return datastore.ErrNotExist
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (env *Operations) Delete(ctx context.Context, id int32) error {
+	if err := env.db.Delete(&model.Operation{}, id).Error; err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return datastore.ErrNotExist
+		}
+		return err
+	}
+
+	return nil
+}
diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go
index 8c64dc5..21152b2 100644
--- a/pkg/internal/oas_client_gen.go
+++ b/pkg/internal/oas_client_gen.go
@@ -46,6 +46,12 @@ type Invoker interface {
 	//
 	// POST /mapfixes/{MapfixID}/status/validator-validated
 	ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error
+	// ActionOperationFailed invokes actionOperationFailed operation.
+	//
+	// (Internal endpoint) Fail an operation and write a StatusMessage.
+	//
+	// POST /operations/{OperationID}/failed
+	ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error
 	// ActionSubmissionAccepted invokes actionSubmissionAccepted operation.
 	//
 	// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
@@ -64,6 +70,12 @@ type Invoker interface {
 	//
 	// POST /submissions/{SubmissionID}/status/validator-validated
 	ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
+	// CreateMapfix invokes createMapfix operation.
+	//
+	// Create a mapfix.
+	//
+	// POST /mapfixes
+	CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error)
 	// CreateScript invokes createScript operation.
 	//
 	// Create a new script.
@@ -76,6 +88,12 @@ type Invoker interface {
 	//
 	// POST /script-policy
 	CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error)
+	// CreateSubmission invokes createSubmission operation.
+	//
+	// Create a new submission.
+	//
+	// POST /submissions
+	CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error)
 	// GetScript invokes getScript operation.
 	//
 	// Get the specified script by ID.
@@ -446,6 +464,115 @@ func (c *Client) sendActionMapfixValidated(ctx context.Context, params ActionMap
 	return result, nil
 }
 
+// ActionOperationFailed invokes actionOperationFailed operation.
+//
+// (Internal endpoint) Fail an operation and write a StatusMessage.
+//
+// POST /operations/{OperationID}/failed
+func (c *Client) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error {
+	_, err := c.sendActionOperationFailed(ctx, params)
+	return err
+}
+
+func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) (res *ActionOperationFailedNoContent, err error) {
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("actionOperationFailed"),
+		semconv.HTTPRequestMethodKey.String("POST"),
+		semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"),
+	}
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		elapsedDuration := time.Since(startTime)
+		c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
+	}()
+
+	// Increment request counter.
+	c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+
+	// Start a span for this request.
+	ctx, span := c.cfg.Tracer.Start(ctx, ActionOperationFailedOperation,
+		trace.WithAttributes(otelAttrs...),
+		clientSpanKind,
+	)
+	// Track stage for error reporting.
+	var stage string
+	defer func() {
+		if err != nil {
+			span.RecordError(err)
+			span.SetStatus(codes.Error, stage)
+			c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+		}
+		span.End()
+	}()
+
+	stage = "BuildURL"
+	u := uri.Clone(c.requestURL(ctx))
+	var pathParts [3]string
+	pathParts[0] = "/operations/"
+	{
+		// Encode "OperationID" parameter.
+		e := uri.NewPathEncoder(uri.PathEncoderConfig{
+			Param:   "OperationID",
+			Style:   uri.PathStyleSimple,
+			Explode: false,
+		})
+		if err := func() error {
+			return e.EncodeValue(conv.Int32ToString(params.OperationID))
+		}(); err != nil {
+			return res, errors.Wrap(err, "encode path")
+		}
+		encoded, err := e.Result()
+		if err != nil {
+			return res, errors.Wrap(err, "encode path")
+		}
+		pathParts[1] = encoded
+	}
+	pathParts[2] = "/failed"
+	uri.AddPathParts(u, pathParts[:]...)
+
+	stage = "EncodeQueryParams"
+	q := uri.NewQueryEncoder()
+	{
+		// Encode "StatusMessage" parameter.
+		cfg := uri.QueryParameterEncodingConfig{
+			Name:    "StatusMessage",
+			Style:   uri.QueryStyleForm,
+			Explode: true,
+		}
+
+		if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
+			return e.EncodeValue(conv.StringToString(params.StatusMessage))
+		}); err != nil {
+			return res, errors.Wrap(err, "encode query")
+		}
+	}
+	u.RawQuery = q.Values().Encode()
+
+	stage = "EncodeRequest"
+	r, err := ht.NewRequest(ctx, "POST", u)
+	if err != nil {
+		return res, errors.Wrap(err, "create request")
+	}
+
+	stage = "SendRequest"
+	resp, err := c.cfg.Client.Do(r)
+	if err != nil {
+		return res, errors.Wrap(err, "do request")
+	}
+	defer resp.Body.Close()
+
+	stage = "DecodeResponse"
+	result, err := decodeActionOperationFailedResponse(resp)
+	if err != nil {
+		return res, errors.Wrap(err, "decode response")
+	}
+
+	return result, nil
+}
+
 // ActionSubmissionAccepted invokes actionSubmissionAccepted operation.
 //
 // (Internal endpoint) Role Validator changes status from Validating -> Accepted.
@@ -755,6 +882,81 @@ func (c *Client) sendActionSubmissionValidated(ctx context.Context, params Actio
 	return result, nil
 }
 
+// CreateMapfix invokes createMapfix operation.
+//
+// Create a mapfix.
+//
+// POST /mapfixes
+func (c *Client) CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) {
+	res, err := c.sendCreateMapfix(ctx, request)
+	return res, err
+}
+
+func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (res *ID, err error) {
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("createMapfix"),
+		semconv.HTTPRequestMethodKey.String("POST"),
+		semconv.HTTPRouteKey.String("/mapfixes"),
+	}
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		elapsedDuration := time.Since(startTime)
+		c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
+	}()
+
+	// Increment request counter.
+	c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+
+	// Start a span for this request.
+	ctx, span := c.cfg.Tracer.Start(ctx, CreateMapfixOperation,
+		trace.WithAttributes(otelAttrs...),
+		clientSpanKind,
+	)
+	// Track stage for error reporting.
+	var stage string
+	defer func() {
+		if err != nil {
+			span.RecordError(err)
+			span.SetStatus(codes.Error, stage)
+			c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+		}
+		span.End()
+	}()
+
+	stage = "BuildURL"
+	u := uri.Clone(c.requestURL(ctx))
+	var pathParts [1]string
+	pathParts[0] = "/mapfixes"
+	uri.AddPathParts(u, pathParts[:]...)
+
+	stage = "EncodeRequest"
+	r, err := ht.NewRequest(ctx, "POST", u)
+	if err != nil {
+		return res, errors.Wrap(err, "create request")
+	}
+	if err := encodeCreateMapfixRequest(request, r); err != nil {
+		return res, errors.Wrap(err, "encode request")
+	}
+
+	stage = "SendRequest"
+	resp, err := c.cfg.Client.Do(r)
+	if err != nil {
+		return res, errors.Wrap(err, "do request")
+	}
+	defer resp.Body.Close()
+
+	stage = "DecodeResponse"
+	result, err := decodeCreateMapfixResponse(resp)
+	if err != nil {
+		return res, errors.Wrap(err, "decode response")
+	}
+
+	return result, nil
+}
+
 // CreateScript invokes createScript operation.
 //
 // Create a new script.
@@ -905,6 +1107,81 @@ func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPoli
 	return result, nil
 }
 
+// CreateSubmission invokes createSubmission operation.
+//
+// Create a new submission.
+//
+// POST /submissions
+func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) {
+	res, err := c.sendCreateSubmission(ctx, request)
+	return res, err
+}
+
+func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCreate) (res *ID, err error) {
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("createSubmission"),
+		semconv.HTTPRequestMethodKey.String("POST"),
+		semconv.HTTPRouteKey.String("/submissions"),
+	}
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		elapsedDuration := time.Since(startTime)
+		c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
+	}()
+
+	// Increment request counter.
+	c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+
+	// Start a span for this request.
+	ctx, span := c.cfg.Tracer.Start(ctx, CreateSubmissionOperation,
+		trace.WithAttributes(otelAttrs...),
+		clientSpanKind,
+	)
+	// Track stage for error reporting.
+	var stage string
+	defer func() {
+		if err != nil {
+			span.RecordError(err)
+			span.SetStatus(codes.Error, stage)
+			c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
+		}
+		span.End()
+	}()
+
+	stage = "BuildURL"
+	u := uri.Clone(c.requestURL(ctx))
+	var pathParts [1]string
+	pathParts[0] = "/submissions"
+	uri.AddPathParts(u, pathParts[:]...)
+
+	stage = "EncodeRequest"
+	r, err := ht.NewRequest(ctx, "POST", u)
+	if err != nil {
+		return res, errors.Wrap(err, "create request")
+	}
+	if err := encodeCreateSubmissionRequest(request, r); err != nil {
+		return res, errors.Wrap(err, "encode request")
+	}
+
+	stage = "SendRequest"
+	resp, err := c.cfg.Client.Do(r)
+	if err != nil {
+		return res, errors.Wrap(err, "do request")
+	}
+	defer resp.Body.Close()
+
+	stage = "DecodeResponse"
+	result, err := decodeCreateSubmissionResponse(resp)
+	if err != nil {
+		return res, errors.Wrap(err, "decode response")
+	}
+
+	return result, nil
+}
+
 // GetScript invokes getScript operation.
 //
 // Get the specified script by ID.
diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go
index 346fefb..99977b4 100644
--- a/pkg/internal/oas_handlers_gen.go
+++ b/pkg/internal/oas_handlers_gen.go
@@ -481,6 +481,159 @@ func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped
 	}
 }
 
+// handleActionOperationFailedRequest handles actionOperationFailed operation.
+//
+// (Internal endpoint) Fail an operation and write a StatusMessage.
+//
+// POST /operations/{OperationID}/failed
+func (s *Server) handleActionOperationFailedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
+	statusWriter := &codeRecorder{ResponseWriter: w}
+	w = statusWriter
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("actionOperationFailed"),
+		semconv.HTTPRequestMethodKey.String("POST"),
+		semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"),
+	}
+
+	// Start a span for this request.
+	ctx, span := s.cfg.Tracer.Start(r.Context(), ActionOperationFailedOperation,
+		trace.WithAttributes(otelAttrs...),
+		serverSpanKind,
+	)
+	defer span.End()
+
+	// Add Labeler to context.
+	labeler := &Labeler{attrs: otelAttrs}
+	ctx = contextWithLabeler(ctx, labeler)
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		elapsedDuration := time.Since(startTime)
+
+		attrSet := labeler.AttributeSet()
+		attrs := attrSet.ToSlice()
+		code := statusWriter.status
+		if code != 0 {
+			codeAttr := semconv.HTTPResponseStatusCode(code)
+			attrs = append(attrs, codeAttr)
+			span.SetAttributes(codeAttr)
+		}
+		attrOpt := metric.WithAttributes(attrs...)
+
+		// Increment request counter.
+		s.requests.Add(ctx, 1, attrOpt)
+
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
+	}()
+
+	var (
+		recordError = func(stage string, err error) {
+			span.RecordError(err)
+
+			// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
+			// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
+			// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
+			// max redirects exceeded), in which case status MUST be set to Error.
+			code := statusWriter.status
+			if code >= 100 && code < 500 {
+				span.SetStatus(codes.Error, stage)
+			}
+
+			attrSet := labeler.AttributeSet()
+			attrs := attrSet.ToSlice()
+			if code != 0 {
+				attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
+			}
+
+			s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
+		}
+		err          error
+		opErrContext = ogenerrors.OperationContext{
+			Name: ActionOperationFailedOperation,
+			ID:   "actionOperationFailed",
+		}
+	)
+	params, err := decodeActionOperationFailedParams(args, argsEscaped, r)
+	if err != nil {
+		err = &ogenerrors.DecodeParamsError{
+			OperationContext: opErrContext,
+			Err:              err,
+		}
+		defer recordError("DecodeParams", err)
+		s.cfg.ErrorHandler(ctx, w, r, err)
+		return
+	}
+
+	var response *ActionOperationFailedNoContent
+	if m := s.cfg.Middleware; m != nil {
+		mreq := middleware.Request{
+			Context:          ctx,
+			OperationName:    ActionOperationFailedOperation,
+			OperationSummary: "(Internal endpoint) Fail an operation and write a StatusMessage",
+			OperationID:      "actionOperationFailed",
+			Body:             nil,
+			Params: middleware.Parameters{
+				{
+					Name: "OperationID",
+					In:   "path",
+				}: params.OperationID,
+				{
+					Name: "StatusMessage",
+					In:   "query",
+				}: params.StatusMessage,
+			},
+			Raw: r,
+		}
+
+		type (
+			Request  = struct{}
+			Params   = ActionOperationFailedParams
+			Response = *ActionOperationFailedNoContent
+		)
+		response, err = middleware.HookMiddleware[
+			Request,
+			Params,
+			Response,
+		](
+			m,
+			mreq,
+			unpackActionOperationFailedParams,
+			func(ctx context.Context, request Request, params Params) (response Response, err error) {
+				err = s.h.ActionOperationFailed(ctx, params)
+				return response, err
+			},
+		)
+	} else {
+		err = s.h.ActionOperationFailed(ctx, params)
+	}
+	if err != nil {
+		if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
+			if err := encodeErrorResponse(errRes, w, span); err != nil {
+				defer recordError("Internal", err)
+			}
+			return
+		}
+		if errors.Is(err, ht.ErrNotImplemented) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+			return
+		}
+		if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
+			defer recordError("Internal", err)
+		}
+		return
+	}
+
+	if err := encodeActionOperationFailedResponse(response, w, span); err != nil {
+		defer recordError("EncodeResponse", err)
+		if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+		}
+		return
+	}
+}
+
 // handleActionSubmissionAcceptedRequest handles actionSubmissionAccepted operation.
 //
 // (Internal endpoint) Role Validator changes status from Validating -> Accepted.
@@ -936,6 +1089,155 @@ func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEsca
 	}
 }
 
+// handleCreateMapfixRequest handles createMapfix operation.
+//
+// Create a mapfix.
+//
+// POST /mapfixes
+func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
+	statusWriter := &codeRecorder{ResponseWriter: w}
+	w = statusWriter
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("createMapfix"),
+		semconv.HTTPRequestMethodKey.String("POST"),
+		semconv.HTTPRouteKey.String("/mapfixes"),
+	}
+
+	// Start a span for this request.
+	ctx, span := s.cfg.Tracer.Start(r.Context(), CreateMapfixOperation,
+		trace.WithAttributes(otelAttrs...),
+		serverSpanKind,
+	)
+	defer span.End()
+
+	// Add Labeler to context.
+	labeler := &Labeler{attrs: otelAttrs}
+	ctx = contextWithLabeler(ctx, labeler)
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		elapsedDuration := time.Since(startTime)
+
+		attrSet := labeler.AttributeSet()
+		attrs := attrSet.ToSlice()
+		code := statusWriter.status
+		if code != 0 {
+			codeAttr := semconv.HTTPResponseStatusCode(code)
+			attrs = append(attrs, codeAttr)
+			span.SetAttributes(codeAttr)
+		}
+		attrOpt := metric.WithAttributes(attrs...)
+
+		// Increment request counter.
+		s.requests.Add(ctx, 1, attrOpt)
+
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
+	}()
+
+	var (
+		recordError = func(stage string, err error) {
+			span.RecordError(err)
+
+			// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
+			// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
+			// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
+			// max redirects exceeded), in which case status MUST be set to Error.
+			code := statusWriter.status
+			if code >= 100 && code < 500 {
+				span.SetStatus(codes.Error, stage)
+			}
+
+			attrSet := labeler.AttributeSet()
+			attrs := attrSet.ToSlice()
+			if code != 0 {
+				attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
+			}
+
+			s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
+		}
+		err          error
+		opErrContext = ogenerrors.OperationContext{
+			Name: CreateMapfixOperation,
+			ID:   "createMapfix",
+		}
+	)
+	request, close, err := s.decodeCreateMapfixRequest(r)
+	if err != nil {
+		err = &ogenerrors.DecodeRequestError{
+			OperationContext: opErrContext,
+			Err:              err,
+		}
+		defer recordError("DecodeRequest", err)
+		s.cfg.ErrorHandler(ctx, w, r, err)
+		return
+	}
+	defer func() {
+		if err := close(); err != nil {
+			recordError("CloseRequest", err)
+		}
+	}()
+
+	var response *ID
+	if m := s.cfg.Middleware; m != nil {
+		mreq := middleware.Request{
+			Context:          ctx,
+			OperationName:    CreateMapfixOperation,
+			OperationSummary: "Create a mapfix",
+			OperationID:      "createMapfix",
+			Body:             request,
+			Params:           middleware.Parameters{},
+			Raw:              r,
+		}
+
+		type (
+			Request  = *MapfixCreate
+			Params   = struct{}
+			Response = *ID
+		)
+		response, err = middleware.HookMiddleware[
+			Request,
+			Params,
+			Response,
+		](
+			m,
+			mreq,
+			nil,
+			func(ctx context.Context, request Request, params Params) (response Response, err error) {
+				response, err = s.h.CreateMapfix(ctx, request)
+				return response, err
+			},
+		)
+	} else {
+		response, err = s.h.CreateMapfix(ctx, request)
+	}
+	if err != nil {
+		if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
+			if err := encodeErrorResponse(errRes, w, span); err != nil {
+				defer recordError("Internal", err)
+			}
+			return
+		}
+		if errors.Is(err, ht.ErrNotImplemented) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+			return
+		}
+		if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
+			defer recordError("Internal", err)
+		}
+		return
+	}
+
+	if err := encodeCreateMapfixResponse(response, w, span); err != nil {
+		defer recordError("EncodeResponse", err)
+		if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+		}
+		return
+	}
+}
+
 // handleCreateScriptRequest handles createScript operation.
 //
 // Create a new script.
@@ -1234,6 +1536,155 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo
 	}
 }
 
+// handleCreateSubmissionRequest handles createSubmission operation.
+//
+// Create a new submission.
+//
+// POST /submissions
+func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
+	statusWriter := &codeRecorder{ResponseWriter: w}
+	w = statusWriter
+	otelAttrs := []attribute.KeyValue{
+		otelogen.OperationID("createSubmission"),
+		semconv.HTTPRequestMethodKey.String("POST"),
+		semconv.HTTPRouteKey.String("/submissions"),
+	}
+
+	// Start a span for this request.
+	ctx, span := s.cfg.Tracer.Start(r.Context(), CreateSubmissionOperation,
+		trace.WithAttributes(otelAttrs...),
+		serverSpanKind,
+	)
+	defer span.End()
+
+	// Add Labeler to context.
+	labeler := &Labeler{attrs: otelAttrs}
+	ctx = contextWithLabeler(ctx, labeler)
+
+	// Run stopwatch.
+	startTime := time.Now()
+	defer func() {
+		elapsedDuration := time.Since(startTime)
+
+		attrSet := labeler.AttributeSet()
+		attrs := attrSet.ToSlice()
+		code := statusWriter.status
+		if code != 0 {
+			codeAttr := semconv.HTTPResponseStatusCode(code)
+			attrs = append(attrs, codeAttr)
+			span.SetAttributes(codeAttr)
+		}
+		attrOpt := metric.WithAttributes(attrs...)
+
+		// Increment request counter.
+		s.requests.Add(ctx, 1, attrOpt)
+
+		// Use floating point division here for higher precision (instead of Millisecond method).
+		s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
+	}()
+
+	var (
+		recordError = func(stage string, err error) {
+			span.RecordError(err)
+
+			// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
+			// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
+			// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
+			// max redirects exceeded), in which case status MUST be set to Error.
+			code := statusWriter.status
+			if code >= 100 && code < 500 {
+				span.SetStatus(codes.Error, stage)
+			}
+
+			attrSet := labeler.AttributeSet()
+			attrs := attrSet.ToSlice()
+			if code != 0 {
+				attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
+			}
+
+			s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
+		}
+		err          error
+		opErrContext = ogenerrors.OperationContext{
+			Name: CreateSubmissionOperation,
+			ID:   "createSubmission",
+		}
+	)
+	request, close, err := s.decodeCreateSubmissionRequest(r)
+	if err != nil {
+		err = &ogenerrors.DecodeRequestError{
+			OperationContext: opErrContext,
+			Err:              err,
+		}
+		defer recordError("DecodeRequest", err)
+		s.cfg.ErrorHandler(ctx, w, r, err)
+		return
+	}
+	defer func() {
+		if err := close(); err != nil {
+			recordError("CloseRequest", err)
+		}
+	}()
+
+	var response *ID
+	if m := s.cfg.Middleware; m != nil {
+		mreq := middleware.Request{
+			Context:          ctx,
+			OperationName:    CreateSubmissionOperation,
+			OperationSummary: "Create a new submission",
+			OperationID:      "createSubmission",
+			Body:             request,
+			Params:           middleware.Parameters{},
+			Raw:              r,
+		}
+
+		type (
+			Request  = *SubmissionCreate
+			Params   = struct{}
+			Response = *ID
+		)
+		response, err = middleware.HookMiddleware[
+			Request,
+			Params,
+			Response,
+		](
+			m,
+			mreq,
+			nil,
+			func(ctx context.Context, request Request, params Params) (response Response, err error) {
+				response, err = s.h.CreateSubmission(ctx, request)
+				return response, err
+			},
+		)
+	} else {
+		response, err = s.h.CreateSubmission(ctx, request)
+	}
+	if err != nil {
+		if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
+			if err := encodeErrorResponse(errRes, w, span); err != nil {
+				defer recordError("Internal", err)
+			}
+			return
+		}
+		if errors.Is(err, ht.ErrNotImplemented) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+			return
+		}
+		if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
+			defer recordError("Internal", err)
+		}
+		return
+	}
+
+	if err := encodeCreateSubmissionResponse(response, w, span); err != nil {
+		defer recordError("EncodeResponse", err)
+		if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
+			s.cfg.ErrorHandler(ctx, w, r, err)
+		}
+		return
+	}
+}
+
 // handleGetScriptRequest handles getScript operation.
 //
 // Get the specified script by ID.
diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go
index 7b03e7b..7ad30b5 100644
--- a/pkg/internal/oas_json_gen.go
+++ b/pkg/internal/oas_json_gen.go
@@ -221,6 +221,221 @@ func (s *ID) UnmarshalJSON(data []byte) error {
 	return s.Decode(d)
 }
 
+// Encode implements json.Marshaler.
+func (s *MapfixCreate) Encode(e *jx.Encoder) {
+	e.ObjStart()
+	s.encodeFields(e)
+	e.ObjEnd()
+}
+
+// encodeFields encodes fields.
+func (s *MapfixCreate) encodeFields(e *jx.Encoder) {
+	{
+		e.FieldStart("OperationID")
+		e.Int32(s.OperationID)
+	}
+	{
+		e.FieldStart("AssetOwner")
+		e.Int64(s.AssetOwner)
+	}
+	{
+		e.FieldStart("DisplayName")
+		e.Str(s.DisplayName)
+	}
+	{
+		e.FieldStart("Creator")
+		e.Str(s.Creator)
+	}
+	{
+		e.FieldStart("GameID")
+		e.Int32(s.GameID)
+	}
+	{
+		e.FieldStart("AssetID")
+		e.Int64(s.AssetID)
+	}
+	{
+		e.FieldStart("AssetVersion")
+		e.Int64(s.AssetVersion)
+	}
+	{
+		e.FieldStart("TargetAssetID")
+		e.Int64(s.TargetAssetID)
+	}
+}
+
+var jsonFieldsNameOfMapfixCreate = [8]string{
+	0: "OperationID",
+	1: "AssetOwner",
+	2: "DisplayName",
+	3: "Creator",
+	4: "GameID",
+	5: "AssetID",
+	6: "AssetVersion",
+	7: "TargetAssetID",
+}
+
+// Decode decodes MapfixCreate from json.
+func (s *MapfixCreate) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode MapfixCreate to nil")
+	}
+	var requiredBitSet [1]uint8
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		case "OperationID":
+			requiredBitSet[0] |= 1 << 0
+			if err := func() error {
+				v, err := d.Int32()
+				s.OperationID = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"OperationID\"")
+			}
+		case "AssetOwner":
+			requiredBitSet[0] |= 1 << 1
+			if err := func() error {
+				v, err := d.Int64()
+				s.AssetOwner = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"AssetOwner\"")
+			}
+		case "DisplayName":
+			requiredBitSet[0] |= 1 << 2
+			if err := func() error {
+				v, err := d.Str()
+				s.DisplayName = string(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"DisplayName\"")
+			}
+		case "Creator":
+			requiredBitSet[0] |= 1 << 3
+			if err := func() error {
+				v, err := d.Str()
+				s.Creator = string(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"Creator\"")
+			}
+		case "GameID":
+			requiredBitSet[0] |= 1 << 4
+			if err := func() error {
+				v, err := d.Int32()
+				s.GameID = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"GameID\"")
+			}
+		case "AssetID":
+			requiredBitSet[0] |= 1 << 5
+			if err := func() error {
+				v, err := d.Int64()
+				s.AssetID = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"AssetID\"")
+			}
+		case "AssetVersion":
+			requiredBitSet[0] |= 1 << 6
+			if err := func() error {
+				v, err := d.Int64()
+				s.AssetVersion = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"AssetVersion\"")
+			}
+		case "TargetAssetID":
+			requiredBitSet[0] |= 1 << 7
+			if err := func() error {
+				v, err := d.Int64()
+				s.TargetAssetID = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"TargetAssetID\"")
+			}
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode MapfixCreate")
+	}
+	// Validate required fields.
+	var failures []validate.FieldError
+	for i, mask := range [1]uint8{
+		0b11111111,
+	} {
+		if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
+			// Mask only required fields and check equality to mask using XOR.
+			//
+			// If XOR result is not zero, result is not equal to expected, so some fields are missed.
+			// Bits of fields which would be set are actually bits of missed fields.
+			missed := bits.OnesCount8(result)
+			for bitN := 0; bitN < missed; bitN++ {
+				bitIdx := bits.TrailingZeros8(result)
+				fieldIdx := i*8 + bitIdx
+				var name string
+				if fieldIdx < len(jsonFieldsNameOfMapfixCreate) {
+					name = jsonFieldsNameOfMapfixCreate[fieldIdx]
+				} else {
+					name = strconv.Itoa(fieldIdx)
+				}
+				failures = append(failures, validate.FieldError{
+					Name:  name,
+					Error: validate.ErrFieldRequired,
+				})
+				// Reset bit.
+				result &^= 1 << bitIdx
+			}
+		}
+	}
+	if len(failures) > 0 {
+		return &validate.Error{Fields: failures}
+	}
+
+	return nil
+}
+
+// MarshalJSON implements stdjson.Marshaler.
+func (s *MapfixCreate) MarshalJSON() ([]byte, error) {
+	e := jx.Encoder{}
+	s.Encode(&e)
+	return e.Bytes(), nil
+}
+
+// UnmarshalJSON implements stdjson.Unmarshaler.
+func (s *MapfixCreate) UnmarshalJSON(data []byte) error {
+	d := jx.DecodeBytes(data)
+	return s.Decode(d)
+}
+
 // Encode encodes int64 as json.
 func (o OptInt64) Encode(e *jx.Encoder) {
 	if !o.Set {
@@ -860,3 +1075,201 @@ func (s *ScriptPolicyCreate) UnmarshalJSON(data []byte) error {
 	d := jx.DecodeBytes(data)
 	return s.Decode(d)
 }
+
+// Encode implements json.Marshaler.
+func (s *SubmissionCreate) Encode(e *jx.Encoder) {
+	e.ObjStart()
+	s.encodeFields(e)
+	e.ObjEnd()
+}
+
+// encodeFields encodes fields.
+func (s *SubmissionCreate) encodeFields(e *jx.Encoder) {
+	{
+		e.FieldStart("OperationID")
+		e.Int32(s.OperationID)
+	}
+	{
+		e.FieldStart("AssetOwner")
+		e.Int64(s.AssetOwner)
+	}
+	{
+		e.FieldStart("DisplayName")
+		e.Str(s.DisplayName)
+	}
+	{
+		e.FieldStart("Creator")
+		e.Str(s.Creator)
+	}
+	{
+		e.FieldStart("GameID")
+		e.Int32(s.GameID)
+	}
+	{
+		e.FieldStart("AssetID")
+		e.Int64(s.AssetID)
+	}
+	{
+		e.FieldStart("AssetVersion")
+		e.Int64(s.AssetVersion)
+	}
+}
+
+var jsonFieldsNameOfSubmissionCreate = [7]string{
+	0: "OperationID",
+	1: "AssetOwner",
+	2: "DisplayName",
+	3: "Creator",
+	4: "GameID",
+	5: "AssetID",
+	6: "AssetVersion",
+}
+
+// Decode decodes SubmissionCreate from json.
+func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode SubmissionCreate to nil")
+	}
+	var requiredBitSet [1]uint8
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		case "OperationID":
+			requiredBitSet[0] |= 1 << 0
+			if err := func() error {
+				v, err := d.Int32()
+				s.OperationID = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"OperationID\"")
+			}
+		case "AssetOwner":
+			requiredBitSet[0] |= 1 << 1
+			if err := func() error {
+				v, err := d.Int64()
+				s.AssetOwner = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"AssetOwner\"")
+			}
+		case "DisplayName":
+			requiredBitSet[0] |= 1 << 2
+			if err := func() error {
+				v, err := d.Str()
+				s.DisplayName = string(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"DisplayName\"")
+			}
+		case "Creator":
+			requiredBitSet[0] |= 1 << 3
+			if err := func() error {
+				v, err := d.Str()
+				s.Creator = string(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"Creator\"")
+			}
+		case "GameID":
+			requiredBitSet[0] |= 1 << 4
+			if err := func() error {
+				v, err := d.Int32()
+				s.GameID = int32(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"GameID\"")
+			}
+		case "AssetID":
+			requiredBitSet[0] |= 1 << 5
+			if err := func() error {
+				v, err := d.Int64()
+				s.AssetID = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"AssetID\"")
+			}
+		case "AssetVersion":
+			requiredBitSet[0] |= 1 << 6
+			if err := func() error {
+				v, err := d.Int64()
+				s.AssetVersion = int64(v)
+				if err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"AssetVersion\"")
+			}
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode SubmissionCreate")
+	}
+	// Validate required fields.
+	var failures []validate.FieldError
+	for i, mask := range [1]uint8{
+		0b01111111,
+	} {
+		if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
+			// Mask only required fields and check equality to mask using XOR.
+			//
+			// If XOR result is not zero, result is not equal to expected, so some fields are missed.
+			// Bits of fields which would be set are actually bits of missed fields.
+			missed := bits.OnesCount8(result)
+			for bitN := 0; bitN < missed; bitN++ {
+				bitIdx := bits.TrailingZeros8(result)
+				fieldIdx := i*8 + bitIdx
+				var name string
+				if fieldIdx < len(jsonFieldsNameOfSubmissionCreate) {
+					name = jsonFieldsNameOfSubmissionCreate[fieldIdx]
+				} else {
+					name = strconv.Itoa(fieldIdx)
+				}
+				failures = append(failures, validate.FieldError{
+					Name:  name,
+					Error: validate.ErrFieldRequired,
+				})
+				// Reset bit.
+				result &^= 1 << bitIdx
+			}
+		}
+	}
+	if len(failures) > 0 {
+		return &validate.Error{Fields: failures}
+	}
+
+	return nil
+}
+
+// MarshalJSON implements stdjson.Marshaler.
+func (s *SubmissionCreate) MarshalJSON() ([]byte, error) {
+	e := jx.Encoder{}
+	s.Encode(&e)
+	return e.Bytes(), nil
+}
+
+// UnmarshalJSON implements stdjson.Unmarshaler.
+func (s *SubmissionCreate) UnmarshalJSON(data []byte) error {
+	d := jx.DecodeBytes(data)
+	return s.Decode(d)
+}
diff --git a/pkg/internal/oas_operations_gen.go b/pkg/internal/oas_operations_gen.go
index d472077..9e4ecd3 100644
--- a/pkg/internal/oas_operations_gen.go
+++ b/pkg/internal/oas_operations_gen.go
@@ -9,11 +9,14 @@ const (
 	ActionMapfixAcceptedOperation           OperationName = "ActionMapfixAccepted"
 	ActionMapfixUploadedOperation           OperationName = "ActionMapfixUploaded"
 	ActionMapfixValidatedOperation          OperationName = "ActionMapfixValidated"
+	ActionOperationFailedOperation          OperationName = "ActionOperationFailed"
 	ActionSubmissionAcceptedOperation       OperationName = "ActionSubmissionAccepted"
 	ActionSubmissionUploadedOperation       OperationName = "ActionSubmissionUploaded"
 	ActionSubmissionValidatedOperation      OperationName = "ActionSubmissionValidated"
+	CreateMapfixOperation                   OperationName = "CreateMapfix"
 	CreateScriptOperation                   OperationName = "CreateScript"
 	CreateScriptPolicyOperation             OperationName = "CreateScriptPolicy"
+	CreateSubmissionOperation               OperationName = "CreateSubmission"
 	GetScriptOperation                      OperationName = "GetScript"
 	ListScriptPolicyOperation               OperationName = "ListScriptPolicy"
 	ListScriptsOperation                    OperationName = "ListScripts"
diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go
index 5ce0a6b..9d171fc 100644
--- a/pkg/internal/oas_parameters_gen.go
+++ b/pkg/internal/oas_parameters_gen.go
@@ -274,6 +274,133 @@ func decodeActionMapfixValidatedParams(args [1]string, argsEscaped bool, r *http
 	return params, nil
 }
 
+// ActionOperationFailedParams is parameters of actionOperationFailed operation.
+type ActionOperationFailedParams struct {
+	// The unique identifier for a long-running operation.
+	OperationID   int32
+	StatusMessage string
+}
+
+func unpackActionOperationFailedParams(packed middleware.Parameters) (params ActionOperationFailedParams) {
+	{
+		key := middleware.ParameterKey{
+			Name: "OperationID",
+			In:   "path",
+		}
+		params.OperationID = packed[key].(int32)
+	}
+	{
+		key := middleware.ParameterKey{
+			Name: "StatusMessage",
+			In:   "query",
+		}
+		params.StatusMessage = packed[key].(string)
+	}
+	return params
+}
+
+func decodeActionOperationFailedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionOperationFailedParams, _ error) {
+	q := uri.NewQueryDecoder(r.URL.Query())
+	// Decode path: OperationID.
+	if err := func() error {
+		param := args[0]
+		if argsEscaped {
+			unescaped, err := url.PathUnescape(args[0])
+			if err != nil {
+				return errors.Wrap(err, "unescape path")
+			}
+			param = unescaped
+		}
+		if len(param) > 0 {
+			d := uri.NewPathDecoder(uri.PathDecoderConfig{
+				Param:   "OperationID",
+				Value:   param,
+				Style:   uri.PathStyleSimple,
+				Explode: false,
+			})
+
+			if err := func() error {
+				val, err := d.DecodeValue()
+				if err != nil {
+					return err
+				}
+
+				c, err := conv.ToInt32(val)
+				if err != nil {
+					return err
+				}
+
+				params.OperationID = c
+				return nil
+			}(); err != nil {
+				return err
+			}
+		} else {
+			return validate.ErrFieldRequired
+		}
+		return nil
+	}(); err != nil {
+		return params, &ogenerrors.DecodeParamError{
+			Name: "OperationID",
+			In:   "path",
+			Err:  err,
+		}
+	}
+	// Decode query: StatusMessage.
+	if err := func() error {
+		cfg := uri.QueryParameterDecodingConfig{
+			Name:    "StatusMessage",
+			Style:   uri.QueryStyleForm,
+			Explode: true,
+		}
+
+		if err := q.HasParam(cfg); err == nil {
+			if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
+				val, err := d.DecodeValue()
+				if err != nil {
+					return err
+				}
+
+				c, err := conv.ToString(val)
+				if err != nil {
+					return err
+				}
+
+				params.StatusMessage = c
+				return nil
+			}); err != nil {
+				return err
+			}
+			if err := func() error {
+				if err := (validate.String{
+					MinLength:    0,
+					MinLengthSet: true,
+					MaxLength:    4096,
+					MaxLengthSet: true,
+					Email:        false,
+					Hostname:     false,
+					Regex:        nil,
+				}).Validate(string(params.StatusMessage)); err != nil {
+					return errors.Wrap(err, "string")
+				}
+				return nil
+			}(); err != nil {
+				return err
+			}
+		} else {
+			return err
+		}
+		return nil
+	}(); err != nil {
+		return params, &ogenerrors.DecodeParamError{
+			Name: "StatusMessage",
+			In:   "query",
+			Err:  err,
+		}
+	}
+	return params, nil
+}
+
 // ActionSubmissionAcceptedParams is parameters of actionSubmissionAccepted operation.
 type ActionSubmissionAcceptedParams struct {
 	// The unique identifier for a submission.
diff --git a/pkg/internal/oas_request_decoders_gen.go b/pkg/internal/oas_request_decoders_gen.go
index db79c40..5be039e 100644
--- a/pkg/internal/oas_request_decoders_gen.go
+++ b/pkg/internal/oas_request_decoders_gen.go
@@ -15,6 +15,77 @@ import (
 	"github.com/ogen-go/ogen/validate"
 )
 
+func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
+	req *MapfixCreate,
+	close func() error,
+	rerr error,
+) {
+	var closers []func() error
+	close = func() error {
+		var merr error
+		// Close in reverse order, to match defer behavior.
+		for i := len(closers) - 1; i >= 0; i-- {
+			c := closers[i]
+			merr = multierr.Append(merr, c())
+		}
+		return merr
+	}
+	defer func() {
+		if rerr != nil {
+			rerr = multierr.Append(rerr, close())
+		}
+	}()
+	ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
+	if err != nil {
+		return req, close, errors.Wrap(err, "parse media type")
+	}
+	switch {
+	case ct == "application/json":
+		if r.ContentLength == 0 {
+			return req, close, validate.ErrBodyRequired
+		}
+		buf, err := io.ReadAll(r.Body)
+		if err != nil {
+			return req, close, err
+		}
+
+		if len(buf) == 0 {
+			return req, close, validate.ErrBodyRequired
+		}
+
+		d := jx.DecodeBytes(buf)
+
+		var request MapfixCreate
+		if err := func() error {
+			if err := request.Decode(d); err != nil {
+				return err
+			}
+			if err := d.Skip(); err != io.EOF {
+				return errors.New("unexpected trailing data")
+			}
+			return nil
+		}(); err != nil {
+			err = &ogenerrors.DecodeBodyError{
+				ContentType: ct,
+				Body:        buf,
+				Err:         err,
+			}
+			return req, close, err
+		}
+		if err := func() error {
+			if err := request.Validate(); err != nil {
+				return err
+			}
+			return nil
+		}(); err != nil {
+			return req, close, errors.Wrap(err, "validate")
+		}
+		return &request, close, nil
+	default:
+		return req, close, validate.InvalidContentType(ct)
+	}
+}
+
 func (s *Server) decodeCreateScriptRequest(r *http.Request) (
 	req *ScriptCreate,
 	close func() error,
@@ -148,3 +219,74 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) (
 		return req, close, validate.InvalidContentType(ct)
 	}
 }
+
+func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
+	req *SubmissionCreate,
+	close func() error,
+	rerr error,
+) {
+	var closers []func() error
+	close = func() error {
+		var merr error
+		// Close in reverse order, to match defer behavior.
+		for i := len(closers) - 1; i >= 0; i-- {
+			c := closers[i]
+			merr = multierr.Append(merr, c())
+		}
+		return merr
+	}
+	defer func() {
+		if rerr != nil {
+			rerr = multierr.Append(rerr, close())
+		}
+	}()
+	ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
+	if err != nil {
+		return req, close, errors.Wrap(err, "parse media type")
+	}
+	switch {
+	case ct == "application/json":
+		if r.ContentLength == 0 {
+			return req, close, validate.ErrBodyRequired
+		}
+		buf, err := io.ReadAll(r.Body)
+		if err != nil {
+			return req, close, err
+		}
+
+		if len(buf) == 0 {
+			return req, close, validate.ErrBodyRequired
+		}
+
+		d := jx.DecodeBytes(buf)
+
+		var request SubmissionCreate
+		if err := func() error {
+			if err := request.Decode(d); err != nil {
+				return err
+			}
+			if err := d.Skip(); err != io.EOF {
+				return errors.New("unexpected trailing data")
+			}
+			return nil
+		}(); err != nil {
+			err = &ogenerrors.DecodeBodyError{
+				ContentType: ct,
+				Body:        buf,
+				Err:         err,
+			}
+			return req, close, err
+		}
+		if err := func() error {
+			if err := request.Validate(); err != nil {
+				return err
+			}
+			return nil
+		}(); err != nil {
+			return req, close, errors.Wrap(err, "validate")
+		}
+		return &request, close, nil
+	default:
+		return req, close, validate.InvalidContentType(ct)
+	}
+}
diff --git a/pkg/internal/oas_request_encoders_gen.go b/pkg/internal/oas_request_encoders_gen.go
index a0e7305..1e38200 100644
--- a/pkg/internal/oas_request_encoders_gen.go
+++ b/pkg/internal/oas_request_encoders_gen.go
@@ -11,6 +11,20 @@ import (
 	ht "github.com/ogen-go/ogen/http"
 )
 
+func encodeCreateMapfixRequest(
+	req *MapfixCreate,
+	r *http.Request,
+) error {
+	const contentType = "application/json"
+	e := new(jx.Encoder)
+	{
+		req.Encode(e)
+	}
+	encoded := e.Bytes()
+	ht.SetBody(r, bytes.NewReader(encoded), contentType)
+	return nil
+}
+
 func encodeCreateScriptRequest(
 	req *ScriptCreate,
 	r *http.Request,
@@ -38,3 +52,17 @@ func encodeCreateScriptPolicyRequest(
 	ht.SetBody(r, bytes.NewReader(encoded), contentType)
 	return nil
 }
+
+func encodeCreateSubmissionRequest(
+	req *SubmissionCreate,
+	r *http.Request,
+) error {
+	const contentType = "application/json"
+	e := new(jx.Encoder)
+	{
+		req.Encode(e)
+	}
+	encoded := e.Bytes()
+	ht.SetBody(r, bytes.NewReader(encoded), contentType)
+	return nil
+}
diff --git a/pkg/internal/oas_response_decoders_gen.go b/pkg/internal/oas_response_decoders_gen.go
index 226883f..1c492e1 100644
--- a/pkg/internal/oas_response_decoders_gen.go
+++ b/pkg/internal/oas_response_decoders_gen.go
@@ -168,6 +168,57 @@ func decodeActionMapfixValidatedResponse(resp *http.Response) (res *ActionMapfix
 	return res, errors.Wrap(defRes, "error")
 }
 
+func decodeActionOperationFailedResponse(resp *http.Response) (res *ActionOperationFailedNoContent, _ error) {
+	switch resp.StatusCode {
+	case 204:
+		// Code 204.
+		return &ActionOperationFailedNoContent{}, nil
+	}
+	// Convenient error response.
+	defRes, err := func() (res *ErrorStatusCode, err error) {
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response Error
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			return &ErrorStatusCode{
+				StatusCode: resp.StatusCode,
+				Response:   response,
+			}, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}()
+	if err != nil {
+		return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
+	}
+	return res, errors.Wrap(defRes, "error")
+}
+
 func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSubmissionAcceptedNoContent, _ error) {
 	switch resp.StatusCode {
 	case 204:
@@ -321,6 +372,89 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu
 	return res, errors.Wrap(defRes, "error")
 }
 
+func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) {
+	switch resp.StatusCode {
+	case 201:
+		// Code 201.
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response ID
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			return &response, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}
+	// Convenient error response.
+	defRes, err := func() (res *ErrorStatusCode, err error) {
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response Error
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			return &ErrorStatusCode{
+				StatusCode: resp.StatusCode,
+				Response:   response,
+			}, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}()
+	if err != nil {
+		return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
+	}
+	return res, errors.Wrap(defRes, "error")
+}
+
 func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) {
 	switch resp.StatusCode {
 	case 201:
@@ -487,6 +621,89 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) {
 	return res, errors.Wrap(defRes, "error")
 }
 
+func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) {
+	switch resp.StatusCode {
+	case 201:
+		// Code 201.
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response ID
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			return &response, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}
+	// Convenient error response.
+	defRes, err := func() (res *ErrorStatusCode, err error) {
+		ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+		if err != nil {
+			return res, errors.Wrap(err, "parse media type")
+		}
+		switch {
+		case ct == "application/json":
+			buf, err := io.ReadAll(resp.Body)
+			if err != nil {
+				return res, err
+			}
+			d := jx.DecodeBytes(buf)
+
+			var response Error
+			if err := func() error {
+				if err := response.Decode(d); err != nil {
+					return err
+				}
+				if err := d.Skip(); err != io.EOF {
+					return errors.New("unexpected trailing data")
+				}
+				return nil
+			}(); err != nil {
+				err = &ogenerrors.DecodeBodyError{
+					ContentType: ct,
+					Body:        buf,
+					Err:         err,
+				}
+				return res, err
+			}
+			return &ErrorStatusCode{
+				StatusCode: resp.StatusCode,
+				Response:   response,
+			}, nil
+		default:
+			return res, validate.InvalidContentType(ct)
+		}
+	}()
+	if err != nil {
+		return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
+	}
+	return res, errors.Wrap(defRes, "error")
+}
+
 func decodeGetScriptResponse(resp *http.Response) (res *Script, _ error) {
 	switch resp.StatusCode {
 	case 200:
diff --git a/pkg/internal/oas_response_encoders_gen.go b/pkg/internal/oas_response_encoders_gen.go
index c7d2b1f..ab1b12d 100644
--- a/pkg/internal/oas_response_encoders_gen.go
+++ b/pkg/internal/oas_response_encoders_gen.go
@@ -34,6 +34,13 @@ func encodeActionMapfixValidatedResponse(response *ActionMapfixValidatedNoConten
 	return nil
 }
 
+func encodeActionOperationFailedResponse(response *ActionOperationFailedNoContent, w http.ResponseWriter, span trace.Span) error {
+	w.WriteHeader(204)
+	span.SetStatus(codes.Ok, http.StatusText(204))
+
+	return nil
+}
+
 func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNoContent, w http.ResponseWriter, span trace.Span) error {
 	w.WriteHeader(204)
 	span.SetStatus(codes.Ok, http.StatusText(204))
@@ -55,6 +62,20 @@ func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidated
 	return nil
 }
 
+func encodeCreateMapfixResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	w.WriteHeader(201)
+	span.SetStatus(codes.Ok, http.StatusText(201))
+
+	e := new(jx.Encoder)
+	response.Encode(e)
+	if _, err := e.WriteTo(w); err != nil {
+		return errors.Wrap(err, "write")
+	}
+
+	return nil
+}
+
 func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 	w.WriteHeader(201)
@@ -83,6 +104,20 @@ func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span
 	return nil
 }
 
+func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span trace.Span) error {
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	w.WriteHeader(201)
+	span.SetStatus(codes.Ok, http.StatusText(201))
+
+	e := new(jx.Encoder)
+	response.Encode(e)
+	if _, err := e.WriteTo(w); err != nil {
+		return errors.Wrap(err, "write")
+	}
+
+	return nil
+}
+
 func encodeGetScriptResponse(response *Script, w http.ResponseWriter, span trace.Span) error {
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 	w.WriteHeader(200)
diff --git a/pkg/internal/oas_router_gen.go b/pkg/internal/oas_router_gen.go
index 77e8063..0531e26 100644
--- a/pkg/internal/oas_router_gen.go
+++ b/pkg/internal/oas_router_gen.go
@@ -61,15 +61,175 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 				break
 			}
 			switch elem[0] {
-			case 'm': // Prefix: "mapfixes/"
+			case 'm': // Prefix: "mapfixes"
 
-				if l := len("mapfixes/"); len(elem) >= l && elem[0:l] == "mapfixes/" {
+				if l := len("mapfixes"); len(elem) >= l && elem[0:l] == "mapfixes" {
 					elem = elem[l:]
 				} else {
 					break
 				}
 
-				// Param: "MapfixID"
+				if len(elem) == 0 {
+					switch r.Method {
+					case "POST":
+						s.handleCreateMapfixRequest([0]string{}, elemIsEscaped, w, r)
+					default:
+						s.notAllowed(w, r, "POST")
+					}
+
+					return
+				}
+				switch elem[0] {
+				case '/': // Prefix: "/"
+
+					if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
+						elem = elem[l:]
+					} else {
+						break
+					}
+
+					// Param: "MapfixID"
+					// Match until "/"
+					idx := strings.IndexByte(elem, '/')
+					if idx < 0 {
+						idx = len(elem)
+					}
+					args[0] = elem[:idx]
+					elem = elem[idx:]
+
+					if len(elem) == 0 {
+						break
+					}
+					switch elem[0] {
+					case '/': // Prefix: "/"
+
+						if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
+							elem = elem[l:]
+						} else {
+							break
+						}
+
+						if len(elem) == 0 {
+							break
+						}
+						switch elem[0] {
+						case 's': // Prefix: "status/validator-"
+
+							if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
+								elem = elem[l:]
+							} else {
+								break
+							}
+
+							if len(elem) == 0 {
+								break
+							}
+							switch elem[0] {
+							case 'f': // Prefix: "failed"
+
+								if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									// Leaf node.
+									switch r.Method {
+									case "POST":
+										s.handleActionMapfixAcceptedRequest([1]string{
+											args[0],
+										}, elemIsEscaped, w, r)
+									default:
+										s.notAllowed(w, r, "POST")
+									}
+
+									return
+								}
+
+							case 'u': // Prefix: "uploaded"
+
+								if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									// Leaf node.
+									switch r.Method {
+									case "POST":
+										s.handleActionMapfixUploadedRequest([1]string{
+											args[0],
+										}, elemIsEscaped, w, r)
+									default:
+										s.notAllowed(w, r, "POST")
+									}
+
+									return
+								}
+
+							case 'v': // Prefix: "validated"
+
+								if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									// Leaf node.
+									switch r.Method {
+									case "POST":
+										s.handleActionMapfixValidatedRequest([1]string{
+											args[0],
+										}, elemIsEscaped, w, r)
+									default:
+										s.notAllowed(w, r, "POST")
+									}
+
+									return
+								}
+
+							}
+
+						case 'v': // Prefix: "validated-model"
+
+							if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
+								elem = elem[l:]
+							} else {
+								break
+							}
+
+							if len(elem) == 0 {
+								// Leaf node.
+								switch r.Method {
+								case "POST":
+									s.handleUpdateMapfixValidatedModelRequest([1]string{
+										args[0],
+									}, elemIsEscaped, w, r)
+								default:
+									s.notAllowed(w, r, "POST")
+								}
+
+								return
+							}
+
+						}
+
+					}
+
+				}
+
+			case 'o': // Prefix: "operations/"
+
+				if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" {
+					elem = elem[l:]
+				} else {
+					break
+				}
+
+				// Param: "OperationID"
 				// Match until "/"
 				idx := strings.IndexByte(elem, '/')
 				if idx < 0 {
@@ -82,120 +242,26 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 					break
 				}
 				switch elem[0] {
-				case '/': // Prefix: "/"
+				case '/': // Prefix: "/failed"
 
-					if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
+					if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" {
 						elem = elem[l:]
 					} else {
 						break
 					}
 
 					if len(elem) == 0 {
-						break
-					}
-					switch elem[0] {
-					case 's': // Prefix: "status/validator-"
-
-						if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
-							elem = elem[l:]
-						} else {
-							break
-						}
-
-						if len(elem) == 0 {
-							break
-						}
-						switch elem[0] {
-						case 'f': // Prefix: "failed"
-
-							if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch r.Method {
-								case "POST":
-									s.handleActionMapfixAcceptedRequest([1]string{
-										args[0],
-									}, elemIsEscaped, w, r)
-								default:
-									s.notAllowed(w, r, "POST")
-								}
-
-								return
-							}
-
-						case 'u': // Prefix: "uploaded"
-
-							if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch r.Method {
-								case "POST":
-									s.handleActionMapfixUploadedRequest([1]string{
-										args[0],
-									}, elemIsEscaped, w, r)
-								default:
-									s.notAllowed(w, r, "POST")
-								}
-
-								return
-							}
-
-						case 'v': // Prefix: "validated"
-
-							if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch r.Method {
-								case "POST":
-									s.handleActionMapfixValidatedRequest([1]string{
-										args[0],
-									}, elemIsEscaped, w, r)
-								default:
-									s.notAllowed(w, r, "POST")
-								}
-
-								return
-							}
-
-						}
-
-					case 'v': // Prefix: "validated-model"
-
-						if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
-							elem = elem[l:]
-						} else {
-							break
-						}
-
-						if len(elem) == 0 {
-							// Leaf node.
-							switch r.Method {
-							case "POST":
-								s.handleUpdateMapfixValidatedModelRequest([1]string{
-									args[0],
-								}, elemIsEscaped, w, r)
-							default:
-								s.notAllowed(w, r, "POST")
-							}
-
-							return
+						// Leaf node.
+						switch r.Method {
+						case "POST":
+							s.handleActionOperationFailedRequest([1]string{
+								args[0],
+							}, elemIsEscaped, w, r)
+						default:
+							s.notAllowed(w, r, "POST")
 						}
 
+						return
 					}
 
 				}
@@ -302,25 +368,23 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 					}
 
-				case 'u': // Prefix: "ubmissions/"
+				case 'u': // Prefix: "ubmissions"
 
-					if l := len("ubmissions/"); len(elem) >= l && elem[0:l] == "ubmissions/" {
+					if l := len("ubmissions"); len(elem) >= l && elem[0:l] == "ubmissions" {
 						elem = elem[l:]
 					} else {
 						break
 					}
 
-					// Param: "SubmissionID"
-					// Match until "/"
-					idx := strings.IndexByte(elem, '/')
-					if idx < 0 {
-						idx = len(elem)
-					}
-					args[0] = elem[:idx]
-					elem = elem[idx:]
-
 					if len(elem) == 0 {
-						break
+						switch r.Method {
+						case "POST":
+							s.handleCreateSubmissionRequest([0]string{}, elemIsEscaped, w, r)
+						default:
+							s.notAllowed(w, r, "POST")
+						}
+
+						return
 					}
 					switch elem[0] {
 					case '/': // Prefix: "/"
@@ -331,13 +395,22 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 							break
 						}
 
+						// Param: "SubmissionID"
+						// Match until "/"
+						idx := strings.IndexByte(elem, '/')
+						if idx < 0 {
+							idx = len(elem)
+						}
+						args[0] = elem[:idx]
+						elem = elem[idx:]
+
 						if len(elem) == 0 {
 							break
 						}
 						switch elem[0] {
-						case 's': // Prefix: "status/validator-"
+						case '/': // Prefix: "/"
 
-							if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
+							if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
 								elem = elem[l:]
 							} else {
 								break
@@ -347,9 +420,89 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 								break
 							}
 							switch elem[0] {
-							case 'f': // Prefix: "failed"
+							case 's': // Prefix: "status/validator-"
 
-								if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
+								if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									break
+								}
+								switch elem[0] {
+								case 'f': // Prefix: "failed"
+
+									if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
+										elem = elem[l:]
+									} else {
+										break
+									}
+
+									if len(elem) == 0 {
+										// Leaf node.
+										switch r.Method {
+										case "POST":
+											s.handleActionSubmissionAcceptedRequest([1]string{
+												args[0],
+											}, elemIsEscaped, w, r)
+										default:
+											s.notAllowed(w, r, "POST")
+										}
+
+										return
+									}
+
+								case 'u': // Prefix: "uploaded"
+
+									if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
+										elem = elem[l:]
+									} else {
+										break
+									}
+
+									if len(elem) == 0 {
+										// Leaf node.
+										switch r.Method {
+										case "POST":
+											s.handleActionSubmissionUploadedRequest([1]string{
+												args[0],
+											}, elemIsEscaped, w, r)
+										default:
+											s.notAllowed(w, r, "POST")
+										}
+
+										return
+									}
+
+								case 'v': // Prefix: "validated"
+
+									if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
+										elem = elem[l:]
+									} else {
+										break
+									}
+
+									if len(elem) == 0 {
+										// Leaf node.
+										switch r.Method {
+										case "POST":
+											s.handleActionSubmissionValidatedRequest([1]string{
+												args[0],
+											}, elemIsEscaped, w, r)
+										default:
+											s.notAllowed(w, r, "POST")
+										}
+
+										return
+									}
+
+								}
+
+							case 'v': // Prefix: "validated-model"
+
+								if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
 									elem = elem[l:]
 								} else {
 									break
@@ -359,7 +512,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 									// Leaf node.
 									switch r.Method {
 									case "POST":
-										s.handleActionSubmissionAcceptedRequest([1]string{
+										s.handleUpdateSubmissionValidatedModelRequest([1]string{
 											args[0],
 										}, elemIsEscaped, w, r)
 									default:
@@ -369,72 +522,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 									return
 								}
 
-							case 'u': // Prefix: "uploaded"
-
-								if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
-									elem = elem[l:]
-								} else {
-									break
-								}
-
-								if len(elem) == 0 {
-									// Leaf node.
-									switch r.Method {
-									case "POST":
-										s.handleActionSubmissionUploadedRequest([1]string{
-											args[0],
-										}, elemIsEscaped, w, r)
-									default:
-										s.notAllowed(w, r, "POST")
-									}
-
-									return
-								}
-
-							case 'v': // Prefix: "validated"
-
-								if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
-									elem = elem[l:]
-								} else {
-									break
-								}
-
-								if len(elem) == 0 {
-									// Leaf node.
-									switch r.Method {
-									case "POST":
-										s.handleActionSubmissionValidatedRequest([1]string{
-											args[0],
-										}, elemIsEscaped, w, r)
-									default:
-										s.notAllowed(w, r, "POST")
-									}
-
-									return
-								}
-
-							}
-
-						case 'v': // Prefix: "validated-model"
-
-							if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch r.Method {
-								case "POST":
-									s.handleUpdateSubmissionValidatedModelRequest([1]string{
-										args[0],
-									}, elemIsEscaped, w, r)
-								default:
-									s.notAllowed(w, r, "POST")
-								}
-
-								return
 							}
 
 						}
@@ -537,15 +624,187 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 				break
 			}
 			switch elem[0] {
-			case 'm': // Prefix: "mapfixes/"
+			case 'm': // Prefix: "mapfixes"
 
-				if l := len("mapfixes/"); len(elem) >= l && elem[0:l] == "mapfixes/" {
+				if l := len("mapfixes"); len(elem) >= l && elem[0:l] == "mapfixes" {
 					elem = elem[l:]
 				} else {
 					break
 				}
 
-				// Param: "MapfixID"
+				if len(elem) == 0 {
+					switch method {
+					case "POST":
+						r.name = CreateMapfixOperation
+						r.summary = "Create a mapfix"
+						r.operationID = "createMapfix"
+						r.pathPattern = "/mapfixes"
+						r.args = args
+						r.count = 0
+						return r, true
+					default:
+						return
+					}
+				}
+				switch elem[0] {
+				case '/': // Prefix: "/"
+
+					if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
+						elem = elem[l:]
+					} else {
+						break
+					}
+
+					// Param: "MapfixID"
+					// Match until "/"
+					idx := strings.IndexByte(elem, '/')
+					if idx < 0 {
+						idx = len(elem)
+					}
+					args[0] = elem[:idx]
+					elem = elem[idx:]
+
+					if len(elem) == 0 {
+						break
+					}
+					switch elem[0] {
+					case '/': // Prefix: "/"
+
+						if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
+							elem = elem[l:]
+						} else {
+							break
+						}
+
+						if len(elem) == 0 {
+							break
+						}
+						switch elem[0] {
+						case 's': // Prefix: "status/validator-"
+
+							if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
+								elem = elem[l:]
+							} else {
+								break
+							}
+
+							if len(elem) == 0 {
+								break
+							}
+							switch elem[0] {
+							case 'f': // Prefix: "failed"
+
+								if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									// Leaf node.
+									switch method {
+									case "POST":
+										r.name = ActionMapfixAcceptedOperation
+										r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted"
+										r.operationID = "actionMapfixAccepted"
+										r.pathPattern = "/mapfixes/{MapfixID}/status/validator-failed"
+										r.args = args
+										r.count = 1
+										return r, true
+									default:
+										return
+									}
+								}
+
+							case 'u': // Prefix: "uploaded"
+
+								if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									// Leaf node.
+									switch method {
+									case "POST":
+										r.name = ActionMapfixUploadedOperation
+										r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded"
+										r.operationID = "actionMapfixUploaded"
+										r.pathPattern = "/mapfixes/{MapfixID}/status/validator-uploaded"
+										r.args = args
+										r.count = 1
+										return r, true
+									default:
+										return
+									}
+								}
+
+							case 'v': // Prefix: "validated"
+
+								if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									// Leaf node.
+									switch method {
+									case "POST":
+										r.name = ActionMapfixValidatedOperation
+										r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
+										r.operationID = "actionMapfixValidated"
+										r.pathPattern = "/mapfixes/{MapfixID}/status/validator-validated"
+										r.args = args
+										r.count = 1
+										return r, true
+									default:
+										return
+									}
+								}
+
+							}
+
+						case 'v': // Prefix: "validated-model"
+
+							if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
+								elem = elem[l:]
+							} else {
+								break
+							}
+
+							if len(elem) == 0 {
+								// Leaf node.
+								switch method {
+								case "POST":
+									r.name = UpdateMapfixValidatedModelOperation
+									r.summary = "Update validated model"
+									r.operationID = "updateMapfixValidatedModel"
+									r.pathPattern = "/mapfixes/{MapfixID}/validated-model"
+									r.args = args
+									r.count = 1
+									return r, true
+								default:
+									return
+								}
+							}
+
+						}
+
+					}
+
+				}
+
+			case 'o': // Prefix: "operations/"
+
+				if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" {
+					elem = elem[l:]
+				} else {
+					break
+				}
+
+				// Param: "OperationID"
 				// Match until "/"
 				idx := strings.IndexByte(elem, '/')
 				if idx < 0 {
@@ -558,128 +817,28 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 					break
 				}
 				switch elem[0] {
-				case '/': // Prefix: "/"
+				case '/': // Prefix: "/failed"
 
-					if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
+					if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" {
 						elem = elem[l:]
 					} else {
 						break
 					}
 
 					if len(elem) == 0 {
-						break
-					}
-					switch elem[0] {
-					case 's': // Prefix: "status/validator-"
-
-						if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
-							elem = elem[l:]
-						} else {
-							break
+						// Leaf node.
+						switch method {
+						case "POST":
+							r.name = ActionOperationFailedOperation
+							r.summary = "(Internal endpoint) Fail an operation and write a StatusMessage"
+							r.operationID = "actionOperationFailed"
+							r.pathPattern = "/operations/{OperationID}/failed"
+							r.args = args
+							r.count = 1
+							return r, true
+						default:
+							return
 						}
-
-						if len(elem) == 0 {
-							break
-						}
-						switch elem[0] {
-						case 'f': // Prefix: "failed"
-
-							if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch method {
-								case "POST":
-									r.name = ActionMapfixAcceptedOperation
-									r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted"
-									r.operationID = "actionMapfixAccepted"
-									r.pathPattern = "/mapfixes/{MapfixID}/status/validator-failed"
-									r.args = args
-									r.count = 1
-									return r, true
-								default:
-									return
-								}
-							}
-
-						case 'u': // Prefix: "uploaded"
-
-							if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch method {
-								case "POST":
-									r.name = ActionMapfixUploadedOperation
-									r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded"
-									r.operationID = "actionMapfixUploaded"
-									r.pathPattern = "/mapfixes/{MapfixID}/status/validator-uploaded"
-									r.args = args
-									r.count = 1
-									return r, true
-								default:
-									return
-								}
-							}
-
-						case 'v': // Prefix: "validated"
-
-							if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch method {
-								case "POST":
-									r.name = ActionMapfixValidatedOperation
-									r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
-									r.operationID = "actionMapfixValidated"
-									r.pathPattern = "/mapfixes/{MapfixID}/status/validator-validated"
-									r.args = args
-									r.count = 1
-									return r, true
-								default:
-									return
-								}
-							}
-
-						}
-
-					case 'v': // Prefix: "validated-model"
-
-						if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
-							elem = elem[l:]
-						} else {
-							break
-						}
-
-						if len(elem) == 0 {
-							// Leaf node.
-							switch method {
-							case "POST":
-								r.name = UpdateMapfixValidatedModelOperation
-								r.summary = "Update validated model"
-								r.operationID = "updateMapfixValidatedModel"
-								r.pathPattern = "/mapfixes/{MapfixID}/validated-model"
-								r.args = args
-								r.count = 1
-								return r, true
-							default:
-								return
-							}
-						}
-
 					}
 
 				}
@@ -808,25 +967,27 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 
 					}
 
-				case 'u': // Prefix: "ubmissions/"
+				case 'u': // Prefix: "ubmissions"
 
-					if l := len("ubmissions/"); len(elem) >= l && elem[0:l] == "ubmissions/" {
+					if l := len("ubmissions"); len(elem) >= l && elem[0:l] == "ubmissions" {
 						elem = elem[l:]
 					} else {
 						break
 					}
 
-					// Param: "SubmissionID"
-					// Match until "/"
-					idx := strings.IndexByte(elem, '/')
-					if idx < 0 {
-						idx = len(elem)
-					}
-					args[0] = elem[:idx]
-					elem = elem[idx:]
-
 					if len(elem) == 0 {
-						break
+						switch method {
+						case "POST":
+							r.name = CreateSubmissionOperation
+							r.summary = "Create a new submission"
+							r.operationID = "createSubmission"
+							r.pathPattern = "/submissions"
+							r.args = args
+							r.count = 0
+							return r, true
+						default:
+							return
+						}
 					}
 					switch elem[0] {
 					case '/': // Prefix: "/"
@@ -837,13 +998,22 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 							break
 						}
 
+						// Param: "SubmissionID"
+						// Match until "/"
+						idx := strings.IndexByte(elem, '/')
+						if idx < 0 {
+							idx = len(elem)
+						}
+						args[0] = elem[:idx]
+						elem = elem[idx:]
+
 						if len(elem) == 0 {
 							break
 						}
 						switch elem[0] {
-						case 's': // Prefix: "status/validator-"
+						case '/': // Prefix: "/"
 
-							if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
+							if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
 								elem = elem[l:]
 							} else {
 								break
@@ -853,9 +1023,95 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 								break
 							}
 							switch elem[0] {
-							case 'f': // Prefix: "failed"
+							case 's': // Prefix: "status/validator-"
 
-								if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
+								if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" {
+									elem = elem[l:]
+								} else {
+									break
+								}
+
+								if len(elem) == 0 {
+									break
+								}
+								switch elem[0] {
+								case 'f': // Prefix: "failed"
+
+									if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" {
+										elem = elem[l:]
+									} else {
+										break
+									}
+
+									if len(elem) == 0 {
+										// Leaf node.
+										switch method {
+										case "POST":
+											r.name = ActionSubmissionAcceptedOperation
+											r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted"
+											r.operationID = "actionSubmissionAccepted"
+											r.pathPattern = "/submissions/{SubmissionID}/status/validator-failed"
+											r.args = args
+											r.count = 1
+											return r, true
+										default:
+											return
+										}
+									}
+
+								case 'u': // Prefix: "uploaded"
+
+									if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
+										elem = elem[l:]
+									} else {
+										break
+									}
+
+									if len(elem) == 0 {
+										// Leaf node.
+										switch method {
+										case "POST":
+											r.name = ActionSubmissionUploadedOperation
+											r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded"
+											r.operationID = "actionSubmissionUploaded"
+											r.pathPattern = "/submissions/{SubmissionID}/status/validator-uploaded"
+											r.args = args
+											r.count = 1
+											return r, true
+										default:
+											return
+										}
+									}
+
+								case 'v': // Prefix: "validated"
+
+									if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
+										elem = elem[l:]
+									} else {
+										break
+									}
+
+									if len(elem) == 0 {
+										// Leaf node.
+										switch method {
+										case "POST":
+											r.name = ActionSubmissionValidatedOperation
+											r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
+											r.operationID = "actionSubmissionValidated"
+											r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated"
+											r.args = args
+											r.count = 1
+											return r, true
+										default:
+											return
+										}
+									}
+
+								}
+
+							case 'v': // Prefix: "validated-model"
+
+								if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
 									elem = elem[l:]
 								} else {
 									break
@@ -865,10 +1121,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 									// Leaf node.
 									switch method {
 									case "POST":
-										r.name = ActionSubmissionAcceptedOperation
-										r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted"
-										r.operationID = "actionSubmissionAccepted"
-										r.pathPattern = "/submissions/{SubmissionID}/status/validator-failed"
+										r.name = UpdateSubmissionValidatedModelOperation
+										r.summary = "Update validated model"
+										r.operationID = "updateSubmissionValidatedModel"
+										r.pathPattern = "/submissions/{SubmissionID}/validated-model"
 										r.args = args
 										r.count = 1
 										return r, true
@@ -877,78 +1133,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
 									}
 								}
 
-							case 'u': // Prefix: "uploaded"
-
-								if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
-									elem = elem[l:]
-								} else {
-									break
-								}
-
-								if len(elem) == 0 {
-									// Leaf node.
-									switch method {
-									case "POST":
-										r.name = ActionSubmissionUploadedOperation
-										r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded"
-										r.operationID = "actionSubmissionUploaded"
-										r.pathPattern = "/submissions/{SubmissionID}/status/validator-uploaded"
-										r.args = args
-										r.count = 1
-										return r, true
-									default:
-										return
-									}
-								}
-
-							case 'v': // Prefix: "validated"
-
-								if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
-									elem = elem[l:]
-								} else {
-									break
-								}
-
-								if len(elem) == 0 {
-									// Leaf node.
-									switch method {
-									case "POST":
-										r.name = ActionSubmissionValidatedOperation
-										r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
-										r.operationID = "actionSubmissionValidated"
-										r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated"
-										r.args = args
-										r.count = 1
-										return r, true
-									default:
-										return
-									}
-								}
-
-							}
-
-						case 'v': // Prefix: "validated-model"
-
-							if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" {
-								elem = elem[l:]
-							} else {
-								break
-							}
-
-							if len(elem) == 0 {
-								// Leaf node.
-								switch method {
-								case "POST":
-									r.name = UpdateSubmissionValidatedModelOperation
-									r.summary = "Update validated model"
-									r.operationID = "updateSubmissionValidatedModel"
-									r.pathPattern = "/submissions/{SubmissionID}/validated-model"
-									r.args = args
-									r.count = 1
-									return r, true
-								default:
-									return
-								}
 							}
 
 						}
diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go
index 2f06835..cc5c4ed 100644
--- a/pkg/internal/oas_schemas_gen.go
+++ b/pkg/internal/oas_schemas_gen.go
@@ -19,6 +19,9 @@ type ActionMapfixUploadedNoContent struct{}
 // ActionMapfixValidatedNoContent is response for ActionMapfixValidated operation.
 type ActionMapfixValidatedNoContent struct{}
 
+// ActionOperationFailedNoContent is response for ActionOperationFailed operation.
+type ActionOperationFailedNoContent struct{}
+
 // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation.
 type ActionSubmissionAcceptedNoContent struct{}
 
@@ -96,6 +99,98 @@ func (s *ID) SetID(val int64) {
 	s.ID = val
 }
 
+// Ref: #/components/schemas/MapfixCreate
+type MapfixCreate struct {
+	OperationID   int32  `json:"OperationID"`
+	AssetOwner    int64  `json:"AssetOwner"`
+	DisplayName   string `json:"DisplayName"`
+	Creator       string `json:"Creator"`
+	GameID        int32  `json:"GameID"`
+	AssetID       int64  `json:"AssetID"`
+	AssetVersion  int64  `json:"AssetVersion"`
+	TargetAssetID int64  `json:"TargetAssetID"`
+}
+
+// GetOperationID returns the value of OperationID.
+func (s *MapfixCreate) GetOperationID() int32 {
+	return s.OperationID
+}
+
+// GetAssetOwner returns the value of AssetOwner.
+func (s *MapfixCreate) GetAssetOwner() int64 {
+	return s.AssetOwner
+}
+
+// GetDisplayName returns the value of DisplayName.
+func (s *MapfixCreate) GetDisplayName() string {
+	return s.DisplayName
+}
+
+// GetCreator returns the value of Creator.
+func (s *MapfixCreate) GetCreator() string {
+	return s.Creator
+}
+
+// GetGameID returns the value of GameID.
+func (s *MapfixCreate) GetGameID() int32 {
+	return s.GameID
+}
+
+// GetAssetID returns the value of AssetID.
+func (s *MapfixCreate) GetAssetID() int64 {
+	return s.AssetID
+}
+
+// GetAssetVersion returns the value of AssetVersion.
+func (s *MapfixCreate) GetAssetVersion() int64 {
+	return s.AssetVersion
+}
+
+// GetTargetAssetID returns the value of TargetAssetID.
+func (s *MapfixCreate) GetTargetAssetID() int64 {
+	return s.TargetAssetID
+}
+
+// SetOperationID sets the value of OperationID.
+func (s *MapfixCreate) SetOperationID(val int32) {
+	s.OperationID = val
+}
+
+// SetAssetOwner sets the value of AssetOwner.
+func (s *MapfixCreate) SetAssetOwner(val int64) {
+	s.AssetOwner = val
+}
+
+// SetDisplayName sets the value of DisplayName.
+func (s *MapfixCreate) SetDisplayName(val string) {
+	s.DisplayName = val
+}
+
+// SetCreator sets the value of Creator.
+func (s *MapfixCreate) SetCreator(val string) {
+	s.Creator = val
+}
+
+// SetGameID sets the value of GameID.
+func (s *MapfixCreate) SetGameID(val int32) {
+	s.GameID = val
+}
+
+// SetAssetID sets the value of AssetID.
+func (s *MapfixCreate) SetAssetID(val int64) {
+	s.AssetID = val
+}
+
+// SetAssetVersion sets the value of AssetVersion.
+func (s *MapfixCreate) SetAssetVersion(val int64) {
+	s.AssetVersion = val
+}
+
+// SetTargetAssetID sets the value of TargetAssetID.
+func (s *MapfixCreate) SetTargetAssetID(val int64) {
+	s.TargetAssetID = val
+}
+
 // NewOptInt32 returns new OptInt32 with value set to v.
 func NewOptInt32(v int32) OptInt32 {
 	return OptInt32{
@@ -437,6 +532,87 @@ func (s *ScriptPolicyCreate) SetPolicy(val int32) {
 	s.Policy = val
 }
 
+// Ref: #/components/schemas/SubmissionCreate
+type SubmissionCreate struct {
+	OperationID  int32  `json:"OperationID"`
+	AssetOwner   int64  `json:"AssetOwner"`
+	DisplayName  string `json:"DisplayName"`
+	Creator      string `json:"Creator"`
+	GameID       int32  `json:"GameID"`
+	AssetID      int64  `json:"AssetID"`
+	AssetVersion int64  `json:"AssetVersion"`
+}
+
+// GetOperationID returns the value of OperationID.
+func (s *SubmissionCreate) GetOperationID() int32 {
+	return s.OperationID
+}
+
+// GetAssetOwner returns the value of AssetOwner.
+func (s *SubmissionCreate) GetAssetOwner() int64 {
+	return s.AssetOwner
+}
+
+// GetDisplayName returns the value of DisplayName.
+func (s *SubmissionCreate) GetDisplayName() string {
+	return s.DisplayName
+}
+
+// GetCreator returns the value of Creator.
+func (s *SubmissionCreate) GetCreator() string {
+	return s.Creator
+}
+
+// GetGameID returns the value of GameID.
+func (s *SubmissionCreate) GetGameID() int32 {
+	return s.GameID
+}
+
+// GetAssetID returns the value of AssetID.
+func (s *SubmissionCreate) GetAssetID() int64 {
+	return s.AssetID
+}
+
+// GetAssetVersion returns the value of AssetVersion.
+func (s *SubmissionCreate) GetAssetVersion() int64 {
+	return s.AssetVersion
+}
+
+// SetOperationID sets the value of OperationID.
+func (s *SubmissionCreate) SetOperationID(val int32) {
+	s.OperationID = val
+}
+
+// SetAssetOwner sets the value of AssetOwner.
+func (s *SubmissionCreate) SetAssetOwner(val int64) {
+	s.AssetOwner = val
+}
+
+// SetDisplayName sets the value of DisplayName.
+func (s *SubmissionCreate) SetDisplayName(val string) {
+	s.DisplayName = val
+}
+
+// SetCreator sets the value of Creator.
+func (s *SubmissionCreate) SetCreator(val string) {
+	s.Creator = val
+}
+
+// SetGameID sets the value of GameID.
+func (s *SubmissionCreate) SetGameID(val int32) {
+	s.GameID = val
+}
+
+// SetAssetID sets the value of AssetID.
+func (s *SubmissionCreate) SetAssetID(val int64) {
+	s.AssetID = val
+}
+
+// SetAssetVersion sets the value of AssetVersion.
+func (s *SubmissionCreate) SetAssetVersion(val int64) {
+	s.AssetVersion = val
+}
+
 // UpdateMapfixValidatedModelNoContent is response for UpdateMapfixValidatedModel operation.
 type UpdateMapfixValidatedModelNoContent struct{}
 
diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go
index 846550e..0423592 100644
--- a/pkg/internal/oas_server_gen.go
+++ b/pkg/internal/oas_server_gen.go
@@ -26,6 +26,12 @@ type Handler interface {
 	//
 	// POST /mapfixes/{MapfixID}/status/validator-validated
 	ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error
+	// ActionOperationFailed implements actionOperationFailed operation.
+	//
+	// (Internal endpoint) Fail an operation and write a StatusMessage.
+	//
+	// POST /operations/{OperationID}/failed
+	ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error
 	// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
 	//
 	// (Internal endpoint) Role Validator changes status from Validating -> Accepted.
@@ -44,6 +50,12 @@ type Handler interface {
 	//
 	// POST /submissions/{SubmissionID}/status/validator-validated
 	ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
+	// CreateMapfix implements createMapfix operation.
+	//
+	// Create a mapfix.
+	//
+	// POST /mapfixes
+	CreateMapfix(ctx context.Context, req *MapfixCreate) (*ID, error)
 	// CreateScript implements createScript operation.
 	//
 	// Create a new script.
@@ -56,6 +68,12 @@ type Handler interface {
 	//
 	// POST /script-policy
 	CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error)
+	// CreateSubmission implements createSubmission operation.
+	//
+	// Create a new submission.
+	//
+	// POST /submissions
+	CreateSubmission(ctx context.Context, req *SubmissionCreate) (*ID, error)
 	// GetScript implements getScript operation.
 	//
 	// Get the specified script by ID.
diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go
index 1a08c02..0f56dad 100644
--- a/pkg/internal/oas_unimplemented_gen.go
+++ b/pkg/internal/oas_unimplemented_gen.go
@@ -40,6 +40,15 @@ func (UnimplementedHandler) ActionMapfixValidated(ctx context.Context, params Ac
 	return ht.ErrNotImplemented
 }
 
+// ActionOperationFailed implements actionOperationFailed operation.
+//
+// (Internal endpoint) Fail an operation and write a StatusMessage.
+//
+// POST /operations/{OperationID}/failed
+func (UnimplementedHandler) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error {
+	return ht.ErrNotImplemented
+}
+
 // ActionSubmissionAccepted implements actionSubmissionAccepted operation.
 //
 // (Internal endpoint) Role Validator changes status from Validating -> Accepted.
@@ -67,6 +76,15 @@ func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, param
 	return ht.ErrNotImplemented
 }
 
+// CreateMapfix implements createMapfix operation.
+//
+// Create a mapfix.
+//
+// POST /mapfixes
+func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) (r *ID, _ error) {
+	return r, ht.ErrNotImplemented
+}
+
 // CreateScript implements createScript operation.
 //
 // Create a new script.
@@ -85,6 +103,15 @@ func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptP
 	return r, ht.ErrNotImplemented
 }
 
+// CreateSubmission implements createSubmission operation.
+//
+// Create a new submission.
+//
+// POST /submissions
+func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *ID, _ error) {
+	return r, ht.ErrNotImplemented
+}
+
 // GetScript implements getScript operation.
 //
 // Get the specified script by ID.
diff --git a/pkg/internal/oas_validators_gen.go b/pkg/internal/oas_validators_gen.go
index 973f07b..733e7f4 100644
--- a/pkg/internal/oas_validators_gen.go
+++ b/pkg/internal/oas_validators_gen.go
@@ -8,6 +8,56 @@ import (
 	"github.com/ogen-go/ogen/validate"
 )
 
+func (s *MapfixCreate) Validate() error {
+	if s == nil {
+		return validate.ErrNilPointer
+	}
+
+	var failures []validate.FieldError
+	if err := func() error {
+		if err := (validate.String{
+			MinLength:    0,
+			MinLengthSet: false,
+			MaxLength:    128,
+			MaxLengthSet: true,
+			Email:        false,
+			Hostname:     false,
+			Regex:        nil,
+		}).Validate(string(s.DisplayName)); err != nil {
+			return errors.Wrap(err, "string")
+		}
+		return nil
+	}(); err != nil {
+		failures = append(failures, validate.FieldError{
+			Name:  "DisplayName",
+			Error: err,
+		})
+	}
+	if err := func() error {
+		if err := (validate.String{
+			MinLength:    0,
+			MinLengthSet: false,
+			MaxLength:    128,
+			MaxLengthSet: true,
+			Email:        false,
+			Hostname:     false,
+			Regex:        nil,
+		}).Validate(string(s.Creator)); err != nil {
+			return errors.Wrap(err, "string")
+		}
+		return nil
+	}(); err != nil {
+		failures = append(failures, validate.FieldError{
+			Name:  "Creator",
+			Error: err,
+		})
+	}
+	if len(failures) > 0 {
+		return &validate.Error{Fields: failures}
+	}
+	return nil
+}
+
 func (s *Script) Validate() error {
 	if s == nil {
 		return validate.ErrNilPointer
@@ -157,3 +207,53 @@ func (s *ScriptPolicy) Validate() error {
 	}
 	return nil
 }
+
+func (s *SubmissionCreate) Validate() error {
+	if s == nil {
+		return validate.ErrNilPointer
+	}
+
+	var failures []validate.FieldError
+	if err := func() error {
+		if err := (validate.String{
+			MinLength:    0,
+			MinLengthSet: false,
+			MaxLength:    128,
+			MaxLengthSet: true,
+			Email:        false,
+			Hostname:     false,
+			Regex:        nil,
+		}).Validate(string(s.DisplayName)); err != nil {
+			return errors.Wrap(err, "string")
+		}
+		return nil
+	}(); err != nil {
+		failures = append(failures, validate.FieldError{
+			Name:  "DisplayName",
+			Error: err,
+		})
+	}
+	if err := func() error {
+		if err := (validate.String{
+			MinLength:    0,
+			MinLengthSet: false,
+			MaxLength:    128,
+			MaxLengthSet: true,
+			Email:        false,
+			Hostname:     false,
+			Regex:        nil,
+		}).Validate(string(s.Creator)); err != nil {
+			return errors.Wrap(err, "string")
+		}
+		return nil
+	}(); err != nil {
+		failures = append(failures, validate.FieldError{
+			Name:  "Creator",
+			Error: err,
+		})
+	}
+	if len(failures) > 0 {
+		return &validate.Error{Fields: failures}
+	}
+	return nil
+}
diff --git a/pkg/model/nats.go b/pkg/model/nats.go
index 6add04f..832c340 100644
--- a/pkg/model/nats.go
+++ b/pkg/model/nats.go
@@ -5,6 +5,18 @@ package model
 
 // Requests are sent from maps-service to validator
 
+type CreateSubmissionRequest struct {
+	// operation_id is passed back in the response message
+	OperationID   int32
+	ModelID       int64
+}
+
+type CreateMapfixRequest struct {
+	OperationID   int32
+	ModelID       int64
+	TargetAssetID int64
+}
+
 type ValidateSubmissionRequest struct {
 	// submission_id is passed back in the response message
 	SubmissionID     int64
diff --git a/pkg/model/operation.go b/pkg/model/operation.go
new file mode 100644
index 0000000..4949a92
--- /dev/null
+++ b/pkg/model/operation.go
@@ -0,0 +1,19 @@
+package model
+
+import "time"
+
+type OperationStatus int32
+const (
+	OperationStatusCreated   OperationStatus = 0
+	OperationStatusCompleted OperationStatus = 1
+	OperationStatusFailed    OperationStatus = 2
+)
+
+type Operation struct {
+	ID            int32 `gorm:"primaryKey"`
+	CreatedAt     time.Time
+	Owner         int64 // UserID
+	StatusID      OperationStatus
+	StatusMessage string
+	Path          string // redirect to view completed operation e.g. "/mapfixes/4"
+}
diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go
index ba38707..a0e9e02 100644
--- a/pkg/service/mapfixes.go
+++ b/pkg/service/mapfixes.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"time"
 
+	"git.itzana.me/strafesnet/go-grpc/maps"
 	"git.itzana.me/strafesnet/maps-service/pkg/api"
 	"git.itzana.me/strafesnet/maps-service/pkg/datastore"
 	"git.itzana.me/strafesnet/maps-service/pkg/model"
@@ -19,16 +20,6 @@ var(
 		model.MapfixStatusSubmitted,
 		model.MapfixStatusUnderConstruction,
 	}
-	// prevent two mapfixes with same asset id
-	ActiveMapfixStatuses = []model.MapfixStatus{
-		model.MapfixStatusUploading,
-		model.MapfixStatusValidated,
-		model.MapfixStatusValidating,
-		model.MapfixStatusAccepted,
-		model.MapfixStatusChangesRequested,
-		model.MapfixStatusSubmitted,
-		model.MapfixStatusUnderConstruction,
-	}
 	// limit mapfixes in the pipeline to one per target map
 	ActiveAcceptedMapfixStatuses = []model.MapfixStatus{
 		model.MapfixStatusUploading,
@@ -40,13 +31,12 @@ var(
 
 var (
 	ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20")
-	ErrActiveMapfixSameAssetID = errors.New("There is an active mapfix with the same AssetID")
 	ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix with the same TargetAssetID")
 	ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied)
 )
 
 // POST /mapfixes
-func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixCreate) (*api.ID, error) {
+func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTriggerCreate) (*api.OperationID, error) {
 	userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
 	if !ok {
 		return nil, ErrUserInfo
@@ -75,41 +65,40 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixCreate)
 		}
 	}
 
-	// Check if an active mapfix with the same asset id exists
+	// Check if TargetAssetID actually exists
 	{
-		filter := datastore.Optional()
-		filter.Add("asset_id", request.AssetID)
-		filter.Add("asset_version", request.AssetVersion)
-		filter.Add("status_id", ActiveMapfixStatuses)
-		active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{
-			Number: 1,
-			Size:   1,
-		},datastore.ListSortDisabled)
+		_, err := svc.Client.Get(ctx, &maps.IdMessage{
+			ID: request.TargetAssetID,
+		})
 		if err != nil {
+			// TODO: match specific does not exist grpc error
 			return nil, err
 		}
-		if len(active_mapfixes) != 0{
-			return nil, ErrActiveMapfixSameAssetID
-		}
 	}
 
-	mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{
-		ID:            0,
-		DisplayName:   request.DisplayName,
-		Creator:       request.Creator,
-		GameID:        request.GameID,
-		Submitter:     int64(userId),
-		AssetID:       request.AssetID,
-		AssetVersion:  request.AssetVersion,
-		Completed:     false,
-		TargetAssetID: request.TargetAssetID,
-		StatusID:      model.MapfixStatusUnderConstruction,
+	operation, err := svc.DB.Operations().Create(ctx, model.Operation{
+		Owner:         int64(userId),
+		StatusID:      model.OperationStatusCreated,
 	})
 	if err != nil {
 		return nil, err
 	}
-	return &api.ID{
-		ID: mapfix.ID,
+
+	create_request := model.CreateMapfixRequest{
+		OperationID:   operation.ID,
+		ModelID:       request.AssetID,
+		TargetAssetID: request.TargetAssetID,
+	}
+
+	j, err := json.Marshal(create_request)
+	if err != nil {
+		return nil, err
+	}
+
+	svc.Nats.Publish("maptest.mapfixes.create", []byte(j))
+
+	return &api.OperationID{
+		OperationID: operation.ID,
 	}, nil
 }
 
diff --git a/pkg/service/operations.go b/pkg/service/operations.go
new file mode 100644
index 0000000..eb48adc
--- /dev/null
+++ b/pkg/service/operations.go
@@ -0,0 +1,44 @@
+package service
+
+import (
+	"context"
+
+	"git.itzana.me/strafesnet/maps-service/pkg/api"
+)
+
+// GetOperation implements getOperation operation.
+//
+// Get the specified operation by ID.
+//
+// GET /operations/{OperationID}
+func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationParams) (*api.Operation, error) {
+	userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
+	if !ok {
+		return nil, ErrUserInfo
+	}
+
+	// You must be the operation owner to read it
+
+	operation, err := svc.DB.Operations().Get(ctx, params.OperationID)
+	if err != nil {
+		return nil, err
+	}
+
+	has_role, err := userInfo.IsSubmitter(uint64(operation.Owner))
+	if err != nil {
+		return nil, err
+	}
+	// check if caller is operation owner
+	if !has_role {
+		return nil, ErrPermissionDeniedNotSubmitter
+	}
+
+	return &api.Operation{
+		OperationID:   operation.ID,
+		Date:          operation.CreatedAt.Unix(),
+		Owner:         operation.Owner,
+		Status:        int32(operation.StatusID),
+		StatusMessage: operation.StatusMessage,
+		Path:          operation.Path,
+	}, nil
+}
diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go
index 5944eff..f36a18a 100644
--- a/pkg/service/submissions.go
+++ b/pkg/service/submissions.go
@@ -20,16 +20,6 @@ var(
 		model.SubmissionStatusSubmitted,
 		model.SubmissionStatusUnderConstruction,
 	}
-	// prevent two mapfixes with same asset id
-	ActiveSubmissionStatuses = []model.SubmissionStatus{
-		model.SubmissionStatusUploading,
-		model.SubmissionStatusValidated,
-		model.SubmissionStatusValidating,
-		model.SubmissionStatusAccepted,
-		model.SubmissionStatusChangesRequested,
-		model.SubmissionStatusSubmitted,
-		model.SubmissionStatusUnderConstruction,
-	}
 	// limit mapfixes in the pipeline to one per target map
 	ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{
 		model.SubmissionStatusUploading,
@@ -41,7 +31,6 @@ var(
 
 var (
 	ErrCreationPhaseSubmissionsLimit = errors.New("Active submissions limited to 20")
-	ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID")
 	ErrUploadedAssetIDAlreadyExists = errors.New("The submission UploadedAssetID is already set")
 	ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released")
 	ErrReleaseNoUploadedAssetID = errors.New("Only submissions with a UploadedAssetID can be released")
@@ -49,7 +38,7 @@ var (
 )
 
 // POST /submissions
-func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionCreate) (*api.ID, error) {
+func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) {
 	userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
 	if !ok {
 		return nil, ErrUserInfo
@@ -77,41 +66,28 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
 			return nil, ErrCreationPhaseSubmissionsLimit
 		}
 	}
-
-	// Check if an active submission with the same asset id exists
-	{
-		filter := datastore.Optional()
-		filter.Add("asset_id", request.AssetID)
-		filter.Add("asset_version", request.AssetVersion)
-		filter.Add("status_id", ActiveSubmissionStatuses)
-		active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
-			Number: 1,
-			Size:   1,
-		},datastore.ListSortDisabled)
-		if err != nil {
-			return nil, err
-		}
-		if len(active_submissions) != 0{
-			return nil, ErrActiveSubmissionSameAssetID
-		}
-	}
-
-	submission, err := svc.DB.Submissions().Create(ctx, model.Submission{
-		ID:            0,
-		DisplayName:   request.DisplayName,
-		Creator:       request.Creator,
-		GameID:        request.GameID,
-		Submitter:     int64(userId),
-		AssetID:       request.AssetID,
-		AssetVersion:  request.AssetVersion,
-		Completed:     false,
-		StatusID:      model.SubmissionStatusUnderConstruction,
+	operation, err := svc.DB.Operations().Create(ctx, model.Operation{
+		Owner:         int64(userId),
+		StatusID:      model.OperationStatusCreated,
 	})
 	if err != nil {
 		return nil, err
 	}
-	return &api.ID{
-		ID: submission.ID,
+
+	create_request := model.CreateSubmissionRequest{
+		OperationID:   operation.ID,
+		ModelID:       request.AssetID,
+	}
+
+	j, err := json.Marshal(create_request)
+	if err != nil {
+		return nil, err
+	}
+
+	svc.Nats.Publish("maptest.submissions.create", []byte(j))
+
+	return &api.OperationID{
+		OperationID: operation.ID,
 	}, nil
 }
 
diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go
index cfdf31a..c8a8c79 100644
--- a/pkg/service_internal/mapfixes.go
+++ b/pkg/service_internal/mapfixes.go
@@ -2,12 +2,31 @@ package service_internal
 
 import (
 	"context"
+	"errors"
 
-	internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
 	"git.itzana.me/strafesnet/maps-service/pkg/datastore"
+	internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
 	"git.itzana.me/strafesnet/maps-service/pkg/model"
 )
 
+var(
+	// prevent two mapfixes with same asset id
+	ActiveMapfixStatuses = []model.MapfixStatus{
+		model.MapfixStatusUploading,
+		model.MapfixStatusValidated,
+		model.MapfixStatusValidating,
+		model.MapfixStatusAccepted,
+		model.MapfixStatusChangesRequested,
+		model.MapfixStatusSubmitted,
+		model.MapfixStatusUnderConstruction,
+	}
+)
+
+var(
+	ErrActiveMapfixSameAssetID = errors.New("There is an active mapfix with the same AssetID")
+	ErrNotAssetOwner = errors.New("You can only submit an asset you own")
+)
+
 // UpdateMapfixValidatedModel implements patchMapfixModel operation.
 //
 // Update model following role restrictions.
@@ -59,3 +78,54 @@ func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.Ac
 	smap.Add("status_id", model.MapfixStatusUploaded)
 	return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap)
 }
+
+// POST /mapfixes
+func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.ID, error) {
+	// Check if an active mapfix with the same asset id exists
+	{
+		filter := datastore.Optional()
+		filter.Add("asset_id", request.AssetID)
+		filter.Add("asset_version", request.AssetVersion)
+		filter.Add("status_id", ActiveMapfixStatuses)
+		active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{
+			Number: 1,
+			Size:   1,
+		},datastore.ListSortDisabled)
+		if err != nil {
+			return nil, err
+		}
+		if len(active_mapfixes) != 0{
+			return nil, ErrActiveMapfixSameAssetID
+		}
+	}
+
+	operation, err := svc.DB.Operations().Get(ctx, request.OperationID)
+	if err != nil {
+		return nil, err
+	}
+
+	// check if user owns asset
+	// TODO: allow bypass by admin
+	if operation.Owner != request.AssetOwner {
+		return nil, ErrNotAssetOwner
+	}
+
+	mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{
+		ID:            0,
+		DisplayName:   request.DisplayName,
+		Creator:       request.Creator,
+		GameID:        request.GameID,
+		Submitter:     request.AssetOwner,
+		AssetID:       request.AssetID,
+		AssetVersion:  request.AssetVersion,
+		Completed:     false,
+		TargetAssetID: request.TargetAssetID,
+		StatusID:      model.MapfixStatusUnderConstruction,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return &internal.ID{
+		ID: mapfix.ID,
+	}, nil
+}
diff --git a/pkg/service_internal/operations.go b/pkg/service_internal/operations.go
new file mode 100644
index 0000000..2deeffd
--- /dev/null
+++ b/pkg/service_internal/operations.go
@@ -0,0 +1,21 @@
+package service_internal
+
+import (
+	"context"
+
+	"git.itzana.me/strafesnet/maps-service/pkg/datastore"
+	internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
+	"git.itzana.me/strafesnet/maps-service/pkg/model"
+)
+
+// ActionOperationFailed implements actionOperationFailed operation.
+//
+// Fail the specified OperationID with a StatusMessage.
+//
+// POST /operations/{OperationID}/failed
+func (svc *Service) ActionOperationFailed(ctx context.Context, params internal.ActionOperationFailedParams) (error) {
+	pmap := datastore.Optional()
+	pmap.Add("status_id", model.OperationStatusFailed)
+	pmap.Add("status_message", params.StatusMessage)
+	return svc.DB.Operations().Update(ctx, params.OperationID, pmap)
+}
diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go
index c9846d1..02a5533 100644
--- a/pkg/service_internal/submissions.go
+++ b/pkg/service_internal/submissions.go
@@ -2,12 +2,30 @@ package service_internal
 
 import (
 	"context"
+	"errors"
 
-	internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
 	"git.itzana.me/strafesnet/maps-service/pkg/datastore"
+	internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
 	"git.itzana.me/strafesnet/maps-service/pkg/model"
 )
 
+var(
+	// prevent two mapfixes with same asset id
+	ActiveSubmissionStatuses = []model.SubmissionStatus{
+		model.SubmissionStatusUploading,
+		model.SubmissionStatusValidated,
+		model.SubmissionStatusValidating,
+		model.SubmissionStatusAccepted,
+		model.SubmissionStatusChangesRequested,
+		model.SubmissionStatusSubmitted,
+		model.SubmissionStatusUnderConstruction,
+	}
+)
+
+var(
+	ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID")
+)
+
 // UpdateSubmissionValidatedModel implements patchSubmissionModel operation.
 //
 // Update model following role restrictions.
@@ -60,3 +78,53 @@ func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params interna
 	smap.Add("uploaded_asset_id", params.UploadedAssetID)
 	return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
 }
+
+// POST /submissions
+func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.ID, error) {
+	// Check if an active submission with the same asset id exists
+	{
+		filter := datastore.Optional()
+		filter.Add("asset_id", request.AssetID)
+		filter.Add("asset_version", request.AssetVersion)
+		filter.Add("status_id", ActiveSubmissionStatuses)
+		active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
+			Number: 1,
+			Size:   1,
+		},datastore.ListSortDisabled)
+		if err != nil {
+			return nil, err
+		}
+		if len(active_submissions) != 0{
+			return nil, ErrActiveSubmissionSameAssetID
+		}
+	}
+
+	operation, err := svc.DB.Operations().Get(ctx, request.OperationID)
+	if err != nil {
+		return nil, err
+	}
+
+	// check if user owns asset
+	// TODO: allow bypass by admin
+	if operation.Owner != request.AssetOwner {
+		return nil, ErrNotAssetOwner
+	}
+
+	submission, err := svc.DB.Submissions().Create(ctx, model.Submission{
+		ID:            0,
+		DisplayName:   request.DisplayName,
+		Creator:       request.Creator,
+		GameID:        request.GameID,
+		Submitter:     request.AssetOwner,
+		AssetID:       request.AssetID,
+		AssetVersion:  request.AssetVersion,
+		Completed:     false,
+		StatusID:      model.SubmissionStatusUnderConstruction,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return &internal.ID{
+		ID: submission.ID,
+	}, nil
+}
diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs
index 9897f9f..ed0e85c 100644
--- a/validation/api/src/internal.rs
+++ b/validation/api/src/internal.rs
@@ -150,6 +150,17 @@ impl Context{
 		).await.map_err(Error::Response)?
 		.json().await.map_err(Error::ReqwestJson)
 	}
+	pub async fn create_submission<'a>(&self,config:CreateSubmissionRequest<'a>)->Result<SubmissionIDResponse,Error>{
+		let url_raw=format!("{}/submissions",self.0.base_url);
+		let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
+
+		let body=serde_json::to_string(&config).map_err(Error::JSON)?;
+
+		response_ok(
+			self.0.post(url,body).await.map_err(Error::Reqwest)?
+		).await.map_err(Error::Response)?
+		.json().await.map_err(Error::ReqwestJson)
+	}
 	// simple submission endpoints
 	action!("submissions",action_submission_validated,config,SubmissionID,"validator-validated",config.0,);
 	action!("submissions",update_submission_validated_model,config,UpdateSubmissionModelRequest,"validated-model",config.SubmissionID,
@@ -162,6 +173,17 @@ impl Context{
 	action!("submissions",action_submission_accepted,config,ActionSubmissionAcceptedRequest,"validator-failed",config.SubmissionID,
 		("StatusMessage",config.StatusMessage.as_str())
 	);
+	pub async fn create_mapfix<'a>(&self,config:CreateMapfixRequest<'a>)->Result<MapfixIDResponse,Error>{
+		let url_raw=format!("{}/mapfixes",self.0.base_url);
+		let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
+
+		let body=serde_json::to_string(&config).map_err(Error::JSON)?;
+
+		response_ok(
+			self.0.post(url,body).await.map_err(Error::Reqwest)?
+		).await.map_err(Error::Response)?
+		.json().await.map_err(Error::ReqwestJson)
+	}
 	// simple mapfixes endpoints
 	action!("mapfixes",action_mapfix_validated,config,MapfixID,"validator-validated",config.0,);
 	action!("mapfixes",update_mapfix_validated_model,config,UpdateMapfixModelRequest,"validated-model",config.MapfixID,
@@ -172,4 +194,8 @@ impl Context{
 	action!("mapfixes",action_mapfix_accepted,config,ActionMapfixAcceptedRequest,"validator-failed",config.MapfixID,
 		("StatusMessage",config.StatusMessage.as_str())
 	);
+	// simple operation endpoint
+	action!("operations",action_operation_failed,config,ActionOperationFailedRequest,"operation-failed",config.OperationID,
+		("StatusMessage",config.StatusMessage.as_str())
+	);
 }
diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs
index 6cdd357..56743b2 100644
--- a/validation/api/src/types.rs
+++ b/validation/api/src/types.rs
@@ -61,6 +61,42 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R
 	}
 }
 
+
+#[allow(nonstandard_style)]
+#[derive(Clone,Debug,serde::Serialize)]
+pub struct CreateMapfixRequest<'a>{
+	pub OperationID:i32,
+	pub AssetOwner:i64,
+	pub DisplayName:&'a str,
+	pub Creator:&'a str,
+	pub GameID:i32,
+	pub AssetID:u64,
+	pub AssetVersion:u64,
+	pub TargetAssetID:u64,
+}
+#[allow(nonstandard_style)]
+#[derive(Clone,Debug,serde::Deserialize)]
+pub struct MapfixIDResponse{
+	pub ID:MapfixID,
+}
+
+#[allow(nonstandard_style)]
+#[derive(Clone,Debug,serde::Serialize)]
+pub struct CreateSubmissionRequest<'a>{
+	pub OperationID:i32,
+	pub AssetOwner:i64,
+	pub DisplayName:&'a str,
+	pub Creator:&'a str,
+	pub GameID:i32,
+	pub AssetID:u64,
+	pub AssetVersion:u64,
+}
+#[allow(nonstandard_style)]
+#[derive(Clone,Debug,serde::Deserialize)]
+pub struct SubmissionIDResponse{
+	pub ID:SubmissionID,
+}
+
 #[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)]
 pub struct ScriptID(pub(crate)i64);
 #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)]
@@ -200,7 +236,7 @@ pub struct ActionSubmissionAcceptedRequest{
 	pub StatusMessage:String,
 }
 
-#[derive(Clone,Copy,Debug)]
+#[derive(Clone,Copy,Debug,serde::Deserialize)]
 pub struct SubmissionID(pub i64);
 
 #[allow(nonstandard_style)]
@@ -224,5 +260,12 @@ pub struct ActionMapfixAcceptedRequest{
 	pub StatusMessage:String,
 }
 
-#[derive(Clone,Copy,Debug)]
+#[derive(Clone,Copy,Debug,serde::Deserialize)]
 pub struct MapfixID(pub i64);
+
+#[allow(nonstandard_style)]
+#[derive(Clone,Debug)]
+pub struct ActionOperationFailedRequest{
+	pub OperationID:i32,
+	pub StatusMessage:String,
+}
diff --git a/validation/src/create.rs b/validation/src/create.rs
new file mode 100644
index 0000000..1513904
--- /dev/null
+++ b/validation/src/create.rs
@@ -0,0 +1,77 @@
+use crate::rbx_util::{get_mapinfo,read_dom,MapInfo,ReadDomError,GetMapInfoError,ParseGameIDError};
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum Error{
+	ModelVersionsPage(rbx_asset::cookie::PageError),
+	EmptyVersionsPage,
+	WrongCreatorType,
+	ModelFileDownload(rbx_asset::cookie::GetError),
+	ModelFileDecode(ReadDomError),
+	GetMapInfo(GetMapInfoError),
+	ParseGameID(ParseGameIDError),
+}
+impl std::fmt::Display for Error{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for Error{}
+
+#[allow(nonstandard_style)]
+pub struct CreateRequest{
+	pub ModelID:u64,
+}
+#[allow(nonstandard_style)]
+pub struct CreateResult{
+	pub AssetOwner:i64,
+	pub DisplayName:String,
+	pub Creator:String,
+	pub GameID:i32,
+	pub AssetVersion:u64,
+}
+impl crate::message_handler::MessageHandler{
+	pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{
+		// discover the latest asset version
+		let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{
+			asset_id:create_info.ModelID,
+			cursor:None
+		}).await.map_err(Error::ModelVersionsPage)?;
+
+		// grab version info
+		let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?;
+
+		if first_version.creatorType!="User"{
+			return Err(Error::WrongCreatorType);
+		}
+
+		let asset_creator_id=first_version.creatorTargetId;
+		let asset_version=first_version.assetVersionNumber;
+
+		// download the map model version
+		let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{
+			asset_id:create_info.ModelID,
+			version:Some(asset_version),
+		}).await.map_err(Error::ModelFileDownload)?;
+
+		// decode dom (slow!)
+		let dom=read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?;
+
+		// parse create fields out of asset
+		let MapInfo{
+			display_name,
+			creator,
+			game_id,
+		}=get_mapinfo(&dom).map_err(Error::GetMapInfo)?;
+
+		let game_id=game_id.map_err(Error::ParseGameID)?;
+
+		Ok(CreateResult{
+			AssetOwner:asset_creator_id as i64,
+			DisplayName:display_name.unwrap_or_default().to_owned(),
+			Creator:creator.unwrap_or_default().to_owned(),
+			GameID:game_id as i32,
+			AssetVersion:asset_version,
+		})
+	}
+}
diff --git a/validation/src/create_mapfix.rs b/validation/src/create_mapfix.rs
new file mode 100644
index 0000000..6dbe905
--- /dev/null
+++ b/validation/src/create_mapfix.rs
@@ -0,0 +1,47 @@
+use crate::nats_types::CreateMapfixRequest;
+use crate::create::CreateRequest;
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum Error{
+	ApiActionMapfixCreate(submissions_api::Error),
+	ApiActionOperationFailed(submissions_api::Error),
+}
+impl std::fmt::Display for Error{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for Error{}
+
+impl crate::message_handler::MessageHandler{
+	pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),Error>{
+		let create_result=self.create_inner(CreateRequest{
+			ModelID:create_info.ModelID,
+		}).await;
+
+		match create_result{
+			Ok(create_request)=>{
+				// call create on api
+				self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{
+					OperationID:create_info.OperationID,
+					AssetOwner:create_request.AssetOwner,
+					DisplayName:create_request.DisplayName.as_str(),
+					Creator:create_request.Creator.as_str(),
+					GameID:create_request.GameID,
+					AssetID:create_info.ModelID,
+					AssetVersion:create_request.AssetVersion,
+					TargetAssetID:create_info.TargetAssetID,
+				}).await.map_err(Error::ApiActionMapfixCreate)?;
+			},
+			Err(e)=>{
+				self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
+					OperationID:create_info.OperationID,
+					StatusMessage:format!("{e}"),
+				}).await.map_err(Error::ApiActionOperationFailed)?;
+			},
+		}
+
+		Ok(())
+	}
+}
diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs
new file mode 100644
index 0000000..31f2a0e
--- /dev/null
+++ b/validation/src/create_submission.rs
@@ -0,0 +1,46 @@
+use crate::nats_types::CreateSubmissionRequest;
+use crate::create::CreateRequest;
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum Error{
+	ApiActionSubmissionCreate(submissions_api::Error),
+	ApiActionOperationFailed(submissions_api::Error),
+}
+impl std::fmt::Display for Error{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for Error{}
+
+impl crate::message_handler::MessageHandler{
+	pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{
+		let create_result=self.create_inner(CreateRequest{
+			ModelID:create_info.ModelID,
+		}).await;
+
+		match create_result{
+			Ok(create_request)=>{
+				// call create on api
+				self.api.create_submission(submissions_api::types::CreateSubmissionRequest{
+					OperationID:create_info.OperationID,
+					AssetOwner:create_request.AssetOwner,
+					DisplayName:create_request.DisplayName.as_str(),
+					Creator:create_request.Creator.as_str(),
+					GameID:create_request.GameID,
+					AssetID:create_info.ModelID,
+					AssetVersion:create_request.AssetVersion,
+				}).await.map_err(Error::ApiActionSubmissionCreate)?;
+			},
+			Err(e)=>{
+				self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
+					OperationID:create_info.OperationID,
+					StatusMessage:format!("{e}"),
+				}).await.map_err(Error::ApiActionOperationFailed)?;
+			},
+		}
+
+		Ok(())
+	}
+}
diff --git a/validation/src/main.rs b/validation/src/main.rs
index 9234aa8..30b443d 100644
--- a/validation/src/main.rs
+++ b/validation/src/main.rs
@@ -1,9 +1,12 @@
 use futures::StreamExt;
 
+mod rbx_util;
 mod message_handler;
 mod nats_types;
 mod types;
-mod uploader;
+mod create;
+mod create_mapfix;
+mod create_submission;
 mod upload_mapfix;
 mod upload_submission;
 mod validator;
@@ -84,7 +87,7 @@ async fn main()->Result<(),StartupError>{
 					Err(e)=>println!("[Validation] There was an error, oopsie! {e}"),
 				}
 				// explicitly call drop to make the move semantics and permit release more obvious
-				core::mem::drop(permit);
+				drop(permit);
 			});
 		}
 	};
diff --git a/validation/src/message_handler.rs b/validation/src/message_handler.rs
index c6cd200..bff0f10 100644
--- a/validation/src/message_handler.rs
+++ b/validation/src/message_handler.rs
@@ -5,10 +5,12 @@ pub enum HandleMessageError{
 	DoubleAck(async_nats::Error),
 	Json(serde_json::Error),
 	UnknownSubject(String),
-	UploadMapfix(crate::upload_mapfix::UploadError),
-	UploadSubmission(crate::upload_submission::UploadError),
-	ValidateMapfix(crate::validate_mapfix::ValidateMapfixError),
-	ValidateSubmission(crate::validate_submission::ValidateSubmissionError),
+	CreateMapfix(crate::create_mapfix::Error),
+	CreateSubmission(crate::create_submission::Error),
+	UploadMapfix(crate::upload_mapfix::Error),
+	UploadSubmission(crate::upload_submission::Error),
+	ValidateMapfix(crate::validate_mapfix::Error),
+	ValidateSubmission(crate::validate_submission::Error),
 }
 impl std::fmt::Display for HandleMessageError{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -24,10 +26,9 @@ fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleM
 }
 
 pub struct MessageHandler{
-	upload_mapfix:crate::upload_mapfix::Uploader,
-	upload_submission:crate::upload_submission::Uploader,
-	validate_mapfix:crate::validate_mapfix::Validator,
-	validate_submission:crate::validate_submission::Validator,
+	pub(crate) cookie_context:rbx_asset::cookie::CookieContext,
+	pub(crate) group_id:Option<u64>,
+	pub(crate) api:submissions_api::internal::Context,
 }
 
 impl MessageHandler{
@@ -37,20 +38,21 @@ impl MessageHandler{
 		api:submissions_api::internal::Context,
 	)->Self{
 		Self{
-			upload_mapfix:crate::upload_mapfix::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())),
-			upload_submission:crate::upload_submission::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())),
-			validate_mapfix:crate::validate_mapfix::Validator::new(crate::validator::Validator::new(cookie_context.clone(),api.clone())),
-			validate_submission:crate::validate_submission::Validator::new(crate::validator::Validator::new(cookie_context,api)),
+			cookie_context,
+			group_id,
+			api,
 		}
 	}
 	pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{
 		let message=message_result.map_err(HandleMessageError::Messages)?;
 		message.double_ack().await.map_err(HandleMessageError::DoubleAck)?;
 		match message.subject.as_str(){
-			"maptest.mapfixes.upload"=>self.upload_mapfix.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix),
-			"maptest.submissions.upload"=>self.upload_submission.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission),
-			"maptest.mapfixes.validate"=>self.validate_mapfix.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix),
-			"maptest.submissions.validate"=>self.validate_submission.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateSubmission),
+			"maptest.mapfixes.create"=>self.create_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateMapfix),
+			"maptest.submissions.create"=>self.create_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateSubmission),
+			"maptest.mapfixes.upload"=>self.upload_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix),
+			"maptest.submissions.upload"=>self.upload_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission),
+			"maptest.mapfixes.validate"=>self.validate_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix),
+			"maptest.submissions.validate"=>self.validate_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateSubmission),
 			other=>Err(HandleMessageError::UnknownSubject(other.to_owned()))
 		}
 	}
diff --git a/validation/src/nats_types.rs b/validation/src/nats_types.rs
index 15f0924..1401f83 100644
--- a/validation/src/nats_types.rs
+++ b/validation/src/nats_types.rs
@@ -4,6 +4,22 @@
 // Requests are sent from maps-service to validator
 // Validation invokes the REST api to update the submissions
 
+#[allow(nonstandard_style)]
+#[derive(serde::Deserialize)]
+pub struct CreateSubmissionRequest{
+	// operation_id is passed back in the response message
+	pub OperationID:i32,
+	pub ModelID:u64,
+}
+
+#[allow(nonstandard_style)]
+#[derive(serde::Deserialize)]
+pub struct CreateMapfixRequest{
+	pub OperationID:i32,
+	pub ModelID:u64,
+	pub TargetAssetID:u64,
+}
+
 #[allow(nonstandard_style)]
 #[derive(serde::Deserialize)]
 pub struct ValidateSubmissionRequest{
diff --git a/validation/src/rbx_util.rs b/validation/src/rbx_util.rs
new file mode 100644
index 0000000..947bb9c
--- /dev/null
+++ b/validation/src/rbx_util.rs
@@ -0,0 +1,119 @@
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum ReadDomError{
+	Binary(rbx_binary::DecodeError),
+	Xml(rbx_xml::DecodeError),
+	Read(std::io::Error),
+	Seek(std::io::Error),
+	UnknownFormat([u8;8]),
+}
+impl std::fmt::Display for ReadDomError{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for ReadDomError{}
+
+pub fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{
+	let mut first_8=[0u8;8];
+	std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?;
+	std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?;
+	match &first_8[0..4]{
+		b"<rob"=>{
+			match &first_8[4..8]{
+				b"lox!"=>rbx_binary::from_reader(input).map_err(ReadDomError::Binary),
+				b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(ReadDomError::Xml),
+				_=>Err(ReadDomError::UnknownFormat(first_8)),
+			}
+		},
+		_=>Err(ReadDomError::UnknownFormat(first_8)),
+	}
+}
+
+pub fn class_is_a(class:&str,superclass:&str)->bool{
+	if class==superclass{
+		return true
+	}
+	let class_descriptor=rbx_reflection_database::get().classes.get(class);
+	if let Some(descriptor)=&class_descriptor{
+		if let Some(class_super)=&descriptor.superclass{
+			return class_is_a(&class_super,superclass)
+		}
+	}
+	false
+}
+
+pub fn find_first_child_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str,class:&str)->Option<&'a rbx_dom_weak::Instance>{
+	for &referent in instance.children(){
+		if let Some(c)=dom.get_by_ref(referent){
+			if c.name==name&&class_is_a(c.class.as_str(),class) {
+				return Some(c);
+			}
+		}
+	}
+	None
+}
+
+pub enum GameID{
+	Bhop=1,
+	Surf=2,
+	FlyTrials=5,
+}
+#[derive(Debug)]
+pub struct ParseGameIDError;
+impl std::str::FromStr for GameID{
+	type Err=ParseGameIDError;
+	fn from_str(s:&str)->Result<Self,Self::Err>{
+		if s.starts_with("bhop_"){
+			return Ok(GameID::Bhop);
+		}
+		if s.starts_with("surf_"){
+			return Ok(GameID::Surf);
+		}
+		if s.starts_with("flytrials_"){
+			return Ok(GameID::FlyTrials);
+		}
+		return Err(ParseGameIDError);
+	}
+}
+
+pub struct MapInfo<'a>{
+	pub display_name:Result<&'a str,StringValueError>,
+	pub creator:Result<&'a str,StringValueError>,
+	pub game_id:Result<GameID,ParseGameIDError>,
+}
+
+pub enum StringValueError{
+	ObjectNotFound,
+	ValueNotSet,
+	NonStringValue,
+}
+
+fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringValueError>{
+	let instance=instance.ok_or(StringValueError::ObjectNotFound)?;
+	let value=instance.properties.get("Value").ok_or(StringValueError::ValueNotSet)?;
+	match value{
+		rbx_dom_weak::types::Variant::String(value)=>Ok(value),
+		_=>Err(StringValueError::NonStringValue),
+	}
+}
+
+#[derive(Debug)]
+pub enum GetMapInfoError{
+	ModelFileRootMustHaveOneChild,
+	ModelFileChildRefIsNil,
+}
+
+pub fn get_mapinfo(dom:&rbx_dom_weak::WeakDom)->Result<MapInfo,GetMapInfoError>{
+	let &[map_ref]=dom.root().children()else{
+		return Err(GetMapInfoError::ModelFileRootMustHaveOneChild);
+	};
+	let model_instance=dom.get_by_ref(map_ref).ok_or(GetMapInfoError::ModelFileChildRefIsNil)?;
+
+	Ok(MapInfo{
+		display_name:string_value(find_first_child_class(dom,model_instance,"DisplayName","StringValue")),
+		creator:string_value(find_first_child_class(dom,model_instance,"Creator","StringValue")),
+		game_id:model_instance.name.parse(),
+	})
+}
diff --git a/validation/src/upload_mapfix.rs b/validation/src/upload_mapfix.rs
index 71c37fd..78108a4 100644
--- a/validation/src/upload_mapfix.rs
+++ b/validation/src/upload_mapfix.rs
@@ -2,48 +2,43 @@ use crate::nats_types::UploadMapfixRequest;
 
 #[allow(dead_code)]
 #[derive(Debug)]
-pub enum UploadError{
+pub enum Error{
 	Get(rbx_asset::cookie::GetError),
 	Json(serde_json::Error),
 	Upload(rbx_asset::cookie::UploadError),
 	ApiActionMapfixUploaded(submissions_api::Error),
 }
-impl std::fmt::Display for UploadError{
+impl std::fmt::Display for Error{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
 		write!(f,"{self:?}")
 	}
 }
-impl std::error::Error for UploadError{}
+impl std::error::Error for Error{}
 
-pub struct Uploader(crate::uploader::Uploader);
-impl Uploader{
-	pub const fn new(inner:crate::uploader::Uploader)->Self{
-		Self(inner)
-	}
-	pub async fn upload(&self,upload_info:UploadMapfixRequest)->Result<(),UploadError>{
-		let Self(uploader)=self;
+impl crate::message_handler::MessageHandler{
+	pub async fn upload_mapfix(&self,upload_info:UploadMapfixRequest)->Result<(),Error>{
 		// download the map model version
-		let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
+		let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{
 			asset_id:upload_info.ModelID,
 			version:Some(upload_info.ModelVersion),
-		}).await.map_err(UploadError::Get)?;
+		}).await.map_err(Error::Get)?;
 
 		// upload the map to the strafesnet group
-		let _upload_response=uploader.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{
+		let _upload_response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{
 			assetid:upload_info.TargetAssetID,
-			groupId:uploader.group_id,
+			groupId:self.group_id,
 			name:None,
 			description:None,
 			ispublic:None,
 			allowComments:None,
-		},model_data).await.map_err(UploadError::Upload)?;
+		},model_data).await.map_err(Error::Upload)?;
 
 		// that's it, the database entry does not need to be changed.
 
 		// mark mapfix as uploaded, TargetAssetID is unchanged
-		uploader.api.action_mapfix_uploaded(submissions_api::types::ActionMapfixUploadedRequest{
+		self.api.action_mapfix_uploaded(submissions_api::types::ActionMapfixUploadedRequest{
 			MapfixID:upload_info.MapfixID,
-		}).await.map_err(UploadError::ApiActionMapfixUploaded)?;
+		}).await.map_err(Error::ApiActionMapfixUploaded)?;
 
 		Ok(())
 	}
diff --git a/validation/src/upload_submission.rs b/validation/src/upload_submission.rs
index d5c0983..ef8618a 100644
--- a/validation/src/upload_submission.rs
+++ b/validation/src/upload_submission.rs
@@ -2,47 +2,42 @@ use crate::nats_types::UploadSubmissionRequest;
 
 #[allow(dead_code)]
 #[derive(Debug)]
-pub enum UploadError{
+pub enum Error{
 	Get(rbx_asset::cookie::GetError),
 	Json(serde_json::Error),
 	Create(rbx_asset::cookie::CreateError),
 	SystemTime(std::time::SystemTimeError),
 	ApiActionSubmissionUploaded(submissions_api::Error),
 }
-impl std::fmt::Display for UploadError{
+impl std::fmt::Display for Error{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
 		write!(f,"{self:?}")
 	}
 }
-impl std::error::Error for UploadError{}
+impl std::error::Error for Error{}
 
-pub struct Uploader(crate::uploader::Uploader);
-impl Uploader{
-	pub const fn new(inner:crate::uploader::Uploader)->Self{
-		Self(inner)
-	}
-	pub async fn upload(&self,upload_info:UploadSubmissionRequest)->Result<(),UploadError>{
-		let Self(uploader)=self;
+impl crate::message_handler::MessageHandler{
+	pub async fn upload_submission(&self,upload_info:UploadSubmissionRequest)->Result<(),Error>{
 		// download the map model version
-		let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
+		let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{
 			asset_id:upload_info.ModelID,
 			version:Some(upload_info.ModelVersion),
-		}).await.map_err(UploadError::Get)?;
+		}).await.map_err(Error::Get)?;
 
 		// upload the map to the strafesnet group
-		let upload_response=uploader.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
+		let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{
 			name:upload_info.ModelName.clone(),
 			description:"".to_owned(),
 			ispublic:false,
 			allowComments:false,
-			groupId:uploader.group_id,
-		},model_data).await.map_err(UploadError::Create)?;
+			groupId:self.group_id,
+		},model_data).await.map_err(Error::Create)?;
 
 		// note the asset id of the created model for later release, and mark the submission as uploaded
-		uploader.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{
+		self.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{
 			SubmissionID:upload_info.SubmissionID,
 			UploadedAssetID:upload_response.AssetId,
-		}).await.map_err(UploadError::ApiActionSubmissionUploaded)?;
+		}).await.map_err(Error::ApiActionSubmissionUploaded)?;
 
 		Ok(())
 	}
diff --git a/validation/src/uploader.rs b/validation/src/uploader.rs
deleted file mode 100644
index ca1ba06..0000000
--- a/validation/src/uploader.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-pub struct Uploader{
-	pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext,
-	pub(crate) group_id:Option<u64>,
-	pub(crate) api:submissions_api::internal::Context,
-}
-impl Uploader{
-	pub const fn new(
-		roblox_cookie:rbx_asset::cookie::CookieContext,
-		group_id:Option<u64>,
-		api:submissions_api::internal::Context,
-	)->Self{
-		Self{
-			roblox_cookie,
-			group_id,
-			api,
-		}
-	}
-}
diff --git a/validation/src/validate_mapfix.rs b/validation/src/validate_mapfix.rs
index f9334d5..c1607f6 100644
--- a/validation/src/validate_mapfix.rs
+++ b/validation/src/validate_mapfix.rs
@@ -2,41 +2,35 @@ use crate::nats_types::ValidateMapfixRequest;
 
 #[allow(dead_code)]
 #[derive(Debug)]
-pub enum ValidateMapfixError{
+pub enum Error{
 	ApiActionMapfixValidate(submissions_api::Error),
 }
-impl std::fmt::Display for ValidateMapfixError{
+impl std::fmt::Display for Error{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
 		write!(f,"{self:?}")
 	}
 }
-impl std::error::Error for ValidateMapfixError{}
-
-pub struct Validator(crate::validator::Validator);
-impl Validator{
-	pub const fn new(inner:crate::validator::Validator)->Self{
-		Self(inner)
-	}
-	pub async fn validate(&self,validate_info:ValidateMapfixRequest)->Result<(),ValidateMapfixError>{
-		let Self(validator)=self;
+impl std::error::Error for Error{}
 
+impl crate::message_handler::MessageHandler{
+	pub async fn validate_mapfix(&self,validate_info:ValidateMapfixRequest)->Result<(),Error>{
 		let mapfix_id=validate_info.MapfixID;
-		let validate_result=validator.validate(validate_info.into()).await;
+		let validate_result=self.validate_inner(validate_info.into()).await;
 
 		// update the mapfix depending on the result
 		match &validate_result{
 			Ok(())=>{
 				// update the mapfix model status to validated
-				validator.api.action_mapfix_validated(
+				self.api.action_mapfix_validated(
 					submissions_api::types::MapfixID(mapfix_id)
-				).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?;
+				).await.map_err(Error::ApiActionMapfixValidate)?;
 			},
 			Err(e)=>{
 				// update the mapfix model status to accepted
-				validator.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{
+				self.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{
 					MapfixID:mapfix_id,
 					StatusMessage:format!("{e}"),
-				}).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?;
+				}).await.map_err(Error::ApiActionMapfixValidate)?;
 			},
 		}
 
diff --git a/validation/src/validate_submission.rs b/validation/src/validate_submission.rs
index 51ae1f3..5859bd6 100644
--- a/validation/src/validate_submission.rs
+++ b/validation/src/validate_submission.rs
@@ -2,41 +2,35 @@ use crate::nats_types::ValidateSubmissionRequest;
 
 #[allow(dead_code)]
 #[derive(Debug)]
-pub enum ValidateSubmissionError{
+pub enum Error{
 	ApiActionSubmissionValidate(submissions_api::Error),
 }
-impl std::fmt::Display for ValidateSubmissionError{
+impl std::fmt::Display for Error{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
 		write!(f,"{self:?}")
 	}
 }
-impl std::error::Error for ValidateSubmissionError{}
-
-pub struct Validator(crate::validator::Validator);
-impl Validator{
-	pub const fn new(inner:crate::validator::Validator)->Self{
-		Self(inner)
-	}
-	pub async fn validate(&self,validate_info:ValidateSubmissionRequest)->Result<(),ValidateSubmissionError>{
-		let Self(validator)=self;
+impl std::error::Error for Error{}
 
+impl crate::message_handler::MessageHandler{
+	pub async fn validate_submission(&self,validate_info:ValidateSubmissionRequest)->Result<(),Error>{
 		let submission_id=validate_info.SubmissionID;
-		let validate_result=validator.validate(validate_info.into()).await;
+		let validate_result=self.validate_inner(validate_info.into()).await;
 
 		// update the submission depending on the result
 		match &validate_result{
 			Ok(())=>{
 				// update the submission model status to validated
-				validator.api.action_submission_validated(
+				self.api.action_submission_validated(
 					submissions_api::types::SubmissionID(submission_id)
-				).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?;
+				).await.map_err(Error::ApiActionSubmissionValidate)?;
 			},
 			Err(e)=>{
 				// update the submission model status to accepted
-				validator.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{
+				self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{
 					SubmissionID:submission_id,
 					StatusMessage:format!("{e}"),
-				}).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?;
+				}).await.map_err(Error::ApiActionSubmissionValidate)?;
 			},
 		}
 
diff --git a/validation/src/validator.rs b/validation/src/validator.rs
index 5d3b1ed..c114ca3 100644
--- a/validation/src/validator.rs
+++ b/validation/src/validator.rs
@@ -1,6 +1,7 @@
 use futures::TryStreamExt;
 use submissions_api::types::ResourceType;
 
+use crate::rbx_util::{class_is_a,read_dom,ReadDomError};
 use crate::types::ResourceID;
 
 const SCRIPT_CONCURRENCY:usize=16;
@@ -86,24 +87,10 @@ impl From<crate::nats_types::ValidateSubmissionRequest> for ValidateRequest{
 	}
 }
 
-pub struct Validator{
-	pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext,
-	pub(crate) api:submissions_api::internal::Context,
-}
-
-impl Validator{
-	pub const fn new(
-		roblox_cookie:rbx_asset::cookie::CookieContext,
-		api:submissions_api::internal::Context,
-	)->Self{
-		Self{
-			roblox_cookie,
-			api,
-		}
-	}
-	pub async fn validate(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{
+impl crate::message_handler::MessageHandler{
+	pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{
 		// download map
-		let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
+		let data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{
 			asset_id:validate_info.ModelID,
 			version:Some(validate_info.ModelVersion),
 		}).await.map_err(ValidateError::ModelFileDownload)?;
@@ -238,7 +225,7 @@ impl Validator{
 			// upload a model lol
 			let model_id=if let Some(model_id)=validate_info.ValidatedModelID{
 				// upload to existing id
-				let response=self.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{
+				let response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{
 					assetid:model_id,
 					name:None,
 					description:None,
@@ -254,7 +241,7 @@ impl Validator{
 					return Err(ValidateError::ModelFileChildRefIsNil);
 				};
 				// create new model
-				let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
+				let response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{
 					name:map_instance.name.clone(),
 					description:"".to_owned(),
 					ispublic:true,
@@ -289,51 +276,6 @@ impl Validator{
 	}
 }
 
-#[allow(dead_code)]
-#[derive(Debug)]
-pub enum ReadDomError{
-	Binary(rbx_binary::DecodeError),
-	Xml(rbx_xml::DecodeError),
-	Read(std::io::Error),
-	Seek(std::io::Error),
-	UnknownFormat([u8;8]),
-}
-impl std::fmt::Display for ReadDomError{
-	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
-		write!(f,"{self:?}")
-	}
-}
-impl std::error::Error for ReadDomError{}
-
-fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{
-	let mut first_8=[0u8;8];
-	std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?;
-	std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?;
-	match &first_8[0..4]{
-		b"<rob"=>{
-			match &first_8[4..8]{
-				b"lox!"=>rbx_binary::from_reader(input).map_err(ReadDomError::Binary),
-				b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(ReadDomError::Xml),
-				_=>Err(ReadDomError::UnknownFormat(first_8)),
-			}
-		},
-		_=>Err(ReadDomError::UnknownFormat(first_8)),
-	}
-}
-
-fn class_is_a(class:&str,superclass:&str)->bool{
-	if class==superclass{
-		return true
-	}
-	let class_descriptor=rbx_reflection_database::get().classes.get(class);
-	if let Some(descriptor)=&class_descriptor{
-		if let Some(class_super)=&descriptor.superclass{
-			return class_is_a(&class_super,superclass)
-		}
-	}
-	false
-}
-
 fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str){
 	for &referent in instance.children(){
 		if let Some(c)=dom.get_by_ref(referent){
diff --git a/web/src/app/maps/[mapId]/fix/_game.tsx b/web/src/app/maps/[mapId]/fix/_game.tsx
deleted file mode 100644
index e754601..0000000
--- a/web/src/app/maps/[mapId]/fix/_game.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { FormControl, Select, InputLabel, MenuItem } from "@mui/material";
-import { styled } from '@mui/material/styles';
-import InputBase from '@mui/material/InputBase';
-import React from "react";
-import { SelectChangeEvent } from "@mui/material";
-
-// TODO: Properly style everything instead of pasting 🤚
-
-type GameSelectionProps = {
-	game: number;
-	setGame: React.Dispatch<React.SetStateAction<number>>;
-};
-
-const BootstrapInput = styled(InputBase)(({ theme }) => ({
-	'label + &': {
-	  marginTop: theme.spacing(3),
-	},
-	'& .MuiInputBase-input': {
-	  backgroundColor: '#0000',
-	  color: '#FFF',
-	  border: '1px solid rgba(175, 175, 175, 0.66)',
-	  fontSize: 16,
-	  padding: '10px 26px 10px 12px',
-	  transition: theme.transitions.create(['border-color', 'box-shadow']),
-	  fontFamily: [
-		'-apple-system',
-		'BlinkMacSystemFont',
-		'"Segoe UI"',
-		'Roboto',
-		'"Helvetica Neue"',
-		'Arial',
-		'sans-serif',
-		'"Apple Color Emoji"',
-		'"Segoe UI Emoji"',
-		'"Segoe UI Symbol"',
-	  ].join(','),
-	  '&:focus': {
-		borderRadius: 4,
-		borderColor: '#80bdff',
-		boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
-	  },
-	},
-  }));
-
-export default function GameSelection({ game, setGame }: GameSelectionProps) {
-	const handleChange = (event: SelectChangeEvent) => {
-		setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this
-	};
-
-	return (
-		<FormControl>
-			<InputLabel sx={{ color: "#646464" }}>Game</InputLabel>
-			<Select
-				value={String(game)}
-				label="Game"
-				onChange={handleChange}
-				input={<BootstrapInput />}
-			>
-				<MenuItem value={1}>Bhop</MenuItem>
-				<MenuItem value={2}>Surf</MenuItem>
-				<MenuItem value={3}>Fly Trials</MenuItem>
-			</Select>
-		</FormControl>
-	);
-}
\ No newline at end of file
diff --git a/web/src/app/maps/[mapId]/fix/page.tsx b/web/src/app/maps/[mapId]/fix/page.tsx
index 5e68374..7408e43 100644
--- a/web/src/app/maps/[mapId]/fix/page.tsx
+++ b/web/src/app/maps/[mapId]/fix/page.tsx
@@ -2,20 +2,14 @@
 
 import { Button, TextField } from "@mui/material"
 
-import GameSelection from "./_game";
 import SendIcon from '@mui/icons-material/Send';
 import Webpage from "@/app/_components/webpage";
 import { useParams } from "next/navigation";
-import React, { useState } from "react";
 
 import "./(styles)/page.scss"
 
 interface MapfixPayload {
-	DisplayName: string;
-	Creator: string;
-	GameID: number;
 	AssetID: number;
-	AssetVersion: number;
 	TargetAssetID: number;
 }
 interface IdResponse {
@@ -23,7 +17,6 @@ interface IdResponse {
 }
 
 export default function MapfixInfoPage() {
-	const [game, setGame] = useState(1);
 	const dynamicId = useParams<{ mapId: string }>();
 
 	const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
@@ -33,11 +26,7 @@ export default function MapfixInfoPage() {
 		const formData = new FormData(form);
 
 		const payload: MapfixPayload = {
-			DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change
-			Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change
-			GameID: game,
 			AssetID: Number((formData.get("asset-id") as string) ?? "0"),
-			AssetVersion: 0,
 			TargetAssetID: Number(dynamicId.mapId),
 		};
 
@@ -80,11 +69,7 @@ export default function MapfixInfoPage() {
 				</header>
 				<form onSubmit={handleSubmit}>
 					{/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */}
-					<TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/>
-					<TextField className="form-field" id="creator"      name="creator"      label="Creator"      variant="outlined"/>
 					<TextField className="form-field" id="asset-id"     name="asset-id"     label="Asset ID"     variant="outlined"/>
-					{/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */}
-					<GameSelection game={game} setGame={setGame} />
 					<span className="spacer form-spacer"></span>
 					<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
 						width: "400px",
diff --git a/web/src/app/submit/_game.tsx b/web/src/app/submit/_game.tsx
deleted file mode 100644
index e754601..0000000
--- a/web/src/app/submit/_game.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { FormControl, Select, InputLabel, MenuItem } from "@mui/material";
-import { styled } from '@mui/material/styles';
-import InputBase from '@mui/material/InputBase';
-import React from "react";
-import { SelectChangeEvent } from "@mui/material";
-
-// TODO: Properly style everything instead of pasting 🤚
-
-type GameSelectionProps = {
-	game: number;
-	setGame: React.Dispatch<React.SetStateAction<number>>;
-};
-
-const BootstrapInput = styled(InputBase)(({ theme }) => ({
-	'label + &': {
-	  marginTop: theme.spacing(3),
-	},
-	'& .MuiInputBase-input': {
-	  backgroundColor: '#0000',
-	  color: '#FFF',
-	  border: '1px solid rgba(175, 175, 175, 0.66)',
-	  fontSize: 16,
-	  padding: '10px 26px 10px 12px',
-	  transition: theme.transitions.create(['border-color', 'box-shadow']),
-	  fontFamily: [
-		'-apple-system',
-		'BlinkMacSystemFont',
-		'"Segoe UI"',
-		'Roboto',
-		'"Helvetica Neue"',
-		'Arial',
-		'sans-serif',
-		'"Apple Color Emoji"',
-		'"Segoe UI Emoji"',
-		'"Segoe UI Symbol"',
-	  ].join(','),
-	  '&:focus': {
-		borderRadius: 4,
-		borderColor: '#80bdff',
-		boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
-	  },
-	},
-  }));
-
-export default function GameSelection({ game, setGame }: GameSelectionProps) {
-	const handleChange = (event: SelectChangeEvent) => {
-		setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this
-	};
-
-	return (
-		<FormControl>
-			<InputLabel sx={{ color: "#646464" }}>Game</InputLabel>
-			<Select
-				value={String(game)}
-				label="Game"
-				onChange={handleChange}
-				input={<BootstrapInput />}
-			>
-				<MenuItem value={1}>Bhop</MenuItem>
-				<MenuItem value={2}>Surf</MenuItem>
-				<MenuItem value={3}>Fly Trials</MenuItem>
-			</Select>
-		</FormControl>
-	);
-}
\ No newline at end of file
diff --git a/web/src/app/submit/page.tsx b/web/src/app/submit/page.tsx
index b7108e9..caaef78 100644
--- a/web/src/app/submit/page.tsx
+++ b/web/src/app/submit/page.tsx
@@ -2,26 +2,19 @@
 
 import { Button, TextField } from "@mui/material"
 
-import GameSelection from "./_game";
 import SendIcon from '@mui/icons-material/Send';
 import Webpage from "@/app/_components/webpage"
-import React, { useState } from "react";
 
 import "./(styles)/page.scss"
 
 interface SubmissionPayload {
-	DisplayName: string;
-	Creator: string;
-	GameID: number;
 	AssetID: number;
-	AssetVersion: number;
 }
 interface IdResponse {
 	ID: number;
 }
 
 export default function SubmissionInfoPage() {
-	const [game, setGame] = useState(1);
 
 	const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
 		event.preventDefault();
@@ -30,11 +23,7 @@ export default function SubmissionInfoPage() {
 		const formData = new FormData(form);
 
 		const payload: SubmissionPayload = {
-			DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change
-			Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change
-			GameID: game,
 			AssetID: Number((formData.get("asset-id") as string) ?? "0"),
-			AssetVersion: 0,
 		};
 
 		console.log(payload)
@@ -75,12 +64,7 @@ export default function SubmissionInfoPage() {
 					<span className="spacer form-spacer"></span>
 				</header>
 				<form onSubmit={handleSubmit}>
-					{/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */}
-					<TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/>
-					<TextField className="form-field" id="creator"      name="creator"      label="Creator"      variant="outlined"/>
 					<TextField className="form-field" id="asset-id"     name="asset-id"     label="Asset ID"     variant="outlined"/>
-					{/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */}
-					<GameSelection game={game} setGame={setGame} />
 					<span className="spacer form-spacer"></span>
 					<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
 						width: "400px",