Repository: kubeflow/model-registry

Issue:
[MLMD Removal] Remove the MLMD sidecar from the current manifests and switch to the new internal service
### Description

This task involves removing the MLMD sidecar from the current deployment manifests and transitioning to the new internal service that manages metadata operations. The updated manifests should reflect any necessary configuration changes related to the new service, such as new environment variables or health-check endpoints.

### Acceptance Criteria

- Manifest Updates:

  - All deployment manifests (Kubernetes YAML files, Docker Compose files, etc.) have been updated to remove the MLMD sidecar container.
  - Updated manifests should now reference and launch the new internal service in place of the legacy MLMD component.

- Configuration Consistency:

  - Any new configuration requirements (such as environment variables or connection strings specific to the new internal service) are clearly defined and integrated into the manifests.
  - Health-checks and dependency definitions are adapted to monitor the new service correctly.

- Seamless Transition:
 
  - The system continues to provide full API functionality with no observable behavior changes from the external user's perspective.
  - Automated integration and end-to-end tests pass, demonstrating that the new internal service is operating effectively and that there is no regression in performance or functionality.

- Documentation & Validation:

  - Documentation is updated to reflect the removal of the MLMD sidecar and the adoption of the internal service, including any changes in the deployment process and configuration guidelines.



Discussion:

Solution patch:
diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore
index 18a46d80..806321a6 100644
--- a/.openapi-generator-ignore
+++ b/.openapi-generator-ignore
@@ -29,7 +29,6 @@ pkg/openapi/git_push.sh
 pkg/openapi/.gitignore
 pkg/openapi/.travis.yml
 pkg/openapi/.openapi-generator-ignore
-
 pkg/openapi/README.md
 pkg/openapi/docs/**.md
 pkg/openapi/test
diff --git a/Dockerfile b/Dockerfile
index 5ad4cea8..8506803e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,8 +18,9 @@ COPY api/ api/
 COPY internal/ internal/
 COPY scripts/ scripts/
 COPY pkg/ pkg/
-COPY patches/ patches/
 COPY templates/ templates/
+COPY patches/ patches/
+COPY catalog/ catalog/
 
 ###### Dev stage - start ######
 # see: https://github.com/kubeflow/model-registry/pull/984#discussion_r2048732415
diff --git a/Makefile b/Makefile
index 223ff811..2ef96564 100644
--- a/Makefile
+++ b/Makefile
@@ -110,14 +110,16 @@ openapi/validate: bin/openapi-generator-cli bin/yq
 
 # generate the openapi server implementation
 .PHONY: gen/openapi-server
-gen/openapi-server: bin/openapi-generator-cli openapi/validate internal/server/openapi/api_model_registry_service.go
+gen/openapi-server: bin/openapi-generator-cli api/openapi/model-registry.yaml api/openapi/catalog.yaml openapi/validate internal/server/openapi/api_model_registry_service.go
+	make -C catalog $@
 
 internal/server/openapi/api_model_registry_service.go: bin/openapi-generator-cli api/openapi/model-registry.yaml
-	ROOT_FOLDER=${PROJECT_PATH} ./scripts/gen_openapi_server.sh
+	./scripts/gen_openapi_server.sh
 
 # generate the openapi schema model and client
 .PHONY: gen/openapi
-gen/openapi: api/openapi/model-registry.yaml bin/openapi-generator-cli openapi/validate pkg/openapi/client.go
+gen/openapi: bin/openapi-generator-cli api/openapi/model-registry.yaml api/openapi/catalog.yaml openapi/validate pkg/openapi/client.go
+	make -C catalog $@
 
 pkg/openapi/client.go: bin/openapi-generator-cli api/openapi/model-registry.yaml clean-pkg-openapi
 	${OPENAPI_GENERATOR} generate \
@@ -153,10 +155,12 @@ clean/csi:
 .PHONY: clean-pkg-openapi
 clean-pkg-openapi:
 	while IFS= read -r file; do rm -f "pkg/openapi/$$file"; done < pkg/openapi/.openapi-generator/FILES
+	make -C catalog $@
 
 .PHONY: clean-internal-server-openapi
 clean-internal-server-openapi:
 	while IFS= read -r file; do rm -f "internal/server/openapi/$$file"; done < internal/server/openapi/.openapi-generator/FILES
+	make -C catalog $@
 
 .PHONY: clean
 clean: clean-pkg-openapi clean-internal-server-openapi clean/csi
diff --git a/api/openapi/catalog.yaml b/api/openapi/catalog.yaml
index fe6f3735..cbe2267c 100644
--- a/api/openapi/catalog.yaml
+++ b/api/openapi/catalog.yaml
@@ -48,7 +48,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: getAllCatalogModels
+      operationId: findModels
   /api/model_catalog/v1alpha1/sources:
     summary: Path used to get the list of catalog sources.
     description: >-
@@ -74,7 +74,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: getCatalogSources
+      operationId: findSources
       description: Gets a list of all `CatalogSource` entities.
   /api/model_catalog/v1alpha1/sources/{source_id}/models/{model_name}:
     description: >-
@@ -92,7 +92,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: getCatalogModel
+      operationId: getModel
     parameters:
       - name: source_id
         description: A unique identifier for a `CatalogSource`.
@@ -122,7 +122,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: findCatalogModelArtifacts
+      operationId: getAllModelArtifacts
     parameters:
       - name: source_id
         description: A unique identifier for a `CatalogSource`.
@@ -288,9 +288,13 @@ components:
     CatalogSource:
       description: A catalog source. A catalog source has CatalogModel children.
       required:
+        - id
         - name
       type: object
       properties:
+        id:
+          description: A unique identifier for a `CatalogSource`.
+          type: string
         name:
           description: The name of the catalog source.
           type: string
@@ -431,6 +435,7 @@ components:
         - CREATE_TIME
         - LAST_UPDATE_TIME
         - ID
+        - NAME
       type: string
     SortOrder:
       description: Supported sort direction for ordering result entities.
@@ -450,7 +455,7 @@ components:
         application/json:
           schema:
             $ref: "#/components/schemas/CatalogModelArtifactList"
-      description: A response containing a list of CatalogSource entities.
+      description: A response containing a list of CatalogModelArtifact entities.
     CatalogModelListResponse:
       content:
         application/json:
@@ -512,6 +517,18 @@ components:
             $ref: "#/components/schemas/Error"
       description: Unprocessable Entity error
   parameters:
+    orderBy:
+      style: form
+      explode: true
+      examples:
+        orderBy:
+          value: ID
+      name: orderBy
+      description: Specifies the order by criteria for listing entities.
+      schema:
+        $ref: "#/components/schemas/OrderByField"
+      in: query
+      required: false
     id:
       name: id
       description: The ID of resource.
@@ -566,18 +583,6 @@ components:
         type: string
       in: query
       required: false
-    orderBy:
-      style: form
-      explode: true
-      examples:
-        orderBy:
-          value: ID
-      name: orderBy
-      description: Specifies the order by criteria for listing entities.
-      schema:
-        $ref: "#/components/schemas/OrderByField"
-      in: query
-      required: false
     sortOrder:
       style: form
       explode: true
diff --git a/api/openapi/model-registry.yaml b/api/openapi/model-registry.yaml
index b31694ac..d8d8be8b 100644
--- a/api/openapi/model-registry.yaml
+++ b/api/openapi/model-registry.yaml
@@ -2112,6 +2112,18 @@ components:
             $ref: "#/components/schemas/Error"
       description: Unprocessable Entity error
   parameters:
+    orderBy:
+      style: form
+      explode: true
+      examples:
+        orderBy:
+          value: ID
+      name: orderBy
+      description: Specifies the order by criteria for listing entities.
+      schema:
+        $ref: "#/components/schemas/OrderByField"
+      in: query
+      required: false
     id:
       name: id
       description: The ID of resource.
@@ -2166,18 +2178,6 @@ components:
         type: string
       in: query
       required: false
-    orderBy:
-      style: form
-      explode: true
-      examples:
-        orderBy:
-          value: ID
-      name: orderBy
-      description: Specifies the order by criteria for listing entities.
-      schema:
-        $ref: "#/components/schemas/OrderByField"
-      in: query
-      required: false
     sortOrder:
       style: form
       explode: true
diff --git a/api/openapi/src/catalog.yaml b/api/openapi/src/catalog.yaml
index d44a6d3f..cfa548f2 100644
--- a/api/openapi/src/catalog.yaml
+++ b/api/openapi/src/catalog.yaml
@@ -48,7 +48,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: getAllCatalogModels
+      operationId: findModels
   /api/model_catalog/v1alpha1/sources:
     summary: Path used to get the list of catalog sources.
     description: >-
@@ -74,7 +74,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: getCatalogSources
+      operationId: findSources
       description: Gets a list of all `CatalogSource` entities.
   /api/model_catalog/v1alpha1/sources/{source_id}/models/{model_name}:
     description: >-
@@ -92,7 +92,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: getCatalogModel
+      operationId: getModel
     parameters:
       - name: source_id
         description: A unique identifier for a `CatalogSource`.
@@ -122,7 +122,7 @@ paths:
           $ref: "#/components/responses/NotFound"
         "500":
           $ref: "#/components/responses/InternalServerError"
-      operationId: findCatalogModelArtifacts
+      operationId: getAllModelArtifacts
     parameters:
       - name: source_id
         description: A unique identifier for a `CatalogSource`.
@@ -200,9 +200,13 @@ components:
     CatalogSource:
       description: A catalog source. A catalog source has CatalogModel children.
       required:
+        - id
         - name
       type: object
       properties:
+        id:
+          description: A unique identifier for a `CatalogSource`.
+          type: string
         name:
           description: The name of the catalog source.
           type: string
@@ -217,6 +221,14 @@ components:
               items:
                 $ref: "#/components/schemas/CatalogSource"
         - $ref: "#/components/schemas/BaseResourceList"
+    OrderByField:
+      description: Supported fields for ordering result entities.
+      enum:
+        - CREATE_TIME
+        - LAST_UPDATE_TIME
+        - ID
+        - NAME
+      type: string
 
   responses:
     CatalogModelArtifactListResponse:
@@ -224,7 +236,7 @@ components:
         application/json:
           schema:
             $ref: "#/components/schemas/CatalogModelArtifactList"
-      description: A response containing a list of CatalogSource entities.
+      description: A response containing a list of CatalogModelArtifact entities.
     CatalogModelListResponse:
       content:
         application/json:
@@ -249,4 +261,18 @@ components:
           schema:
             $ref: "#/components/schemas/CatalogSource"
       description: A response containing a `CatalogSource` entity.
-tags: []
\ No newline at end of file
+
+  parameters:
+    orderBy:
+      style: form
+      explode: true
+      examples:
+        orderBy:
+          value: ID
+      name: orderBy
+      description: Specifies the order by criteria for listing entities.
+      schema:
+        $ref: "#/components/schemas/OrderByField"
+      in: query
+      required: false
+tags: []
diff --git a/api/openapi/src/lib/common.yaml b/api/openapi/src/lib/common.yaml
index b748fbde..918b18e6 100644
--- a/api/openapi/src/lib/common.yaml
+++ b/api/openapi/src/lib/common.yaml
@@ -208,13 +208,6 @@ components:
       example:
         string_value: my_value
         metadataType: MetadataStringValue
-    OrderByField:
-      description: Supported fields for ordering result entities.
-      enum:
-        - CREATE_TIME
-        - LAST_UPDATE_TIME
-        - ID
-      type: string
     SortOrder:
       description: Supported sort direction for ordering result entities.
       enum:
@@ -319,18 +312,6 @@ components:
         type: string
       in: query
       required: false
-    orderBy:
-      style: form
-      explode: true
-      examples:
-        orderBy:
-          value: ID
-      name: orderBy
-      description: Specifies the order by criteria for listing entities.
-      schema:
-        $ref: "#/components/schemas/OrderByField"
-      in: query
-      required: false
     sortOrder:
       style: form
       explode: true
diff --git a/api/openapi/src/model-registry.yaml b/api/openapi/src/model-registry.yaml
index 1f185251..b637b33c 100644
--- a/api/openapi/src/model-registry.yaml
+++ b/api/openapi/src/model-registry.yaml
@@ -1517,6 +1517,13 @@ components:
             author:
               description: Name of the author.
               type: string
+    OrderByField:
+      description: Supported fields for ordering result entities.
+      enum:
+        - CREATE_TIME
+        - LAST_UPDATE_TIME
+        - ID
+      type: string
     RegisteredModel:
       description: A registered model in model registry. A registered model has ModelVersion children.
       allOf:
@@ -1848,7 +1855,19 @@ components:
           $ref: '#/components/links/SearchServingEnvironmentByExternalId'
         SearchServingEnvironmentByName:
           $ref: '#/components/links/SearchServingEnvironmentByName'
-  parameters: {}
+  parameters:
+    orderBy:
+      style: form
+      explode: true
+      examples:
+        orderBy:
+          value: ID
+      name: orderBy
+      description: Specifies the order by criteria for listing entities.
+      schema:
+        $ref: "#/components/schemas/OrderByField"
+      in: query
+      required: false
   securitySchemes: {}
   links:
     # Artifact
diff --git a/catalog/.openapi-generator-ignore b/catalog/.openapi-generator-ignore
new file mode 100644
index 00000000..aa24d770
--- /dev/null
+++ b/catalog/.openapi-generator-ignore
@@ -0,0 +1,44 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
+
+# model and client files
+pkg/openapi/api
+pkg/openapi/api/**
+pkg/openapi/git_push.sh
+pkg/openapi/.gitignore
+pkg/openapi/.travis.yml
+pkg/openapi/.openapi-generator-ignore
+pkg/openapi/README.md
+pkg/openapi/docs/**.md
+pkg/openapi/test
+pkg/openapi/test/**
+pkg/openapi/**all_of.go
+pkg/openapi/go.mod
+pkg/openapi/go.sum
+
+# server files to ignore
+internal/server/openapi/api
+internal/server/openapi/api/**
+internal/server/openapi/.openapi-generator-ignore
+internal/server/openapi/README.md
diff --git a/catalog/Makefile b/catalog/Makefile
new file mode 100644
index 00000000..493112d8
--- /dev/null
+++ b/catalog/Makefile
@@ -0,0 +1,25 @@
+PROJECT_BIN := $(CURDIR)/../bin
+OPENAPI_GENERATOR := $(PROJECT_BIN)/openapi-generator-cli
+
+.PHONY: gen/openapi-server
+gen/openapi-server: internal/server/openapi/api_model_catalog_service.go
+
+internal/server/openapi/api_model_catalog_service.go: ../api/openapi/catalog.yaml
+	OPENAPI_GENERATOR="$(OPENAPI_GENERATOR)" ./scripts/gen_openapi_server.sh
+
+.PHONY: gen/openapi
+gen/openapi: pkg/openapi/client.go
+
+pkg/openapi/client.go: ../api/openapi/catalog.yaml
+	${OPENAPI_GENERATOR} generate \
+		-i ../api/openapi/catalog.yaml -g go -o pkg/openapi --package-name openapi \
+		--ignore-file-override ./.openapi-generator-ignore --additional-properties=isGoSubmodule=true,enumClassPrefix=true,useOneOfDiscriminatorLookup=true
+	gofmt -w pkg/openapi
+
+.PHONY: clean-pkg-openapi
+clean-pkg-openapi:
+	while IFS= read -r file; do rm -f "pkg/openapi/$$file"; done < pkg/openapi/.openapi-generator/FILES
+
+.PHONY: clean-internal-server-openapi
+clean-internal-server-openapi:
+	while IFS= read -r file; do rm -f "internal/server/openapi/$$file"; done < internal/server/openapi/.openapi-generator/FILES
diff --git a/catalog/cmd/catalog.go b/catalog/cmd/catalog.go
new file mode 100644
index 00000000..f4cb8da0
--- /dev/null
+++ b/catalog/cmd/catalog.go
@@ -0,0 +1,44 @@
+package cmd
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/golang/glog"
+	"github.com/kubeflow/model-registry/catalog/internal/catalog"
+	"github.com/kubeflow/model-registry/catalog/internal/server/openapi"
+	"github.com/spf13/cobra"
+)
+
+var catalogCfg = struct {
+	ListenAddress string
+	ConfigPath    string
+}{
+	ListenAddress: "0.0.0.0:8080",
+	ConfigPath:    "sources.yaml",
+}
+
+var CatalogCmd = &cobra.Command{
+	Use:   "catalog",
+	Short: "Catalog API server",
+	Long:  `Launch the API server for the model catalog`,
+	RunE:  runCatalogServer,
+}
+
+func init() {
+	CatalogCmd.Flags().StringVarP(&catalogCfg.ListenAddress, "listen", "l", catalogCfg.ListenAddress, "Address to listen on")
+	CatalogCmd.Flags().StringVar(&catalogCfg.ConfigPath, "catalogs-path", catalogCfg.ConfigPath, "Path to catalog source configuration file")
+}
+
+func runCatalogServer(cmd *cobra.Command, args []string) error {
+	sources, err := catalog.LoadCatalogSources(catalogCfg.ConfigPath)
+	if err != nil {
+		return fmt.Errorf("error loading catalog sources: %v", err)
+	}
+
+	svc := openapi.NewModelCatalogServiceAPIService(sources)
+	ctrl := openapi.NewModelCatalogServiceAPIController(svc)
+
+	glog.Infof("Catalog API server listening on %s", catalogCfg.ListenAddress)
+	return http.ListenAndServe(catalogCfg.ListenAddress, openapi.NewRouter(ctrl))
+}
diff --git a/catalog/internal/catalog/catalog.go b/catalog/internal/catalog/catalog.go
new file mode 100644
index 00000000..af66cf1b
--- /dev/null
+++ b/catalog/internal/catalog/catalog.go
@@ -0,0 +1,142 @@
+package catalog
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/golang/glog"
+	"k8s.io/apimachinery/pkg/util/yaml"
+
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+type SortDirection int
+
+const (
+	SortDirectionAscending SortDirection = iota
+	SortDirectionDescending
+)
+
+type SortField int
+
+const (
+	SortByUnspecified SortField = iota
+	SortByName
+	SortByPublished
+)
+
+type ListModelsParams struct {
+	Query         string
+	SortBy        SortField
+	SortDirection SortDirection
+}
+
+// ModelProvider is implemented by catalog types, e.g. YamlCatalog
+type ModelProvider interface {
+	GetModel(ctx context.Context, name string) (model.CatalogModel, error)
+	ListModels(ctx context.Context, params ListModelsParams) (model.CatalogModelList, error)
+}
+
+// CatalogSourceConfig is a single entry from the catalog sources YAML file.
+type CatalogSourceConfig struct {
+	model.CatalogSource `json:",inline"`
+
+	// Catalog type to use, must match one of the registered types
+	Type string `json:"type"`
+
+	// Properties used for configuring the catalog connection based on catalog implementation
+	Properties map[string]any `json:"properties,omitempty"`
+}
+
+// sourceConfig is the structure for the catalog sources YAML file.
+type sourceConfig struct {
+	Catalogs []CatalogSourceConfig `json:"catalogs"`
+}
+
+type CatalogTypeRegisterFunc func(source *CatalogSourceConfig) (ModelProvider, error)
+
+var registeredCatalogTypes = make(map[string]CatalogTypeRegisterFunc, 0)
+
+func RegisterCatalogType(catalogType string, callback CatalogTypeRegisterFunc) error {
+	if _, exists := registeredCatalogTypes[catalogType]; exists {
+		return fmt.Errorf("catalog type %s already exists", catalogType)
+	}
+	registeredCatalogTypes[catalogType] = callback
+	return nil
+}
+
+type CatalogSource struct {
+	ModelProvider ModelProvider
+	Metadata      model.CatalogSource
+}
+
+func LoadCatalogSources(catalogsPath string) (map[string]CatalogSource, error) {
+	// Get absolute path of the catalog config file
+	absConfigPath, err := filepath.Abs(catalogsPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get absolute path for %s: %v", catalogsPath, err)
+	}
+
+	// Get the directory of the config file to resolve relative paths
+	configDir := filepath.Dir(absConfigPath)
+
+	// Save current working directory
+	originalWd, err := os.Getwd()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get current working directory: %v", err)
+	}
+
+	// Change to the config directory to make relative paths work
+	if err := os.Chdir(configDir); err != nil {
+		return nil, fmt.Errorf("failed to change to config directory %s: %v", configDir, err)
+	}
+
+	// Ensure we restore the original working directory when we're done
+	defer func() {
+		if err := os.Chdir(originalWd); err != nil {
+			glog.Errorf("failed to restore original working directory %s: %v", originalWd, err)
+		}
+	}()
+
+	config := sourceConfig{}
+	bytes, err := os.ReadFile(absConfigPath)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = yaml.UnmarshalStrict(bytes, &config); err != nil {
+		return nil, err
+	}
+
+	catalogs := make(map[string]CatalogSource, len(config.Catalogs))
+	for _, catalogConfig := range config.Catalogs {
+		catalogType := catalogConfig.Type
+		glog.Infof("reading config type %s...", catalogType)
+		registerFunc, ok := registeredCatalogTypes[catalogType]
+		if !ok {
+			return nil, fmt.Errorf("catalog type %s not registered", catalogType)
+		}
+		id := catalogConfig.GetId()
+		if len(id) == 0 {
+			return nil, fmt.Errorf("invalid catalog id %s", id)
+		}
+		if _, exists := catalogs[id]; exists {
+			return nil, fmt.Errorf("duplicate catalog id %s", id)
+		}
+		provider, err := registerFunc(&catalogConfig)
+		if err != nil {
+			return nil, fmt.Errorf("error reading catalog type %s with id %s: %v", catalogType, id, err)
+		}
+
+		catalogs[id] = CatalogSource{
+			ModelProvider: provider,
+			Metadata:      catalogConfig.CatalogSource,
+		}
+
+		glog.Infof("loaded config %s of type %s", id, catalogType)
+	}
+
+	return catalogs, nil
+}
diff --git a/catalog/internal/catalog/yaml_catalog.go b/catalog/internal/catalog/yaml_catalog.go
new file mode 100644
index 00000000..c9bede00
--- /dev/null
+++ b/catalog/internal/catalog/yaml_catalog.go
@@ -0,0 +1,83 @@
+package catalog
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"k8s.io/apimachinery/pkg/util/yaml"
+
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+type YamlArtifacts struct {
+	Protocol string `yaml:"protocol"`
+	URI      string `yaml:"uri"`
+}
+
+type YamlModel struct {
+	model.CatalogModel `yaml:",inline"`
+	Artifacts          []YamlArtifacts `yaml:"artifacts"`
+}
+
+type YamlCatalog struct {
+	Source string      `yaml:"source"`
+	Models []YamlModel `yaml:"models"`
+}
+
+type yamlCatalogImpl struct {
+	contents *YamlCatalog
+	source   *CatalogSourceConfig
+}
+
+func (y yamlCatalogImpl) GetModel(ctx context.Context, name string) (model.CatalogModel, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (y yamlCatalogImpl) ListModels(ctx context.Context, params ListModelsParams) (model.CatalogModelList, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (y yamlCatalogImpl) GetCatalogSource() (model.CatalogSource, error) {
+	return y.source.CatalogSource, nil
+}
+
+// TODO start background thread to watch file
+
+var _ ModelProvider = &yamlCatalogImpl{}
+
+const yamlCatalogPath = "yamlCatalogPath"
+
+func NewYamlCatalog(source *CatalogSourceConfig) (ModelProvider, error) {
+	var contents YamlCatalog
+	properties := source.Properties
+	if len(properties) == 0 {
+		return nil, fmt.Errorf("missing yaml catalog private properties")
+	}
+	yamlModelFile, exists := properties[yamlCatalogPath].(string)
+	if !exists || len(yamlModelFile) == 0 {
+		return nil, fmt.Errorf("missing %s string property", yamlCatalogPath)
+	}
+	bytes, err := os.ReadFile(yamlModelFile)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read %s file: %v", yamlCatalogPath, err)
+	}
+	if err = yaml.UnmarshalStrict(bytes, &contents); err != nil {
+		return nil, fmt.Errorf("failed to parse %s file: %v", yamlCatalogPath, err)
+	}
+
+	// override catalog name from Yaml Catalog File if set
+	if len(source.Name) > 0 {
+		source.Name = contents.Source
+	}
+
+	return &yamlCatalogImpl{source: source, contents: &contents}, nil
+}
+
+func init() {
+	if err := RegisterCatalogType("yaml", NewYamlCatalog); err != nil {
+		panic(err)
+	}
+}
diff --git a/catalog/internal/server/openapi/.openapi-generator/FILES b/catalog/internal/server/openapi/.openapi-generator/FILES
new file mode 100644
index 00000000..2735c88c
--- /dev/null
+++ b/catalog/internal/server/openapi/.openapi-generator/FILES
@@ -0,0 +1,26 @@
+api.go
+api_model_catalog_service.go
+error.go
+helpers.go
+impl.go
+logger.go
+model_base_model.go
+model_base_resource_dates.go
+model_base_resource_list.go
+model_catalog_model.go
+model_catalog_model_artifact.go
+model_catalog_model_artifact_list.go
+model_catalog_model_list.go
+model_catalog_source.go
+model_catalog_source_list.go
+model_error.go
+model_metadata_bool_value.go
+model_metadata_double_value.go
+model_metadata_int_value.go
+model_metadata_proto_value.go
+model_metadata_string_value.go
+model_metadata_struct_value.go
+model_metadata_value.go
+model_order_by_field.go
+model_sort_order.go
+routers.go
diff --git a/catalog/internal/server/openapi/.openapi-generator/VERSION b/catalog/internal/server/openapi/.openapi-generator/VERSION
new file mode 100644
index 00000000..eb1dc6a5
--- /dev/null
+++ b/catalog/internal/server/openapi/.openapi-generator/VERSION
@@ -0,0 +1 @@
+7.13.0
diff --git a/catalog/internal/server/openapi/api.go b/catalog/internal/server/openapi/api.go
new file mode 100644
index 00000000..5a58ecb9
--- /dev/null
+++ b/catalog/internal/server/openapi/api.go
@@ -0,0 +1,39 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+import (
+	"context"
+	"net/http"
+
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+// ModelCatalogServiceAPIRouter defines the required methods for binding the api requests to a responses for the ModelCatalogServiceAPI
+// The ModelCatalogServiceAPIRouter implementation should parse necessary information from the http request,
+// pass the data to a ModelCatalogServiceAPIServicer to perform the required actions, then write the service results to the http response.
+type ModelCatalogServiceAPIRouter interface {
+	FindModels(http.ResponseWriter, *http.Request)
+	FindSources(http.ResponseWriter, *http.Request)
+	GetModel(http.ResponseWriter, *http.Request)
+	GetAllModelArtifacts(http.ResponseWriter, *http.Request)
+}
+
+// ModelCatalogServiceAPIServicer defines the api actions for the ModelCatalogServiceAPI service
+// This interface intended to stay up to date with the openapi yaml used to generate it,
+// while the service implementation can be ignored with the .openapi-generator-ignore file
+// and updated with the logic required for the API.
+type ModelCatalogServiceAPIServicer interface {
+	FindModels(context.Context, string, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
+	FindSources(context.Context, string, string, model.OrderByField, model.SortOrder, string) (ImplResponse, error)
+	GetModel(context.Context, string, string) (ImplResponse, error)
+	GetAllModelArtifacts(context.Context, string, string) (ImplResponse, error)
+}
diff --git a/catalog/internal/server/openapi/api_model_catalog_service.go b/catalog/internal/server/openapi/api_model_catalog_service.go
new file mode 100644
index 00000000..29fb34b0
--- /dev/null
+++ b/catalog/internal/server/openapi/api_model_catalog_service.go
@@ -0,0 +1,141 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/go-chi/chi/v5"
+
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+// ModelCatalogServiceAPIController binds http requests to an api service and writes the service results to the http response
+type ModelCatalogServiceAPIController struct {
+	service      ModelCatalogServiceAPIServicer
+	errorHandler ErrorHandler
+}
+
+// ModelCatalogServiceAPIOption for how the controller is set up.
+type ModelCatalogServiceAPIOption func(*ModelCatalogServiceAPIController)
+
+// WithModelCatalogServiceAPIErrorHandler inject ErrorHandler into controller
+func WithModelCatalogServiceAPIErrorHandler(h ErrorHandler) ModelCatalogServiceAPIOption {
+	return func(c *ModelCatalogServiceAPIController) {
+		c.errorHandler = h
+	}
+}
+
+// NewModelCatalogServiceAPIController creates a default api controller
+func NewModelCatalogServiceAPIController(s ModelCatalogServiceAPIServicer, opts ...ModelCatalogServiceAPIOption) Router {
+	controller := &ModelCatalogServiceAPIController{
+		service:      s,
+		errorHandler: DefaultErrorHandler,
+	}
+
+	for _, opt := range opts {
+		opt(controller)
+	}
+
+	return controller
+}
+
+// Routes returns all the api routes for the ModelCatalogServiceAPIController
+func (c *ModelCatalogServiceAPIController) Routes() Routes {
+	return Routes{
+		"FindModels": Route{
+			strings.ToUpper("Get"),
+			"/api/model_catalog/v1alpha1/models",
+			c.FindModels,
+		},
+		"FindSources": Route{
+			strings.ToUpper("Get"),
+			"/api/model_catalog/v1alpha1/sources",
+			c.FindSources,
+		},
+		"GetModel": Route{
+			strings.ToUpper("Get"),
+			"/api/model_catalog/v1alpha1/sources/{source_id}/models/{model_name}",
+			c.GetModel,
+		},
+		"GetAllModelArtifacts": Route{
+			strings.ToUpper("Get"),
+			"/api/model_catalog/v1alpha1/sources/{source_id}/models/{model_name}/artifacts",
+			c.GetAllModelArtifacts,
+		},
+	}
+}
+
+// FindModels - Search catalog models across sources.
+func (c *ModelCatalogServiceAPIController) FindModels(w http.ResponseWriter, r *http.Request) {
+	query := r.URL.Query()
+	sourceParam := query.Get("source")
+	qParam := query.Get("q")
+	pageSizeParam := query.Get("pageSize")
+	orderByParam := query.Get("orderBy")
+	sortOrderParam := query.Get("sortOrder")
+	nextPageTokenParam := query.Get("nextPageToken")
+	result, err := c.service.FindModels(r.Context(), sourceParam, qParam, pageSizeParam, model.OrderByField(orderByParam), model.SortOrder(sortOrderParam), nextPageTokenParam)
+	// If an error occurred, encode the error with the status code
+	if err != nil {
+		c.errorHandler(w, r, err, &result)
+		return
+	}
+	// If no error, encode the body and the result code
+	EncodeJSONResponse(result.Body, &result.Code, w)
+}
+
+// FindSources - List All CatalogSources
+func (c *ModelCatalogServiceAPIController) FindSources(w http.ResponseWriter, r *http.Request) {
+	query := r.URL.Query()
+	nameParam := query.Get("name")
+	pageSizeParam := query.Get("pageSize")
+	orderByParam := query.Get("orderBy")
+	sortOrderParam := query.Get("sortOrder")
+	nextPageTokenParam := query.Get("nextPageToken")
+	result, err := c.service.FindSources(r.Context(), nameParam, pageSizeParam, model.OrderByField(orderByParam), model.SortOrder(sortOrderParam), nextPageTokenParam)
+	// If an error occurred, encode the error with the status code
+	if err != nil {
+		c.errorHandler(w, r, err, &result)
+		return
+	}
+	// If no error, encode the body and the result code
+	EncodeJSONResponse(result.Body, &result.Code, w)
+}
+
+// GetModel - Get a `CatalogModel`.
+func (c *ModelCatalogServiceAPIController) GetModel(w http.ResponseWriter, r *http.Request) {
+	sourceIdParam := chi.URLParam(r, "source_id")
+	modelNameParam := chi.URLParam(r, "model_name")
+	result, err := c.service.GetModel(r.Context(), sourceIdParam, modelNameParam)
+	// If an error occurred, encode the error with the status code
+	if err != nil {
+		c.errorHandler(w, r, err, &result)
+		return
+	}
+	// If no error, encode the body and the result code
+	EncodeJSONResponse(result.Body, &result.Code, w)
+}
+
+// GetAllModelArtifacts - List CatalogModelArtifacts.
+func (c *ModelCatalogServiceAPIController) GetAllModelArtifacts(w http.ResponseWriter, r *http.Request) {
+	sourceIdParam := chi.URLParam(r, "source_id")
+	modelNameParam := chi.URLParam(r, "model_name")
+	result, err := c.service.GetAllModelArtifacts(r.Context(), sourceIdParam, modelNameParam)
+	// If an error occurred, encode the error with the status code
+	if err != nil {
+		c.errorHandler(w, r, err, &result)
+		return
+	}
+	// If no error, encode the body and the result code
+	EncodeJSONResponse(result.Body, &result.Code, w)
+}
diff --git a/catalog/internal/server/openapi/api_model_catalog_service_service.go b/catalog/internal/server/openapi/api_model_catalog_service_service.go
new file mode 100644
index 00000000..2290276a
--- /dev/null
+++ b/catalog/internal/server/openapi/api_model_catalog_service_service.go
@@ -0,0 +1,124 @@
+package openapi
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"math"
+	"net/http"
+	"slices"
+	"strconv"
+	"strings"
+
+	"github.com/kubeflow/model-registry/catalog/internal/catalog"
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+// ModelCatalogServiceAPIService is a service that implements the logic for the ModelCatalogServiceAPIServicer
+// This service should implement the business logic for every endpoint for the ModelCatalogServiceAPI s.coreApi.
+// Include any external packages or services that will be required by this service.
+type ModelCatalogServiceAPIService struct {
+	modelCatalogs map[string]catalog.CatalogSource
+}
+
+func (m ModelCatalogServiceAPIService) GetAllModelArtifacts(context.Context, string, string) (ImplResponse, error) {
+	return Response(http.StatusNotImplemented, "Not implemented"), nil
+}
+
+func (m ModelCatalogServiceAPIService) FindModels(ctx context.Context, source string, q string, pageSize string, orderBy model.OrderByField, sortOder model.SortOrder, nextPageToken string) (ImplResponse, error) {
+	return Response(http.StatusNotImplemented, "Not implemented"), nil
+}
+
+func (m ModelCatalogServiceAPIService) GetModel(ctx context.Context, source string, name string) (ImplResponse, error) {
+	return Response(http.StatusNotImplemented, "Not implemented"), nil
+}
+
+func (m *ModelCatalogServiceAPIService) FindSources(ctx context.Context, name string, strPageSize string, orderBy model.OrderByField, sortOrder model.SortOrder, nextPageToken string) (ImplResponse, error) {
+	// TODO: Implement real pagination in here by reusing the nextPageToken
+	// code from https://github.com/kubeflow/model-registry/pull/1205.
+
+	if len(m.modelCatalogs) > math.MaxInt32 {
+		err := errors.New("too many registered models")
+		return ErrorResponse(http.StatusInternalServerError, err), err
+	}
+
+	var pageSize int32 = 10
+	if strPageSize != "" {
+		pageSize64, err := strconv.ParseInt(strPageSize, 10, 32)
+		if err != nil {
+			return ErrorResponse(http.StatusBadRequest, err), err
+		}
+		pageSize = int32(pageSize64)
+	}
+
+	items := make([]model.CatalogSource, 0, len(m.modelCatalogs))
+
+	name = strings.ToLower(name)
+
+	for _, v := range m.modelCatalogs {
+		if !strings.Contains(strings.ToLower(v.Metadata.Name), name) {
+			continue
+		}
+
+		items = append(items, v.Metadata)
+	}
+
+	cmpFunc, err := genCatalogCmpFunc(orderBy, sortOrder)
+	if err != nil {
+		return ErrorResponse(http.StatusBadRequest, err), err
+	}
+	slices.SortStableFunc(items, cmpFunc)
+
+	total := int32(len(items))
+	if total > pageSize {
+		items = items[:pageSize]
+	}
+
+	res := model.CatalogSourceList{
+		PageSize:      pageSize,
+		Items:         items,
+		Size:          total,
+		NextPageToken: "",
+	}
+	return Response(http.StatusOK, res), nil
+}
+
+func genCatalogCmpFunc(orderBy model.OrderByField, sortOrder model.SortOrder) (func(model.CatalogSource, model.CatalogSource) int, error) {
+	multiplier := 1
+	switch model.SortOrder(strings.ToUpper(string(sortOrder))) {
+	case model.SORTORDER_DESC:
+		multiplier = -1
+	case model.SORTORDER_ASC, "":
+		multiplier = 1
+	default:
+		return nil, fmt.Errorf("unsupported sort order field")
+	}
+
+	switch model.OrderByField(strings.ToUpper(string(orderBy))) {
+	case model.ORDERBYFIELD_ID, "":
+		return func(a, b model.CatalogSource) int {
+			return multiplier * strings.Compare(a.Id, b.Id)
+		}, nil
+	case model.ORDERBYFIELD_NAME:
+		return func(a, b model.CatalogSource) int {
+			return multiplier * strings.Compare(a.Name, b.Name)
+		}, nil
+	default:
+		return nil, fmt.Errorf("unsupported order by field")
+	}
+}
+
+//nolint:unused
+func missingCatalogError(id string) (ImplResponse, error) {
+	err := fmt.Errorf("missing catalog %s", id)
+	return ErrorResponse(http.StatusNotFound, err), err
+}
+
+var _ ModelCatalogServiceAPIServicer = &ModelCatalogServiceAPIService{}
+
+// NewModelCatalogServiceAPIService creates a default api service
+func NewModelCatalogServiceAPIService(modelCatalogs map[string]catalog.CatalogSource) ModelCatalogServiceAPIServicer {
+	return &ModelCatalogServiceAPIService{
+		modelCatalogs: modelCatalogs,
+	}
+}
diff --git a/catalog/internal/server/openapi/error.go b/catalog/internal/server/openapi/error.go
new file mode 100644
index 00000000..e7cfcc12
--- /dev/null
+++ b/catalog/internal/server/openapi/error.go
@@ -0,0 +1,61 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+)
+
+// ErrTypeAssertionError is thrown when type an interface does not match the asserted type
+var ErrTypeAssertionError = errors.New("unable to assert type")
+
+// ParsingError indicates that an error has occurred when parsing request parameters
+type ParsingError struct {
+	Err error
+}
+
+func (e *ParsingError) Unwrap() error {
+	return e.Err
+}
+
+func (e *ParsingError) Error() string {
+	return e.Err.Error()
+}
+
+// RequiredError indicates that an error has occurred when parsing request parameters
+type RequiredError struct {
+	Field string
+}
+
+func (e *RequiredError) Error() string {
+	return fmt.Sprintf("required field '%s' is zero value.", e.Field)
+}
+
+// ErrorHandler defines the required method for handling error. You may implement it and inject this into a controller if
+// you would like errors to be handled differently from the DefaultErrorHandler
+type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error, result *ImplResponse)
+
+// DefaultErrorHandler defines the default logic on how to handle errors from the controller. Any errors from parsing
+// request params will return a StatusBadRequest. Otherwise, the error code originating from the servicer will be used.
+func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, result *ImplResponse) {
+	if _, ok := err.(*ParsingError); ok {
+		// Handle parsing errors
+		EncodeJSONResponse(ErrorResponse(http.StatusBadRequest, err).Body, func(i int) *int { return &i }(http.StatusBadRequest), w)
+	} else if _, ok := err.(*RequiredError); ok {
+		// Handle missing required errors
+		EncodeJSONResponse(ErrorResponse(http.StatusBadRequest, err).Body, func(i int) *int { return &i }(http.StatusUnprocessableEntity), w)
+	} else {
+		// Handle all other errors
+		EncodeJSONResponse(result.Body, &result.Code, w)
+	}
+}
diff --git a/catalog/internal/server/openapi/helpers.go b/catalog/internal/server/openapi/helpers.go
new file mode 100644
index 00000000..7b9f19aa
--- /dev/null
+++ b/catalog/internal/server/openapi/helpers.go
@@ -0,0 +1,70 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+import (
+	"reflect"
+
+	model "github.com/kubeflow/model-registry/pkg/openapi"
+)
+
+// Response return a ImplResponse struct filled
+func Response(code int, body interface{}) ImplResponse {
+	return ImplResponse{
+		Code: code,
+		Body: body,
+	}
+}
+
+func ErrorResponse(code int, err error) ImplResponse {
+	return ImplResponse{
+		Code: code,
+		Body: model.Error{Message: err.Error()},
+	}
+}
+
+// IsZeroValue checks if the val is the zero-ed value.
+func IsZeroValue(val interface{}) bool {
+	return val == nil || reflect.DeepEqual(val, reflect.Zero(reflect.TypeOf(val)).Interface())
+}
+
+// AssertRecurseInterfaceRequired recursively checks each struct in a slice against the callback.
+// This method traverse nested slices in a preorder fashion.
+func AssertRecurseInterfaceRequired[T any](obj interface{}, callback func(T) error) error {
+	return AssertRecurseValueRequired(reflect.ValueOf(obj), callback)
+}
+
+// AssertRecurseValueRequired checks each struct in the nested slice against the callback.
+// This method traverse nested slices in a preorder fashion. ErrTypeAssertionError is thrown if
+// the underlying struct does not match type T.
+func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) error) error {
+	switch value.Kind() {
+	// If it is a struct we check using callback
+	case reflect.Struct:
+		obj, ok := value.Interface().(T)
+		if !ok {
+			return ErrTypeAssertionError
+		}
+
+		if err := callback(obj); err != nil {
+			return err
+		}
+
+	// If it is a slice we continue recursion
+	case reflect.Slice:
+		for i := 0; i < value.Len(); i += 1 {
+			if err := AssertRecurseValueRequired(value.Index(i), callback); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/catalog/internal/server/openapi/impl.go b/catalog/internal/server/openapi/impl.go
new file mode 100644
index 00000000..23096a31
--- /dev/null
+++ b/catalog/internal/server/openapi/impl.go
@@ -0,0 +1,17 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+// ImplResponse defines an implementation response with error code and the associated body
+type ImplResponse struct {
+	Code int
+	Body interface{}
+}
diff --git a/catalog/internal/server/openapi/logger.go b/catalog/internal/server/openapi/logger.go
new file mode 100644
index 00000000..e8b38e69
--- /dev/null
+++ b/catalog/internal/server/openapi/logger.go
@@ -0,0 +1,20 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+import (
+	"github.com/go-chi/chi/v5/middleware"
+	"net/http"
+)
+
+func Logger(inner http.Handler) http.Handler {
+	return middleware.Logger(inner)
+}
diff --git a/catalog/internal/server/openapi/routers.go b/catalog/internal/server/openapi/routers.go
new file mode 100644
index 00000000..f2c4ceec
--- /dev/null
+++ b/catalog/internal/server/openapi/routers.go
@@ -0,0 +1,310 @@
+/*
+ * Model Catalog REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: v1alpha1
+ */
+
+// Generated by: OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+	"errors"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/go-chi/chi/v5"
+	"github.com/go-chi/chi/v5/middleware"
+	"github.com/go-chi/cors"
+	"github.com/golang/glog"
+)
+
+//lint:file-ignore U1000 Ignore all unused code, it's generated
+
+// A Route defines the parameters for an api endpoint
+type Route struct {
+	Method      string
+	Pattern     string
+	HandlerFunc http.HandlerFunc
+}
+
+// Routes is a map of defined api endpoints
+type Routes map[string]Route
+
+// Router defines the required methods for retrieving api routes
+type Router interface {
+	Routes() Routes
+}
+
+const errMsgRequiredMissing = "required parameter is missing"
+const errMsgMinValueConstraint = "provided parameter is not respecting minimum value constraint"
+const errMsgMaxValueConstraint = "provided parameter is not respecting maximum value constraint"
+
+// NewRouter creates a new router for any number of api routers
+func NewRouter(routers ...Router) chi.Router {
+	router := chi.NewRouter()
+	router.Use(middleware.Logger)
+	router.Use(cors.Handler(cors.Options{
+		AllowedOrigins:   []string{"https://*", "http://*"},
+		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-PINGOTHER"},
+		ExposedHeaders:   []string{"Link"},
+		AllowCredentials: false,
+		MaxAge:           300, // Maximum value not ignored by any of major browsers
+	}))
+	for _, api := range routers {
+		for _, route := range api.Routes() {
+			handler := route.HandlerFunc
+			router.Method(route.Method, route.Pattern, handler)
+		}
+	}
+
+	return router
+}
+
+// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code
+func EncodeJSONResponse(i interface{}, status *int, w http.ResponseWriter) {
+	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+	if status != nil {
+		w.WriteHeader(*status)
+	} else {
+		w.WriteHeader(http.StatusOK)
+	}
+
+	if i != nil {
+		if err := json.NewEncoder(w).Encode(i); err != nil {
+			// FIXME: is it too late to inform the client of an error at this point??
+			glog.Errorf("error encoding JSON response: %v", err)
+		}
+	}
+}
+
+// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file
+func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) {
+	_, fileHeader, err := r.FormFile(key)
+	if err != nil {
+		return nil, err
+	}
+
+	return readFileHeaderToTempFile(fileHeader)
+}
+
+// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files
+func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) {
+	if err := r.ParseMultipartForm(32 << 20); err != nil {
+		return nil, err
+	}
+
+	files := make([]*os.File, 0, len(r.MultipartForm.File[key]))
+
+	for _, fileHeader := range r.MultipartForm.File[key] {
+		file, err := readFileHeaderToTempFile(fileHeader)
+		if err != nil {
+			return nil, err
+		}
+
+		files = append(files, file)
+	}
+
+	return files, nil
+}
+
+// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file
+func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) {
+	formFile, err := fileHeader.Open()
+	if err != nil {
+		return nil, err
+	}
+
+	//nolint:errcheck
+	defer formFile.Close()
+
+	fileBytes, err := io.ReadAll(formFile)
+	if err != nil {
+		return nil, err
+	}
+
+	file, err := os.CreateTemp("", fileHeader.Filename)
+	if err != nil {
+		return nil, err
+	}
+
+	//nolint:errcheck
+	defer file.Close()
+
+	// FIXME: return values are ignored!!!
+	_, _ = file.Write(fileBytes)
+
+	return file, nil
+}
+
+type Number interface {
+	~int32 | ~int64 | ~float32 | ~float64
+}
+
+type ParseString[T Number | string | bool] func(v string) (T, error)
+
+// parseFloat64 parses a string parameter to an float64.
+func parseFloat64(param string) (float64, error) {
+	if param == "" {
+		return 0, nil
+	}
+
+	return strconv.ParseFloat(param, 64)
+}
+
+// parseFloat32 parses a string parameter to an float32.
+func parseFloat32(param string) (float32, error) {
+	if param == "" {
+		return 0, nil
+	}
+
+	v, err := strconv.ParseFloat(param, 32)
+	return float32(v), err
+}
+
+// parseInt64 parses a string parameter to an int64.
+func parseInt64(param string) (int64, error) {
+	if param == "" {
+		return 0, nil
+	}
+
+	return strconv.ParseInt(param, 10, 64)
+}
+
+// parseInt32 parses a string parameter to an int32.
+func parseInt32(param string) (int32, error) {
+	if param == "" {
+		return 0, nil
+	}
+
+	val, err := strconv.ParseInt(param, 10, 32)
+	return int32(val), err
+}
+
+// parseBool parses a string parameter to an bool.
+func parseBool(param string) (bool, error) {
+	if param == "" {
+		return false, nil
+	}
+
+	return strconv.ParseBool(param)
+}
+
+type Operation[T Number | string | bool] func(actual string) (T, bool, error)
+
+func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] {
+	var empty T
+	return func(actual string) (T, bool, error) {
+		if actual == "" {
+			return empty, false, errors.New(errMsgRequiredMissing)
+		}
+
+		v, err := parse(actual)
+		return v, false, err
+	}
+}
+
+func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] {
+	return func(actual string) (T, bool, error) {
+		if actual == "" {
+			return def, true, nil
+		}
+
+		v, err := parse(actual)
+		return v, false, err
+	}
+}
+
+func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] {
+	return func(actual string) (T, bool, error) {
+		v, err := parse(actual)
+		return v, false, err
+	}
+}
+
+type Constraint[T Number | string | bool] func(actual T) error
+
+func WithMinimum[T Number](expected T) Constraint[T] {
+	return func(actual T) error {
+		if actual < expected {
+			return errors.New(errMsgMinValueConstraint)
+		}
+
+		return nil
+	}
+}
+
+func WithMaximum[T Number](expected T) Constraint[T] {
+	return func(actual T) error {
+		if actual > expected {
+			return errors.New(errMsgMaxValueConstraint)
+		}
+
+		return nil
+	}
+}
+
+// parseNumericParameter parses a numeric parameter to its respective type.
+func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) {
+	v, ok, err := fn(param)
+	if err != nil {
+		return 0, err
+	}
+
+	if !ok {
+		for _, check := range checks {
+			if err := check(v); err != nil {
+				return 0, err
+			}
+		}
+	}
+
+	return v, nil
+}
+
+// parseBoolParameter parses a string parameter to a bool
+func parseBoolParameter(param string, fn Operation[bool]) (bool, error) {
+	v, _, err := fn(param)
+	return v, err
+}
+
+// parseNumericArrayParameter parses a string parameter containing array of values to its respective type.
+func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) {
+	if param == "" {
+		if required {
+			return nil, errors.New(errMsgRequiredMissing)
+		}
+
+		return nil, nil
+	}
+
+	str := strings.Split(param, delim)
+	values := make([]T, len(str))
+
+	for i, s := range str {
+		v, ok, err := fn(s)
+		if err != nil {
+			return nil, err
+		}
+
+		if !ok {
+			for _, check := range checks {
+				if err := check(v); err != nil {
+					return nil, err
+				}
+			}
+		}
+
+		values[i] = v
+	}
+
+	return values, nil
+}
diff --git a/catalog/internal/server/openapi/type_asserts.go b/catalog/internal/server/openapi/type_asserts.go
new file mode 100644
index 00000000..90958923
--- /dev/null
+++ b/catalog/internal/server/openapi/type_asserts.go
@@ -0,0 +1,357 @@
+/*
+ * Model Registry REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: 1.0.0
+ * Generated by: OpenAPI Generator (https://openapi-generator.tech)
+ *
+ */
+
+// File generated by scripts/gen_type_assert.sh - DO NOT EDIT
+
+package openapi
+
+import (
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+// AssertBaseModelConstraints checks if the values respects the defined constraints
+func AssertBaseModelConstraints(obj model.BaseModel) error {
+	return nil
+}
+
+// AssertBaseModelRequired checks if the required fields are not zero-ed
+func AssertBaseModelRequired(obj model.BaseModel) error {
+	return nil
+}
+
+// AssertBaseResourceDatesConstraints checks if the values respects the defined constraints
+func AssertBaseResourceDatesConstraints(obj model.BaseResourceDates) error {
+	return nil
+}
+
+// AssertBaseResourceDatesRequired checks if the required fields are not zero-ed
+func AssertBaseResourceDatesRequired(obj model.BaseResourceDates) error {
+	return nil
+}
+
+// AssertBaseResourceListConstraints checks if the values respects the defined constraints
+func AssertBaseResourceListConstraints(obj model.BaseResourceList) error {
+	return nil
+}
+
+// AssertBaseResourceListRequired checks if the required fields are not zero-ed
+func AssertBaseResourceListRequired(obj model.BaseResourceList) error {
+	elements := map[string]interface{}{
+		"nextPageToken": obj.NextPageToken,
+		"pageSize":      obj.PageSize,
+		"size":          obj.Size,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertCatalogModelArtifactConstraints checks if the values respects the defined constraints
+func AssertCatalogModelArtifactConstraints(obj model.CatalogModelArtifact) error {
+	return nil
+}
+
+// AssertCatalogModelArtifactListConstraints checks if the values respects the defined constraints
+func AssertCatalogModelArtifactListConstraints(obj model.CatalogModelArtifactList) error {
+	return nil
+}
+
+// AssertCatalogModelArtifactListRequired checks if the required fields are not zero-ed
+func AssertCatalogModelArtifactListRequired(obj model.CatalogModelArtifactList) error {
+	elements := map[string]interface{}{
+		"nextPageToken": obj.NextPageToken,
+		"pageSize":      obj.PageSize,
+		"size":          obj.Size,
+		"items":         obj.Items,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	for _, el := range obj.Items {
+		if err := AssertCatalogModelArtifactRequired(el); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// AssertCatalogModelArtifactRequired checks if the required fields are not zero-ed
+func AssertCatalogModelArtifactRequired(obj model.CatalogModelArtifact) error {
+	elements := map[string]interface{}{
+		"uri": obj.Uri,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertCatalogModelConstraints checks if the values respects the defined constraints
+func AssertCatalogModelConstraints(obj model.CatalogModel) error {
+	return nil
+}
+
+// AssertCatalogModelListConstraints checks if the values respects the defined constraints
+func AssertCatalogModelListConstraints(obj model.CatalogModelList) error {
+	return nil
+}
+
+// AssertCatalogModelListRequired checks if the required fields are not zero-ed
+func AssertCatalogModelListRequired(obj model.CatalogModelList) error {
+	elements := map[string]interface{}{
+		"nextPageToken": obj.NextPageToken,
+		"pageSize":      obj.PageSize,
+		"size":          obj.Size,
+		"items":         obj.Items,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	for _, el := range obj.Items {
+		if err := AssertCatalogModelRequired(el); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// AssertCatalogModelRequired checks if the required fields are not zero-ed
+func AssertCatalogModelRequired(obj model.CatalogModel) error {
+	elements := map[string]interface{}{
+		"name": obj.Name,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertCatalogSourceConstraints checks if the values respects the defined constraints
+func AssertCatalogSourceConstraints(obj model.CatalogSource) error {
+	return nil
+}
+
+// AssertCatalogSourceListConstraints checks if the values respects the defined constraints
+func AssertCatalogSourceListConstraints(obj model.CatalogSourceList) error {
+	return nil
+}
+
+// AssertCatalogSourceListRequired checks if the required fields are not zero-ed
+func AssertCatalogSourceListRequired(obj model.CatalogSourceList) error {
+	elements := map[string]interface{}{
+		"nextPageToken": obj.NextPageToken,
+		"pageSize":      obj.PageSize,
+		"size":          obj.Size,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	for _, el := range obj.Items {
+		if err := AssertCatalogSourceRequired(el); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// AssertCatalogSourceRequired checks if the required fields are not zero-ed
+func AssertCatalogSourceRequired(obj model.CatalogSource) error {
+	elements := map[string]interface{}{
+		"id":   obj.Id,
+		"name": obj.Name,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertErrorConstraints checks if the values respects the defined constraints
+func AssertErrorConstraints(obj model.Error) error {
+	return nil
+}
+
+// AssertErrorRequired checks if the required fields are not zero-ed
+func AssertErrorRequired(obj model.Error) error {
+	elements := map[string]interface{}{
+		"code":    obj.Code,
+		"message": obj.Message,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertMetadataBoolValueConstraints checks if the values respects the defined constraints
+func AssertMetadataBoolValueConstraints(obj model.MetadataBoolValue) error {
+	return nil
+}
+
+// AssertMetadataBoolValueRequired checks if the required fields are not zero-ed
+func AssertMetadataBoolValueRequired(obj model.MetadataBoolValue) error {
+	elements := map[string]interface{}{
+		"bool_value":   obj.BoolValue,
+		"metadataType": obj.MetadataType,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertMetadataDoubleValueConstraints checks if the values respects the defined constraints
+func AssertMetadataDoubleValueConstraints(obj model.MetadataDoubleValue) error {
+	return nil
+}
+
+// AssertMetadataDoubleValueRequired checks if the required fields are not zero-ed
+func AssertMetadataDoubleValueRequired(obj model.MetadataDoubleValue) error {
+	elements := map[string]interface{}{
+		"double_value": obj.DoubleValue,
+		"metadataType": obj.MetadataType,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertMetadataIntValueConstraints checks if the values respects the defined constraints
+func AssertMetadataIntValueConstraints(obj model.MetadataIntValue) error {
+	return nil
+}
+
+// AssertMetadataIntValueRequired checks if the required fields are not zero-ed
+func AssertMetadataIntValueRequired(obj model.MetadataIntValue) error {
+	elements := map[string]interface{}{
+		"int_value":    obj.IntValue,
+		"metadataType": obj.MetadataType,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertMetadataProtoValueConstraints checks if the values respects the defined constraints
+func AssertMetadataProtoValueConstraints(obj model.MetadataProtoValue) error {
+	return nil
+}
+
+// AssertMetadataProtoValueRequired checks if the required fields are not zero-ed
+func AssertMetadataProtoValueRequired(obj model.MetadataProtoValue) error {
+	elements := map[string]interface{}{
+		"type":         obj.Type,
+		"proto_value":  obj.ProtoValue,
+		"metadataType": obj.MetadataType,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertMetadataStringValueConstraints checks if the values respects the defined constraints
+func AssertMetadataStringValueConstraints(obj model.MetadataStringValue) error {
+	return nil
+}
+
+// AssertMetadataStringValueRequired checks if the required fields are not zero-ed
+func AssertMetadataStringValueRequired(obj model.MetadataStringValue) error {
+	elements := map[string]interface{}{
+		"string_value": obj.StringValue,
+		"metadataType": obj.MetadataType,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertMetadataStructValueConstraints checks if the values respects the defined constraints
+func AssertMetadataStructValueConstraints(obj model.MetadataStructValue) error {
+	return nil
+}
+
+// AssertMetadataStructValueRequired checks if the required fields are not zero-ed
+func AssertMetadataStructValueRequired(obj model.MetadataStructValue) error {
+	elements := map[string]interface{}{
+		"struct_value": obj.StructValue,
+		"metadataType": obj.MetadataType,
+	}
+	for name, el := range elements {
+		if isZero := IsZeroValue(el); isZero {
+			return &RequiredError{Field: name}
+		}
+	}
+
+	return nil
+}
+
+// AssertOrderByFieldConstraints checks if the values respects the defined constraints
+func AssertOrderByFieldConstraints(obj model.OrderByField) error {
+	return nil
+}
+
+// AssertOrderByFieldRequired checks if the required fields are not zero-ed
+func AssertOrderByFieldRequired(obj model.OrderByField) error {
+	return nil
+}
+
+// AssertSortOrderConstraints checks if the values respects the defined constraints
+func AssertSortOrderConstraints(obj model.SortOrder) error {
+	return nil
+}
+
+// AssertSortOrderRequired checks if the required fields are not zero-ed
+func AssertSortOrderRequired(obj model.SortOrder) error {
+	return nil
+}
diff --git a/catalog/openapitools.json b/catalog/openapitools.json
new file mode 100644
index 00000000..151c200f
--- /dev/null
+++ b/catalog/openapitools.json
@@ -0,0 +1,7 @@
+{
+  "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
+  "spaces": 2,
+  "generator-cli": {
+    "version": "7.13.0"
+  }
+}
diff --git a/catalog/pkg/openapi/.openapi-generator/FILES b/catalog/pkg/openapi/.openapi-generator/FILES
new file mode 100644
index 00000000..dafe389d
--- /dev/null
+++ b/catalog/pkg/openapi/.openapi-generator/FILES
@@ -0,0 +1,24 @@
+api_model_catalog_service.go
+client.go
+configuration.go
+model_base_model.go
+model_base_resource_dates.go
+model_base_resource_list.go
+model_catalog_model.go
+model_catalog_model_artifact.go
+model_catalog_model_artifact_list.go
+model_catalog_model_list.go
+model_catalog_source.go
+model_catalog_source_list.go
+model_error.go
+model_metadata_bool_value.go
+model_metadata_double_value.go
+model_metadata_int_value.go
+model_metadata_proto_value.go
+model_metadata_string_value.go
+model_metadata_struct_value.go
+model_metadata_value.go
+model_order_by_field.go
+model_sort_order.go
+response.go
+utils.go
diff --git a/catalog/pkg/openapi/.openapi-generator/VERSION b/catalog/pkg/openapi/.openapi-generator/VERSION
new file mode 100644
index 00000000..73a86b19
--- /dev/null
+++ b/catalog/pkg/openapi/.openapi-generator/VERSION
@@ -0,0 +1 @@
+7.0.1
\ No newline at end of file
diff --git a/catalog/pkg/openapi/api_model_catalog_service.go b/catalog/pkg/openapi/api_model_catalog_service.go
new file mode 100644
index 00000000..2df62f31
--- /dev/null
+++ b/catalog/pkg/openapi/api_model_catalog_service.go
@@ -0,0 +1,693 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+// ModelCatalogServiceAPIService ModelCatalogServiceAPI service
+type ModelCatalogServiceAPIService service
+
+type ApiFindModelsRequest struct {
+	ctx           context.Context
+	ApiService    *ModelCatalogServiceAPIService
+	source        *string
+	q             *string
+	pageSize      *string
+	orderBy       *OrderByField
+	sortOrder     *SortOrder
+	nextPageToken *string
+}
+
+// Filter models by source. If not provided, models from all sources are returned. If multiple sources are provided, models from any of the sources are returned.
+func (r ApiFindModelsRequest) Source(source string) ApiFindModelsRequest {
+	r.source = &source
+	return r
+}
+
+// Free-form keyword search used to filter the response.
+func (r ApiFindModelsRequest) Q(q string) ApiFindModelsRequest {
+	r.q = &q
+	return r
+}
+
+// Number of entities in each page.
+func (r ApiFindModelsRequest) PageSize(pageSize string) ApiFindModelsRequest {
+	r.pageSize = &pageSize
+	return r
+}
+
+// Specifies the order by criteria for listing entities.
+func (r ApiFindModelsRequest) OrderBy(orderBy OrderByField) ApiFindModelsRequest {
+	r.orderBy = &orderBy
+	return r
+}
+
+// Specifies the sort order for listing entities, defaults to ASC.
+func (r ApiFindModelsRequest) SortOrder(sortOrder SortOrder) ApiFindModelsRequest {
+	r.sortOrder = &sortOrder
+	return r
+}
+
+// Token to use to retrieve next page of results.
+func (r ApiFindModelsRequest) NextPageToken(nextPageToken string) ApiFindModelsRequest {
+	r.nextPageToken = &nextPageToken
+	return r
+}
+
+func (r ApiFindModelsRequest) Execute() (*CatalogModelList, *http.Response, error) {
+	return r.ApiService.FindModelsExecute(r)
+}
+
+/*
+FindModels Search catalog models across sources.
+
+	@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+	@return ApiFindModelsRequest
+*/
+func (a *ModelCatalogServiceAPIService) FindModels(ctx context.Context) ApiFindModelsRequest {
+	return ApiFindModelsRequest{
+		ApiService: a,
+		ctx:        ctx,
+	}
+}
+
+// Execute executes the request
+//
+//	@return CatalogModelList
+func (a *ModelCatalogServiceAPIService) FindModelsExecute(r ApiFindModelsRequest) (*CatalogModelList, *http.Response, error) {
+	var (
+		localVarHTTPMethod  = http.MethodGet
+		localVarPostBody    interface{}
+		formFiles           []formFile
+		localVarReturnValue *CatalogModelList
+	)
+
+	localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ModelCatalogServiceAPIService.FindModels")
+	if err != nil {
+		return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
+	}
+
+	localVarPath := localBasePath + "/api/model_catalog/v1alpha1/models"
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := url.Values{}
+	localVarFormParams := url.Values{}
+
+	if r.source != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "source", r.source, "")
+	}
+	if r.q != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "q", r.q, "")
+	}
+	if r.pageSize != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "pageSize", r.pageSize, "")
+	}
+	if r.orderBy != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "orderBy", r.orderBy, "")
+	}
+	if r.sortOrder != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "sortOrder", r.sortOrder, "")
+	}
+	if r.nextPageToken != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "nextPageToken", r.nextPageToken, "")
+	}
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(req)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
+	if err != nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 400 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 401 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 404 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 500 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
+
+type ApiFindSourcesRequest struct {
+	ctx           context.Context
+	ApiService    *ModelCatalogServiceAPIService
+	name          *string
+	pageSize      *string
+	orderBy       *OrderByField
+	sortOrder     *SortOrder
+	nextPageToken *string
+}
+
+// Name of entity to search.
+func (r ApiFindSourcesRequest) Name(name string) ApiFindSourcesRequest {
+	r.name = &name
+	return r
+}
+
+// Number of entities in each page.
+func (r ApiFindSourcesRequest) PageSize(pageSize string) ApiFindSourcesRequest {
+	r.pageSize = &pageSize
+	return r
+}
+
+// Specifies the order by criteria for listing entities.
+func (r ApiFindSourcesRequest) OrderBy(orderBy OrderByField) ApiFindSourcesRequest {
+	r.orderBy = &orderBy
+	return r
+}
+
+// Specifies the sort order for listing entities, defaults to ASC.
+func (r ApiFindSourcesRequest) SortOrder(sortOrder SortOrder) ApiFindSourcesRequest {
+	r.sortOrder = &sortOrder
+	return r
+}
+
+// Token to use to retrieve next page of results.
+func (r ApiFindSourcesRequest) NextPageToken(nextPageToken string) ApiFindSourcesRequest {
+	r.nextPageToken = &nextPageToken
+	return r
+}
+
+func (r ApiFindSourcesRequest) Execute() (*CatalogSourceList, *http.Response, error) {
+	return r.ApiService.FindSourcesExecute(r)
+}
+
+/*
+FindSources List All CatalogSources
+
+Gets a list of all `CatalogSource` entities.
+
+	@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+	@return ApiFindSourcesRequest
+*/
+func (a *ModelCatalogServiceAPIService) FindSources(ctx context.Context) ApiFindSourcesRequest {
+	return ApiFindSourcesRequest{
+		ApiService: a,
+		ctx:        ctx,
+	}
+}
+
+// Execute executes the request
+//
+//	@return CatalogSourceList
+func (a *ModelCatalogServiceAPIService) FindSourcesExecute(r ApiFindSourcesRequest) (*CatalogSourceList, *http.Response, error) {
+	var (
+		localVarHTTPMethod  = http.MethodGet
+		localVarPostBody    interface{}
+		formFiles           []formFile
+		localVarReturnValue *CatalogSourceList
+	)
+
+	localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ModelCatalogServiceAPIService.FindSources")
+	if err != nil {
+		return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
+	}
+
+	localVarPath := localBasePath + "/api/model_catalog/v1alpha1/sources"
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := url.Values{}
+	localVarFormParams := url.Values{}
+
+	if r.name != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "name", r.name, "")
+	}
+	if r.pageSize != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "pageSize", r.pageSize, "")
+	}
+	if r.orderBy != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "orderBy", r.orderBy, "")
+	}
+	if r.sortOrder != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "sortOrder", r.sortOrder, "")
+	}
+	if r.nextPageToken != nil {
+		parameterAddToHeaderOrQuery(localVarQueryParams, "nextPageToken", r.nextPageToken, "")
+	}
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(req)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
+	if err != nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 400 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 401 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 404 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 500 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
+
+type ApiGetAllModelArtifactsRequest struct {
+	ctx        context.Context
+	ApiService *ModelCatalogServiceAPIService
+	sourceId   string
+	modelName  string
+}
+
+func (r ApiGetAllModelArtifactsRequest) Execute() (*CatalogModelArtifactList, *http.Response, error) {
+	return r.ApiService.GetAllModelArtifactsExecute(r)
+}
+
+/*
+GetAllModelArtifacts List CatalogModelArtifacts.
+
+	@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+	@param sourceId A unique identifier for a `CatalogSource`.
+	@param modelName A unique identifier for the model.
+	@return ApiGetAllModelArtifactsRequest
+*/
+func (a *ModelCatalogServiceAPIService) GetAllModelArtifacts(ctx context.Context, sourceId string, modelName string) ApiGetAllModelArtifactsRequest {
+	return ApiGetAllModelArtifactsRequest{
+		ApiService: a,
+		ctx:        ctx,
+		sourceId:   sourceId,
+		modelName:  modelName,
+	}
+}
+
+// Execute executes the request
+//
+//	@return CatalogModelArtifactList
+func (a *ModelCatalogServiceAPIService) GetAllModelArtifactsExecute(r ApiGetAllModelArtifactsRequest) (*CatalogModelArtifactList, *http.Response, error) {
+	var (
+		localVarHTTPMethod  = http.MethodGet
+		localVarPostBody    interface{}
+		formFiles           []formFile
+		localVarReturnValue *CatalogModelArtifactList
+	)
+
+	localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ModelCatalogServiceAPIService.GetAllModelArtifacts")
+	if err != nil {
+		return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
+	}
+
+	localVarPath := localBasePath + "/api/model_catalog/v1alpha1/sources/{source_id}/models/{model_name}/artifacts"
+	localVarPath = strings.Replace(localVarPath, "{"+"source_id"+"}", url.PathEscape(parameterValueToString(r.sourceId, "sourceId")), -1)
+	localVarPath = strings.Replace(localVarPath, "{"+"model_name"+"}", url.PathEscape(parameterValueToString(r.modelName, "modelName")), -1)
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := url.Values{}
+	localVarFormParams := url.Values{}
+
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(req)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
+	if err != nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 401 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 404 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 500 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
+
+type ApiGetModelRequest struct {
+	ctx        context.Context
+	ApiService *ModelCatalogServiceAPIService
+	sourceId   string
+	modelName  string
+}
+
+func (r ApiGetModelRequest) Execute() (*CatalogModel, *http.Response, error) {
+	return r.ApiService.GetModelExecute(r)
+}
+
+/*
+GetModel Get a `CatalogModel`.
+
+	@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+	@param sourceId A unique identifier for a `CatalogSource`.
+	@param modelName A unique identifier for the model.
+	@return ApiGetModelRequest
+*/
+func (a *ModelCatalogServiceAPIService) GetModel(ctx context.Context, sourceId string, modelName string) ApiGetModelRequest {
+	return ApiGetModelRequest{
+		ApiService: a,
+		ctx:        ctx,
+		sourceId:   sourceId,
+		modelName:  modelName,
+	}
+}
+
+// Execute executes the request
+//
+//	@return CatalogModel
+func (a *ModelCatalogServiceAPIService) GetModelExecute(r ApiGetModelRequest) (*CatalogModel, *http.Response, error) {
+	var (
+		localVarHTTPMethod  = http.MethodGet
+		localVarPostBody    interface{}
+		formFiles           []formFile
+		localVarReturnValue *CatalogModel
+	)
+
+	localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "ModelCatalogServiceAPIService.GetModel")
+	if err != nil {
+		return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
+	}
+
+	localVarPath := localBasePath + "/api/model_catalog/v1alpha1/sources/{source_id}/models/{model_name}"
+	localVarPath = strings.Replace(localVarPath, "{"+"source_id"+"}", url.PathEscape(parameterValueToString(r.sourceId, "sourceId")), -1)
+	localVarPath = strings.Replace(localVarPath, "{"+"model_name"+"}", url.PathEscape(parameterValueToString(r.modelName, "modelName")), -1)
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := url.Values{}
+	localVarFormParams := url.Values{}
+
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(req)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
+	if err != nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 401 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 404 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 500 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
+			newErr.model = v
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := &GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
diff --git a/catalog/pkg/openapi/client.go b/catalog/pkg/openapi/client.go
new file mode 100644
index 00000000..fcd8e7ea
--- /dev/null
+++ b/catalog/pkg/openapi/client.go
@@ -0,0 +1,666 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"mime/multipart"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"os"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+	"unicode/utf8"
+)
+
+var (
+	jsonCheck       = regexp.MustCompile(`(?i:(?:application|text)/(?:vnd\.[^;]+\+)?json)`)
+	xmlCheck        = regexp.MustCompile(`(?i:(?:application|text)/xml)`)
+	queryParamSplit = regexp.MustCompile(`(^|&)([^&]+)`)
+	queryDescape    = strings.NewReplacer("%5B", "[", "%5D", "]")
+)
+
+// APIClient manages communication with the Model Catalog REST API API vv1alpha1
+// In most cases there should be only one, shared, APIClient.
+type APIClient struct {
+	cfg    *Configuration
+	common service // Reuse a single struct instead of allocating one for each service on the heap.
+
+	// API Services
+
+	ModelCatalogServiceAPI *ModelCatalogServiceAPIService
+}
+
+type service struct {
+	client *APIClient
+}
+
+// NewAPIClient creates a new API client. Requires a userAgent string describing your application.
+// optionally a custom http.Client to allow for advanced features such as caching.
+func NewAPIClient(cfg *Configuration) *APIClient {
+	if cfg.HTTPClient == nil {
+		cfg.HTTPClient = http.DefaultClient
+	}
+
+	c := &APIClient{}
+	c.cfg = cfg
+	c.common.client = c
+
+	// API Services
+	c.ModelCatalogServiceAPI = (*ModelCatalogServiceAPIService)(&c.common)
+
+	return c
+}
+
+func atoi(in string) (int, error) {
+	return strconv.Atoi(in)
+}
+
+// selectHeaderContentType select a content type from the available list.
+func selectHeaderContentType(contentTypes []string) string {
+	if len(contentTypes) == 0 {
+		return ""
+	}
+	if contains(contentTypes, "application/json") {
+		return "application/json"
+	}
+	return contentTypes[0] // use the first content type specified in 'consumes'
+}
+
+// selectHeaderAccept join all accept types and return
+func selectHeaderAccept(accepts []string) string {
+	if len(accepts) == 0 {
+		return ""
+	}
+
+	if contains(accepts, "application/json") {
+		return "application/json"
+	}
+
+	return strings.Join(accepts, ",")
+}
+
+// contains is a case insensitive match, finding needle in a haystack
+func contains(haystack []string, needle string) bool {
+	for _, a := range haystack {
+		if strings.EqualFold(a, needle) {
+			return true
+		}
+	}
+	return false
+}
+
+// Verify optional parameters are of the correct type.
+func typeCheckParameter(obj interface{}, expected string, name string) error {
+	// Make sure there is an object.
+	if obj == nil {
+		return nil
+	}
+
+	// Check the type is as expected.
+	if reflect.TypeOf(obj).String() != expected {
+		return fmt.Errorf("expected %s to be of type %s but received %s", name, expected, reflect.TypeOf(obj).String())
+	}
+	return nil
+}
+
+func parameterValueToString(obj interface{}, key string) string {
+	if reflect.TypeOf(obj).Kind() != reflect.Ptr {
+		return fmt.Sprintf("%v", obj)
+	}
+	var param, ok = obj.(MappedNullable)
+	if !ok {
+		return ""
+	}
+	dataMap, err := param.ToMap()
+	if err != nil {
+		return ""
+	}
+	return fmt.Sprintf("%v", dataMap[key])
+}
+
+// parameterAddToHeaderOrQuery adds the provided object to the request header or url query
+// supporting deep object syntax
+func parameterAddToHeaderOrQuery(headerOrQueryParams interface{}, keyPrefix string, obj interface{}, collectionType string) {
+	var v = reflect.ValueOf(obj)
+	var value = ""
+	if v == reflect.ValueOf(nil) {
+		value = "null"
+	} else {
+		switch v.Kind() {
+		case reflect.Invalid:
+			value = "invalid"
+
+		case reflect.Struct:
+			if t, ok := obj.(MappedNullable); ok {
+				dataMap, err := t.ToMap()
+				if err != nil {
+					return
+				}
+				parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, dataMap, collectionType)
+				return
+			}
+			if t, ok := obj.(time.Time); ok {
+				parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, t.Format(time.RFC3339), collectionType)
+				return
+			}
+			value = v.Type().String() + " value"
+		case reflect.Slice:
+			var indValue = reflect.ValueOf(obj)
+			if indValue == reflect.ValueOf(nil) {
+				return
+			}
+			var lenIndValue = indValue.Len()
+			for i := 0; i < lenIndValue; i++ {
+				var arrayValue = indValue.Index(i)
+				parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, arrayValue.Interface(), collectionType)
+			}
+			return
+
+		case reflect.Map:
+			var indValue = reflect.ValueOf(obj)
+			if indValue == reflect.ValueOf(nil) {
+				return
+			}
+			iter := indValue.MapRange()
+			for iter.Next() {
+				k, v := iter.Key(), iter.Value()
+				parameterAddToHeaderOrQuery(headerOrQueryParams, fmt.Sprintf("%s[%s]", keyPrefix, k.String()), v.Interface(), collectionType)
+			}
+			return
+
+		case reflect.Interface:
+			fallthrough
+		case reflect.Ptr:
+			parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, v.Elem().Interface(), collectionType)
+			return
+
+		case reflect.Int, reflect.Int8, reflect.Int16,
+			reflect.Int32, reflect.Int64:
+			value = strconv.FormatInt(v.Int(), 10)
+		case reflect.Uint, reflect.Uint8, reflect.Uint16,
+			reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+			value = strconv.FormatUint(v.Uint(), 10)
+		case reflect.Float32, reflect.Float64:
+			value = strconv.FormatFloat(v.Float(), 'g', -1, 32)
+		case reflect.Bool:
+			value = strconv.FormatBool(v.Bool())
+		case reflect.String:
+			value = v.String()
+		default:
+			value = v.Type().String() + " value"
+		}
+	}
+
+	switch valuesMap := headerOrQueryParams.(type) {
+	case url.Values:
+		if collectionType == "csv" && valuesMap.Get(keyPrefix) != "" {
+			valuesMap.Set(keyPrefix, valuesMap.Get(keyPrefix)+","+value)
+		} else {
+			valuesMap.Add(keyPrefix, value)
+		}
+		break
+	case map[string]string:
+		valuesMap[keyPrefix] = value
+		break
+	}
+}
+
+// helper for converting interface{} parameters to json strings
+func parameterToJson(obj interface{}) (string, error) {
+	jsonBuf, err := json.Marshal(obj)
+	if err != nil {
+		return "", err
+	}
+	return string(jsonBuf), err
+}
+
+// callAPI do the request.
+func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) {
+	if c.cfg.Debug {
+		dump, err := httputil.DumpRequestOut(request, true)
+		if err != nil {
+			return nil, err
+		}
+		log.Printf("\n%s\n", string(dump))
+	}
+
+	resp, err := c.cfg.HTTPClient.Do(request)
+	if err != nil {
+		return resp, err
+	}
+
+	if c.cfg.Debug {
+		dump, err := httputil.DumpResponse(resp, true)
+		if err != nil {
+			return resp, err
+		}
+		log.Printf("\n%s\n", string(dump))
+	}
+	return resp, err
+}
+
+// Allow modification of underlying config for alternate implementations and testing
+// Caution: modifying the configuration while live can cause data races and potentially unwanted behavior
+func (c *APIClient) GetConfig() *Configuration {
+	return c.cfg
+}
+
+type formFile struct {
+	fileBytes    []byte
+	fileName     string
+	formFileName string
+}
+
+// prepareRequest build the request
+func (c *APIClient) prepareRequest(
+	ctx context.Context,
+	path string, method string,
+	postBody interface{},
+	headerParams map[string]string,
+	queryParams url.Values,
+	formParams url.Values,
+	formFiles []formFile) (localVarRequest *http.Request, err error) {
+
+	var body *bytes.Buffer
+
+	// Detect postBody type and post.
+	if postBody != nil {
+		contentType := headerParams["Content-Type"]
+		if contentType == "" {
+			contentType = detectContentType(postBody)
+			headerParams["Content-Type"] = contentType
+		}
+
+		body, err = setBody(postBody, contentType)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// add form parameters and file if available.
+	if strings.HasPrefix(headerParams["Content-Type"], "multipart/form-data") && len(formParams) > 0 || (len(formFiles) > 0) {
+		if body != nil {
+			return nil, errors.New("Cannot specify postBody and multipart form at the same time.")
+		}
+		body = &bytes.Buffer{}
+		w := multipart.NewWriter(body)
+
+		for k, v := range formParams {
+			for _, iv := range v {
+				if strings.HasPrefix(k, "@") { // file
+					err = addFile(w, k[1:], iv)
+					if err != nil {
+						return nil, err
+					}
+				} else { // form value
+					w.WriteField(k, iv)
+				}
+			}
+		}
+		for _, formFile := range formFiles {
+			if len(formFile.fileBytes) > 0 && formFile.fileName != "" {
+				w.Boundary()
+				part, err := w.CreateFormFile(formFile.formFileName, filepath.Base(formFile.fileName))
+				if err != nil {
+					return nil, err
+				}
+				_, err = part.Write(formFile.fileBytes)
+				if err != nil {
+					return nil, err
+				}
+			}
+		}
+
+		// Set the Boundary in the Content-Type
+		headerParams["Content-Type"] = w.FormDataContentType()
+
+		// Set Content-Length
+		headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len())
+		w.Close()
+	}
+
+	if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 {
+		if body != nil {
+			return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.")
+		}
+		body = &bytes.Buffer{}
+		body.WriteString(formParams.Encode())
+		// Set Content-Length
+		headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len())
+	}
+
+	// Setup path and query parameters
+	url, err := url.Parse(path)
+	if err != nil {
+		return nil, err
+	}
+
+	// Override request host, if applicable
+	if c.cfg.Host != "" {
+		url.Host = c.cfg.Host
+	}
+
+	// Override request scheme, if applicable
+	if c.cfg.Scheme != "" {
+		url.Scheme = c.cfg.Scheme
+	}
+
+	// Adding Query Param
+	query := url.Query()
+	for k, v := range queryParams {
+		for _, iv := range v {
+			query.Add(k, iv)
+		}
+	}
+
+	// Encode the parameters.
+	url.RawQuery = queryParamSplit.ReplaceAllStringFunc(query.Encode(), func(s string) string {
+		pieces := strings.Split(s, "=")
+		pieces[0] = queryDescape.Replace(pieces[0])
+		return strings.Join(pieces, "=")
+	})
+
+	// Generate a new request
+	if body != nil {
+		localVarRequest, err = http.NewRequest(method, url.String(), body)
+	} else {
+		localVarRequest, err = http.NewRequest(method, url.String(), nil)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	// add header parameters, if any
+	if len(headerParams) > 0 {
+		headers := http.Header{}
+		for h, v := range headerParams {
+			headers[h] = []string{v}
+		}
+		localVarRequest.Header = headers
+	}
+
+	// Add the user agent to the request.
+	localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent)
+
+	if ctx != nil {
+		// add context to the request
+		localVarRequest = localVarRequest.WithContext(ctx)
+
+		// Walk through any authentication.
+
+		// AccessToken Authentication
+		if auth, ok := ctx.Value(ContextAccessToken).(string); ok {
+			localVarRequest.Header.Add("Authorization", "Bearer "+auth)
+		}
+
+	}
+
+	for header, value := range c.cfg.DefaultHeader {
+		localVarRequest.Header.Add(header, value)
+	}
+	return localVarRequest, nil
+}
+
+func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) {
+	if len(b) == 0 {
+		return nil
+	}
+	if s, ok := v.(*string); ok {
+		*s = string(b)
+		return nil
+	}
+	if f, ok := v.(*os.File); ok {
+		f, err = os.CreateTemp("", "HttpClientFile")
+		if err != nil {
+			return
+		}
+		_, err = f.Write(b)
+		if err != nil {
+			return
+		}
+		_, err = f.Seek(0, io.SeekStart)
+		err = os.Remove(f.Name())
+		return
+	}
+	if f, ok := v.(**os.File); ok {
+		*f, err = os.CreateTemp("", "HttpClientFile")
+		if err != nil {
+			return
+		}
+		_, err = (*f).Write(b)
+		if err != nil {
+			return
+		}
+		_, err = (*f).Seek(0, io.SeekStart)
+		err = os.Remove((*f).Name())
+		return
+	}
+	if xmlCheck.MatchString(contentType) {
+		if err = xml.Unmarshal(b, v); err != nil {
+			return err
+		}
+		return nil
+	}
+	if jsonCheck.MatchString(contentType) {
+		if actualObj, ok := v.(interface{ GetActualInstance() interface{} }); ok { // oneOf, anyOf schemas
+			if unmarshalObj, ok := actualObj.(interface{ UnmarshalJSON([]byte) error }); ok { // make sure it has UnmarshalJSON defined
+				if err = unmarshalObj.UnmarshalJSON(b); err != nil {
+					return err
+				}
+			} else {
+				return errors.New("Unknown type with GetActualInstance but no unmarshalObj.UnmarshalJSON defined")
+			}
+		} else if err = json.Unmarshal(b, v); err != nil { // simple model
+			return err
+		}
+		return nil
+	}
+	return errors.New("undefined response type")
+}
+
+// Add a file to the multipart request
+func addFile(w *multipart.Writer, fieldName, path string) error {
+	file, err := os.Open(filepath.Clean(path))
+	if err != nil {
+		return err
+	}
+	err = file.Close()
+	if err != nil {
+		return err
+	}
+
+	part, err := w.CreateFormFile(fieldName, filepath.Base(path))
+	if err != nil {
+		return err
+	}
+	_, err = io.Copy(part, file)
+
+	return err
+}
+
+// Prevent trying to import "fmt"
+func reportError(format string, a ...interface{}) error {
+	return fmt.Errorf(format, a...)
+}
+
+// A wrapper for strict JSON decoding
+func newStrictDecoder(data []byte) *json.Decoder {
+	dec := json.NewDecoder(bytes.NewBuffer(data))
+	dec.DisallowUnknownFields()
+	return dec
+}
+
+// Set request body from an interface{}
+func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) {
+	if bodyBuf == nil {
+		bodyBuf = &bytes.Buffer{}
+	}
+
+	if reader, ok := body.(io.Reader); ok {
+		_, err = bodyBuf.ReadFrom(reader)
+	} else if fp, ok := body.(*os.File); ok {
+		_, err = bodyBuf.ReadFrom(fp)
+	} else if b, ok := body.([]byte); ok {
+		_, err = bodyBuf.Write(b)
+	} else if s, ok := body.(string); ok {
+		_, err = bodyBuf.WriteString(s)
+	} else if s, ok := body.(*string); ok {
+		_, err = bodyBuf.WriteString(*s)
+	} else if jsonCheck.MatchString(contentType) {
+		err = json.NewEncoder(bodyBuf).Encode(body)
+	} else if xmlCheck.MatchString(contentType) {
+		var bs []byte
+		bs, err = xml.Marshal(body)
+		if err == nil {
+			bodyBuf.Write(bs)
+		}
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	if bodyBuf.Len() == 0 {
+		err = fmt.Errorf("invalid body type %s\n", contentType)
+		return nil, err
+	}
+	return bodyBuf, nil
+}
+
+// detectContentType method is used to figure out `Request.Body` content type for request header
+func detectContentType(body interface{}) string {
+	contentType := "text/plain; charset=utf-8"
+	kind := reflect.TypeOf(body).Kind()
+
+	switch kind {
+	case reflect.Struct, reflect.Map, reflect.Ptr:
+		contentType = "application/json; charset=utf-8"
+	case reflect.String:
+		contentType = "text/plain; charset=utf-8"
+	default:
+		if b, ok := body.([]byte); ok {
+			contentType = http.DetectContentType(b)
+		} else if kind == reflect.Slice {
+			contentType = "application/json; charset=utf-8"
+		}
+	}
+
+	return contentType
+}
+
+// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go
+type cacheControl map[string]string
+
+func parseCacheControl(headers http.Header) cacheControl {
+	cc := cacheControl{}
+	ccHeader := headers.Get("Cache-Control")
+	for _, part := range strings.Split(ccHeader, ",") {
+		part = strings.Trim(part, " ")
+		if part == "" {
+			continue
+		}
+		if strings.ContainsRune(part, '=') {
+			keyval := strings.Split(part, "=")
+			cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",")
+		} else {
+			cc[part] = ""
+		}
+	}
+	return cc
+}
+
+// CacheExpires helper function to determine remaining time before repeating a request.
+func CacheExpires(r *http.Response) time.Time {
+	// Figure out when the cache expires.
+	var expires time.Time
+	now, err := time.Parse(time.RFC1123, r.Header.Get("date"))
+	if err != nil {
+		return time.Now()
+	}
+	respCacheControl := parseCacheControl(r.Header)
+
+	if maxAge, ok := respCacheControl["max-age"]; ok {
+		lifetime, err := time.ParseDuration(maxAge + "s")
+		if err != nil {
+			expires = now
+		} else {
+			expires = now.Add(lifetime)
+		}
+	} else {
+		expiresHeader := r.Header.Get("Expires")
+		if expiresHeader != "" {
+			expires, err = time.Parse(time.RFC1123, expiresHeader)
+			if err != nil {
+				expires = now
+			}
+		}
+	}
+	return expires
+}
+
+func strlen(s string) int {
+	return utf8.RuneCountInString(s)
+}
+
+// GenericOpenAPIError Provides access to the body, error and model on returned errors.
+type GenericOpenAPIError struct {
+	body  []byte
+	error string
+	model interface{}
+}
+
+// Error returns non-empty string if there was an error.
+func (e GenericOpenAPIError) Error() string {
+	return e.error
+}
+
+// Body returns the raw bytes of the response
+func (e GenericOpenAPIError) Body() []byte {
+	return e.body
+}
+
+// Model returns the unpacked model of the error
+func (e GenericOpenAPIError) Model() interface{} {
+	return e.model
+}
+
+// format error message using title and detail when model implements rfc7807
+func formatErrorMessage(status string, v interface{}) string {
+	str := ""
+	metaValue := reflect.ValueOf(v).Elem()
+
+	if metaValue.Kind() == reflect.Struct {
+		field := metaValue.FieldByName("Title")
+		if field != (reflect.Value{}) {
+			str = fmt.Sprintf("%s", field.Interface())
+		}
+
+		field = metaValue.FieldByName("Detail")
+		if field != (reflect.Value{}) {
+			str = fmt.Sprintf("%s (%s)", str, field.Interface())
+		}
+	}
+
+	return strings.TrimSpace(fmt.Sprintf("%s %s", status, str))
+}
diff --git a/catalog/pkg/openapi/configuration.go b/catalog/pkg/openapi/configuration.go
new file mode 100644
index 00000000..f51dbfb7
--- /dev/null
+++ b/catalog/pkg/openapi/configuration.go
@@ -0,0 +1,221 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+)
+
+// contextKeys are used to identify the type of value in the context.
+// Since these are string, it is possible to get a short description of the
+// context key for logging and debugging using key.String().
+
+type contextKey string
+
+func (c contextKey) String() string {
+	return "auth " + string(c)
+}
+
+var (
+	// ContextAccessToken takes a string oauth2 access token as authentication for the request.
+	ContextAccessToken = contextKey("accesstoken")
+
+	// ContextServerIndex uses a server configuration from the index.
+	ContextServerIndex = contextKey("serverIndex")
+
+	// ContextOperationServerIndices uses a server configuration from the index mapping.
+	ContextOperationServerIndices = contextKey("serverOperationIndices")
+
+	// ContextServerVariables overrides a server configuration variables.
+	ContextServerVariables = contextKey("serverVariables")
+
+	// ContextOperationServerVariables overrides a server configuration variables using operation specific values.
+	ContextOperationServerVariables = contextKey("serverOperationVariables")
+)
+
+// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth
+type BasicAuth struct {
+	UserName string `json:"userName,omitempty"`
+	Password string `json:"password,omitempty"`
+}
+
+// APIKey provides API key based authentication to a request passed via context using ContextAPIKey
+type APIKey struct {
+	Key    string
+	Prefix string
+}
+
+// ServerVariable stores the information about a server variable
+type ServerVariable struct {
+	Description  string
+	DefaultValue string
+	EnumValues   []string
+}
+
+// ServerConfiguration stores the information about a server
+type ServerConfiguration struct {
+	URL         string
+	Description string
+	Variables   map[string]ServerVariable
+}
+
+// ServerConfigurations stores multiple ServerConfiguration items
+type ServerConfigurations []ServerConfiguration
+
+// Configuration stores the configuration of the API client
+type Configuration struct {
+	Host             string            `json:"host,omitempty"`
+	Scheme           string            `json:"scheme,omitempty"`
+	DefaultHeader    map[string]string `json:"defaultHeader,omitempty"`
+	UserAgent        string            `json:"userAgent,omitempty"`
+	Debug            bool              `json:"debug,omitempty"`
+	Servers          ServerConfigurations
+	OperationServers map[string]ServerConfigurations
+	HTTPClient       *http.Client
+}
+
+// NewConfiguration returns a new Configuration object
+func NewConfiguration() *Configuration {
+	cfg := &Configuration{
+		DefaultHeader: make(map[string]string),
+		UserAgent:     "OpenAPI-Generator/1.0.0/go",
+		Debug:         false,
+		Servers: ServerConfigurations{
+			{
+				URL:         "https://localhost:8080",
+				Description: "No description provided",
+			},
+			{
+				URL:         "http://localhost:8080",
+				Description: "No description provided",
+			},
+		},
+		OperationServers: map[string]ServerConfigurations{},
+	}
+	return cfg
+}
+
+// AddDefaultHeader adds a new HTTP header to the default header in the request
+func (c *Configuration) AddDefaultHeader(key string, value string) {
+	c.DefaultHeader[key] = value
+}
+
+// URL formats template on a index using given variables
+func (sc ServerConfigurations) URL(index int, variables map[string]string) (string, error) {
+	if index < 0 || len(sc) <= index {
+		return "", fmt.Errorf("index %v out of range %v", index, len(sc)-1)
+	}
+	server := sc[index]
+	url := server.URL
+
+	// go through variables and replace placeholders
+	for name, variable := range server.Variables {
+		if value, ok := variables[name]; ok {
+			found := bool(len(variable.EnumValues) == 0)
+			for _, enumValue := range variable.EnumValues {
+				if value == enumValue {
+					found = true
+				}
+			}
+			if !found {
+				return "", fmt.Errorf("the variable %s in the server URL has invalid value %v. Must be %v", name, value, variable.EnumValues)
+			}
+			url = strings.Replace(url, "{"+name+"}", value, -1)
+		} else {
+			url = strings.Replace(url, "{"+name+"}", variable.DefaultValue, -1)
+		}
+	}
+	return url, nil
+}
+
+// ServerURL returns URL based on server settings
+func (c *Configuration) ServerURL(index int, variables map[string]string) (string, error) {
+	return c.Servers.URL(index, variables)
+}
+
+func getServerIndex(ctx context.Context) (int, error) {
+	si := ctx.Value(ContextServerIndex)
+	if si != nil {
+		if index, ok := si.(int); ok {
+			return index, nil
+		}
+		return 0, reportError("Invalid type %T should be int", si)
+	}
+	return 0, nil
+}
+
+func getServerOperationIndex(ctx context.Context, endpoint string) (int, error) {
+	osi := ctx.Value(ContextOperationServerIndices)
+	if osi != nil {
+		if operationIndices, ok := osi.(map[string]int); !ok {
+			return 0, reportError("Invalid type %T should be map[string]int", osi)
+		} else {
+			index, ok := operationIndices[endpoint]
+			if ok {
+				return index, nil
+			}
+		}
+	}
+	return getServerIndex(ctx)
+}
+
+func getServerVariables(ctx context.Context) (map[string]string, error) {
+	sv := ctx.Value(ContextServerVariables)
+	if sv != nil {
+		if variables, ok := sv.(map[string]string); ok {
+			return variables, nil
+		}
+		return nil, reportError("ctx value of ContextServerVariables has invalid type %T should be map[string]string", sv)
+	}
+	return nil, nil
+}
+
+func getServerOperationVariables(ctx context.Context, endpoint string) (map[string]string, error) {
+	osv := ctx.Value(ContextOperationServerVariables)
+	if osv != nil {
+		if operationVariables, ok := osv.(map[string]map[string]string); !ok {
+			return nil, reportError("ctx value of ContextOperationServerVariables has invalid type %T should be map[string]map[string]string", osv)
+		} else {
+			variables, ok := operationVariables[endpoint]
+			if ok {
+				return variables, nil
+			}
+		}
+	}
+	return getServerVariables(ctx)
+}
+
+// ServerURLWithContext returns a new server URL given an endpoint
+func (c *Configuration) ServerURLWithContext(ctx context.Context, endpoint string) (string, error) {
+	sc, ok := c.OperationServers[endpoint]
+	if !ok {
+		sc = c.Servers
+	}
+
+	if ctx == nil {
+		return sc.URL(0, nil)
+	}
+
+	index, err := getServerOperationIndex(ctx, endpoint)
+	if err != nil {
+		return "", err
+	}
+
+	variables, err := getServerOperationVariables(ctx, endpoint)
+	if err != nil {
+		return "", err
+	}
+
+	return sc.URL(index, variables)
+}
diff --git a/catalog/pkg/openapi/model_base_model.go b/catalog/pkg/openapi/model_base_model.go
new file mode 100644
index 00000000..654d7fe3
--- /dev/null
+++ b/catalog/pkg/openapi/model_base_model.go
@@ -0,0 +1,494 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the BaseModel type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &BaseModel{}
+
+// BaseModel struct for BaseModel
+type BaseModel struct {
+	// Human-readable description of the model.
+	Description *string `json:"description,omitempty"`
+	// Model documentation in Markdown.
+	Readme *string `json:"readme,omitempty"`
+	// Maturity level of the model.
+	Maturity *string `json:"maturity,omitempty"`
+	// List of supported languages (https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).
+	Language []string `json:"language,omitempty"`
+	// List of tasks the model is designed for.
+	Tasks []string `json:"tasks,omitempty"`
+	// Name of the organization or entity that provides the model.
+	Provider *string `json:"provider,omitempty"`
+	// URL to the model's logo. A [data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data) is recommended.
+	Logo *string `json:"logo,omitempty"`
+	// Short name of the model's license.
+	License *string `json:"license,omitempty"`
+	// URL to the license text.
+	LicenseLink *string `json:"licenseLink,omitempty"`
+	LibraryName *string `json:"libraryName,omitempty"`
+	// User provided custom properties which are not defined by its type.
+	CustomProperties *map[string]MetadataValue `json:"customProperties,omitempty"`
+}
+
+// NewBaseModel instantiates a new BaseModel object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewBaseModel() *BaseModel {
+	this := BaseModel{}
+	return &this
+}
+
+// NewBaseModelWithDefaults instantiates a new BaseModel object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewBaseModelWithDefaults() *BaseModel {
+	this := BaseModel{}
+	return &this
+}
+
+// GetDescription returns the Description field value if set, zero value otherwise.
+func (o *BaseModel) GetDescription() string {
+	if o == nil || IsNil(o.Description) {
+		var ret string
+		return ret
+	}
+	return *o.Description
+}
+
+// GetDescriptionOk returns a tuple with the Description field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetDescriptionOk() (*string, bool) {
+	if o == nil || IsNil(o.Description) {
+		return nil, false
+	}
+	return o.Description, true
+}
+
+// HasDescription returns a boolean if a field has been set.
+func (o *BaseModel) HasDescription() bool {
+	if o != nil && !IsNil(o.Description) {
+		return true
+	}
+
+	return false
+}
+
+// SetDescription gets a reference to the given string and assigns it to the Description field.
+func (o *BaseModel) SetDescription(v string) {
+	o.Description = &v
+}
+
+// GetReadme returns the Readme field value if set, zero value otherwise.
+func (o *BaseModel) GetReadme() string {
+	if o == nil || IsNil(o.Readme) {
+		var ret string
+		return ret
+	}
+	return *o.Readme
+}
+
+// GetReadmeOk returns a tuple with the Readme field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetReadmeOk() (*string, bool) {
+	if o == nil || IsNil(o.Readme) {
+		return nil, false
+	}
+	return o.Readme, true
+}
+
+// HasReadme returns a boolean if a field has been set.
+func (o *BaseModel) HasReadme() bool {
+	if o != nil && !IsNil(o.Readme) {
+		return true
+	}
+
+	return false
+}
+
+// SetReadme gets a reference to the given string and assigns it to the Readme field.
+func (o *BaseModel) SetReadme(v string) {
+	o.Readme = &v
+}
+
+// GetMaturity returns the Maturity field value if set, zero value otherwise.
+func (o *BaseModel) GetMaturity() string {
+	if o == nil || IsNil(o.Maturity) {
+		var ret string
+		return ret
+	}
+	return *o.Maturity
+}
+
+// GetMaturityOk returns a tuple with the Maturity field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetMaturityOk() (*string, bool) {
+	if o == nil || IsNil(o.Maturity) {
+		return nil, false
+	}
+	return o.Maturity, true
+}
+
+// HasMaturity returns a boolean if a field has been set.
+func (o *BaseModel) HasMaturity() bool {
+	if o != nil && !IsNil(o.Maturity) {
+		return true
+	}
+
+	return false
+}
+
+// SetMaturity gets a reference to the given string and assigns it to the Maturity field.
+func (o *BaseModel) SetMaturity(v string) {
+	o.Maturity = &v
+}
+
+// GetLanguage returns the Language field value if set, zero value otherwise.
+func (o *BaseModel) GetLanguage() []string {
+	if o == nil || IsNil(o.Language) {
+		var ret []string
+		return ret
+	}
+	return o.Language
+}
+
+// GetLanguageOk returns a tuple with the Language field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetLanguageOk() ([]string, bool) {
+	if o == nil || IsNil(o.Language) {
+		return nil, false
+	}
+	return o.Language, true
+}
+
+// HasLanguage returns a boolean if a field has been set.
+func (o *BaseModel) HasLanguage() bool {
+	if o != nil && !IsNil(o.Language) {
+		return true
+	}
+
+	return false
+}
+
+// SetLanguage gets a reference to the given []string and assigns it to the Language field.
+func (o *BaseModel) SetLanguage(v []string) {
+	o.Language = v
+}
+
+// GetTasks returns the Tasks field value if set, zero value otherwise.
+func (o *BaseModel) GetTasks() []string {
+	if o == nil || IsNil(o.Tasks) {
+		var ret []string
+		return ret
+	}
+	return o.Tasks
+}
+
+// GetTasksOk returns a tuple with the Tasks field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetTasksOk() ([]string, bool) {
+	if o == nil || IsNil(o.Tasks) {
+		return nil, false
+	}
+	return o.Tasks, true
+}
+
+// HasTasks returns a boolean if a field has been set.
+func (o *BaseModel) HasTasks() bool {
+	if o != nil && !IsNil(o.Tasks) {
+		return true
+	}
+
+	return false
+}
+
+// SetTasks gets a reference to the given []string and assigns it to the Tasks field.
+func (o *BaseModel) SetTasks(v []string) {
+	o.Tasks = v
+}
+
+// GetProvider returns the Provider field value if set, zero value otherwise.
+func (o *BaseModel) GetProvider() string {
+	if o == nil || IsNil(o.Provider) {
+		var ret string
+		return ret
+	}
+	return *o.Provider
+}
+
+// GetProviderOk returns a tuple with the Provider field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetProviderOk() (*string, bool) {
+	if o == nil || IsNil(o.Provider) {
+		return nil, false
+	}
+	return o.Provider, true
+}
+
+// HasProvider returns a boolean if a field has been set.
+func (o *BaseModel) HasProvider() bool {
+	if o != nil && !IsNil(o.Provider) {
+		return true
+	}
+
+	return false
+}
+
+// SetProvider gets a reference to the given string and assigns it to the Provider field.
+func (o *BaseModel) SetProvider(v string) {
+	o.Provider = &v
+}
+
+// GetLogo returns the Logo field value if set, zero value otherwise.
+func (o *BaseModel) GetLogo() string {
+	if o == nil || IsNil(o.Logo) {
+		var ret string
+		return ret
+	}
+	return *o.Logo
+}
+
+// GetLogoOk returns a tuple with the Logo field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetLogoOk() (*string, bool) {
+	if o == nil || IsNil(o.Logo) {
+		return nil, false
+	}
+	return o.Logo, true
+}
+
+// HasLogo returns a boolean if a field has been set.
+func (o *BaseModel) HasLogo() bool {
+	if o != nil && !IsNil(o.Logo) {
+		return true
+	}
+
+	return false
+}
+
+// SetLogo gets a reference to the given string and assigns it to the Logo field.
+func (o *BaseModel) SetLogo(v string) {
+	o.Logo = &v
+}
+
+// GetLicense returns the License field value if set, zero value otherwise.
+func (o *BaseModel) GetLicense() string {
+	if o == nil || IsNil(o.License) {
+		var ret string
+		return ret
+	}
+	return *o.License
+}
+
+// GetLicenseOk returns a tuple with the License field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetLicenseOk() (*string, bool) {
+	if o == nil || IsNil(o.License) {
+		return nil, false
+	}
+	return o.License, true
+}
+
+// HasLicense returns a boolean if a field has been set.
+func (o *BaseModel) HasLicense() bool {
+	if o != nil && !IsNil(o.License) {
+		return true
+	}
+
+	return false
+}
+
+// SetLicense gets a reference to the given string and assigns it to the License field.
+func (o *BaseModel) SetLicense(v string) {
+	o.License = &v
+}
+
+// GetLicenseLink returns the LicenseLink field value if set, zero value otherwise.
+func (o *BaseModel) GetLicenseLink() string {
+	if o == nil || IsNil(o.LicenseLink) {
+		var ret string
+		return ret
+	}
+	return *o.LicenseLink
+}
+
+// GetLicenseLinkOk returns a tuple with the LicenseLink field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetLicenseLinkOk() (*string, bool) {
+	if o == nil || IsNil(o.LicenseLink) {
+		return nil, false
+	}
+	return o.LicenseLink, true
+}
+
+// HasLicenseLink returns a boolean if a field has been set.
+func (o *BaseModel) HasLicenseLink() bool {
+	if o != nil && !IsNil(o.LicenseLink) {
+		return true
+	}
+
+	return false
+}
+
+// SetLicenseLink gets a reference to the given string and assigns it to the LicenseLink field.
+func (o *BaseModel) SetLicenseLink(v string) {
+	o.LicenseLink = &v
+}
+
+// GetLibraryName returns the LibraryName field value if set, zero value otherwise.
+func (o *BaseModel) GetLibraryName() string {
+	if o == nil || IsNil(o.LibraryName) {
+		var ret string
+		return ret
+	}
+	return *o.LibraryName
+}
+
+// GetLibraryNameOk returns a tuple with the LibraryName field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetLibraryNameOk() (*string, bool) {
+	if o == nil || IsNil(o.LibraryName) {
+		return nil, false
+	}
+	return o.LibraryName, true
+}
+
+// HasLibraryName returns a boolean if a field has been set.
+func (o *BaseModel) HasLibraryName() bool {
+	if o != nil && !IsNil(o.LibraryName) {
+		return true
+	}
+
+	return false
+}
+
+// SetLibraryName gets a reference to the given string and assigns it to the LibraryName field.
+func (o *BaseModel) SetLibraryName(v string) {
+	o.LibraryName = &v
+}
+
+// GetCustomProperties returns the CustomProperties field value if set, zero value otherwise.
+func (o *BaseModel) GetCustomProperties() map[string]MetadataValue {
+	if o == nil || IsNil(o.CustomProperties) {
+		var ret map[string]MetadataValue
+		return ret
+	}
+	return *o.CustomProperties
+}
+
+// GetCustomPropertiesOk returns a tuple with the CustomProperties field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseModel) GetCustomPropertiesOk() (*map[string]MetadataValue, bool) {
+	if o == nil || IsNil(o.CustomProperties) {
+		return nil, false
+	}
+	return o.CustomProperties, true
+}
+
+// HasCustomProperties returns a boolean if a field has been set.
+func (o *BaseModel) HasCustomProperties() bool {
+	if o != nil && !IsNil(o.CustomProperties) {
+		return true
+	}
+
+	return false
+}
+
+// SetCustomProperties gets a reference to the given map[string]MetadataValue and assigns it to the CustomProperties field.
+func (o *BaseModel) SetCustomProperties(v map[string]MetadataValue) {
+	o.CustomProperties = &v
+}
+
+func (o BaseModel) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o BaseModel) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	if !IsNil(o.Description) {
+		toSerialize["description"] = o.Description
+	}
+	if !IsNil(o.Readme) {
+		toSerialize["readme"] = o.Readme
+	}
+	if !IsNil(o.Maturity) {
+		toSerialize["maturity"] = o.Maturity
+	}
+	if !IsNil(o.Language) {
+		toSerialize["language"] = o.Language
+	}
+	if !IsNil(o.Tasks) {
+		toSerialize["tasks"] = o.Tasks
+	}
+	if !IsNil(o.Provider) {
+		toSerialize["provider"] = o.Provider
+	}
+	if !IsNil(o.Logo) {
+		toSerialize["logo"] = o.Logo
+	}
+	if !IsNil(o.License) {
+		toSerialize["license"] = o.License
+	}
+	if !IsNil(o.LicenseLink) {
+		toSerialize["licenseLink"] = o.LicenseLink
+	}
+	if !IsNil(o.LibraryName) {
+		toSerialize["libraryName"] = o.LibraryName
+	}
+	if !IsNil(o.CustomProperties) {
+		toSerialize["customProperties"] = o.CustomProperties
+	}
+	return toSerialize, nil
+}
+
+type NullableBaseModel struct {
+	value *BaseModel
+	isSet bool
+}
+
+func (v NullableBaseModel) Get() *BaseModel {
+	return v.value
+}
+
+func (v *NullableBaseModel) Set(val *BaseModel) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableBaseModel) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableBaseModel) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableBaseModel(val *BaseModel) *NullableBaseModel {
+	return &NullableBaseModel{value: val, isSet: true}
+}
+
+func (v NullableBaseModel) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableBaseModel) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_base_resource_dates.go b/catalog/pkg/openapi/model_base_resource_dates.go
new file mode 100644
index 00000000..cd717ca0
--- /dev/null
+++ b/catalog/pkg/openapi/model_base_resource_dates.go
@@ -0,0 +1,162 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the BaseResourceDates type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &BaseResourceDates{}
+
+// BaseResourceDates Common timestamp fields for resources
+type BaseResourceDates struct {
+	// Output only. Create time of the resource in millisecond since epoch.
+	CreateTimeSinceEpoch *string `json:"createTimeSinceEpoch,omitempty"`
+	// Output only. Last update time of the resource since epoch in millisecond since epoch.
+	LastUpdateTimeSinceEpoch *string `json:"lastUpdateTimeSinceEpoch,omitempty"`
+}
+
+// NewBaseResourceDates instantiates a new BaseResourceDates object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewBaseResourceDates() *BaseResourceDates {
+	this := BaseResourceDates{}
+	return &this
+}
+
+// NewBaseResourceDatesWithDefaults instantiates a new BaseResourceDates object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewBaseResourceDatesWithDefaults() *BaseResourceDates {
+	this := BaseResourceDates{}
+	return &this
+}
+
+// GetCreateTimeSinceEpoch returns the CreateTimeSinceEpoch field value if set, zero value otherwise.
+func (o *BaseResourceDates) GetCreateTimeSinceEpoch() string {
+	if o == nil || IsNil(o.CreateTimeSinceEpoch) {
+		var ret string
+		return ret
+	}
+	return *o.CreateTimeSinceEpoch
+}
+
+// GetCreateTimeSinceEpochOk returns a tuple with the CreateTimeSinceEpoch field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseResourceDates) GetCreateTimeSinceEpochOk() (*string, bool) {
+	if o == nil || IsNil(o.CreateTimeSinceEpoch) {
+		return nil, false
+	}
+	return o.CreateTimeSinceEpoch, true
+}
+
+// HasCreateTimeSinceEpoch returns a boolean if a field has been set.
+func (o *BaseResourceDates) HasCreateTimeSinceEpoch() bool {
+	if o != nil && !IsNil(o.CreateTimeSinceEpoch) {
+		return true
+	}
+
+	return false
+}
+
+// SetCreateTimeSinceEpoch gets a reference to the given string and assigns it to the CreateTimeSinceEpoch field.
+func (o *BaseResourceDates) SetCreateTimeSinceEpoch(v string) {
+	o.CreateTimeSinceEpoch = &v
+}
+
+// GetLastUpdateTimeSinceEpoch returns the LastUpdateTimeSinceEpoch field value if set, zero value otherwise.
+func (o *BaseResourceDates) GetLastUpdateTimeSinceEpoch() string {
+	if o == nil || IsNil(o.LastUpdateTimeSinceEpoch) {
+		var ret string
+		return ret
+	}
+	return *o.LastUpdateTimeSinceEpoch
+}
+
+// GetLastUpdateTimeSinceEpochOk returns a tuple with the LastUpdateTimeSinceEpoch field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *BaseResourceDates) GetLastUpdateTimeSinceEpochOk() (*string, bool) {
+	if o == nil || IsNil(o.LastUpdateTimeSinceEpoch) {
+		return nil, false
+	}
+	return o.LastUpdateTimeSinceEpoch, true
+}
+
+// HasLastUpdateTimeSinceEpoch returns a boolean if a field has been set.
+func (o *BaseResourceDates) HasLastUpdateTimeSinceEpoch() bool {
+	if o != nil && !IsNil(o.LastUpdateTimeSinceEpoch) {
+		return true
+	}
+
+	return false
+}
+
+// SetLastUpdateTimeSinceEpoch gets a reference to the given string and assigns it to the LastUpdateTimeSinceEpoch field.
+func (o *BaseResourceDates) SetLastUpdateTimeSinceEpoch(v string) {
+	o.LastUpdateTimeSinceEpoch = &v
+}
+
+func (o BaseResourceDates) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o BaseResourceDates) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	if !IsNil(o.CreateTimeSinceEpoch) {
+		toSerialize["createTimeSinceEpoch"] = o.CreateTimeSinceEpoch
+	}
+	if !IsNil(o.LastUpdateTimeSinceEpoch) {
+		toSerialize["lastUpdateTimeSinceEpoch"] = o.LastUpdateTimeSinceEpoch
+	}
+	return toSerialize, nil
+}
+
+type NullableBaseResourceDates struct {
+	value *BaseResourceDates
+	isSet bool
+}
+
+func (v NullableBaseResourceDates) Get() *BaseResourceDates {
+	return v.value
+}
+
+func (v *NullableBaseResourceDates) Set(val *BaseResourceDates) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableBaseResourceDates) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableBaseResourceDates) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableBaseResourceDates(val *BaseResourceDates) *NullableBaseResourceDates {
+	return &NullableBaseResourceDates{value: val, isSet: true}
+}
+
+func (v NullableBaseResourceDates) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableBaseResourceDates) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_base_resource_list.go b/catalog/pkg/openapi/model_base_resource_list.go
new file mode 100644
index 00000000..829e14c9
--- /dev/null
+++ b/catalog/pkg/openapi/model_base_resource_list.go
@@ -0,0 +1,172 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the BaseResourceList type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &BaseResourceList{}
+
+// BaseResourceList struct for BaseResourceList
+type BaseResourceList struct {
+	// Token to use to retrieve next page of results.
+	NextPageToken string `json:"nextPageToken"`
+	// Maximum number of resources to return in the result.
+	PageSize int32 `json:"pageSize"`
+	// Number of items in result list.
+	Size int32 `json:"size"`
+}
+
+// NewBaseResourceList instantiates a new BaseResourceList object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewBaseResourceList(nextPageToken string, pageSize int32, size int32) *BaseResourceList {
+	this := BaseResourceList{}
+	this.NextPageToken = nextPageToken
+	this.PageSize = pageSize
+	this.Size = size
+	return &this
+}
+
+// NewBaseResourceListWithDefaults instantiates a new BaseResourceList object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewBaseResourceListWithDefaults() *BaseResourceList {
+	this := BaseResourceList{}
+	return &this
+}
+
+// GetNextPageToken returns the NextPageToken field value
+func (o *BaseResourceList) GetNextPageToken() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.NextPageToken
+}
+
+// GetNextPageTokenOk returns a tuple with the NextPageToken field value
+// and a boolean to check if the value has been set.
+func (o *BaseResourceList) GetNextPageTokenOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.NextPageToken, true
+}
+
+// SetNextPageToken sets field value
+func (o *BaseResourceList) SetNextPageToken(v string) {
+	o.NextPageToken = v
+}
+
+// GetPageSize returns the PageSize field value
+func (o *BaseResourceList) GetPageSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.PageSize
+}
+
+// GetPageSizeOk returns a tuple with the PageSize field value
+// and a boolean to check if the value has been set.
+func (o *BaseResourceList) GetPageSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.PageSize, true
+}
+
+// SetPageSize sets field value
+func (o *BaseResourceList) SetPageSize(v int32) {
+	o.PageSize = v
+}
+
+// GetSize returns the Size field value
+func (o *BaseResourceList) GetSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.Size
+}
+
+// GetSizeOk returns a tuple with the Size field value
+// and a boolean to check if the value has been set.
+func (o *BaseResourceList) GetSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Size, true
+}
+
+// SetSize sets field value
+func (o *BaseResourceList) SetSize(v int32) {
+	o.Size = v
+}
+
+func (o BaseResourceList) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o BaseResourceList) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["nextPageToken"] = o.NextPageToken
+	toSerialize["pageSize"] = o.PageSize
+	toSerialize["size"] = o.Size
+	return toSerialize, nil
+}
+
+type NullableBaseResourceList struct {
+	value *BaseResourceList
+	isSet bool
+}
+
+func (v NullableBaseResourceList) Get() *BaseResourceList {
+	return v.value
+}
+
+func (v *NullableBaseResourceList) Set(val *BaseResourceList) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableBaseResourceList) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableBaseResourceList) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableBaseResourceList(val *BaseResourceList) *NullableBaseResourceList {
+	return &NullableBaseResourceList{value: val, isSet: true}
+}
+
+func (v NullableBaseResourceList) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableBaseResourceList) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_catalog_model.go b/catalog/pkg/openapi/model_catalog_model.go
new file mode 100644
index 00000000..0ece8c75
--- /dev/null
+++ b/catalog/pkg/openapi/model_catalog_model.go
@@ -0,0 +1,633 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the CatalogModel type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CatalogModel{}
+
+// CatalogModel A model in the model catalog.
+type CatalogModel struct {
+	// Output only. Create time of the resource in millisecond since epoch.
+	CreateTimeSinceEpoch *string `json:"createTimeSinceEpoch,omitempty"`
+	// Output only. Last update time of the resource since epoch in millisecond since epoch.
+	LastUpdateTimeSinceEpoch *string `json:"lastUpdateTimeSinceEpoch,omitempty"`
+	// Human-readable description of the model.
+	Description *string `json:"description,omitempty"`
+	// Model documentation in Markdown.
+	Readme *string `json:"readme,omitempty"`
+	// Maturity level of the model.
+	Maturity *string `json:"maturity,omitempty"`
+	// List of supported languages (https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).
+	Language []string `json:"language,omitempty"`
+	// List of tasks the model is designed for.
+	Tasks []string `json:"tasks,omitempty"`
+	// Name of the organization or entity that provides the model.
+	Provider *string `json:"provider,omitempty"`
+	// URL to the model's logo. A [data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data) is recommended.
+	Logo *string `json:"logo,omitempty"`
+	// Short name of the model's license.
+	License *string `json:"license,omitempty"`
+	// URL to the license text.
+	LicenseLink *string `json:"licenseLink,omitempty"`
+	LibraryName *string `json:"libraryName,omitempty"`
+	// User provided custom properties which are not defined by its type.
+	CustomProperties *map[string]MetadataValue `json:"customProperties,omitempty"`
+	// Name of the model. Must be unique within a source.
+	Name string `json:"name"`
+	// ID of the source this model belongs to.
+	SourceId *string `json:"source_id,omitempty"`
+}
+
+// NewCatalogModel instantiates a new CatalogModel object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCatalogModel(name string) *CatalogModel {
+	this := CatalogModel{}
+	this.Name = name
+	return &this
+}
+
+// NewCatalogModelWithDefaults instantiates a new CatalogModel object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCatalogModelWithDefaults() *CatalogModel {
+	this := CatalogModel{}
+	return &this
+}
+
+// GetCreateTimeSinceEpoch returns the CreateTimeSinceEpoch field value if set, zero value otherwise.
+func (o *CatalogModel) GetCreateTimeSinceEpoch() string {
+	if o == nil || IsNil(o.CreateTimeSinceEpoch) {
+		var ret string
+		return ret
+	}
+	return *o.CreateTimeSinceEpoch
+}
+
+// GetCreateTimeSinceEpochOk returns a tuple with the CreateTimeSinceEpoch field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetCreateTimeSinceEpochOk() (*string, bool) {
+	if o == nil || IsNil(o.CreateTimeSinceEpoch) {
+		return nil, false
+	}
+	return o.CreateTimeSinceEpoch, true
+}
+
+// HasCreateTimeSinceEpoch returns a boolean if a field has been set.
+func (o *CatalogModel) HasCreateTimeSinceEpoch() bool {
+	if o != nil && !IsNil(o.CreateTimeSinceEpoch) {
+		return true
+	}
+
+	return false
+}
+
+// SetCreateTimeSinceEpoch gets a reference to the given string and assigns it to the CreateTimeSinceEpoch field.
+func (o *CatalogModel) SetCreateTimeSinceEpoch(v string) {
+	o.CreateTimeSinceEpoch = &v
+}
+
+// GetLastUpdateTimeSinceEpoch returns the LastUpdateTimeSinceEpoch field value if set, zero value otherwise.
+func (o *CatalogModel) GetLastUpdateTimeSinceEpoch() string {
+	if o == nil || IsNil(o.LastUpdateTimeSinceEpoch) {
+		var ret string
+		return ret
+	}
+	return *o.LastUpdateTimeSinceEpoch
+}
+
+// GetLastUpdateTimeSinceEpochOk returns a tuple with the LastUpdateTimeSinceEpoch field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetLastUpdateTimeSinceEpochOk() (*string, bool) {
+	if o == nil || IsNil(o.LastUpdateTimeSinceEpoch) {
+		return nil, false
+	}
+	return o.LastUpdateTimeSinceEpoch, true
+}
+
+// HasLastUpdateTimeSinceEpoch returns a boolean if a field has been set.
+func (o *CatalogModel) HasLastUpdateTimeSinceEpoch() bool {
+	if o != nil && !IsNil(o.LastUpdateTimeSinceEpoch) {
+		return true
+	}
+
+	return false
+}
+
+// SetLastUpdateTimeSinceEpoch gets a reference to the given string and assigns it to the LastUpdateTimeSinceEpoch field.
+func (o *CatalogModel) SetLastUpdateTimeSinceEpoch(v string) {
+	o.LastUpdateTimeSinceEpoch = &v
+}
+
+// GetDescription returns the Description field value if set, zero value otherwise.
+func (o *CatalogModel) GetDescription() string {
+	if o == nil || IsNil(o.Description) {
+		var ret string
+		return ret
+	}
+	return *o.Description
+}
+
+// GetDescriptionOk returns a tuple with the Description field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetDescriptionOk() (*string, bool) {
+	if o == nil || IsNil(o.Description) {
+		return nil, false
+	}
+	return o.Description, true
+}
+
+// HasDescription returns a boolean if a field has been set.
+func (o *CatalogModel) HasDescription() bool {
+	if o != nil && !IsNil(o.Description) {
+		return true
+	}
+
+	return false
+}
+
+// SetDescription gets a reference to the given string and assigns it to the Description field.
+func (o *CatalogModel) SetDescription(v string) {
+	o.Description = &v
+}
+
+// GetReadme returns the Readme field value if set, zero value otherwise.
+func (o *CatalogModel) GetReadme() string {
+	if o == nil || IsNil(o.Readme) {
+		var ret string
+		return ret
+	}
+	return *o.Readme
+}
+
+// GetReadmeOk returns a tuple with the Readme field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetReadmeOk() (*string, bool) {
+	if o == nil || IsNil(o.Readme) {
+		return nil, false
+	}
+	return o.Readme, true
+}
+
+// HasReadme returns a boolean if a field has been set.
+func (o *CatalogModel) HasReadme() bool {
+	if o != nil && !IsNil(o.Readme) {
+		return true
+	}
+
+	return false
+}
+
+// SetReadme gets a reference to the given string and assigns it to the Readme field.
+func (o *CatalogModel) SetReadme(v string) {
+	o.Readme = &v
+}
+
+// GetMaturity returns the Maturity field value if set, zero value otherwise.
+func (o *CatalogModel) GetMaturity() string {
+	if o == nil || IsNil(o.Maturity) {
+		var ret string
+		return ret
+	}
+	return *o.Maturity
+}
+
+// GetMaturityOk returns a tuple with the Maturity field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetMaturityOk() (*string, bool) {
+	if o == nil || IsNil(o.Maturity) {
+		return nil, false
+	}
+	return o.Maturity, true
+}
+
+// HasMaturity returns a boolean if a field has been set.
+func (o *CatalogModel) HasMaturity() bool {
+	if o != nil && !IsNil(o.Maturity) {
+		return true
+	}
+
+	return false
+}
+
+// SetMaturity gets a reference to the given string and assigns it to the Maturity field.
+func (o *CatalogModel) SetMaturity(v string) {
+	o.Maturity = &v
+}
+
+// GetLanguage returns the Language field value if set, zero value otherwise.
+func (o *CatalogModel) GetLanguage() []string {
+	if o == nil || IsNil(o.Language) {
+		var ret []string
+		return ret
+	}
+	return o.Language
+}
+
+// GetLanguageOk returns a tuple with the Language field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetLanguageOk() ([]string, bool) {
+	if o == nil || IsNil(o.Language) {
+		return nil, false
+	}
+	return o.Language, true
+}
+
+// HasLanguage returns a boolean if a field has been set.
+func (o *CatalogModel) HasLanguage() bool {
+	if o != nil && !IsNil(o.Language) {
+		return true
+	}
+
+	return false
+}
+
+// SetLanguage gets a reference to the given []string and assigns it to the Language field.
+func (o *CatalogModel) SetLanguage(v []string) {
+	o.Language = v
+}
+
+// GetTasks returns the Tasks field value if set, zero value otherwise.
+func (o *CatalogModel) GetTasks() []string {
+	if o == nil || IsNil(o.Tasks) {
+		var ret []string
+		return ret
+	}
+	return o.Tasks
+}
+
+// GetTasksOk returns a tuple with the Tasks field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetTasksOk() ([]string, bool) {
+	if o == nil || IsNil(o.Tasks) {
+		return nil, false
+	}
+	return o.Tasks, true
+}
+
+// HasTasks returns a boolean if a field has been set.
+func (o *CatalogModel) HasTasks() bool {
+	if o != nil && !IsNil(o.Tasks) {
+		return true
+	}
+
+	return false
+}
+
+// SetTasks gets a reference to the given []string and assigns it to the Tasks field.
+func (o *CatalogModel) SetTasks(v []string) {
+	o.Tasks = v
+}
+
+// GetProvider returns the Provider field value if set, zero value otherwise.
+func (o *CatalogModel) GetProvider() string {
+	if o == nil || IsNil(o.Provider) {
+		var ret string
+		return ret
+	}
+	return *o.Provider
+}
+
+// GetProviderOk returns a tuple with the Provider field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetProviderOk() (*string, bool) {
+	if o == nil || IsNil(o.Provider) {
+		return nil, false
+	}
+	return o.Provider, true
+}
+
+// HasProvider returns a boolean if a field has been set.
+func (o *CatalogModel) HasProvider() bool {
+	if o != nil && !IsNil(o.Provider) {
+		return true
+	}
+
+	return false
+}
+
+// SetProvider gets a reference to the given string and assigns it to the Provider field.
+func (o *CatalogModel) SetProvider(v string) {
+	o.Provider = &v
+}
+
+// GetLogo returns the Logo field value if set, zero value otherwise.
+func (o *CatalogModel) GetLogo() string {
+	if o == nil || IsNil(o.Logo) {
+		var ret string
+		return ret
+	}
+	return *o.Logo
+}
+
+// GetLogoOk returns a tuple with the Logo field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetLogoOk() (*string, bool) {
+	if o == nil || IsNil(o.Logo) {
+		return nil, false
+	}
+	return o.Logo, true
+}
+
+// HasLogo returns a boolean if a field has been set.
+func (o *CatalogModel) HasLogo() bool {
+	if o != nil && !IsNil(o.Logo) {
+		return true
+	}
+
+	return false
+}
+
+// SetLogo gets a reference to the given string and assigns it to the Logo field.
+func (o *CatalogModel) SetLogo(v string) {
+	o.Logo = &v
+}
+
+// GetLicense returns the License field value if set, zero value otherwise.
+func (o *CatalogModel) GetLicense() string {
+	if o == nil || IsNil(o.License) {
+		var ret string
+		return ret
+	}
+	return *o.License
+}
+
+// GetLicenseOk returns a tuple with the License field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetLicenseOk() (*string, bool) {
+	if o == nil || IsNil(o.License) {
+		return nil, false
+	}
+	return o.License, true
+}
+
+// HasLicense returns a boolean if a field has been set.
+func (o *CatalogModel) HasLicense() bool {
+	if o != nil && !IsNil(o.License) {
+		return true
+	}
+
+	return false
+}
+
+// SetLicense gets a reference to the given string and assigns it to the License field.
+func (o *CatalogModel) SetLicense(v string) {
+	o.License = &v
+}
+
+// GetLicenseLink returns the LicenseLink field value if set, zero value otherwise.
+func (o *CatalogModel) GetLicenseLink() string {
+	if o == nil || IsNil(o.LicenseLink) {
+		var ret string
+		return ret
+	}
+	return *o.LicenseLink
+}
+
+// GetLicenseLinkOk returns a tuple with the LicenseLink field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetLicenseLinkOk() (*string, bool) {
+	if o == nil || IsNil(o.LicenseLink) {
+		return nil, false
+	}
+	return o.LicenseLink, true
+}
+
+// HasLicenseLink returns a boolean if a field has been set.
+func (o *CatalogModel) HasLicenseLink() bool {
+	if o != nil && !IsNil(o.LicenseLink) {
+		return true
+	}
+
+	return false
+}
+
+// SetLicenseLink gets a reference to the given string and assigns it to the LicenseLink field.
+func (o *CatalogModel) SetLicenseLink(v string) {
+	o.LicenseLink = &v
+}
+
+// GetLibraryName returns the LibraryName field value if set, zero value otherwise.
+func (o *CatalogModel) GetLibraryName() string {
+	if o == nil || IsNil(o.LibraryName) {
+		var ret string
+		return ret
+	}
+	return *o.LibraryName
+}
+
+// GetLibraryNameOk returns a tuple with the LibraryName field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetLibraryNameOk() (*string, bool) {
+	if o == nil || IsNil(o.LibraryName) {
+		return nil, false
+	}
+	return o.LibraryName, true
+}
+
+// HasLibraryName returns a boolean if a field has been set.
+func (o *CatalogModel) HasLibraryName() bool {
+	if o != nil && !IsNil(o.LibraryName) {
+		return true
+	}
+
+	return false
+}
+
+// SetLibraryName gets a reference to the given string and assigns it to the LibraryName field.
+func (o *CatalogModel) SetLibraryName(v string) {
+	o.LibraryName = &v
+}
+
+// GetCustomProperties returns the CustomProperties field value if set, zero value otherwise.
+func (o *CatalogModel) GetCustomProperties() map[string]MetadataValue {
+	if o == nil || IsNil(o.CustomProperties) {
+		var ret map[string]MetadataValue
+		return ret
+	}
+	return *o.CustomProperties
+}
+
+// GetCustomPropertiesOk returns a tuple with the CustomProperties field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetCustomPropertiesOk() (*map[string]MetadataValue, bool) {
+	if o == nil || IsNil(o.CustomProperties) {
+		return nil, false
+	}
+	return o.CustomProperties, true
+}
+
+// HasCustomProperties returns a boolean if a field has been set.
+func (o *CatalogModel) HasCustomProperties() bool {
+	if o != nil && !IsNil(o.CustomProperties) {
+		return true
+	}
+
+	return false
+}
+
+// SetCustomProperties gets a reference to the given map[string]MetadataValue and assigns it to the CustomProperties field.
+func (o *CatalogModel) SetCustomProperties(v map[string]MetadataValue) {
+	o.CustomProperties = &v
+}
+
+// GetName returns the Name field value
+func (o *CatalogModel) GetName() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Name
+}
+
+// GetNameOk returns a tuple with the Name field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetNameOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Name, true
+}
+
+// SetName sets field value
+func (o *CatalogModel) SetName(v string) {
+	o.Name = v
+}
+
+// GetSourceId returns the SourceId field value if set, zero value otherwise.
+func (o *CatalogModel) GetSourceId() string {
+	if o == nil || IsNil(o.SourceId) {
+		var ret string
+		return ret
+	}
+	return *o.SourceId
+}
+
+// GetSourceIdOk returns a tuple with the SourceId field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModel) GetSourceIdOk() (*string, bool) {
+	if o == nil || IsNil(o.SourceId) {
+		return nil, false
+	}
+	return o.SourceId, true
+}
+
+// HasSourceId returns a boolean if a field has been set.
+func (o *CatalogModel) HasSourceId() bool {
+	if o != nil && !IsNil(o.SourceId) {
+		return true
+	}
+
+	return false
+}
+
+// SetSourceId gets a reference to the given string and assigns it to the SourceId field.
+func (o *CatalogModel) SetSourceId(v string) {
+	o.SourceId = &v
+}
+
+func (o CatalogModel) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o CatalogModel) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	if !IsNil(o.CreateTimeSinceEpoch) {
+		toSerialize["createTimeSinceEpoch"] = o.CreateTimeSinceEpoch
+	}
+	if !IsNil(o.LastUpdateTimeSinceEpoch) {
+		toSerialize["lastUpdateTimeSinceEpoch"] = o.LastUpdateTimeSinceEpoch
+	}
+	if !IsNil(o.Description) {
+		toSerialize["description"] = o.Description
+	}
+	if !IsNil(o.Readme) {
+		toSerialize["readme"] = o.Readme
+	}
+	if !IsNil(o.Maturity) {
+		toSerialize["maturity"] = o.Maturity
+	}
+	if !IsNil(o.Language) {
+		toSerialize["language"] = o.Language
+	}
+	if !IsNil(o.Tasks) {
+		toSerialize["tasks"] = o.Tasks
+	}
+	if !IsNil(o.Provider) {
+		toSerialize["provider"] = o.Provider
+	}
+	if !IsNil(o.Logo) {
+		toSerialize["logo"] = o.Logo
+	}
+	if !IsNil(o.License) {
+		toSerialize["license"] = o.License
+	}
+	if !IsNil(o.LicenseLink) {
+		toSerialize["licenseLink"] = o.LicenseLink
+	}
+	if !IsNil(o.LibraryName) {
+		toSerialize["libraryName"] = o.LibraryName
+	}
+	if !IsNil(o.CustomProperties) {
+		toSerialize["customProperties"] = o.CustomProperties
+	}
+	toSerialize["name"] = o.Name
+	if !IsNil(o.SourceId) {
+		toSerialize["source_id"] = o.SourceId
+	}
+	return toSerialize, nil
+}
+
+type NullableCatalogModel struct {
+	value *CatalogModel
+	isSet bool
+}
+
+func (v NullableCatalogModel) Get() *CatalogModel {
+	return v.value
+}
+
+func (v *NullableCatalogModel) Set(val *CatalogModel) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableCatalogModel) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableCatalogModel) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableCatalogModel(val *CatalogModel) *NullableCatalogModel {
+	return &NullableCatalogModel{value: val, isSet: true}
+}
+
+func (v NullableCatalogModel) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableCatalogModel) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_catalog_model_artifact.go b/catalog/pkg/openapi/model_catalog_model_artifact.go
new file mode 100644
index 00000000..2f6a0898
--- /dev/null
+++ b/catalog/pkg/openapi/model_catalog_model_artifact.go
@@ -0,0 +1,227 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the CatalogModelArtifact type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CatalogModelArtifact{}
+
+// CatalogModelArtifact A single artifact for a catalog model.
+type CatalogModelArtifact struct {
+	// Output only. Create time of the resource in millisecond since epoch.
+	CreateTimeSinceEpoch *string `json:"createTimeSinceEpoch,omitempty"`
+	// Output only. Last update time of the resource since epoch in millisecond since epoch.
+	LastUpdateTimeSinceEpoch *string `json:"lastUpdateTimeSinceEpoch,omitempty"`
+	// URI where the artifact can be retrieved.
+	Uri string `json:"uri"`
+	// User provided custom properties which are not defined by its type.
+	CustomProperties *map[string]MetadataValue `json:"customProperties,omitempty"`
+}
+
+// NewCatalogModelArtifact instantiates a new CatalogModelArtifact object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCatalogModelArtifact(uri string) *CatalogModelArtifact {
+	this := CatalogModelArtifact{}
+	this.Uri = uri
+	return &this
+}
+
+// NewCatalogModelArtifactWithDefaults instantiates a new CatalogModelArtifact object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCatalogModelArtifactWithDefaults() *CatalogModelArtifact {
+	this := CatalogModelArtifact{}
+	return &this
+}
+
+// GetCreateTimeSinceEpoch returns the CreateTimeSinceEpoch field value if set, zero value otherwise.
+func (o *CatalogModelArtifact) GetCreateTimeSinceEpoch() string {
+	if o == nil || IsNil(o.CreateTimeSinceEpoch) {
+		var ret string
+		return ret
+	}
+	return *o.CreateTimeSinceEpoch
+}
+
+// GetCreateTimeSinceEpochOk returns a tuple with the CreateTimeSinceEpoch field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifact) GetCreateTimeSinceEpochOk() (*string, bool) {
+	if o == nil || IsNil(o.CreateTimeSinceEpoch) {
+		return nil, false
+	}
+	return o.CreateTimeSinceEpoch, true
+}
+
+// HasCreateTimeSinceEpoch returns a boolean if a field has been set.
+func (o *CatalogModelArtifact) HasCreateTimeSinceEpoch() bool {
+	if o != nil && !IsNil(o.CreateTimeSinceEpoch) {
+		return true
+	}
+
+	return false
+}
+
+// SetCreateTimeSinceEpoch gets a reference to the given string and assigns it to the CreateTimeSinceEpoch field.
+func (o *CatalogModelArtifact) SetCreateTimeSinceEpoch(v string) {
+	o.CreateTimeSinceEpoch = &v
+}
+
+// GetLastUpdateTimeSinceEpoch returns the LastUpdateTimeSinceEpoch field value if set, zero value otherwise.
+func (o *CatalogModelArtifact) GetLastUpdateTimeSinceEpoch() string {
+	if o == nil || IsNil(o.LastUpdateTimeSinceEpoch) {
+		var ret string
+		return ret
+	}
+	return *o.LastUpdateTimeSinceEpoch
+}
+
+// GetLastUpdateTimeSinceEpochOk returns a tuple with the LastUpdateTimeSinceEpoch field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifact) GetLastUpdateTimeSinceEpochOk() (*string, bool) {
+	if o == nil || IsNil(o.LastUpdateTimeSinceEpoch) {
+		return nil, false
+	}
+	return o.LastUpdateTimeSinceEpoch, true
+}
+
+// HasLastUpdateTimeSinceEpoch returns a boolean if a field has been set.
+func (o *CatalogModelArtifact) HasLastUpdateTimeSinceEpoch() bool {
+	if o != nil && !IsNil(o.LastUpdateTimeSinceEpoch) {
+		return true
+	}
+
+	return false
+}
+
+// SetLastUpdateTimeSinceEpoch gets a reference to the given string and assigns it to the LastUpdateTimeSinceEpoch field.
+func (o *CatalogModelArtifact) SetLastUpdateTimeSinceEpoch(v string) {
+	o.LastUpdateTimeSinceEpoch = &v
+}
+
+// GetUri returns the Uri field value
+func (o *CatalogModelArtifact) GetUri() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Uri
+}
+
+// GetUriOk returns a tuple with the Uri field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifact) GetUriOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Uri, true
+}
+
+// SetUri sets field value
+func (o *CatalogModelArtifact) SetUri(v string) {
+	o.Uri = v
+}
+
+// GetCustomProperties returns the CustomProperties field value if set, zero value otherwise.
+func (o *CatalogModelArtifact) GetCustomProperties() map[string]MetadataValue {
+	if o == nil || IsNil(o.CustomProperties) {
+		var ret map[string]MetadataValue
+		return ret
+	}
+	return *o.CustomProperties
+}
+
+// GetCustomPropertiesOk returns a tuple with the CustomProperties field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifact) GetCustomPropertiesOk() (*map[string]MetadataValue, bool) {
+	if o == nil || IsNil(o.CustomProperties) {
+		return nil, false
+	}
+	return o.CustomProperties, true
+}
+
+// HasCustomProperties returns a boolean if a field has been set.
+func (o *CatalogModelArtifact) HasCustomProperties() bool {
+	if o != nil && !IsNil(o.CustomProperties) {
+		return true
+	}
+
+	return false
+}
+
+// SetCustomProperties gets a reference to the given map[string]MetadataValue and assigns it to the CustomProperties field.
+func (o *CatalogModelArtifact) SetCustomProperties(v map[string]MetadataValue) {
+	o.CustomProperties = &v
+}
+
+func (o CatalogModelArtifact) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o CatalogModelArtifact) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	if !IsNil(o.CreateTimeSinceEpoch) {
+		toSerialize["createTimeSinceEpoch"] = o.CreateTimeSinceEpoch
+	}
+	if !IsNil(o.LastUpdateTimeSinceEpoch) {
+		toSerialize["lastUpdateTimeSinceEpoch"] = o.LastUpdateTimeSinceEpoch
+	}
+	toSerialize["uri"] = o.Uri
+	if !IsNil(o.CustomProperties) {
+		toSerialize["customProperties"] = o.CustomProperties
+	}
+	return toSerialize, nil
+}
+
+type NullableCatalogModelArtifact struct {
+	value *CatalogModelArtifact
+	isSet bool
+}
+
+func (v NullableCatalogModelArtifact) Get() *CatalogModelArtifact {
+	return v.value
+}
+
+func (v *NullableCatalogModelArtifact) Set(val *CatalogModelArtifact) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableCatalogModelArtifact) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableCatalogModelArtifact) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableCatalogModelArtifact(val *CatalogModelArtifact) *NullableCatalogModelArtifact {
+	return &NullableCatalogModelArtifact{value: val, isSet: true}
+}
+
+func (v NullableCatalogModelArtifact) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableCatalogModelArtifact) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_catalog_model_artifact_list.go b/catalog/pkg/openapi/model_catalog_model_artifact_list.go
new file mode 100644
index 00000000..8fe1a361
--- /dev/null
+++ b/catalog/pkg/openapi/model_catalog_model_artifact_list.go
@@ -0,0 +1,200 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the CatalogModelArtifactList type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CatalogModelArtifactList{}
+
+// CatalogModelArtifactList List of CatalogModel entities.
+type CatalogModelArtifactList struct {
+	// Token to use to retrieve next page of results.
+	NextPageToken string `json:"nextPageToken"`
+	// Maximum number of resources to return in the result.
+	PageSize int32 `json:"pageSize"`
+	// Number of items in result list.
+	Size int32 `json:"size"`
+	// Array of `CatalogModelArtifact` entities.
+	Items []CatalogModelArtifact `json:"items"`
+}
+
+// NewCatalogModelArtifactList instantiates a new CatalogModelArtifactList object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCatalogModelArtifactList(nextPageToken string, pageSize int32, size int32, items []CatalogModelArtifact) *CatalogModelArtifactList {
+	this := CatalogModelArtifactList{}
+	this.NextPageToken = nextPageToken
+	this.PageSize = pageSize
+	this.Size = size
+	this.Items = items
+	return &this
+}
+
+// NewCatalogModelArtifactListWithDefaults instantiates a new CatalogModelArtifactList object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCatalogModelArtifactListWithDefaults() *CatalogModelArtifactList {
+	this := CatalogModelArtifactList{}
+	return &this
+}
+
+// GetNextPageToken returns the NextPageToken field value
+func (o *CatalogModelArtifactList) GetNextPageToken() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.NextPageToken
+}
+
+// GetNextPageTokenOk returns a tuple with the NextPageToken field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifactList) GetNextPageTokenOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.NextPageToken, true
+}
+
+// SetNextPageToken sets field value
+func (o *CatalogModelArtifactList) SetNextPageToken(v string) {
+	o.NextPageToken = v
+}
+
+// GetPageSize returns the PageSize field value
+func (o *CatalogModelArtifactList) GetPageSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.PageSize
+}
+
+// GetPageSizeOk returns a tuple with the PageSize field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifactList) GetPageSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.PageSize, true
+}
+
+// SetPageSize sets field value
+func (o *CatalogModelArtifactList) SetPageSize(v int32) {
+	o.PageSize = v
+}
+
+// GetSize returns the Size field value
+func (o *CatalogModelArtifactList) GetSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.Size
+}
+
+// GetSizeOk returns a tuple with the Size field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifactList) GetSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Size, true
+}
+
+// SetSize sets field value
+func (o *CatalogModelArtifactList) SetSize(v int32) {
+	o.Size = v
+}
+
+// GetItems returns the Items field value
+func (o *CatalogModelArtifactList) GetItems() []CatalogModelArtifact {
+	if o == nil {
+		var ret []CatalogModelArtifact
+		return ret
+	}
+
+	return o.Items
+}
+
+// GetItemsOk returns a tuple with the Items field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelArtifactList) GetItemsOk() ([]CatalogModelArtifact, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return o.Items, true
+}
+
+// SetItems sets field value
+func (o *CatalogModelArtifactList) SetItems(v []CatalogModelArtifact) {
+	o.Items = v
+}
+
+func (o CatalogModelArtifactList) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o CatalogModelArtifactList) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["nextPageToken"] = o.NextPageToken
+	toSerialize["pageSize"] = o.PageSize
+	toSerialize["size"] = o.Size
+	toSerialize["items"] = o.Items
+	return toSerialize, nil
+}
+
+type NullableCatalogModelArtifactList struct {
+	value *CatalogModelArtifactList
+	isSet bool
+}
+
+func (v NullableCatalogModelArtifactList) Get() *CatalogModelArtifactList {
+	return v.value
+}
+
+func (v *NullableCatalogModelArtifactList) Set(val *CatalogModelArtifactList) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableCatalogModelArtifactList) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableCatalogModelArtifactList) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableCatalogModelArtifactList(val *CatalogModelArtifactList) *NullableCatalogModelArtifactList {
+	return &NullableCatalogModelArtifactList{value: val, isSet: true}
+}
+
+func (v NullableCatalogModelArtifactList) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableCatalogModelArtifactList) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_catalog_model_list.go b/catalog/pkg/openapi/model_catalog_model_list.go
new file mode 100644
index 00000000..79b01009
--- /dev/null
+++ b/catalog/pkg/openapi/model_catalog_model_list.go
@@ -0,0 +1,200 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the CatalogModelList type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CatalogModelList{}
+
+// CatalogModelList List of CatalogModel entities.
+type CatalogModelList struct {
+	// Token to use to retrieve next page of results.
+	NextPageToken string `json:"nextPageToken"`
+	// Maximum number of resources to return in the result.
+	PageSize int32 `json:"pageSize"`
+	// Number of items in result list.
+	Size int32 `json:"size"`
+	// Array of `CatalogModel` entities.
+	Items []CatalogModel `json:"items"`
+}
+
+// NewCatalogModelList instantiates a new CatalogModelList object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCatalogModelList(nextPageToken string, pageSize int32, size int32, items []CatalogModel) *CatalogModelList {
+	this := CatalogModelList{}
+	this.NextPageToken = nextPageToken
+	this.PageSize = pageSize
+	this.Size = size
+	this.Items = items
+	return &this
+}
+
+// NewCatalogModelListWithDefaults instantiates a new CatalogModelList object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCatalogModelListWithDefaults() *CatalogModelList {
+	this := CatalogModelList{}
+	return &this
+}
+
+// GetNextPageToken returns the NextPageToken field value
+func (o *CatalogModelList) GetNextPageToken() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.NextPageToken
+}
+
+// GetNextPageTokenOk returns a tuple with the NextPageToken field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelList) GetNextPageTokenOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.NextPageToken, true
+}
+
+// SetNextPageToken sets field value
+func (o *CatalogModelList) SetNextPageToken(v string) {
+	o.NextPageToken = v
+}
+
+// GetPageSize returns the PageSize field value
+func (o *CatalogModelList) GetPageSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.PageSize
+}
+
+// GetPageSizeOk returns a tuple with the PageSize field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelList) GetPageSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.PageSize, true
+}
+
+// SetPageSize sets field value
+func (o *CatalogModelList) SetPageSize(v int32) {
+	o.PageSize = v
+}
+
+// GetSize returns the Size field value
+func (o *CatalogModelList) GetSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.Size
+}
+
+// GetSizeOk returns a tuple with the Size field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelList) GetSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Size, true
+}
+
+// SetSize sets field value
+func (o *CatalogModelList) SetSize(v int32) {
+	o.Size = v
+}
+
+// GetItems returns the Items field value
+func (o *CatalogModelList) GetItems() []CatalogModel {
+	if o == nil {
+		var ret []CatalogModel
+		return ret
+	}
+
+	return o.Items
+}
+
+// GetItemsOk returns a tuple with the Items field value
+// and a boolean to check if the value has been set.
+func (o *CatalogModelList) GetItemsOk() ([]CatalogModel, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return o.Items, true
+}
+
+// SetItems sets field value
+func (o *CatalogModelList) SetItems(v []CatalogModel) {
+	o.Items = v
+}
+
+func (o CatalogModelList) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o CatalogModelList) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["nextPageToken"] = o.NextPageToken
+	toSerialize["pageSize"] = o.PageSize
+	toSerialize["size"] = o.Size
+	toSerialize["items"] = o.Items
+	return toSerialize, nil
+}
+
+type NullableCatalogModelList struct {
+	value *CatalogModelList
+	isSet bool
+}
+
+func (v NullableCatalogModelList) Get() *CatalogModelList {
+	return v.value
+}
+
+func (v *NullableCatalogModelList) Set(val *CatalogModelList) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableCatalogModelList) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableCatalogModelList) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableCatalogModelList(val *CatalogModelList) *NullableCatalogModelList {
+	return &NullableCatalogModelList{value: val, isSet: true}
+}
+
+func (v NullableCatalogModelList) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableCatalogModelList) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_catalog_source.go b/catalog/pkg/openapi/model_catalog_source.go
new file mode 100644
index 00000000..2e0ac895
--- /dev/null
+++ b/catalog/pkg/openapi/model_catalog_source.go
@@ -0,0 +1,144 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the CatalogSource type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CatalogSource{}
+
+// CatalogSource A catalog source. A catalog source has CatalogModel children.
+type CatalogSource struct {
+	// A unique identifier for a `CatalogSource`.
+	Id string `json:"id"`
+	// The name of the catalog source.
+	Name string `json:"name"`
+}
+
+// NewCatalogSource instantiates a new CatalogSource object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCatalogSource(id string, name string) *CatalogSource {
+	this := CatalogSource{}
+	this.Id = id
+	this.Name = name
+	return &this
+}
+
+// NewCatalogSourceWithDefaults instantiates a new CatalogSource object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCatalogSourceWithDefaults() *CatalogSource {
+	this := CatalogSource{}
+	return &this
+}
+
+// GetId returns the Id field value
+func (o *CatalogSource) GetId() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Id
+}
+
+// GetIdOk returns a tuple with the Id field value
+// and a boolean to check if the value has been set.
+func (o *CatalogSource) GetIdOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Id, true
+}
+
+// SetId sets field value
+func (o *CatalogSource) SetId(v string) {
+	o.Id = v
+}
+
+// GetName returns the Name field value
+func (o *CatalogSource) GetName() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Name
+}
+
+// GetNameOk returns a tuple with the Name field value
+// and a boolean to check if the value has been set.
+func (o *CatalogSource) GetNameOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Name, true
+}
+
+// SetName sets field value
+func (o *CatalogSource) SetName(v string) {
+	o.Name = v
+}
+
+func (o CatalogSource) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o CatalogSource) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["id"] = o.Id
+	toSerialize["name"] = o.Name
+	return toSerialize, nil
+}
+
+type NullableCatalogSource struct {
+	value *CatalogSource
+	isSet bool
+}
+
+func (v NullableCatalogSource) Get() *CatalogSource {
+	return v.value
+}
+
+func (v *NullableCatalogSource) Set(val *CatalogSource) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableCatalogSource) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableCatalogSource) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableCatalogSource(val *CatalogSource) *NullableCatalogSource {
+	return &NullableCatalogSource{value: val, isSet: true}
+}
+
+func (v NullableCatalogSource) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableCatalogSource) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_catalog_source_list.go b/catalog/pkg/openapi/model_catalog_source_list.go
new file mode 100644
index 00000000..8d66226f
--- /dev/null
+++ b/catalog/pkg/openapi/model_catalog_source_list.go
@@ -0,0 +1,209 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the CatalogSourceList type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &CatalogSourceList{}
+
+// CatalogSourceList List of CatalogSource entities.
+type CatalogSourceList struct {
+	// Token to use to retrieve next page of results.
+	NextPageToken string `json:"nextPageToken"`
+	// Maximum number of resources to return in the result.
+	PageSize int32 `json:"pageSize"`
+	// Number of items in result list.
+	Size int32 `json:"size"`
+	// Array of `CatalogSource` entities.
+	Items []CatalogSource `json:"items,omitempty"`
+}
+
+// NewCatalogSourceList instantiates a new CatalogSourceList object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewCatalogSourceList(nextPageToken string, pageSize int32, size int32) *CatalogSourceList {
+	this := CatalogSourceList{}
+	this.NextPageToken = nextPageToken
+	this.PageSize = pageSize
+	this.Size = size
+	return &this
+}
+
+// NewCatalogSourceListWithDefaults instantiates a new CatalogSourceList object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewCatalogSourceListWithDefaults() *CatalogSourceList {
+	this := CatalogSourceList{}
+	return &this
+}
+
+// GetNextPageToken returns the NextPageToken field value
+func (o *CatalogSourceList) GetNextPageToken() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.NextPageToken
+}
+
+// GetNextPageTokenOk returns a tuple with the NextPageToken field value
+// and a boolean to check if the value has been set.
+func (o *CatalogSourceList) GetNextPageTokenOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.NextPageToken, true
+}
+
+// SetNextPageToken sets field value
+func (o *CatalogSourceList) SetNextPageToken(v string) {
+	o.NextPageToken = v
+}
+
+// GetPageSize returns the PageSize field value
+func (o *CatalogSourceList) GetPageSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.PageSize
+}
+
+// GetPageSizeOk returns a tuple with the PageSize field value
+// and a boolean to check if the value has been set.
+func (o *CatalogSourceList) GetPageSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.PageSize, true
+}
+
+// SetPageSize sets field value
+func (o *CatalogSourceList) SetPageSize(v int32) {
+	o.PageSize = v
+}
+
+// GetSize returns the Size field value
+func (o *CatalogSourceList) GetSize() int32 {
+	if o == nil {
+		var ret int32
+		return ret
+	}
+
+	return o.Size
+}
+
+// GetSizeOk returns a tuple with the Size field value
+// and a boolean to check if the value has been set.
+func (o *CatalogSourceList) GetSizeOk() (*int32, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Size, true
+}
+
+// SetSize sets field value
+func (o *CatalogSourceList) SetSize(v int32) {
+	o.Size = v
+}
+
+// GetItems returns the Items field value if set, zero value otherwise.
+func (o *CatalogSourceList) GetItems() []CatalogSource {
+	if o == nil || IsNil(o.Items) {
+		var ret []CatalogSource
+		return ret
+	}
+	return o.Items
+}
+
+// GetItemsOk returns a tuple with the Items field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+func (o *CatalogSourceList) GetItemsOk() ([]CatalogSource, bool) {
+	if o == nil || IsNil(o.Items) {
+		return nil, false
+	}
+	return o.Items, true
+}
+
+// HasItems returns a boolean if a field has been set.
+func (o *CatalogSourceList) HasItems() bool {
+	if o != nil && !IsNil(o.Items) {
+		return true
+	}
+
+	return false
+}
+
+// SetItems gets a reference to the given []CatalogSource and assigns it to the Items field.
+func (o *CatalogSourceList) SetItems(v []CatalogSource) {
+	o.Items = v
+}
+
+func (o CatalogSourceList) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o CatalogSourceList) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["nextPageToken"] = o.NextPageToken
+	toSerialize["pageSize"] = o.PageSize
+	toSerialize["size"] = o.Size
+	if !IsNil(o.Items) {
+		toSerialize["items"] = o.Items
+	}
+	return toSerialize, nil
+}
+
+type NullableCatalogSourceList struct {
+	value *CatalogSourceList
+	isSet bool
+}
+
+func (v NullableCatalogSourceList) Get() *CatalogSourceList {
+	return v.value
+}
+
+func (v *NullableCatalogSourceList) Set(val *CatalogSourceList) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableCatalogSourceList) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableCatalogSourceList) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableCatalogSourceList(val *CatalogSourceList) *NullableCatalogSourceList {
+	return &NullableCatalogSourceList{value: val, isSet: true}
+}
+
+func (v NullableCatalogSourceList) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableCatalogSourceList) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_error.go b/catalog/pkg/openapi/model_error.go
new file mode 100644
index 00000000..70732b7f
--- /dev/null
+++ b/catalog/pkg/openapi/model_error.go
@@ -0,0 +1,144 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the Error type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &Error{}
+
+// Error Error code and message.
+type Error struct {
+	// Error code
+	Code string `json:"code"`
+	// Error message
+	Message string `json:"message"`
+}
+
+// NewError instantiates a new Error object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewError(code string, message string) *Error {
+	this := Error{}
+	this.Code = code
+	this.Message = message
+	return &this
+}
+
+// NewErrorWithDefaults instantiates a new Error object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewErrorWithDefaults() *Error {
+	this := Error{}
+	return &this
+}
+
+// GetCode returns the Code field value
+func (o *Error) GetCode() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Code
+}
+
+// GetCodeOk returns a tuple with the Code field value
+// and a boolean to check if the value has been set.
+func (o *Error) GetCodeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Code, true
+}
+
+// SetCode sets field value
+func (o *Error) SetCode(v string) {
+	o.Code = v
+}
+
+// GetMessage returns the Message field value
+func (o *Error) GetMessage() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Message
+}
+
+// GetMessageOk returns a tuple with the Message field value
+// and a boolean to check if the value has been set.
+func (o *Error) GetMessageOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Message, true
+}
+
+// SetMessage sets field value
+func (o *Error) SetMessage(v string) {
+	o.Message = v
+}
+
+func (o Error) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o Error) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["code"] = o.Code
+	toSerialize["message"] = o.Message
+	return toSerialize, nil
+}
+
+type NullableError struct {
+	value *Error
+	isSet bool
+}
+
+func (v NullableError) Get() *Error {
+	return v.value
+}
+
+func (v *NullableError) Set(val *Error) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableError) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableError) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableError(val *Error) *NullableError {
+	return &NullableError{value: val, isSet: true}
+}
+
+func (v NullableError) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableError) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_bool_value.go b/catalog/pkg/openapi/model_metadata_bool_value.go
new file mode 100644
index 00000000..6387dc62
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_bool_value.go
@@ -0,0 +1,144 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the MetadataBoolValue type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &MetadataBoolValue{}
+
+// MetadataBoolValue A bool property value.
+type MetadataBoolValue struct {
+	BoolValue    bool   `json:"bool_value"`
+	MetadataType string `json:"metadataType"`
+}
+
+// NewMetadataBoolValue instantiates a new MetadataBoolValue object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewMetadataBoolValue(boolValue bool, metadataType string) *MetadataBoolValue {
+	this := MetadataBoolValue{}
+	this.BoolValue = boolValue
+	this.MetadataType = metadataType
+	return &this
+}
+
+// NewMetadataBoolValueWithDefaults instantiates a new MetadataBoolValue object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewMetadataBoolValueWithDefaults() *MetadataBoolValue {
+	this := MetadataBoolValue{}
+	var metadataType string = "MetadataBoolValue"
+	this.MetadataType = metadataType
+	return &this
+}
+
+// GetBoolValue returns the BoolValue field value
+func (o *MetadataBoolValue) GetBoolValue() bool {
+	if o == nil {
+		var ret bool
+		return ret
+	}
+
+	return o.BoolValue
+}
+
+// GetBoolValueOk returns a tuple with the BoolValue field value
+// and a boolean to check if the value has been set.
+func (o *MetadataBoolValue) GetBoolValueOk() (*bool, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.BoolValue, true
+}
+
+// SetBoolValue sets field value
+func (o *MetadataBoolValue) SetBoolValue(v bool) {
+	o.BoolValue = v
+}
+
+// GetMetadataType returns the MetadataType field value
+func (o *MetadataBoolValue) GetMetadataType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.MetadataType
+}
+
+// GetMetadataTypeOk returns a tuple with the MetadataType field value
+// and a boolean to check if the value has been set.
+func (o *MetadataBoolValue) GetMetadataTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.MetadataType, true
+}
+
+// SetMetadataType sets field value
+func (o *MetadataBoolValue) SetMetadataType(v string) {
+	o.MetadataType = v
+}
+
+func (o MetadataBoolValue) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o MetadataBoolValue) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["bool_value"] = o.BoolValue
+	toSerialize["metadataType"] = o.MetadataType
+	return toSerialize, nil
+}
+
+type NullableMetadataBoolValue struct {
+	value *MetadataBoolValue
+	isSet bool
+}
+
+func (v NullableMetadataBoolValue) Get() *MetadataBoolValue {
+	return v.value
+}
+
+func (v *NullableMetadataBoolValue) Set(val *MetadataBoolValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataBoolValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataBoolValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataBoolValue(val *MetadataBoolValue) *NullableMetadataBoolValue {
+	return &NullableMetadataBoolValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataBoolValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataBoolValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_double_value.go b/catalog/pkg/openapi/model_metadata_double_value.go
new file mode 100644
index 00000000..a896c030
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_double_value.go
@@ -0,0 +1,144 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the MetadataDoubleValue type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &MetadataDoubleValue{}
+
+// MetadataDoubleValue A double property value.
+type MetadataDoubleValue struct {
+	DoubleValue  float64 `json:"double_value"`
+	MetadataType string  `json:"metadataType"`
+}
+
+// NewMetadataDoubleValue instantiates a new MetadataDoubleValue object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewMetadataDoubleValue(doubleValue float64, metadataType string) *MetadataDoubleValue {
+	this := MetadataDoubleValue{}
+	this.DoubleValue = doubleValue
+	this.MetadataType = metadataType
+	return &this
+}
+
+// NewMetadataDoubleValueWithDefaults instantiates a new MetadataDoubleValue object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewMetadataDoubleValueWithDefaults() *MetadataDoubleValue {
+	this := MetadataDoubleValue{}
+	var metadataType string = "MetadataDoubleValue"
+	this.MetadataType = metadataType
+	return &this
+}
+
+// GetDoubleValue returns the DoubleValue field value
+func (o *MetadataDoubleValue) GetDoubleValue() float64 {
+	if o == nil {
+		var ret float64
+		return ret
+	}
+
+	return o.DoubleValue
+}
+
+// GetDoubleValueOk returns a tuple with the DoubleValue field value
+// and a boolean to check if the value has been set.
+func (o *MetadataDoubleValue) GetDoubleValueOk() (*float64, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.DoubleValue, true
+}
+
+// SetDoubleValue sets field value
+func (o *MetadataDoubleValue) SetDoubleValue(v float64) {
+	o.DoubleValue = v
+}
+
+// GetMetadataType returns the MetadataType field value
+func (o *MetadataDoubleValue) GetMetadataType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.MetadataType
+}
+
+// GetMetadataTypeOk returns a tuple with the MetadataType field value
+// and a boolean to check if the value has been set.
+func (o *MetadataDoubleValue) GetMetadataTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.MetadataType, true
+}
+
+// SetMetadataType sets field value
+func (o *MetadataDoubleValue) SetMetadataType(v string) {
+	o.MetadataType = v
+}
+
+func (o MetadataDoubleValue) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o MetadataDoubleValue) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["double_value"] = o.DoubleValue
+	toSerialize["metadataType"] = o.MetadataType
+	return toSerialize, nil
+}
+
+type NullableMetadataDoubleValue struct {
+	value *MetadataDoubleValue
+	isSet bool
+}
+
+func (v NullableMetadataDoubleValue) Get() *MetadataDoubleValue {
+	return v.value
+}
+
+func (v *NullableMetadataDoubleValue) Set(val *MetadataDoubleValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataDoubleValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataDoubleValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataDoubleValue(val *MetadataDoubleValue) *NullableMetadataDoubleValue {
+	return &NullableMetadataDoubleValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataDoubleValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataDoubleValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_int_value.go b/catalog/pkg/openapi/model_metadata_int_value.go
new file mode 100644
index 00000000..d103797c
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_int_value.go
@@ -0,0 +1,144 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the MetadataIntValue type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &MetadataIntValue{}
+
+// MetadataIntValue An integer (int64) property value.
+type MetadataIntValue struct {
+	IntValue     string `json:"int_value"`
+	MetadataType string `json:"metadataType"`
+}
+
+// NewMetadataIntValue instantiates a new MetadataIntValue object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewMetadataIntValue(intValue string, metadataType string) *MetadataIntValue {
+	this := MetadataIntValue{}
+	this.IntValue = intValue
+	this.MetadataType = metadataType
+	return &this
+}
+
+// NewMetadataIntValueWithDefaults instantiates a new MetadataIntValue object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewMetadataIntValueWithDefaults() *MetadataIntValue {
+	this := MetadataIntValue{}
+	var metadataType string = "MetadataIntValue"
+	this.MetadataType = metadataType
+	return &this
+}
+
+// GetIntValue returns the IntValue field value
+func (o *MetadataIntValue) GetIntValue() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.IntValue
+}
+
+// GetIntValueOk returns a tuple with the IntValue field value
+// and a boolean to check if the value has been set.
+func (o *MetadataIntValue) GetIntValueOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.IntValue, true
+}
+
+// SetIntValue sets field value
+func (o *MetadataIntValue) SetIntValue(v string) {
+	o.IntValue = v
+}
+
+// GetMetadataType returns the MetadataType field value
+func (o *MetadataIntValue) GetMetadataType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.MetadataType
+}
+
+// GetMetadataTypeOk returns a tuple with the MetadataType field value
+// and a boolean to check if the value has been set.
+func (o *MetadataIntValue) GetMetadataTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.MetadataType, true
+}
+
+// SetMetadataType sets field value
+func (o *MetadataIntValue) SetMetadataType(v string) {
+	o.MetadataType = v
+}
+
+func (o MetadataIntValue) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o MetadataIntValue) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["int_value"] = o.IntValue
+	toSerialize["metadataType"] = o.MetadataType
+	return toSerialize, nil
+}
+
+type NullableMetadataIntValue struct {
+	value *MetadataIntValue
+	isSet bool
+}
+
+func (v NullableMetadataIntValue) Get() *MetadataIntValue {
+	return v.value
+}
+
+func (v *NullableMetadataIntValue) Set(val *MetadataIntValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataIntValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataIntValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataIntValue(val *MetadataIntValue) *NullableMetadataIntValue {
+	return &NullableMetadataIntValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataIntValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataIntValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_proto_value.go b/catalog/pkg/openapi/model_metadata_proto_value.go
new file mode 100644
index 00000000..343a92c3
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_proto_value.go
@@ -0,0 +1,173 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the MetadataProtoValue type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &MetadataProtoValue{}
+
+// MetadataProtoValue A proto property value.
+type MetadataProtoValue struct {
+	// url describing proto value
+	Type string `json:"type"`
+	// Base64 encoded bytes for proto value
+	ProtoValue   string `json:"proto_value"`
+	MetadataType string `json:"metadataType"`
+}
+
+// NewMetadataProtoValue instantiates a new MetadataProtoValue object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewMetadataProtoValue(type_ string, protoValue string, metadataType string) *MetadataProtoValue {
+	this := MetadataProtoValue{}
+	this.Type = type_
+	this.ProtoValue = protoValue
+	this.MetadataType = metadataType
+	return &this
+}
+
+// NewMetadataProtoValueWithDefaults instantiates a new MetadataProtoValue object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewMetadataProtoValueWithDefaults() *MetadataProtoValue {
+	this := MetadataProtoValue{}
+	var metadataType string = "MetadataProtoValue"
+	this.MetadataType = metadataType
+	return &this
+}
+
+// GetType returns the Type field value
+func (o *MetadataProtoValue) GetType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.Type
+}
+
+// GetTypeOk returns a tuple with the Type field value
+// and a boolean to check if the value has been set.
+func (o *MetadataProtoValue) GetTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.Type, true
+}
+
+// SetType sets field value
+func (o *MetadataProtoValue) SetType(v string) {
+	o.Type = v
+}
+
+// GetProtoValue returns the ProtoValue field value
+func (o *MetadataProtoValue) GetProtoValue() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.ProtoValue
+}
+
+// GetProtoValueOk returns a tuple with the ProtoValue field value
+// and a boolean to check if the value has been set.
+func (o *MetadataProtoValue) GetProtoValueOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.ProtoValue, true
+}
+
+// SetProtoValue sets field value
+func (o *MetadataProtoValue) SetProtoValue(v string) {
+	o.ProtoValue = v
+}
+
+// GetMetadataType returns the MetadataType field value
+func (o *MetadataProtoValue) GetMetadataType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.MetadataType
+}
+
+// GetMetadataTypeOk returns a tuple with the MetadataType field value
+// and a boolean to check if the value has been set.
+func (o *MetadataProtoValue) GetMetadataTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.MetadataType, true
+}
+
+// SetMetadataType sets field value
+func (o *MetadataProtoValue) SetMetadataType(v string) {
+	o.MetadataType = v
+}
+
+func (o MetadataProtoValue) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o MetadataProtoValue) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["type"] = o.Type
+	toSerialize["proto_value"] = o.ProtoValue
+	toSerialize["metadataType"] = o.MetadataType
+	return toSerialize, nil
+}
+
+type NullableMetadataProtoValue struct {
+	value *MetadataProtoValue
+	isSet bool
+}
+
+func (v NullableMetadataProtoValue) Get() *MetadataProtoValue {
+	return v.value
+}
+
+func (v *NullableMetadataProtoValue) Set(val *MetadataProtoValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataProtoValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataProtoValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataProtoValue(val *MetadataProtoValue) *NullableMetadataProtoValue {
+	return &NullableMetadataProtoValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataProtoValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataProtoValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_string_value.go b/catalog/pkg/openapi/model_metadata_string_value.go
new file mode 100644
index 00000000..65f0e29f
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_string_value.go
@@ -0,0 +1,144 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the MetadataStringValue type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &MetadataStringValue{}
+
+// MetadataStringValue A string property value.
+type MetadataStringValue struct {
+	StringValue  string `json:"string_value"`
+	MetadataType string `json:"metadataType"`
+}
+
+// NewMetadataStringValue instantiates a new MetadataStringValue object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewMetadataStringValue(stringValue string, metadataType string) *MetadataStringValue {
+	this := MetadataStringValue{}
+	this.StringValue = stringValue
+	this.MetadataType = metadataType
+	return &this
+}
+
+// NewMetadataStringValueWithDefaults instantiates a new MetadataStringValue object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewMetadataStringValueWithDefaults() *MetadataStringValue {
+	this := MetadataStringValue{}
+	var metadataType string = "MetadataStringValue"
+	this.MetadataType = metadataType
+	return &this
+}
+
+// GetStringValue returns the StringValue field value
+func (o *MetadataStringValue) GetStringValue() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.StringValue
+}
+
+// GetStringValueOk returns a tuple with the StringValue field value
+// and a boolean to check if the value has been set.
+func (o *MetadataStringValue) GetStringValueOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.StringValue, true
+}
+
+// SetStringValue sets field value
+func (o *MetadataStringValue) SetStringValue(v string) {
+	o.StringValue = v
+}
+
+// GetMetadataType returns the MetadataType field value
+func (o *MetadataStringValue) GetMetadataType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.MetadataType
+}
+
+// GetMetadataTypeOk returns a tuple with the MetadataType field value
+// and a boolean to check if the value has been set.
+func (o *MetadataStringValue) GetMetadataTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.MetadataType, true
+}
+
+// SetMetadataType sets field value
+func (o *MetadataStringValue) SetMetadataType(v string) {
+	o.MetadataType = v
+}
+
+func (o MetadataStringValue) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o MetadataStringValue) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["string_value"] = o.StringValue
+	toSerialize["metadataType"] = o.MetadataType
+	return toSerialize, nil
+}
+
+type NullableMetadataStringValue struct {
+	value *MetadataStringValue
+	isSet bool
+}
+
+func (v NullableMetadataStringValue) Get() *MetadataStringValue {
+	return v.value
+}
+
+func (v *NullableMetadataStringValue) Set(val *MetadataStringValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataStringValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataStringValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataStringValue(val *MetadataStringValue) *NullableMetadataStringValue {
+	return &NullableMetadataStringValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataStringValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataStringValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_struct_value.go b/catalog/pkg/openapi/model_metadata_struct_value.go
new file mode 100644
index 00000000..fdcf084d
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_struct_value.go
@@ -0,0 +1,145 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// checks if the MetadataStructValue type satisfies the MappedNullable interface at compile time
+var _ MappedNullable = &MetadataStructValue{}
+
+// MetadataStructValue A struct property value.
+type MetadataStructValue struct {
+	// Base64 encoded bytes for struct value
+	StructValue  string `json:"struct_value"`
+	MetadataType string `json:"metadataType"`
+}
+
+// NewMetadataStructValue instantiates a new MetadataStructValue object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewMetadataStructValue(structValue string, metadataType string) *MetadataStructValue {
+	this := MetadataStructValue{}
+	this.StructValue = structValue
+	this.MetadataType = metadataType
+	return &this
+}
+
+// NewMetadataStructValueWithDefaults instantiates a new MetadataStructValue object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewMetadataStructValueWithDefaults() *MetadataStructValue {
+	this := MetadataStructValue{}
+	var metadataType string = "MetadataStructValue"
+	this.MetadataType = metadataType
+	return &this
+}
+
+// GetStructValue returns the StructValue field value
+func (o *MetadataStructValue) GetStructValue() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.StructValue
+}
+
+// GetStructValueOk returns a tuple with the StructValue field value
+// and a boolean to check if the value has been set.
+func (o *MetadataStructValue) GetStructValueOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.StructValue, true
+}
+
+// SetStructValue sets field value
+func (o *MetadataStructValue) SetStructValue(v string) {
+	o.StructValue = v
+}
+
+// GetMetadataType returns the MetadataType field value
+func (o *MetadataStructValue) GetMetadataType() string {
+	if o == nil {
+		var ret string
+		return ret
+	}
+
+	return o.MetadataType
+}
+
+// GetMetadataTypeOk returns a tuple with the MetadataType field value
+// and a boolean to check if the value has been set.
+func (o *MetadataStructValue) GetMetadataTypeOk() (*string, bool) {
+	if o == nil {
+		return nil, false
+	}
+	return &o.MetadataType, true
+}
+
+// SetMetadataType sets field value
+func (o *MetadataStructValue) SetMetadataType(v string) {
+	o.MetadataType = v
+}
+
+func (o MetadataStructValue) MarshalJSON() ([]byte, error) {
+	toSerialize, err := o.ToMap()
+	if err != nil {
+		return []byte{}, err
+	}
+	return json.Marshal(toSerialize)
+}
+
+func (o MetadataStructValue) ToMap() (map[string]interface{}, error) {
+	toSerialize := map[string]interface{}{}
+	toSerialize["struct_value"] = o.StructValue
+	toSerialize["metadataType"] = o.MetadataType
+	return toSerialize, nil
+}
+
+type NullableMetadataStructValue struct {
+	value *MetadataStructValue
+	isSet bool
+}
+
+func (v NullableMetadataStructValue) Get() *MetadataStructValue {
+	return v.value
+}
+
+func (v *NullableMetadataStructValue) Set(val *MetadataStructValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataStructValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataStructValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataStructValue(val *MetadataStructValue) *NullableMetadataStructValue {
+	return &NullableMetadataStructValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataStructValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataStructValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_metadata_value.go b/catalog/pkg/openapi/model_metadata_value.go
new file mode 100644
index 00000000..61caad0e
--- /dev/null
+++ b/catalog/pkg/openapi/model_metadata_value.go
@@ -0,0 +1,251 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// MetadataValue - A value in properties.
+type MetadataValue struct {
+	MetadataBoolValue   *MetadataBoolValue
+	MetadataDoubleValue *MetadataDoubleValue
+	MetadataIntValue    *MetadataIntValue
+	MetadataProtoValue  *MetadataProtoValue
+	MetadataStringValue *MetadataStringValue
+	MetadataStructValue *MetadataStructValue
+}
+
+// MetadataBoolValueAsMetadataValue is a convenience function that returns MetadataBoolValue wrapped in MetadataValue
+func MetadataBoolValueAsMetadataValue(v *MetadataBoolValue) MetadataValue {
+	return MetadataValue{
+		MetadataBoolValue: v,
+	}
+}
+
+// MetadataDoubleValueAsMetadataValue is a convenience function that returns MetadataDoubleValue wrapped in MetadataValue
+func MetadataDoubleValueAsMetadataValue(v *MetadataDoubleValue) MetadataValue {
+	return MetadataValue{
+		MetadataDoubleValue: v,
+	}
+}
+
+// MetadataIntValueAsMetadataValue is a convenience function that returns MetadataIntValue wrapped in MetadataValue
+func MetadataIntValueAsMetadataValue(v *MetadataIntValue) MetadataValue {
+	return MetadataValue{
+		MetadataIntValue: v,
+	}
+}
+
+// MetadataProtoValueAsMetadataValue is a convenience function that returns MetadataProtoValue wrapped in MetadataValue
+func MetadataProtoValueAsMetadataValue(v *MetadataProtoValue) MetadataValue {
+	return MetadataValue{
+		MetadataProtoValue: v,
+	}
+}
+
+// MetadataStringValueAsMetadataValue is a convenience function that returns MetadataStringValue wrapped in MetadataValue
+func MetadataStringValueAsMetadataValue(v *MetadataStringValue) MetadataValue {
+	return MetadataValue{
+		MetadataStringValue: v,
+	}
+}
+
+// MetadataStructValueAsMetadataValue is a convenience function that returns MetadataStructValue wrapped in MetadataValue
+func MetadataStructValueAsMetadataValue(v *MetadataStructValue) MetadataValue {
+	return MetadataValue{
+		MetadataStructValue: v,
+	}
+}
+
+// Unmarshal JSON data into one of the pointers in the struct
+func (dst *MetadataValue) UnmarshalJSON(data []byte) error {
+	var err error
+	// use discriminator value to speed up the lookup
+	var jsonDict map[string]interface{}
+	err = newStrictDecoder(data).Decode(&jsonDict)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshal JSON into map for the discriminator lookup")
+	}
+
+	// check if the discriminator value is 'MetadataBoolValue'
+	if jsonDict["metadataType"] == "MetadataBoolValue" {
+		// try to unmarshal JSON data into MetadataBoolValue
+		err = json.Unmarshal(data, &dst.MetadataBoolValue)
+		if err == nil {
+			return nil // data stored in dst.MetadataBoolValue, return on the first match
+		} else {
+			dst.MetadataBoolValue = nil
+			return fmt.Errorf("failed to unmarshal MetadataValue as MetadataBoolValue: %s", err.Error())
+		}
+	}
+
+	// check if the discriminator value is 'MetadataDoubleValue'
+	if jsonDict["metadataType"] == "MetadataDoubleValue" {
+		// try to unmarshal JSON data into MetadataDoubleValue
+		err = json.Unmarshal(data, &dst.MetadataDoubleValue)
+		if err == nil {
+			return nil // data stored in dst.MetadataDoubleValue, return on the first match
+		} else {
+			dst.MetadataDoubleValue = nil
+			return fmt.Errorf("failed to unmarshal MetadataValue as MetadataDoubleValue: %s", err.Error())
+		}
+	}
+
+	// check if the discriminator value is 'MetadataIntValue'
+	if jsonDict["metadataType"] == "MetadataIntValue" {
+		// try to unmarshal JSON data into MetadataIntValue
+		err = json.Unmarshal(data, &dst.MetadataIntValue)
+		if err == nil {
+			return nil // data stored in dst.MetadataIntValue, return on the first match
+		} else {
+			dst.MetadataIntValue = nil
+			return fmt.Errorf("failed to unmarshal MetadataValue as MetadataIntValue: %s", err.Error())
+		}
+	}
+
+	// check if the discriminator value is 'MetadataProtoValue'
+	if jsonDict["metadataType"] == "MetadataProtoValue" {
+		// try to unmarshal JSON data into MetadataProtoValue
+		err = json.Unmarshal(data, &dst.MetadataProtoValue)
+		if err == nil {
+			return nil // data stored in dst.MetadataProtoValue, return on the first match
+		} else {
+			dst.MetadataProtoValue = nil
+			return fmt.Errorf("failed to unmarshal MetadataValue as MetadataProtoValue: %s", err.Error())
+		}
+	}
+
+	// check if the discriminator value is 'MetadataStringValue'
+	if jsonDict["metadataType"] == "MetadataStringValue" {
+		// try to unmarshal JSON data into MetadataStringValue
+		err = json.Unmarshal(data, &dst.MetadataStringValue)
+		if err == nil {
+			return nil // data stored in dst.MetadataStringValue, return on the first match
+		} else {
+			dst.MetadataStringValue = nil
+			return fmt.Errorf("failed to unmarshal MetadataValue as MetadataStringValue: %s", err.Error())
+		}
+	}
+
+	// check if the discriminator value is 'MetadataStructValue'
+	if jsonDict["metadataType"] == "MetadataStructValue" {
+		// try to unmarshal JSON data into MetadataStructValue
+		err = json.Unmarshal(data, &dst.MetadataStructValue)
+		if err == nil {
+			return nil // data stored in dst.MetadataStructValue, return on the first match
+		} else {
+			dst.MetadataStructValue = nil
+			return fmt.Errorf("failed to unmarshal MetadataValue as MetadataStructValue: %s", err.Error())
+		}
+	}
+
+	return nil
+}
+
+// Marshal data from the first non-nil pointers in the struct to JSON
+func (src MetadataValue) MarshalJSON() ([]byte, error) {
+	if src.MetadataBoolValue != nil {
+		return json.Marshal(&src.MetadataBoolValue)
+	}
+
+	if src.MetadataDoubleValue != nil {
+		return json.Marshal(&src.MetadataDoubleValue)
+	}
+
+	if src.MetadataIntValue != nil {
+		return json.Marshal(&src.MetadataIntValue)
+	}
+
+	if src.MetadataProtoValue != nil {
+		return json.Marshal(&src.MetadataProtoValue)
+	}
+
+	if src.MetadataStringValue != nil {
+		return json.Marshal(&src.MetadataStringValue)
+	}
+
+	if src.MetadataStructValue != nil {
+		return json.Marshal(&src.MetadataStructValue)
+	}
+
+	return nil, nil // no data in oneOf schemas
+}
+
+// Get the actual instance
+func (obj *MetadataValue) GetActualInstance() interface{} {
+	if obj == nil {
+		return nil
+	}
+	if obj.MetadataBoolValue != nil {
+		return obj.MetadataBoolValue
+	}
+
+	if obj.MetadataDoubleValue != nil {
+		return obj.MetadataDoubleValue
+	}
+
+	if obj.MetadataIntValue != nil {
+		return obj.MetadataIntValue
+	}
+
+	if obj.MetadataProtoValue != nil {
+		return obj.MetadataProtoValue
+	}
+
+	if obj.MetadataStringValue != nil {
+		return obj.MetadataStringValue
+	}
+
+	if obj.MetadataStructValue != nil {
+		return obj.MetadataStructValue
+	}
+
+	// all schemas are nil
+	return nil
+}
+
+type NullableMetadataValue struct {
+	value *MetadataValue
+	isSet bool
+}
+
+func (v NullableMetadataValue) Get() *MetadataValue {
+	return v.value
+}
+
+func (v *NullableMetadataValue) Set(val *MetadataValue) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableMetadataValue) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableMetadataValue) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableMetadataValue(val *MetadataValue) *NullableMetadataValue {
+	return &NullableMetadataValue{value: val, isSet: true}
+}
+
+func (v NullableMetadataValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableMetadataValue) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_order_by_field.go b/catalog/pkg/openapi/model_order_by_field.go
new file mode 100644
index 00000000..62842ba9
--- /dev/null
+++ b/catalog/pkg/openapi/model_order_by_field.go
@@ -0,0 +1,114 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// OrderByField Supported fields for ordering result entities.
+type OrderByField string
+
+// List of OrderByField
+const (
+	ORDERBYFIELD_CREATE_TIME      OrderByField = "CREATE_TIME"
+	ORDERBYFIELD_LAST_UPDATE_TIME OrderByField = "LAST_UPDATE_TIME"
+	ORDERBYFIELD_ID               OrderByField = "ID"
+	ORDERBYFIELD_NAME             OrderByField = "NAME"
+)
+
+// All allowed values of OrderByField enum
+var AllowedOrderByFieldEnumValues = []OrderByField{
+	"CREATE_TIME",
+	"LAST_UPDATE_TIME",
+	"ID",
+	"NAME",
+}
+
+func (v *OrderByField) UnmarshalJSON(src []byte) error {
+	var value string
+	err := json.Unmarshal(src, &value)
+	if err != nil {
+		return err
+	}
+	enumTypeValue := OrderByField(value)
+	for _, existing := range AllowedOrderByFieldEnumValues {
+		if existing == enumTypeValue {
+			*v = enumTypeValue
+			return nil
+		}
+	}
+
+	return fmt.Errorf("%+v is not a valid OrderByField", value)
+}
+
+// NewOrderByFieldFromValue returns a pointer to a valid OrderByField
+// for the value passed as argument, or an error if the value passed is not allowed by the enum
+func NewOrderByFieldFromValue(v string) (*OrderByField, error) {
+	ev := OrderByField(v)
+	if ev.IsValid() {
+		return &ev, nil
+	} else {
+		return nil, fmt.Errorf("invalid value '%v' for OrderByField: valid values are %v", v, AllowedOrderByFieldEnumValues)
+	}
+}
+
+// IsValid return true if the value is valid for the enum, false otherwise
+func (v OrderByField) IsValid() bool {
+	for _, existing := range AllowedOrderByFieldEnumValues {
+		if existing == v {
+			return true
+		}
+	}
+	return false
+}
+
+// Ptr returns reference to OrderByField value
+func (v OrderByField) Ptr() *OrderByField {
+	return &v
+}
+
+type NullableOrderByField struct {
+	value *OrderByField
+	isSet bool
+}
+
+func (v NullableOrderByField) Get() *OrderByField {
+	return v.value
+}
+
+func (v *NullableOrderByField) Set(val *OrderByField) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableOrderByField) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableOrderByField) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableOrderByField(val *OrderByField) *NullableOrderByField {
+	return &NullableOrderByField{value: val, isSet: true}
+}
+
+func (v NullableOrderByField) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableOrderByField) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/model_sort_order.go b/catalog/pkg/openapi/model_sort_order.go
new file mode 100644
index 00000000..0a9a63ed
--- /dev/null
+++ b/catalog/pkg/openapi/model_sort_order.go
@@ -0,0 +1,110 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// SortOrder Supported sort direction for ordering result entities.
+type SortOrder string
+
+// List of SortOrder
+const (
+	SORTORDER_ASC  SortOrder = "ASC"
+	SORTORDER_DESC SortOrder = "DESC"
+)
+
+// All allowed values of SortOrder enum
+var AllowedSortOrderEnumValues = []SortOrder{
+	"ASC",
+	"DESC",
+}
+
+func (v *SortOrder) UnmarshalJSON(src []byte) error {
+	var value string
+	err := json.Unmarshal(src, &value)
+	if err != nil {
+		return err
+	}
+	enumTypeValue := SortOrder(value)
+	for _, existing := range AllowedSortOrderEnumValues {
+		if existing == enumTypeValue {
+			*v = enumTypeValue
+			return nil
+		}
+	}
+
+	return fmt.Errorf("%+v is not a valid SortOrder", value)
+}
+
+// NewSortOrderFromValue returns a pointer to a valid SortOrder
+// for the value passed as argument, or an error if the value passed is not allowed by the enum
+func NewSortOrderFromValue(v string) (*SortOrder, error) {
+	ev := SortOrder(v)
+	if ev.IsValid() {
+		return &ev, nil
+	} else {
+		return nil, fmt.Errorf("invalid value '%v' for SortOrder: valid values are %v", v, AllowedSortOrderEnumValues)
+	}
+}
+
+// IsValid return true if the value is valid for the enum, false otherwise
+func (v SortOrder) IsValid() bool {
+	for _, existing := range AllowedSortOrderEnumValues {
+		if existing == v {
+			return true
+		}
+	}
+	return false
+}
+
+// Ptr returns reference to SortOrder value
+func (v SortOrder) Ptr() *SortOrder {
+	return &v
+}
+
+type NullableSortOrder struct {
+	value *SortOrder
+	isSet bool
+}
+
+func (v NullableSortOrder) Get() *SortOrder {
+	return v.value
+}
+
+func (v *NullableSortOrder) Set(val *SortOrder) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableSortOrder) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableSortOrder) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableSortOrder(val *SortOrder) *NullableSortOrder {
+	return &NullableSortOrder{value: val, isSet: true}
+}
+
+func (v NullableSortOrder) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableSortOrder) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
diff --git a/catalog/pkg/openapi/response.go b/catalog/pkg/openapi/response.go
new file mode 100644
index 00000000..f881c505
--- /dev/null
+++ b/catalog/pkg/openapi/response.go
@@ -0,0 +1,47 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"net/http"
+)
+
+// APIResponse stores the API response returned by the server.
+type APIResponse struct {
+	*http.Response `json:"-"`
+	Message        string `json:"message,omitempty"`
+	// Operation is the name of the OpenAPI operation.
+	Operation string `json:"operation,omitempty"`
+	// RequestURL is the request URL. This value is always available, even if the
+	// embedded *http.Response is nil.
+	RequestURL string `json:"url,omitempty"`
+	// Method is the HTTP method used for the request.  This value is always
+	// available, even if the embedded *http.Response is nil.
+	Method string `json:"method,omitempty"`
+	// Payload holds the contents of the response body (which may be nil or empty).
+	// This is provided here as the raw response.Body() reader will have already
+	// been drained.
+	Payload []byte `json:"-"`
+}
+
+// NewAPIResponse returns a new APIResponse object.
+func NewAPIResponse(r *http.Response) *APIResponse {
+
+	response := &APIResponse{Response: r}
+	return response
+}
+
+// NewAPIResponseWithError returns a new APIResponse object with the provided error message.
+func NewAPIResponseWithError(errorMessage string) *APIResponse {
+
+	response := &APIResponse{Message: errorMessage}
+	return response
+}
diff --git a/catalog/pkg/openapi/utils.go b/catalog/pkg/openapi/utils.go
new file mode 100644
index 00000000..34bc3963
--- /dev/null
+++ b/catalog/pkg/openapi/utils.go
@@ -0,0 +1,347 @@
+/*
+Model Catalog REST API
+
+REST API for Model Registry to create and manage ML model metadata
+
+API version: v1alpha1
+*/
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+	"reflect"
+	"time"
+)
+
+// PtrBool is a helper routine that returns a pointer to given boolean value.
+func PtrBool(v bool) *bool { return &v }
+
+// PtrInt is a helper routine that returns a pointer to given integer value.
+func PtrInt(v int) *int { return &v }
+
+// PtrInt32 is a helper routine that returns a pointer to given integer value.
+func PtrInt32(v int32) *int32 { return &v }
+
+// PtrInt64 is a helper routine that returns a pointer to given integer value.
+func PtrInt64(v int64) *int64 { return &v }
+
+// PtrFloat32 is a helper routine that returns a pointer to given float value.
+func PtrFloat32(v float32) *float32 { return &v }
+
+// PtrFloat64 is a helper routine that returns a pointer to given float value.
+func PtrFloat64(v float64) *float64 { return &v }
+
+// PtrString is a helper routine that returns a pointer to given string value.
+func PtrString(v string) *string { return &v }
+
+// PtrTime is helper routine that returns a pointer to given Time value.
+func PtrTime(v time.Time) *time.Time { return &v }
+
+type NullableBool struct {
+	value *bool
+	isSet bool
+}
+
+func (v NullableBool) Get() *bool {
+	return v.value
+}
+
+func (v *NullableBool) Set(val *bool) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableBool) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableBool) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableBool(val *bool) *NullableBool {
+	return &NullableBool{value: val, isSet: true}
+}
+
+func (v NullableBool) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableBool) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableInt struct {
+	value *int
+	isSet bool
+}
+
+func (v NullableInt) Get() *int {
+	return v.value
+}
+
+func (v *NullableInt) Set(val *int) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableInt) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableInt) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableInt(val *int) *NullableInt {
+	return &NullableInt{value: val, isSet: true}
+}
+
+func (v NullableInt) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableInt) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableInt32 struct {
+	value *int32
+	isSet bool
+}
+
+func (v NullableInt32) Get() *int32 {
+	return v.value
+}
+
+func (v *NullableInt32) Set(val *int32) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableInt32) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableInt32) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableInt32(val *int32) *NullableInt32 {
+	return &NullableInt32{value: val, isSet: true}
+}
+
+func (v NullableInt32) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableInt32) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableInt64 struct {
+	value *int64
+	isSet bool
+}
+
+func (v NullableInt64) Get() *int64 {
+	return v.value
+}
+
+func (v *NullableInt64) Set(val *int64) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableInt64) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableInt64) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableInt64(val *int64) *NullableInt64 {
+	return &NullableInt64{value: val, isSet: true}
+}
+
+func (v NullableInt64) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableInt64) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableFloat32 struct {
+	value *float32
+	isSet bool
+}
+
+func (v NullableFloat32) Get() *float32 {
+	return v.value
+}
+
+func (v *NullableFloat32) Set(val *float32) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableFloat32) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableFloat32) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableFloat32(val *float32) *NullableFloat32 {
+	return &NullableFloat32{value: val, isSet: true}
+}
+
+func (v NullableFloat32) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableFloat32) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableFloat64 struct {
+	value *float64
+	isSet bool
+}
+
+func (v NullableFloat64) Get() *float64 {
+	return v.value
+}
+
+func (v *NullableFloat64) Set(val *float64) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableFloat64) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableFloat64) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableFloat64(val *float64) *NullableFloat64 {
+	return &NullableFloat64{value: val, isSet: true}
+}
+
+func (v NullableFloat64) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableFloat64) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableString struct {
+	value *string
+	isSet bool
+}
+
+func (v NullableString) Get() *string {
+	return v.value
+}
+
+func (v *NullableString) Set(val *string) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableString) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableString) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableString(val *string) *NullableString {
+	return &NullableString{value: val, isSet: true}
+}
+
+func (v NullableString) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableString) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+type NullableTime struct {
+	value *time.Time
+	isSet bool
+}
+
+func (v NullableTime) Get() *time.Time {
+	return v.value
+}
+
+func (v *NullableTime) Set(val *time.Time) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableTime) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableTime) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableTime(val *time.Time) *NullableTime {
+	return &NullableTime{value: val, isSet: true}
+}
+
+func (v NullableTime) MarshalJSON() ([]byte, error) {
+	return v.value.MarshalJSON()
+}
+
+func (v *NullableTime) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+// IsNil checks if an input is nil
+func IsNil(i interface{}) bool {
+	if i == nil {
+		return true
+	}
+	switch reflect.TypeOf(i).Kind() {
+	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
+		return reflect.ValueOf(i).IsNil()
+	case reflect.Array:
+		return reflect.ValueOf(i).IsZero()
+	}
+	return false
+}
+
+type MappedNullable interface {
+	ToMap() (map[string]interface{}, error)
+}
diff --git a/catalog/scripts/gen_openapi_server.sh b/catalog/scripts/gen_openapi_server.sh
new file mode 100755
index 00000000..a4996b6a
--- /dev/null
+++ b/catalog/scripts/gen_openapi_server.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -e
+
+echo "Generating the OpenAPI server"
+
+OPENAPI_GENERATOR=${OPENAPI_GENERATOR:-openapi-generator-cli}
+
+PROJECT_ROOT=$(realpath "$(dirname "$0")"/..)
+SRC="$PROJECT_ROOT/${1:-../api/openapi/catalog.yaml}"
+DST="$PROJECT_ROOT/${2:-internal/server/openapi}"
+
+"$OPENAPI_GENERATOR" generate \
+    -i "$SRC" -g go-server -o "$DST" --package-name openapi \
+    --ignore-file-override "$PROJECT_ROOT"/.openapi-generator-ignore --additional-properties=outputAsLibrary=true,enumClassPrefix=true,router=chi,sourceFolder=,onlyInterfaces=true,isGoSubmodule=true,enumClassPrefix=true,useOneOfDiscriminatorLookup=true,featureCORS=true \
+    --template-dir "$PROJECT_ROOT"/../templates/go-server
+
+function sed_inplace() {
+    if [[ $(uname) == "Darwin" ]]; then
+        # introduce -i parameter for Mac OSX sed compatibility
+        sed -i '' "$@"
+    else
+        sed -i "$@"
+    fi
+}
+
+sed_inplace 's/, orderByParam/, model.OrderByField(orderByParam)/g' "$PROJECT_ROOT"/internal/server/openapi/api_model_catalog_service.go
+sed_inplace 's/, sortOrderParam/, model.SortOrder(sortOrderParam)/g' "$PROJECT_ROOT"/internal/server/openapi/api_model_catalog_service.go
+
+sed_inplace 's/"encoding\/json"//' "$PROJECT_ROOT"/internal/server/openapi/api_model_catalog_service.go
+
+sed_inplace 's/github.com\/kubeflow\/model-registry\/pkg\/openapi/github.com\/kubeflow\/model-registry\/catalog\/pkg\/openapi/' \
+    "$PROJECT_ROOT"/internal/server/openapi/api_model_catalog_service.go \
+    "$PROJECT_ROOT"/internal/server/openapi/api.go
+
+echo "Assembling type_assert Go file"
+./scripts/gen_type_asserts.sh "$DST"
+
+gofmt -w "$DST"
+
+echo "OpenAPI server generation completed"
diff --git a/catalog/scripts/gen_type_asserts.py b/catalog/scripts/gen_type_asserts.py
new file mode 100644
index 00000000..55d31fed
--- /dev/null
+++ b/catalog/scripts/gen_type_asserts.py
@@ -0,0 +1,81 @@
+import typing as t
+from pathlib import Path
+from textwrap import dedent
+import sys
+
+def get_funcs(models: t.Iterable[Path]) -> t.Iterator[str]:
+    for path in models:
+        with path.open() as f:
+            # skip boilerplate
+            lines = iter(f.readlines())
+            while not next(lines).startswith("package"):
+                continue
+
+            buf = []
+            in_func = False
+            in_import = False
+            for raw in lines:
+                line = raw.rstrip()
+                if not line and not in_func:
+                    continue
+
+                if line.startswith("import "):
+                    in_import = True
+                    continue
+                elif line == ")" and in_import:
+                    in_import = False
+                    buf.clear()
+                    continue
+
+                buf.append(line)
+                if line.startswith("func"):
+                    in_func = True
+                elif line.startswith("}") and in_func:
+                    in_func = False
+                    yield "\n".join(buf)
+                    buf.clear()
+        path.unlink()
+
+
+def get_name(func: str) -> str:
+    # each func is declared as:
+    # // comment
+    # func funcName(args) error ...
+    return func.split("\n")[1].split(" ")[1].split("(")[0]
+
+
+if __name__ == "__main__":
+    # model catalog package root
+    root = Path(__file__).parent.parent.resolve()
+    src = root / "internal/server/openapi"
+
+    print(
+        dedent(f"""
+/*
+ * Model Registry REST API
+ *
+ * REST API for Model Registry to create and manage ML model metadata
+ *
+ * API version: 1.0.0
+ * Generated by: OpenAPI Generator (https://openapi-generator.tech)
+ *
+ */
+
+// File generated by scripts/gen_type_assert.sh - DO NOT EDIT
+
+package openapi
+
+import (
+    model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+)
+
+    """)
+    )
+    print(
+        "\n".join(
+            sorted(
+                get_funcs(path for path in src.rglob("model_*.go")),
+                key=lambda f: get_name(f),
+            )
+        )
+    )
diff --git a/catalog/scripts/gen_type_asserts.sh b/catalog/scripts/gen_type_asserts.sh
new file mode 100755
index 00000000..fb64cb73
--- /dev/null
+++ b/catalog/scripts/gen_type_asserts.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -e
+
+ASSERT_FILE_PATH="$1/type_asserts.go"
+
+PROJECT_ROOT=$(realpath "$(dirname "$0")"/..)
+PATCH="${PROJECT_ROOT}/patches/type_asserts.patch"
+
+# AssertMetadataValueRequired from this file generates with the incorrect logic.
+rm -f $1/model_metadata_value.go
+
+python3 "${PROJECT_ROOT}/scripts/gen_type_asserts.py" $1 >"$ASSERT_FILE_PATH"
+
+gofmt -w "$ASSERT_FILE_PATH"
diff --git a/clients/python/poetry.lock b/clients/python/poetry.lock
index 051de51c..20ef2f83 100644
--- a/clients/python/poetry.lock
+++ b/clients/python/poetry.lock
@@ -1294,44 +1294,44 @@ files = [
 
 [[package]]
 name = "mypy"
-version = "1.16.0"
+version = "1.16.1"
 description = "Optional static typing for Python"
 optional = false
 python-versions = ">=3.9"
 groups = ["dev"]
 files = [
-    {file = "mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c"},
-    {file = "mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571"},
-    {file = "mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491"},
-    {file = "mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777"},
-    {file = "mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b"},
-    {file = "mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93"},
-    {file = "mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab"},
-    {file = "mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2"},
-    {file = "mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff"},
-    {file = "mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666"},
-    {file = "mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c"},
-    {file = "mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b"},
-    {file = "mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13"},
-    {file = "mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090"},
-    {file = "mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1"},
-    {file = "mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8"},
-    {file = "mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730"},
-    {file = "mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec"},
-    {file = "mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b"},
-    {file = "mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0"},
-    {file = "mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b"},
-    {file = "mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d"},
-    {file = "mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52"},
-    {file = "mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb"},
-    {file = "mypy-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f56236114c425620875c7cf71700e3d60004858da856c6fc78998ffe767b73d3"},
-    {file = "mypy-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15486beea80be24ff067d7d0ede673b001d0d684d0095803b3e6e17a886a2a92"},
-    {file = "mypy-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ed0e0847a80655afa2c121835b848ed101cc7b8d8d6ecc5205aedc732b1436"},
-    {file = "mypy-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb5fbc8063cb4fde7787e4c0406aa63094a34a2daf4673f359a1fb64050e9cb2"},
-    {file = "mypy-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a5fcfdb7318c6a8dd127b14b1052743b83e97a970f0edb6c913211507a255e20"},
-    {file = "mypy-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7e0ad35275e02797323a5aa1be0b14a4d03ffdb2e5f2b0489fa07b89c67b21"},
-    {file = "mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031"},
-    {file = "mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab"},
+    {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"},
+    {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"},
+    {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"},
+    {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"},
+    {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"},
+    {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"},
+    {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"},
+    {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"},
+    {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"},
+    {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"},
+    {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"},
+    {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"},
+    {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"},
+    {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"},
+    {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"},
+    {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"},
+    {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"},
+    {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"},
+    {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"},
+    {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"},
+    {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"},
+    {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"},
+    {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"},
+    {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"},
+    {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"},
+    {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"},
+    {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"},
+    {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"},
+    {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"},
+    {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"},
+    {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"},
+    {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"},
 ]
 
 [package.dependencies]
@@ -1589,6 +1589,7 @@ description = ""
 optional = false
 python-versions = ">=3.9"
 groups = ["dev"]
+markers = "python_version < \"3.13\""
 files = [
     {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"},
     {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"},
diff --git a/clients/ui/bff/go.mod b/clients/ui/bff/go.mod
index 4cf27d33..a9a00563 100644
--- a/clients/ui/bff/go.mod
+++ b/clients/ui/bff/go.mod
@@ -11,9 +11,9 @@ require (
 	github.com/onsi/gomega v1.37.0
 	github.com/rs/cors v1.11.1
 	github.com/stretchr/testify v1.10.0
-	k8s.io/api v0.33.1
-	k8s.io/apimachinery v0.33.1
-	k8s.io/client-go v0.33.1
+	k8s.io/api v0.33.2
+	k8s.io/apimachinery v0.33.2
+	k8s.io/client-go v0.33.2
 	sigs.k8s.io/controller-runtime v0.21.0
 )
 
diff --git a/clients/ui/bff/go.sum b/clients/ui/bff/go.sum
index 236a6bcc..415bfb2e 100644
--- a/clients/ui/bff/go.sum
+++ b/clients/ui/bff/go.sum
@@ -160,14 +160,14 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
-k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
+k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
+k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
 k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs=
 k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc=
-k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
-k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
-k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
-k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
+k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
+k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
+k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
diff --git a/clients/ui/frontend/package-lock.json b/clients/ui/frontend/package-lock.json
index a5215ba3..bfd36389 100644
--- a/clients/ui/frontend/package-lock.json
+++ b/clients/ui/frontend/package-lock.json
@@ -66,7 +66,7 @@
         "dotenv": "^16.5.0",
         "dotenv-expand": "^5.1.0",
         "dotenv-webpack": "^6.0.0",
-        "expect": "^29.7.0",
+        "expect": "^30.0.2",
         "file-loader": "^6.2.0",
         "fork-ts-checker-webpack-plugin": "^9.0.2",
         "html-webpack-plugin": "^5.6.3",
@@ -3203,6 +3203,16 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@jest/diff-sequences": {
+      "version": "30.0.1",
+      "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+      "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
     "node_modules/@jest/environment": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
@@ -3246,6 +3256,23 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/@jest/expect/node_modules/expect": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+      "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/expect-utils": "^29.7.0",
+        "jest-get-type": "^29.6.3",
+        "jest-matcher-utils": "^29.7.0",
+        "jest-message-util": "^29.7.0",
+        "jest-util": "^29.7.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
     "node_modules/@jest/fake-timers": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
@@ -3264,6 +3291,16 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/@jest/get-type": {
+      "version": "30.0.1",
+      "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz",
+      "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
     "node_modules/@jest/globals": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
@@ -3280,6 +3317,30 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/@jest/pattern": {
+      "version": "30.0.1",
+      "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+      "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "jest-regex-util": "30.0.1"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/@jest/pattern/node_modules/jest-regex-util": {
+      "version": "30.0.1",
+      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+      "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
     "node_modules/@jest/reporters": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
@@ -5793,6 +5854,23 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/@types/jest/node_modules/expect": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+      "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/expect-utils": "^29.7.0",
+        "jest-get-type": "^29.6.3",
+        "jest-matcher-utils": "^29.7.0",
+        "jest-message-util": "^29.7.0",
+        "jest-util": "^29.7.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
     "node_modules/@types/jest/node_modules/pretty-format": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -11469,22 +11547,209 @@
       }
     },
     "node_modules/expect": {
-      "version": "29.7.0",
-      "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
-      "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.2.tgz",
+      "integrity": "sha512-YN9Mgv2mtTWXVmifQq3QT+ixCL/uLuLJw+fdp8MOjKqu8K3XQh3o5aulMM1tn+O2DdrWNxLZTeJsCY/VofUA0A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@jest/expect-utils": "^29.7.0",
-        "jest-get-type": "^29.6.3",
-        "jest-matcher-utils": "^29.7.0",
-        "jest-message-util": "^29.7.0",
-        "jest-util": "^29.7.0"
+        "@jest/expect-utils": "30.0.2",
+        "@jest/get-type": "30.0.1",
+        "jest-matcher-utils": "30.0.2",
+        "jest-message-util": "30.0.2",
+        "jest-mock": "30.0.2",
+        "jest-util": "30.0.2"
       },
       "engines": {
-        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/@jest/expect-utils": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.2.tgz",
+      "integrity": "sha512-FHF2YdtFBUQOo0/qdgt+6UdBFcNPF/TkVzcc+4vvf8uaBzUlONytGBeeudufIHHW1khRfM1sBbRT1VCK7n/0dQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/get-type": "30.0.1"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/@jest/schemas": {
+      "version": "30.0.1",
+      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz",
+      "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@sinclair/typebox": "^0.34.0"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/@jest/types": {
+      "version": "30.0.1",
+      "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz",
+      "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/pattern": "30.0.1",
+        "@jest/schemas": "30.0.1",
+        "@types/istanbul-lib-coverage": "^2.0.6",
+        "@types/istanbul-reports": "^3.0.4",
+        "@types/node": "*",
+        "@types/yargs": "^17.0.33",
+        "chalk": "^4.1.2"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/@sinclair/typebox": {
+      "version": "0.34.36",
+      "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.36.tgz",
+      "integrity": "sha512-JFHFhF6MqqRE49JDAGX/EPlHwxIukrKMhNwlMoB/wIJBkvu3+ciO335yDYPP3soI01FkhVXWnyNPKEl+EsC4Zw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/expect/node_modules/ansi-styles": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+      "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/expect/node_modules/jest-diff": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.2.tgz",
+      "integrity": "sha512-2UjrNvDJDn/oHFpPrUTVmvYYDNeNtw2DlY3er8bI6vJJb9Fb35ycp/jFLd5RdV59tJ8ekVXX3o/nwPcscgXZJQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/diff-sequences": "30.0.1",
+        "@jest/get-type": "30.0.1",
+        "chalk": "^4.1.2",
+        "pretty-format": "30.0.2"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/jest-matcher-utils": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.2.tgz",
+      "integrity": "sha512-1FKwgJYECR8IT93KMKmjKHSLyru0DqguThov/aWpFccC0wbiXGOxYEu7SScderBD7ruDOpl7lc5NG6w3oxKfaA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/get-type": "30.0.1",
+        "chalk": "^4.1.2",
+        "jest-diff": "30.0.2",
+        "pretty-format": "30.0.2"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
       }
     },
+    "node_modules/expect/node_modules/jest-message-util": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz",
+      "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@jest/types": "30.0.1",
+        "@types/stack-utils": "^2.0.3",
+        "chalk": "^4.1.2",
+        "graceful-fs": "^4.2.11",
+        "micromatch": "^4.0.8",
+        "pretty-format": "30.0.2",
+        "slash": "^3.0.0",
+        "stack-utils": "^2.0.6"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/jest-mock": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz",
+      "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/types": "30.0.1",
+        "@types/node": "*",
+        "jest-util": "30.0.2"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/jest-util": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz",
+      "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/types": "30.0.1",
+        "@types/node": "*",
+        "chalk": "^4.1.2",
+        "ci-info": "^4.2.0",
+        "graceful-fs": "^4.2.11",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/expect/node_modules/pretty-format": {
+      "version": "30.0.2",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+      "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/schemas": "30.0.1",
+        "ansi-styles": "^5.2.0",
+        "react-is": "^18.3.1"
+      },
+      "engines": {
+        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+      }
+    },
+    "node_modules/expect/node_modules/react-is": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+      "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/express": {
       "version": "4.21.2",
       "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
@@ -15394,6 +15659,23 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/jest-snapshot/node_modules/expect": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+      "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jest/expect-utils": "^29.7.0",
+        "jest-get-type": "^29.6.3",
+        "jest-matcher-utils": "^29.7.0",
+        "jest-message-util": "^29.7.0",
+        "jest-util": "^29.7.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
     "node_modules/jest-snapshot/node_modules/pretty-format": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
diff --git a/clients/ui/frontend/package.json b/clients/ui/frontend/package.json
index bb6c7db6..0a81c927 100644
--- a/clients/ui/frontend/package.json
+++ b/clients/ui/frontend/package.json
@@ -68,7 +68,7 @@
     "dotenv": "^16.5.0",
     "dotenv-expand": "^5.1.0",
     "dotenv-webpack": "^6.0.0",
-    "expect": "^29.7.0",
+    "expect": "^30.0.2",
     "file-loader": "^6.2.0",
     "fork-ts-checker-webpack-plugin": "^9.0.2",
     "html-webpack-plugin": "^5.6.3",
diff --git a/cmd/catalog.go b/cmd/catalog.go
new file mode 100644
index 00000000..3e6195ab
--- /dev/null
+++ b/cmd/catalog.go
@@ -0,0 +1,9 @@
+package cmd
+
+import (
+	"github.com/kubeflow/model-registry/catalog/cmd"
+)
+
+func init() {
+	rootCmd.AddCommand(cmd.CatalogCmd)
+}
diff --git a/cmd/csi/Dockerfile.csi b/cmd/csi/Dockerfile.csi
index ba919e39..32b1765d 100644
--- a/cmd/csi/Dockerfile.csi
+++ b/cmd/csi/Dockerfile.csi
@@ -27,8 +27,9 @@ COPY api/ api/
 COPY internal/ internal/
 COPY scripts/ scripts/
 COPY pkg/ pkg/
-COPY patches/ patches/
 COPY templates/ templates/
+COPY catalog/ catalog/
+COPY patches/ patches/
 
 # Download tools
 RUN make deps
diff --git a/cmd/proxy.go b/cmd/proxy.go
index b96c939e..67078dc4 100644
--- a/cmd/proxy.go
+++ b/cmd/proxy.go
@@ -25,7 +25,7 @@ const (
 var (
 	proxyCfg = ProxyConfig{
 		Datastore: datastore.Datastore{
-			Type: "mlmd",
+			Type: "embedmd",
 		},
 	}
 
diff --git a/docs/Model Registry Testing areas.drawio b/docs/Model Registry Testing areas.drawio
index 0199db09..fda22ecb 100644
--- a/docs/Model Registry Testing areas.drawio	
+++ b/docs/Model Registry Testing areas.drawio	
@@ -25,7 +25,7 @@
         <mxCell id="El_kY7I2ZMZImhtPKoh1-9" value="Integration testing&lt;br&gt;(using Testcontainers)&lt;br&gt;&lt;font face=&quot;Courier New&quot;&gt;pkg/core/core_test.go&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;strokeColor=#ff0000;" parent="1" vertex="1">
           <mxGeometry x="50" y="440" width="160" height="60" as="geometry" />
         </mxCell>
-        <mxCell id="El_kY7I2ZMZImhtPKoh1-10" value="&quot;MR REST API&quot;&lt;br&gt;implemented in Go&lt;br&gt;&lt;font face=&quot;Courier New&quot;&gt;internal/server/openapi/routers.go&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+        <mxCell id="El_kY7I2ZMZImhtPKoh1-10" value="&quot;MR REST API&quot;&lt;br&gt;implemented in Go&lt;br&gt;&lt;font face=&quot;Courier New&quot;&gt;internal/server/openapi/registry/routers.go&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
           <mxGeometry x="50" y="289" width="330" height="70" as="geometry" />
         </mxCell>
         <mxCell id="El_kY7I2ZMZImhtPKoh1-17" value="REST" style="html=1;rounded=0;" parent="1" vertex="1">
diff --git a/internal/proxy/readiness.go b/internal/proxy/readiness.go
index b36c1112..71bc6c67 100644
--- a/internal/proxy/readiness.go
+++ b/internal/proxy/readiness.go
@@ -32,19 +32,19 @@ func ReadinessHandler(datastore datastore.Datastore) http.Handler {
 
 		dbConnector, err := db.NewConnector(datastore.EmbedMD.DatabaseType, dsn)
 		if err != nil {
-			http.Error(w, fmt.Sprintf("database conneciton error: %s", err), http.StatusServiceUnavailable)
+			http.Error(w, fmt.Sprintf("database new connector error: %s", err), http.StatusServiceUnavailable)
 			return
 		}
 
 		db, err := dbConnector.Connect()
 		if err != nil {
-			http.Error(w, fmt.Sprintf("database error connecting: %v", err), http.StatusServiceUnavailable)
+			http.Error(w, fmt.Sprintf("database connection error: %v", err), http.StatusServiceUnavailable)
 			return
 		}
 
 		sqlDB, err := db.DB()
 		if err != nil {
-			http.Error(w, fmt.Sprintf("database connection error: %v", err), http.StatusServiceUnavailable)
+			http.Error(w, fmt.Sprintf("database sqlDB connection error: %v", err), http.StatusServiceUnavailable)
 			return
 		}
 		defer func() {
diff --git a/manifests/kustomize/README.md b/manifests/kustomize/README.md
index 65d47875..6d810f70 100644
--- a/manifests/kustomize/README.md
+++ b/manifests/kustomize/README.md
@@ -134,3 +134,13 @@ kubectl delete -k options/istio -n kubeflow
 # Delete model registry db and deployment
 kubectl delete -k overlays/db -n kubeflow
 ```
+
+## Error `error: error connecting to datastore: Dirty database version {version}. Fix and force version.`
+
+If you see this error for your model registry deployment, it means that your schema migration has failed. 
+
+The solution to this problem requires the user to manually resolve the issue and change the database dirty state to '0' before traffic can be routed to the pod. 
+
+You can accomplish this by using `kubectl exec` for the particular model registry db deployment and running the following query:
+
+`USE metadb; UPDATE schema_migrations SET dirty = 0;`
diff --git a/manifests/kustomize/base/model-registry-configmap.yaml b/manifests/kustomize/base/model-registry-configmap.yaml
index b8ff8ce2..3ee7463a 100644
--- a/manifests/kustomize/base/model-registry-configmap.yaml
+++ b/manifests/kustomize/base/model-registry-configmap.yaml
@@ -7,6 +7,4 @@ metadata:
 data:
   MODEL_REGISTRY_REST_SERVICE_HOST: "model-registry-service"
   MODEL_REGISTRY_REST_SERVICE_PORT: "8080"
-  MODEL_REGISTRY_GRPC_SERVICE_HOST: "model-registry-service"
-  MODEL_REGISTRY_GRPC_SERVICE_PORT: "9090"
-  MODEL_REGISTRY_DATA_STORE_TYPE: "mlmd"
+  MODEL_REGISTRY_DATA_STORE_TYPE: "embedmd"
diff --git a/manifests/kustomize/base/model-registry-deployment.yaml b/manifests/kustomize/base/model-registry-deployment.yaml
index aeafcd69..90ad4652 100644
--- a/manifests/kustomize/base/model-registry-deployment.yaml
+++ b/manifests/kustomize/base/model-registry-deployment.yaml
@@ -24,44 +24,12 @@ spec:
           args:
             - --hostname=0.0.0.0
             - --port=8080
-            - --mlmd-hostname=localhost
-            - --mlmd-port=9090
-            - --datastore-type=mlmd
+            - --datastore-type=embedmd
+            - --embedmd-database-dsn=$(DBCONFIG_USER):$(DBCONFIG_PASSWORD)@tcp($(MYSQL_HOST):$(MYSQL_PORT))/$(MYSQL_DATABASE)?charset=utf8mb4
           command:
             - /model-registry
             - proxy
           image: ghcr.io/kubeflow/model-registry/server:latest
-          # empty placeholder environment for patching
-          env: []
-          ports:
-            - name: http-api
-              containerPort: 8080
-          livenessProbe:
-            initialDelaySeconds: 30
-            periodSeconds: 5
-            tcpSocket:
-              port: http-api
-            timeoutSeconds: 2
-          readinessProbe:
-            initialDelaySeconds: 10
-            periodSeconds: 60
-            httpGet:
-              path: /readyz/isDirty
-              port: http-api
-            timeoutSeconds: 2
-          securityContext:
-            allowPrivilegeEscalation: false
-            capabilities:
-              drop:
-                - ALL
-        - name: grpc-container
-          # ! Sync to the same MLMD version:
-          # * backend/metadata_writer/requirements.in and requirements.txt
-          # * @kubeflow/frontend/src/mlmd/generated
-          # * .cloudbuild.yaml and .release.cloudbuild.yaml
-          # * manifests/kustomize/base/metadata/base/model-registry-deployment.yaml
-          # * test/tag_for_hosted.sh
-          image: gcr.io/tfx-oss-public/ml_metadata_store_server:1.14.0
           env:
           - name: DBCONFIG_USER
             valueFrom:
@@ -77,7 +45,7 @@ spec:
             valueFrom:
               configMapKeyRef:
                 name: pipeline-install-config
-                key: mlmdDb
+                key: embedmdDb
           - name: MYSQL_HOST
             valueFrom:
               configMapKeyRef:
@@ -88,33 +56,23 @@ spec:
               configMapKeyRef:
                 name: pipeline-install-config
                 key: dbPort
-          command: ["/bin/metadata_store_server"]
-          args: ["--grpc_port=9090",
-                 "--mysql_config_database=$(MYSQL_DATABASE)",
-                 "--mysql_config_host=$(MYSQL_HOST)",
-                 "--mysql_config_port=MYSQL_PORT_PLACEHOLDER",
-                 "--mysql_config_user=$(DBCONFIG_USER)",
-                 "--mysql_config_password=$(DBCONFIG_PASSWORD)",
-                 "--enable_database_upgrade=true"
-                 ]
           ports:
-          - name: grpc-api
-            containerPort: 9090
+            - name: http-api
+              containerPort: 8080
           livenessProbe:
-            tcpSocket:
-              port: grpc-api
-            initialDelaySeconds: 3
+            initialDelaySeconds: 30
             periodSeconds: 5
+            tcpSocket:
+              port: http-api
             timeoutSeconds: 2
           readinessProbe:
-            tcpSocket:
-              port: grpc-api
-            initialDelaySeconds: 3
-            periodSeconds: 5
+            initialDelaySeconds: 10
+            periodSeconds: 60
+            httpGet:
+              path: /readyz/isDirty
+              port: http-api
             timeoutSeconds: 2
           securityContext:
-            runAsUser: 65534
-            runAsGroup: 65534
             allowPrivilegeEscalation: false
             capabilities:
               drop:
diff --git a/manifests/kustomize/base/model-registry-service.yaml b/manifests/kustomize/base/model-registry-service.yaml
index c4251cf9..65b41a51 100644
--- a/manifests/kustomize/base/model-registry-service.yaml
+++ b/manifests/kustomize/base/model-registry-service.yaml
@@ -21,7 +21,3 @@ spec:
     protocol: TCP
     appProtocol: http
     name: http-api
-  - port: 9090
-    protocol: TCP
-    appProtocol: grpc
-    name: grpc-api
diff --git a/manifests/kustomize/options/catalog/README.md b/manifests/kustomize/options/catalog/README.md
new file mode 100644
index 00000000..c00b4eef
--- /dev/null
+++ b/manifests/kustomize/options/catalog/README.md
@@ -0,0 +1,11 @@
+# Model Catalog Manifests
+
+To deploy the model catalog:
+
+```sh
+kubectl apply -k . -n NAMESPACE
+```
+
+Replace `NAMESPACE` with your desired Kubernetes namespace.
+
+Update `sources.yaml` and `sample-catalog.yaml` to configure catalog models.
diff --git a/manifests/kustomize/options/catalog/deployment.yaml b/manifests/kustomize/options/catalog/deployment.yaml
new file mode 100644
index 00000000..2e953a77
--- /dev/null
+++ b/manifests/kustomize/options/catalog/deployment.yaml
@@ -0,0 +1,57 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: server
+  labels:
+    component: model-catalog-server
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      component: model-catalog-server
+  template:
+    metadata:
+      labels:
+        sidecar.istio.io/inject: "true"
+        component: model-catalog-server
+    spec:
+      securityContext:
+        seccompProfile:
+          type: RuntimeDefault
+        runAsNonRoot: true
+      volumes:
+      - name: sources
+        configMap:
+          name: model-catalog-sources
+      containers:
+        - name: catalog
+          command:
+            - /model-registry
+            - catalog
+          args:
+            - --listen=0.0.0.0:8080
+            - --catalogs-path=/catalog/sources.yaml
+          image: ghcr.io/kubeflow/model-registry/server:latest
+          ports:
+            - name: http-api
+              containerPort: 8080
+          livenessProbe:
+            initialDelaySeconds: 30
+            periodSeconds: 5
+            tcpSocket:
+              port: http-api
+            timeoutSeconds: 2
+          readinessProbe:
+            initialDelaySeconds: 3
+            periodSeconds: 5
+            tcpSocket:
+              port: http-api
+            timeoutSeconds: 2
+          securityContext:
+            allowPrivilegeEscalation: false
+            capabilities:
+              drop:
+                - ALL
+          volumeMounts:
+          - name: sources
+            mountPath: /catalog
diff --git a/manifests/kustomize/options/catalog/kustomization.yaml b/manifests/kustomize/options/catalog/kustomization.yaml
new file mode 100644
index 00000000..ef9521c0
--- /dev/null
+++ b/manifests/kustomize/options/catalog/kustomization.yaml
@@ -0,0 +1,17 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namePrefix: model-catalog-
+
+resources:
+- deployment.yaml
+- service.yaml
+
+configMapGenerator:
+- name: sources
+  options:
+    disableNameSuffixHash: true
+  behavior: create
+  files:
+  - sources.yaml=sources.yaml
+  - sample-catalog.yaml=sample-catalog.yaml
diff --git a/manifests/kustomize/options/catalog/sample-catalog.yaml b/manifests/kustomize/options/catalog/sample-catalog.yaml
new file mode 100644
index 00000000..68ccf604
--- /dev/null
+++ b/manifests/kustomize/options/catalog/sample-catalog.yaml
@@ -0,0 +1,201 @@
+source: Hugging Face
+models:
+- name: mistralai/Mistral-7B-Instruct-v0.3
+  description: The Mistral-7B-Instruct-v0.3 Large Language Model (LLM) is an instruct fine-tuned version of the Mistral-7B-v0.3.
+  readme: |-
+    # Model Card for Mistral-7B-Instruct-v0.3
+
+    The Mistral-7B-Instruct-v0.3 Large Language Model (LLM) is an instruct fine-tuned version of the Mistral-7B-v0.3.
+
+    Mistral-7B-v0.3 has the following changes compared to [Mistral-7B-v0.2](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2/edit/main/README.md)
+    - Extended vocabulary to 32768
+    - Supports v3 Tokenizer
+    - Supports function calling
+
+    ## Installation
+
+    It is recommended to use `mistralai/Mistral-7B-Instruct-v0.3` with [mistral-inference](https://github.com/mistralai/mistral-inference). For HF transformers code snippets, please keep scrolling.
+
+    ```
+    pip install mistral_inference
+    ```
+
+    ## Download
+
+    ```py
+    from huggingface_hub import snapshot_download
+    from pathlib import Path
+
+    mistral_models_path = Path.home().joinpath('mistral_models', '7B-Instruct-v0.3')
+    mistral_models_path.mkdir(parents=True, exist_ok=True)
+
+    snapshot_download(repo_id="mistralai/Mistral-7B-Instruct-v0.3", allow_patterns=["params.json", "consolidated.safetensors", "tokenizer.model.v3"], local_dir=mistral_models_path)
+    ```
+
+    ### Chat
+
+    After installing `mistral_inference`, a `mistral-chat` CLI command should be available in your environment. You can chat with the model using
+
+    ```
+    mistral-chat $HOME/mistral_models/7B-Instruct-v0.3 --instruct --max_tokens 256
+    ```
+
+    ### Instruct following
+
+    ```py
+    from mistral_inference.transformer import Transformer
+    from mistral_inference.generate import generate
+
+    from mistral_common.tokens.tokenizers.mistral import MistralTokenizer
+    from mistral_common.protocol.instruct.messages import UserMessage
+    from mistral_common.protocol.instruct.request import ChatCompletionRequest
+
+
+    tokenizer = MistralTokenizer.from_file(f"{mistral_models_path}/tokenizer.model.v3")
+    model = Transformer.from_folder(mistral_models_path)
+
+    completion_request = ChatCompletionRequest(messages=[UserMessage(content="Explain Machine Learning to me in a nutshell.")])
+
+    tokens = tokenizer.encode_chat_completion(completion_request).tokens
+
+    out_tokens, _ = generate([tokens], model, max_tokens=64, temperature=0.0, eos_id=tokenizer.instruct_tokenizer.tokenizer.eos_id)
+    result = tokenizer.instruct_tokenizer.tokenizer.decode(out_tokens[0])
+
+    print(result)
+    ```
+
+    ### Function calling
+
+    ```py
+    from mistral_common.protocol.instruct.tool_calls import Function, Tool
+    from mistral_inference.transformer import Transformer
+    from mistral_inference.generate import generate
+
+    from mistral_common.tokens.tokenizers.mistral import MistralTokenizer
+    from mistral_common.protocol.instruct.messages import UserMessage
+    from mistral_common.protocol.instruct.request import ChatCompletionRequest
+
+
+    tokenizer = MistralTokenizer.from_file(f"{mistral_models_path}/tokenizer.model.v3")
+    model = Transformer.from_folder(mistral_models_path)
+
+    completion_request = ChatCompletionRequest(
+        tools=[
+            Tool(
+                function=Function(
+                    name="get_current_weather",
+                    description="Get the current weather",
+                    parameters={
+                        "type": "object",
+                        "properties": {
+                            "location": {
+                                "type": "string",
+                                "description": "The city and state, e.g. San Francisco, CA",
+                            },
+                            "format": {
+                                "type": "string",
+                                "enum": ["celsius", "fahrenheit"],
+                                "description": "The temperature unit to use. Infer this from the users location.",
+                            },
+                        },
+                        "required": ["location", "format"],
+                    },
+                )
+            )
+        ],
+        messages=[
+            UserMessage(content="What's the weather like today in Paris?"),
+            ],
+    )
+
+    tokens = tokenizer.encode_chat_completion(completion_request).tokens
+
+    out_tokens, _ = generate([tokens], model, max_tokens=64, temperature=0.0, eos_id=tokenizer.instruct_tokenizer.tokenizer.eos_id)
+    result = tokenizer.instruct_tokenizer.tokenizer.decode(out_tokens[0])
+
+    print(result)
+    ```
+
+    ## Generate with `transformers`
+
+    If you want to use Hugging Face `transformers` to generate text, you can do something like this.
+
+    ```py
+    from transformers import pipeline
+
+    messages = [
+        {"role": "system", "content": "You are a pirate chatbot who always responds in pirate speak!"},
+        {"role": "user", "content": "Who are you?"},
+    ]
+    chatbot = pipeline("text-generation", model="mistralai/Mistral-7B-Instruct-v0.3")
+    chatbot(messages)
+    ```
+
+
+    ## Function calling with `transformers`
+
+    To use this example, you'll need `transformers` version 4.42.0 or higher. Please see the 
+    [function calling guide](https://huggingface.co/docs/transformers/main/chat_templating#advanced-tool-use--function-calling)
+    in the `transformers` docs for more information.
+
+    ```python
+    from transformers import AutoModelForCausalLM, AutoTokenizer
+    import torch
+
+    model_id = "mistralai/Mistral-7B-Instruct-v0.3"
+    tokenizer = AutoTokenizer.from_pretrained(model_id)
+
+    def get_current_weather(location: str, format: str):
+        """
+        Get the current weather
+
+        Args:
+            location: The city and state, e.g. San Francisco, CA
+            format: The temperature unit to use. Infer this from the users location. (choices: ["celsius", "fahrenheit"])
+        """
+        pass
+
+    conversation = [{"role": "user", "content": "What's the weather like in Paris?"}]
+    tools = [get_current_weather]
+
+
+    # format and tokenize the tool use prompt 
+    inputs = tokenizer.apply_chat_template(
+                conversation,
+                tools=tools,
+                add_generation_prompt=True,
+                return_dict=True,
+                return_tensors="pt",
+    )
+
+    model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16, device_map="auto")
+
+    inputs.to(model.device)
+    outputs = model.generate(**inputs, max_new_tokens=1000)
+    print(tokenizer.decode(outputs[0], skip_special_tokens=True))
+    ```
+
+    Note that, for reasons of space, this example does not show a complete cycle of calling a tool and adding the tool call and tool
+    results to the chat history so that the model can use them in its next generation. For a full tool calling example, please
+    see the [function calling guide](https://huggingface.co/docs/transformers/main/chat_templating#advanced-tool-use--function-calling), 
+    and note that Mistral **does** use tool call IDs, so these must be included in your tool calls and tool results. They should be
+    exactly 9 alphanumeric characters.
+
+
+    ## Limitations
+
+    The Mistral 7B Instruct model is a quick demonstration that the base model can be easily fine-tuned to achieve compelling performance. 
+    It does not have any moderation mechanisms. We're looking forward to engaging with the community on ways to
+    make the model finely respect guardrails, allowing for deployment in environments requiring moderated outputs.
+
+    ## The Mistral AI Team
+
+    Albert Jiang, Alexandre Sablayrolles, Alexis Tacnet, Antoine Roux, Arthur Mensch, Audrey Herblin-Stoop, Baptiste Bout, Baudouin de Monicault, Blanche Savary, Bam4d, Caroline Feldman, Devendra Singh Chaplot, Diego de las Casas, Eleonore Arcelin, Emma Bou Hanna, Etienne Metzger, Gianna Lengyel, Guillaume Bour, Guillaume Lample, Harizo Rajaona, Jean-Malo Delignon, Jia Li, Justus Murke, Louis Martin, Louis Ternon, Lucile Saulnier, Lélio Renard Lavaud, Margaret Jennings, Marie Pellat, Marie Torelli, Marie-Anne Lachaux, Nicolas Schuhl, Patrick von Platen, Pierre Stock, Sandeep Subramanian, Sophia Yang, Szymon Antoniak, Teven Le Scao, Thibaut Lavril, Timothée Lacroix, Théophile Gervet, Thomas Wang, Valera Nemychnikova, William El Sayed, William Marshall
+  provider: Mistral AI
+  logo: data:image/png;base64,UklGRlgKAABXRUJQVlA4WAoAAAAQAAAAxwAAxwAAQUxQSAUCAAABkKTtf9pGf8mqc4Fyu2KmFQ1t55kDlJnhAIPnYGbmmbMZJPv/PFn90vw1PKOIcKQ2kiTZEPTVs0/RL6AfhsF/whGoCFZ5EPZzck1NN+/eAe0kJSSs/ebd20jV7bu3OkgJQlM/c8mAJfM4aSFhYw4NK8ZIi6I7cbkFzK0dFcMwGuaSYWH0WHjG5ACH2WAnkxQbTAWjDGZM475TDImZwcwnMK/Mra4gLm/2EXapqHtzeQVxde6i84mT1PExRRCG9tkxrt84i5nyPswWpxa0yvdpDmB28AmmckQNGjGm3e8u+/Bsf3e51tjbhdjbeOk7C3OJy/wdQIy/GhO8A//bXIf/2b8XpmS+0N6DnfM1PWUH4fhVM3Yu2NN+vhrmNeUImBfRdbYQlm9TBIYN5Z7DE2Q0YgPdhLkLnpsbGvX/flt4bsDcgWc4dIkymDH140xSbBArdJ0Ti5hUqYBh4zhjFBtMLWyKIXhunvg5zWCGkg/VZTTNvn/4BPHpw48lo3578PQJ4uMXlxZWVxBX5y+/eAyGfS4YtPj8EAt79PYs6RowtMyWcYXcT7jLTk6YqYklTixoDSEWM+VD8P1XqZJKCdsiU7eRcr+tnNkOLLLEiRVhyvswW5xKCdvyZY5h9uXMQU1omnp+844Ibz09RxoMO/v0lpSw06TpR6iKxKh8CPthGPwntAABAFZQOCAsCAAAUDYAnQEqyADIAD4xEoZDIiELduQQAYJTd8L5hxiAibWW/vX5R+LppXoH49fjd1lO4Xcn91uqdPr0R/Zf6J+0f9h///x3/lf4zfJbzAP7r/If6B+XP877pfmA/nX9U/1X9s92j+y/9L/Se4D0AP1e9J32LP6f/OfYA/gH9h9GD/Hf5z4Pf2b/23+M9jb/h/n/yAH8A7Dv+p+uvnxa0ujsMC7O49H/O+p5pWogPWT6FZfb2jNDLg5E3WJusTdYm5osAQvO6JlhGvYStpFfwEuQmLgy0qvJxwwBo0LlUd0P1HfZl0jZA4UbAmpdgQPmUbH+6uyupsD4tUxH0FJtvPn2lwgY7xD/a2liI/x5VHT15c/Cq5+wicXqpzJPKazD/TvGHZCYEiEZkCDN+D/t1dWdflgxG/w36Ixg0tyEe639CmfL3g/vOLq3qtfM5YNj7rsDKdYbRjgz0vBqxud8op2MW/8boIfH+eZbcUY1Pb+cXMEjFRevA1GnCSUjUFgz6Juk2rK1OpE6Y443HIeUd6IOrFkXRIiUmSQxD2eyA0djLzQUJqm0rDezhsunMgHGaGXByJusTdYm6xN1ZAAA/v7uqgAAAAGh/5bfoptyC8H3ru1DicoB9qjLRWLslhaxj/5OvasYzINz6mljUNF9lU+c2GT1fZ79G2W9ZpNa8L7kNC8rYias0e3Gu3SApBwhRgse77OlwskNHqaEDqKKCwumPlTDE34oNWVPDXb/7z8/jbOS2ddR09kqU6JtKW3w01ohlo//NVGoEXyqbib/YC/+17oJ0u+d5meGOZ3z7F1drP/p6FFWVEACxj30GHepHgeTqBqwkChtLm4DJIa79NKjnH+TESmeSObx3ei8socERZEKYnNvCAqUVHIrgPpgqO7lejQYbRxvubJ1sOHxVJc+9RWYv58pqdiAT5A3EZeXkf+pD2yUGixKvsx1jbo8r61/A2HDHjsCCTd2JcgPWqgtQl36ULq5dnXjEucv43HXaGdymes0GdSZtrcctNY1Ia7//Q68C6cRNajMivWB2UFvuBvPdul/Yn1HiblXXJIvbsd/w7dqlBlv+HbtUIQA4e4Rfra6tCkhChHzVOVon+4U2useIkxSfnDJSYdP6+sHJHpjbJanXnInr1Dp4v9m10ZhB1mQPrZajbpq3CJ67nVr6eY1FeAWTCOW7pWqQbMQV2UHP0eA8yk2OZlGkVcUpaFPHZJEQk0eCmnRFTDqDlOZYUn9G2Q4r0L5Vx0TxO/5z83sIzZhD0cTk/DQLHTmWiA2ylAj4apPlfixAGFpzz1NDAsYa8pb1ZAYXCunM2YiYLRqK7DwWg0RYYbTZWuMd00qwgCR7ctg6syt2V/SyePlW6F/9o7Tsq4CyHaAdK0j5dLUbZeuxqDeJVOIjumstqvEFlUGBt4tu9qctXOMGCSqLXEmiKEHhznsy04297mwhml7UQ83q9At7ISVZ4X/9Gh2CPOg0AJ6wJiS9WE0Ryh+EZrwcdffbWVRoPBrwVF0o4eTxNuwUobv9/R5Nay/TCQwlLjfYnHLvtFIeEdtZfDgh8jbHWs062YWjYpARG1Vn0kXaZudMuxv597gfLFiS337r40EmEBHGRozeOeIweRd6IxtHszUDJhNYVkZnM0b76B9M1si2mtmC/ae9193XZ/3qmrbBNQe1B04T0fzB1WIBUYgLwepaCwE/Ot0hGGlvABrOwYXOEsps+hq//uDuqQ8d2sC4hK07j2W3asboeRE4adlZFXwVGiasPrL4164bGCMTJS+UIrY3FUP28Kyt9GPCLlYf3fY1lxHYJY7AmieyUUMwqjcVzmKCILeR6YNURsa5CkXEXSte7iq9R/c1158SSoeKUa7BsJXYHrq3HlO45NMcluyFLHky5OnpP/YUw643GeHrUvA9a3v3n//xBI1UHCFUqmEX84jY2fmyahBBLALngKHigWZu8ASniwf+AmPO0jO/sjpOqaagDn/KcTKlsTOSZDVtTxAtYVqFZyhhlFvdfQODCmEow3PooqIqJ7asTn+YFVbOnvvzlzBwIMSF8Dr/1ekOfEu0JNcOKx3NDZaxnufrAxRNlSGO9I4347dAKo4ePTgtoEf6Hwd+AR0bhf4/cW2d+lMWMMokU3YO9xC5l6yIHLjwKbsBkFcSCmcRM6MG5jd6TfgVEIU1aqT/R4Lm2foAi68tzsEn0YA1kFHuJkP//woULf03ZBEAf7XcqdihUf/+S+Qtd+35xbGK9DGKurQq6SfIFQah3NxgO0kp/+Mq1RWrSRm9VqcrS8m6dwYGIbLxzCCJA3gYXMdpl/YPq9yYSdL01tvmuq0fKHnsMTHYv18pFhZjdtN2WRvB6ZGSvygUiqav/9kSCaSLoE9FN/2bNH36WN+G7PaDvI08uP2HAA2W3LmCN3iJczffzrBoxuE9vXx8/sRBT8/iMRdNudPIHJRMSgQsyhFoxeNPYFDAdBDWAR9Py0gLxwLYHgan1vHQ9TXY8BoUlP7VyW5zX0g0TiJxqCL2Dwr/I/6XD//faf9x06SwpuSA2DJ0I+wMLI9coGOmenNh2CzxbROOtKI/qHBMTHV2cWP2WGmwhyAsS3ztxJ2kE+Erkcvr6efgM3bc8Zih9DSfRVcKRyz07YUb9aDlPp4rdelKLpohoQwj1RDRilBK6oCLK9WYFKfblH/pip2G+bGuBZmAlFA0PgolD+BF6V9gDUELy+F2doRFG4lbRB7cPIU/CTNhwTffU2CuQOPoRWKslB2M8JJ6vV+I6RbFYxKCWJshz8/4AAAIKAAAAAAAA==
+  license: apache-2.0
+  licenseLink: https://www.apache.org/licenses/LICENSE-2.0.txt
+  libraryName: transformers
+  artifacts:
+    - protocol: http
+      uri: https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.3/resolve/main/consolidated.safetensors
diff --git a/manifests/kustomize/options/catalog/service.yaml b/manifests/kustomize/options/catalog/service.yaml
new file mode 100644
index 00000000..6e87175d
--- /dev/null
+++ b/manifests/kustomize/options/catalog/service.yaml
@@ -0,0 +1,20 @@
+kind: Service
+apiVersion: v1
+metadata:
+  labels:
+    app: model-catalog-service
+    app.kubernetes.io/component: model-catalog
+    app.kubernetes.io/instance: model-catalog-service
+    app.kubernetes.io/name: model-catalog-service
+    app.kubernetes.io/part-of: model-catalog
+    component: model-catalog
+  name: model-catalog-service
+spec:
+  selector:
+    component: model-catalog-server
+  type: ClusterIP
+  ports:
+  - port: 8080
+    protocol: TCP
+    appProtocol: http
+    name: http-api
diff --git a/manifests/kustomize/options/catalog/sources.yaml b/manifests/kustomize/options/catalog/sources.yaml
new file mode 100644
index 00000000..1dcda414
--- /dev/null
+++ b/manifests/kustomize/options/catalog/sources.yaml
@@ -0,0 +1,6 @@
+catalogs:
+- name: Sample Catalog
+  id: sample_catalog
+  type: yaml
+  properties:
+    yamlCatalogPath: sample-catalog.yaml
diff --git a/manifests/kustomize/options/istio/virtual-service.yaml b/manifests/kustomize/options/istio/virtual-service.yaml
index 576c0208..c1d137bc 100644
--- a/manifests/kustomize/options/istio/virtual-service.yaml
+++ b/manifests/kustomize/options/istio/virtual-service.yaml
@@ -16,14 +16,3 @@ spec:
         host: model-registry-service.kubeflow.svc.cluster.local
         port:
           number: 8080
-  - match:
-    - port: 9090
-    - authority:
-        regex: model-registry-service(\..+)?(:9090)?
-    - uri:
-        prefix: /ml_metadata.MetadataStoreService/
-    route:
-    - destination:
-        host: model-registry-service.kubeflow.svc.cluster.local
-        port:
-          number: 9090
diff --git a/manifests/kustomize/overlays/db/kustomization.yaml b/manifests/kustomize/overlays/db/kustomization.yaml
index 13880921..5577470e 100644
--- a/manifests/kustomize/overlays/db/kustomization.yaml
+++ b/manifests/kustomize/overlays/db/kustomization.yaml
@@ -28,38 +28,12 @@ images:
 patches:
 - path: patches/model-registry-deployment.yaml
 replacements:
-- source:
-    fieldPath: metadata.name
-    kind: Service
-    name: model-registry-db
-    version: v1
-  targets:
-  - fieldPaths:
-    - spec.template.spec.containers.1.args.1
-    options:
-      delimiter: =
-      index: 1
-    select:
-      group: apps
-      kind: Deployment
-      name: model-registry-deployment
-      version: v1
 - source:
     fieldPath: data.MYSQL_PORT
     kind: ConfigMap
     name: model-registry-db-parameters
     version: v1
   targets:
-  - fieldPaths:
-    - spec.template.spec.containers.1.args.3
-    options:
-      delimiter: =
-      index: 1
-    select:
-      group: apps
-      kind: Deployment
-      name: model-registry-deployment
-      version: v1
   - fieldPaths:
     - spec.template.metadata.annotations.[traffic.sidecar.istio.io/excludeOutboundPorts]
     select:
diff --git a/manifests/kustomize/overlays/db/model-registry-db-deployment.yaml b/manifests/kustomize/overlays/db/model-registry-db-deployment.yaml
index 2a9e2145..dafe00f7 100644
--- a/manifests/kustomize/overlays/db/model-registry-db-deployment.yaml
+++ b/manifests/kustomize/overlays/db/model-registry-db-deployment.yaml
@@ -60,4 +60,3 @@ spec:
       - name: metadata-mysql
         persistentVolumeClaim:
           claimName: metadata-mysql
-          
\ No newline at end of file
diff --git a/manifests/kustomize/overlays/db/patches/model-registry-deployment.yaml b/manifests/kustomize/overlays/db/patches/model-registry-deployment.yaml
index 0d61bf30..e1d11db8 100644
--- a/manifests/kustomize/overlays/db/patches/model-registry-deployment.yaml
+++ b/manifests/kustomize/overlays/db/patches/model-registry-deployment.yaml
@@ -13,30 +13,16 @@ spec:
         - name: rest-container
           # Remove existing environment variables
           env:
-          - $patch: replace
+            - $patch: replace
           envFrom:
-          - configMapRef:
-              name: model-registry-configmap
+            - configMapRef:
+                name: model-registry-configmap
+            - secretRef:
+                name: model-registry-db-secrets
+            - configMapRef:
+                name: model-registry-db-parameters
           args:
             - --hostname=0.0.0.0
             - --port=$(MODEL_REGISTRY_REST_SERVICE_PORT)
-            - --mlmd-hostname=localhost
-            - --mlmd-port=$(MODEL_REGISTRY_GRPC_SERVICE_PORT)
             - --datastore-type=$(MODEL_REGISTRY_DATA_STORE_TYPE)
-        - name: grpc-container
-          # Remove existing environment variables
-          env:
-          - $patch: replace
-          envFrom:
-          - configMapRef:
-              name: model-registry-db-parameters
-          - secretRef:
-              name: model-registry-db-secrets
-          - configMapRef:
-              name: model-registry-configmap
-          args: ["--grpc_port=$(MODEL_REGISTRY_GRPC_SERVICE_PORT)",
-                 "--mysql_config_host=MLMD_DB_HOST_PLACEHOLDER",
-                 "--mysql_config_database=$(MYSQL_DATABASE)",
-                 "--mysql_config_port=MYSQL_PORT_PLACEHOLDER",
-                 "--mysql_config_user=$(MYSQL_USER_NAME)",
-                 "--mysql_config_password=$(MYSQL_ROOT_PASSWORD)"]
+            - --embedmd-database-dsn=$(MYSQL_USER_NAME):$(MYSQL_ROOT_PASSWORD)@tcp(model-registry-db:$(MYSQL_PORT))/$(MYSQL_DATABASE)?charset=utf8mb4



Test patch:
diff --git a/catalog/internal/catalog/catalog_test.go b/catalog/internal/catalog/catalog_test.go
new file mode 100644
index 00000000..ce374a67
--- /dev/null
+++ b/catalog/internal/catalog/catalog_test.go
@@ -0,0 +1,41 @@
+package catalog
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestLoadCatalogSources(t *testing.T) {
+	type args struct {
+		catalogsPath string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    []string
+		wantErr bool
+	}{
+		{
+			name:    "test-catalog-sources",
+			args:    args{catalogsPath: "testdata/test-catalog-sources.yaml"},
+			want:    []string{"catalog1", "catalog2"},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := LoadCatalogSources(tt.args.catalogsPath)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("LoadCatalogSources() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			gotKeys := make([]string, 0, len(got))
+			for k := range got {
+				gotKeys = append(gotKeys, k)
+			}
+			if !reflect.DeepEqual(gotKeys, tt.want) {
+				t.Errorf("LoadCatalogSources() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/catalog/internal/catalog/testdata/test-catalog-sources.yaml b/catalog/internal/catalog/testdata/test-catalog-sources.yaml
new file mode 100644
index 00000000..fbc21028
--- /dev/null
+++ b/catalog/internal/catalog/testdata/test-catalog-sources.yaml
@@ -0,0 +1,15 @@
+catalogs:
+  - name: "Catalog 1"
+    id: catalog1
+    type: yaml
+    properties:
+      privateProp11: 54321
+      privateProp12: privateStringValue
+      yamlCatalogPath: test-yaml-catalog.yaml
+  - name: "Catalog 2"
+    id: catalog2
+    type: yaml
+    properties:
+      privateProp21: 12345
+      privateProp22: privateStringValue2
+      yamlCatalogPath: test-yaml-catalog.yaml
diff --git a/catalog/internal/catalog/testdata/test-yaml-catalog.yaml b/catalog/internal/catalog/testdata/test-yaml-catalog.yaml
new file mode 100644
index 00000000..5c627c19
--- /dev/null
+++ b/catalog/internal/catalog/testdata/test-yaml-catalog.yaml
@@ -0,0 +1,672 @@
+source: Red Hat
+models:
+  - name: rhelai1/granite-8b-code-base
+    provider: IBM
+    description: |-
+      Granite-8B-Code-Base is a decoder-only code model designed for code
+      generative tasks (e.g., code generation, code explanation, code fixing,
+      etc.). It is trained from scratch with a two-phase training strategy. In
+      phase 1, our model is trained on 4 trillion tokens sourced from 116
+      programming languages, ensuring a comprehensive understanding of
+      programming languages and syntax. In phase 2, our model is trained on 500
+      billion tokens with a carefully designed mixture of high-quality data from
+      code and natural language domains to improve the models’ ability to reason
+      and follow instructions.
+    readme: |-
+      # Granite-3.1-8B-Base
+
+      **Model Summary:**
+      Granite-3.1-8B-Base extends the context length of Granite-3.0-8B-Base from 4K to 128K using a progressive training strategy by increasing the supported context length in increments while adjusting RoPE theta until the model has successfully adapted to desired length of 128K. This long-context pre-training stage was performed using approximately 500B tokens.
+
+      - **Developers:** Granite Team, IBM
+      - **GitHub Repository:** [ibm-granite/granite-3.1-language-models](https://github.com/ibm-granite/granite-3.1-language-models)
+      - **Website**: [Granite Docs](https://www.ibm.com/granite/docs/)
+      - **Paper:** [Granite 3.1 Language Models (coming soon)](https://huggingface.co/collections/ibm-granite/granite-31-language-models-6751dbbf2f3389bec5c6f02d)
+      - **Release Date**: December 18th, 2024
+      - **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
+
+      **Supported Languages:**
+      English, German, Spanish, French, Japanese, Portuguese, Arabic, Czech, Italian, Korean, Dutch, and Chinese. Users may finetune Granite 3.1 models for languages beyond these 12 languages.
+
+      **Intended Use:**
+      Prominent use cases of LLMs in text-to-text generation include summarization, text classification, extraction, question-answering, and other long-context tasks. All Granite Base models are able to handle these tasks as they were trained on a large amount of data from various domains. Moreover, they can serve as baseline to create specialized models for specific application scenarios.
+
+      **Generation:**
+      This is a simple example of how to use Granite-3.1-8B-Base model.
+
+      Install the following libraries:
+
+      ```shell
+      pip install torch torchvision torchaudio
+      pip install accelerate
+      pip install transformers
+      ```
+      Then, copy the code snippet below to run the example.
+
+      ```python
+      from transformers import AutoModelForCausalLM, AutoTokenizer
+      device = "auto"
+      model_path = "ibm-granite/granite-3.1-8B-base"
+      tokenizer = AutoTokenizer.from_pretrained(model_path)
+      # drop device_map if running on CPU
+      model = AutoModelForCausalLM.from_pretrained(model_path, device_map=device)
+      model.eval()
+      # change input text as desired
+      input_text = "Where is the Thomas J. Watson Research Center located?"
+      # tokenize the text
+      input_tokens = tokenizer(input_text, return_tensors="pt").to(device)
+      # generate output tokens
+      output = model.generate(**input_tokens,
+                              max_length=4000)
+      # decode output tokens into text
+      output = tokenizer.batch_decode(output)
+      # print output
+      print(output)
+      ```
+
+      **Evaluation Results:**
+      <table>
+        <caption><b>HuggingFace Open LLM Leaderboard V1</b></caption>
+      <thead>
+        <tr>
+          <th style="text-align:left; background-color: #001d6c; color: white;">Models</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">ARC-Challenge</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Hellaswag</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MMLU</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">TruthfulQA</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Winogrande</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">GSM8K</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Avg</th>
+        </tr></thead>
+        <tbody>
+        <tr>
+          <td style="text-align:left; background-color: #DAE8FF; color: black;">Granite-3.1-8B-Base</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">63.99</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">83.27</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">63.45</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">51.29</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">78.92</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">60.19</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">66.85</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-2B-Base</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">53.58</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">77.67</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">52.86</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">39.02</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">72.84</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">47.99</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">57.32</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-3B-A800M-Base</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">50.76</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">74.45</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">48.31</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">39.91</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">69.29</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">40.56</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">53.88</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-1B-A400M-Base</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">39.42</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">66.13</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">26.53</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">37.67</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">2.03</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">18.87</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">31.78</td>
+        </tr>
+      </tbody></table>
+
+      <table>
+        <caption><b>HuggingFace Open LLM Leaderboard V2</b></caption>
+        <thead>
+        <tr>
+          <th style="text-align:left; background-color: #001d6c; color: white;">Models</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">IFEval</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">BBH</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MATH Lvl 5</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">GPQA</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MUSR</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MMLU-Pro</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Avg</th>
+        </tr></thead>
+        <tbody>
+        <tr>
+          <td style="text-align:left; background-color: #DAE8FF; color: black;">Granite-3.1-8B-Base</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">42.21</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">26.02</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">9.52</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">9.51</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8.36</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">24.8</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">20.07</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-2B-Base</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">35.22</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">16.84</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">5.59</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">3.69</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">3.9</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">13.9</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">13.19</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-3B-A800M-Base</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">29.96</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">11.91</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">4</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">3.69</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">1.11</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">8.81</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">9.91</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-1B-A400M-Base</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">25.19</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">6.43</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">2.19</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">0.22</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">1.76</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">1.55</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">6.22</td>
+        </tr>
+      </tbody></table>
+
+      **Model Architecture:**
+      Granite-3.1-8B-Base is based on a decoder-only dense transformer architecture. Core components of this architecture are: GQA and RoPE, MLP with SwiGLU, RMSNorm, and shared input/output embeddings.
+      <table>
+      <thead>
+        <tr>
+          <th style="text-align:left; background-color: #001d6c; color: white;">Model</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">2B Dense</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">8B Dense</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">1B MoE</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">3B MoE</th>
+        </tr></thead>
+      <tbody>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Embedding size</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">2048</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">4096</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">1024</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">1536</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of layers</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">40</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">40</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">24</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">32</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Attention head size</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">64</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">128</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">64</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">64</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of attention heads</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">32</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">32</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">16</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">24</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of KV heads</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">MLP hidden size</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8192</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">12800</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">512</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">512</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">MLP activation</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">SwiGLU</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">SwiGLU</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">SwiGLU</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">SwiGLU</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of experts</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">—</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">—</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">32</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">40</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">MoE TopK</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">—</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">—</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Initialization std</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">0.1</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">0.1</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">0.1</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">0.1</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Sequence length</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">128K</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">128K</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">128K</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">128K</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Position embedding</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">RoPE</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">RoPE</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">RoPE</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">RoPE</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;"># Parameters</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">2.5B</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8.1B</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">1.3B</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">3.3B</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;"># Active parameters</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">2.5B</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8.1B</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">400M</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">800M</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;"># Training tokens</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">12T</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">12T</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">10T</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">10T</td>
+        </tr>
+      </tbody></table>
+      **Training Data:**
+      This model is trained on a mix of open source and proprietary data following a three-stage training strategy.
+      * Stage 1 data: The data for stage 1 is sourced from diverse domains, such as: web, code, academic sources, books, and math data.
+      * Stage 2 data: The data for stage 2 comprises a curated mix of high-quality data from the same domains, plus multilingual and instruction data. The goal of this second training phase is to enhance the model’s performance on specific tasks.
+      * Stage 3 data: The data for stage 3 consists of original stage-2 pretraining data with additional synthetic long-context data in form of QA/summary pairs where the answer
+      contains a recitation of the related paragraph before the answer.
+
+      A detailed attribution of datasets can be found in the [Granite 3.0 Technical Report](https://github.com/ibm-granite/granite-3.0-language-models/blob/main/paper.pdf), [Granite 3.1 Technical Report (coming soon)](https://huggingface.co/collections/ibm-granite/granite-31-language-models-6751dbbf2f3389bec5c6f02d), and [Accompanying Author List](https://github.com/ibm-granite/granite-3.0-language-models/blob/main/author-ack.pdf).
+
+      **Infrastructure:**
+      We train Granite 3.1 Language Models using IBM's super computing cluster, Blue Vela, which is outfitted with NVIDIA H100 GPUs. This cluster provides a scalable and efficient infrastructure for training our models over thousands of GPUs.
+
+      **Ethical Considerations and Limitations:**
+      The use of Large Language Models involves risks and ethical considerations people must be aware of, including but not limited to: bias and fairness, misinformation, and autonomous decision-making. Granite-3.1-8B-Base model is not the exception in this regard. Even though this model is suited for multiple generative AI tasks, it has not undergone any safety alignment, there it may produce problematic outputs. Additionally, it remains uncertain whether smaller models might exhibit increased susceptibility to hallucination in generation scenarios by copying text verbatim from the training dataset due to their reduced sizes and memorization capacities. This aspect is currently an active area of research, and we anticipate more rigorous exploration, comprehension, and mitigations in this domain. Regarding ethics, a latent risk associated with all Large Language Models is their malicious utilization. We urge the community to use Granite-3.1-8B-Base model with ethical intentions and in a responsible way.
+
+      **Resources**
+      - ⭐️ Learn about the latest updates with Granite: https://www.ibm.com/granite
+      - 📄 Get started with tutorials, best practices, and prompt engineering advice: https://www.ibm.com/granite/docs/
+      - 💡 Learn about the latest Granite learning resources: https://ibm.biz/granite-learning-resources
+    language: ["ar", "cs", "de", "en", "es", "fr", "it", "ja", "ko", "nl", "pt", "zh"]
+    license: apache-2.0
+    licenseLink: https://www.apache.org/licenses/LICENSE-2.0.txt
+    maturity: Generally Available
+    libraryName: transformers
+    tasks:
+      - text-generation
+    createTimeSinceEpoch: "1733514949000"
+    lastUpdateTimeSinceEpoch: "1734637721000"
+    artifacts:
+      - protocol: oci
+        uri: oci://registry.redhat.io/rhelai1/granite-8b-code-base:1.3-1732870892
+  - name: rhelai1/granite-8b-code-instruct
+    provider: IBM
+    description: |-
+      Granite-8B-Code-Instruct is a 8B parameter model fine tuned from
+      Granite-8B-Code-Base on a combination of permissively licensed instruction
+      data to enhance instruction following capabilities including logical
+      reasoning and problem-solving skills.
+    readme: |-
+      # Granite-3.1-8B-Instruct
+
+      **Model Summary:**
+      Granite-3.1-8B-Instruct is a 8B parameter long-context instruct model finetuned from Granite-3.1-8B-Base using a combination of open source instruction datasets with permissive license and internally collected synthetic datasets tailored for solving long context problems. This model is developed using a diverse set of techniques with a structured chat format, including supervised finetuning, model alignment using reinforcement learning, and model merging.
+
+      - **Developers:** Granite Team, IBM
+      - **GitHub Repository:** [ibm-granite/granite-3.1-language-models](https://github.com/ibm-granite/granite-3.1-language-models)
+      - **Website**: [Granite Docs](https://www.ibm.com/granite/docs/)
+      - **Paper:** [Granite 3.1 Language Models (coming soon)](https://huggingface.co/collections/ibm-granite/granite-31-language-models-6751dbbf2f3389bec5c6f02d)
+      - **Release Date**: December 18th, 2024
+      - **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
+
+      **Supported Languages:**
+      English, German, Spanish, French, Japanese, Portuguese, Arabic, Czech, Italian, Korean, Dutch, and Chinese. Users may finetune Granite 3.1 models for languages beyond these 12 languages.
+
+      **Intended Use:**
+      The model is designed to respond to general instructions and can be used to build AI assistants for multiple domains, including business applications.
+
+      *Capabilities*
+      * Summarization
+      * Text classification
+      * Text extraction
+      * Question-answering
+      * Retrieval Augmented Generation (RAG)
+      * Code related tasks
+      * Function-calling tasks
+      * Multilingual dialog use cases
+      * Long-context tasks including long document/meeting summarization, long document QA, etc.
+
+      **Generation:**
+      This is a simple example of how to use Granite-3.1-8B-Instruct model.
+
+      Install the following libraries:
+
+      ```shell
+      pip install torch torchvision torchaudio
+      pip install accelerate
+      pip install transformers
+      ```
+      Then, copy the snippet from the section that is relevant for your use case.
+
+      ```python
+      import torch
+      from transformers import AutoModelForCausalLM, AutoTokenizer
+
+      device = "auto"
+      model_path = "ibm-granite/granite-3.1-8b-instruct"
+      tokenizer = AutoTokenizer.from_pretrained(model_path)
+      # drop device_map if running on CPU
+      model = AutoModelForCausalLM.from_pretrained(model_path, device_map=device)
+      model.eval()
+      # change input text as desired
+      chat = [
+          { "role": "user", "content": "Please list one IBM Research laboratory located in the United States. You should only output its name and location." },
+      ]
+      chat = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
+      # tokenize the text
+      input_tokens = tokenizer(chat, return_tensors="pt").to(device)
+      # generate output tokens
+      output = model.generate(**input_tokens,
+                              max_new_tokens=100)
+      # decode output tokens into text
+      output = tokenizer.batch_decode(output)
+      # print output
+      print(output)
+      ```
+      **Evaluation Results:**
+      <table>
+        <caption><b>HuggingFace Open LLM Leaderboard V1</b></caption>
+      <thead>
+        <tr>
+          <th style="text-align:left; background-color: #001d6c; color: white;">Models</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">ARC-Challenge</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Hellaswag</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MMLU</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">TruthfulQA</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Winogrande</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">GSM8K</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Avg</th>
+        </tr></thead>
+        <tbody>
+        <tr>
+          <td style="text-align:left; background-color: #DAE8FF; color: black;">Granite-3.1-8B-Instruct</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">62.62</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">84.48</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">65.34</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">66.23</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">75.37</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">73.84</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">71.31</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-2B-Instruct</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">54.61</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">75.14</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">55.31</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">59.42</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">67.48</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">52.76</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">60.79</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-3B-A800M-Instruct</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">50.42</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">73.01</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">52.19</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">49.71</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">64.87</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">48.97</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">56.53</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-1B-A400M-Instruct</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">42.66</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">65.97</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">26.13</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">46.77</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">62.35</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">33.88</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">46.29</td>
+        </tr>
+      </tbody></table>
+
+      <table>
+        <caption><b>HuggingFace Open LLM Leaderboard V2</b></caption>
+      <thead>
+        <tr>
+          <th style="text-align:left; background-color: #001d6c; color: white;">Models</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">IFEval</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">BBH</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MATH Lvl 5</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">GPQA</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MUSR</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">MMLU-Pro</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">Avg</th>
+        </tr></thead>
+        <tbody>
+        <tr>
+          <td style="text-align:left; background-color: #DAE8FF; color: black;">Granite-3.1-8B-Instruct</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">72.08</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">34.09</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">21.68</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8.28</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">19.01</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">28.19</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">30.55</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-2B-Instruct</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">62.86</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">21.82</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">11.33</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">5.26</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">4.87</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">20.21</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">21.06</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-3B-A800M-Instruct</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">55.16</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">16.69</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">10.35</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">5.15</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">2.51</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">12.75</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">17.1</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: #2D2D2D;">Granite-3.1-1B-A400M-Instruct</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">46.86</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">6.18</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">4.08</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">0</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">0.78</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">2.41</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: #2D2D2D;">10.05</td>
+        </tr>
+      </tbody></table>
+
+      **Model Architecture:**
+      Granite-3.1-8B-Instruct is based on a decoder-only dense transformer architecture. Core components of this architecture are: GQA and RoPE, MLP with SwiGLU, RMSNorm, and shared input/output embeddings.
+
+      <table>
+      <thead>
+        <tr>
+          <th style="text-align:left; background-color: #001d6c; color: white;">Model</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">2B Dense</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">8B Dense</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">1B MoE</th>
+          <th style="text-align:center; background-color: #001d6c; color: white;">3B MoE</th>
+        </tr></thead>
+      <tbody>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Embedding size</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">2048</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">4096</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">1024</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">1536</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of layers</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">40</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">40</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">24</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">32</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Attention head size</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">64</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">128</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">64</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">64</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of attention heads</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">32</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">32</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">16</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">24</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of KV heads</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">MLP hidden size</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8192</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">12800</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">512</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">512</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">MLP activation</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">SwiGLU</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">SwiGLU</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">SwiGLU</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">SwiGLU</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Number of experts</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">—</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">—</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">32</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">40</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">MoE TopK</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">—</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">—</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">8</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Initialization std</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">0.1</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">0.1</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">0.1</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">0.1</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Sequence length</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">128K</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">128K</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">128K</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">128K</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;">Position embedding</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">RoPE</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">RoPE</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">RoPE</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">RoPE</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;"># Parameters</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">2.5B</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8.1B</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">1.3B</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">3.3B</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;"># Active parameters</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">2.5B</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">8.1B</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">400M</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">800M</td>
+        </tr>
+        <tr>
+          <td style="text-align:left; background-color: #FFFFFF; color: black;"># Training tokens</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">12T</td>
+          <td style="text-align:center; background-color: #DAE8FF; color: black;">12T</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">10T</td>
+          <td style="text-align:center; background-color: #FFFFFF; color: black;">10T</td>
+        </tr>
+      </tbody></table>
+
+      **Training Data:**
+      Overall, our SFT data is largely comprised of three key sources: (1) publicly available datasets with permissive license, (2) internal synthetic data targeting specific capabilities including long-context tasks, and (3) very small amounts of human-curated data. A detailed attribution of datasets can be found in the [Granite 3.0 Technical Report](https://github.com/ibm-granite/granite-3.0-language-models/blob/main/paper.pdf), [Granite 3.1 Technical Report (coming soon)](https://huggingface.co/collections/ibm-granite/granite-31-language-models-6751dbbf2f3389bec5c6f02d), and [Accompanying Author List](https://github.com/ibm-granite/granite-3.0-language-models/blob/main/author-ack.pdf).
+
+      **Infrastructure:**
+      We train Granite 3.1 Language Models using IBM's super computing cluster, Blue Vela, which is outfitted with NVIDIA H100 GPUs. This cluster provides a scalable and efficient infrastructure for training our models over thousands of GPUs.
+
+      **Ethical Considerations and Limitations:**
+      Granite 3.1 Instruct Models are primarily finetuned using instruction-response pairs mostly in English, but also multilingual data covering eleven languages. Although this model can handle multilingual dialog use cases, its performance might not be similar to English tasks. In such case, introducing a small number of examples (few-shot) can help the model in generating more accurate outputs. While this model has been aligned by keeping safety in consideration, the model may in some cases produce inaccurate, biased, or unsafe responses to user prompts. So we urge the community to use this model with proper safety testing and tuning tailored for their specific tasks.
+
+      **Resources**
+      - ⭐️ Learn about the latest updates with Granite: https://www.ibm.com/granite
+      - 📄 Get started with tutorials, best practices, and prompt engineering advice: https://www.ibm.com/granite/docs/
+      - 💡 Learn about the latest Granite learning resources: https://ibm.biz/granite-learning-resources
+
+      <!-- ## Citation
+      ```
+      @misc{granite-models,
+        author = {author 1, author2, ...},
+        title = {},
+        journal = {},
+        volume = {},
+        year = {2024},
+        url = {https://arxiv.org/abs/0000.00000},
+      }
+      ``` -->
+    language: ["ar", "cs", "de", "en", "es", "fr", "it", "ja", "ko", "nl", "pt", "zh"]
+    license: apache-2.0
+    licenseLink: https://www.apache.org/licenses/LICENSE-2.0.txt
+    maturity: Generally Available
+    libraryName: transformers
+    tasks:
+      - text-generation
+    createTimeSinceEpoch: "1733514949000"
+    lastUpdateTimeSinceEpoch: "1734637721000"
+    artifacts:
+      - protocol: oci
+        uri: oci://registry.redhat.io/rhelai1/granite-8b-code-instruct:1.3-1732870892
diff --git a/catalog/internal/server/openapi/api_model_catalog_service_service_test.go b/catalog/internal/server/openapi/api_model_catalog_service_service_test.go
new file mode 100644
index 00000000..43c0f13c
--- /dev/null
+++ b/catalog/internal/server/openapi/api_model_catalog_service_service_test.go
@@ -0,0 +1,392 @@
+package openapi
+
+import (
+	"context"
+	"net/http"
+	"strconv"
+	"testing"
+
+	"github.com/kubeflow/model-registry/catalog/internal/catalog"
+	model "github.com/kubeflow/model-registry/catalog/pkg/openapi"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestListSources(t *testing.T) {
+	// Setup test cases
+	testCases := []struct {
+		name           string
+		catalogs       map[string]catalog.CatalogSource
+		nameFilter     string
+		pageSize       string
+		orderBy        model.OrderByField
+		sortOrder      model.SortOrder
+		nextPageToken  string
+		expectedStatus int
+		expectedSize   int32
+		expectedItems  int
+		checkSorting   bool
+	}{
+		{
+			name:           "Empty catalog list",
+			catalogs:       map[string]catalog.CatalogSource{},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   0,
+			expectedItems:  0,
+		},
+		{
+			name: "Single catalog",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   1,
+			expectedItems:  1,
+		},
+		{
+			name: "Multiple catalogs with no filter",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "Test Catalog 2"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "Another Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   3,
+			expectedItems:  3,
+		},
+		{
+			name: "Filter by name",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "Test Catalog 2"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "Another Catalog"},
+				},
+			},
+			nameFilter:     "Test",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   2,
+			expectedItems:  2,
+		},
+		{
+			name: "Filter by name case insensitive",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "Test Catalog 2"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "Another Catalog"},
+				},
+			},
+			nameFilter:     "test",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   2,
+			expectedItems:  2,
+		},
+		{
+			name: "Pagination - limit results",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "Test Catalog 2"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "Another Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "2",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   3, // Total size should be 3
+			expectedItems:  2, // But only 2 items returned due to page size
+		},
+		{
+			name: "Default page size",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "Test Catalog 2"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "", // Empty to test default
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   2,
+			expectedItems:  2,
+		},
+		{
+			name: "Invalid page size",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "invalid",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusBadRequest,
+		},
+		{
+			name: "Sort by ID ascending",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "B Catalog"},
+				},
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "A Catalog"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "C Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   3,
+			expectedItems:  3,
+			checkSorting:   true,
+		},
+		{
+			name: "Sort by ID descending",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "B Catalog"},
+				},
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "A Catalog"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "C Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      model.SORTORDER_DESC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   3,
+			expectedItems:  3,
+			checkSorting:   true,
+		},
+		{
+			name: "Sort by name ascending",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "B Catalog"},
+				},
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "A Catalog"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "C Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_NAME,
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   3,
+			expectedItems:  3,
+			checkSorting:   true,
+		},
+		{
+			name: "Sort by name descending",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "B Catalog"},
+				},
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "A Catalog"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "C Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_NAME,
+			sortOrder:      model.SORTORDER_DESC,
+			expectedStatus: http.StatusOK,
+			expectedSize:   3,
+			expectedItems:  3,
+			checkSorting:   true,
+		},
+		{
+			name: "Invalid sort order",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        model.ORDERBYFIELD_ID,
+			sortOrder:      "INVALID",
+			expectedStatus: http.StatusBadRequest,
+		},
+		{
+			name: "Invalid order by field",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "Test Catalog 1"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        "INVALID",
+			sortOrder:      model.SORTORDER_ASC,
+			expectedStatus: http.StatusBadRequest,
+		},
+		{
+			name: "Default sort order (ID ascending)",
+			catalogs: map[string]catalog.CatalogSource{
+				"catalog2": {
+					Metadata: model.CatalogSource{Id: "catalog2", Name: "B Catalog"},
+				},
+				"catalog1": {
+					Metadata: model.CatalogSource{Id: "catalog1", Name: "A Catalog"},
+				},
+				"catalog3": {
+					Metadata: model.CatalogSource{Id: "catalog3", Name: "C Catalog"},
+				},
+			},
+			nameFilter:     "",
+			pageSize:       "10",
+			orderBy:        "", // Empty to test default
+			sortOrder:      "", // Empty to test default
+			expectedStatus: http.StatusOK,
+			expectedSize:   3,
+			expectedItems:  3,
+			checkSorting:   true,
+		},
+	}
+
+	// Run test cases
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			// Create service with test catalogs
+			service := NewModelCatalogServiceAPIService(tc.catalogs)
+
+			// Call FindSources
+			resp, err := service.FindSources(
+				context.Background(),
+				tc.nameFilter,
+				tc.pageSize,
+				tc.orderBy,
+				tc.sortOrder,
+				tc.nextPageToken,
+			)
+
+			// Check response status
+			assert.Equal(t, tc.expectedStatus, resp.Code)
+
+			// If we expect an error, we don't need to check the response body
+			if tc.expectedStatus != http.StatusOK {
+				assert.NotNil(t, err)
+				return
+			}
+
+			// For successful responses, check the response body
+			require.NotNil(t, resp.Body)
+
+			// Type assertion to access the CatalogSourceList
+			sourceList, ok := resp.Body.(model.CatalogSourceList)
+			require.True(t, ok, "Response body should be a CatalogSourceList")
+
+			// Check the size matches expected
+			assert.Equal(t, tc.expectedSize, sourceList.Size)
+
+			// Check the number of items matches expected
+			assert.Equal(t, tc.expectedItems, len(sourceList.Items))
+
+			// Check that page size is set correctly
+			if tc.pageSize == "" {
+				// Default page size should be 10
+				assert.Equal(t, int32(10), sourceList.PageSize)
+			} else if pageSizeInt, err := strconv.ParseInt(tc.pageSize, 10, 32); err == nil {
+				assert.Equal(t, int32(pageSizeInt), sourceList.PageSize)
+			}
+
+			// Check sorting if required
+			if tc.checkSorting && len(sourceList.Items) > 1 {
+				switch tc.orderBy {
+				case model.ORDERBYFIELD_ID, "":
+					// Check ID sorting
+					for i := 0; i < len(sourceList.Items)-1; i++ {
+						if tc.sortOrder == "" || tc.sortOrder == model.SORTORDER_ASC {
+							assert.LessOrEqual(t,
+								sourceList.Items[i].Id,
+								sourceList.Items[i+1].Id,
+								"Items should be sorted by ID in ascending order")
+						} else {
+							assert.GreaterOrEqual(t,
+								sourceList.Items[i].Id,
+								sourceList.Items[i+1].Id,
+								"Items should be sorted by ID in descending order")
+						}
+					}
+				case model.ORDERBYFIELD_NAME:
+					// Check name sorting
+					for i := 0; i < len(sourceList.Items)-1; i++ {
+						if tc.sortOrder == "" || tc.sortOrder == model.SORTORDER_ASC {
+							assert.LessOrEqual(t,
+								sourceList.Items[i].Name,
+								sourceList.Items[i+1].Name,
+								"Items should be sorted by name in ascending order")
+						} else {
+							assert.GreaterOrEqual(t,
+								sourceList.Items[i].Name,
+								sourceList.Items[i+1].Name,
+								"Items should be sorted by name in descending order")
+						}
+					}
+				}
+			}
+		})
+	}
+}