diff --git a/api/specs/webserver/openapi-projects-ports.yaml b/api/specs/webserver/openapi-projects-ports.yaml
index 06267415555..af24df97b13 100644
--- a/api/specs/webserver/openapi-projects-ports.yaml
+++ b/api/specs/webserver/openapi-projects-ports.yaml
@@ -20,7 +20,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectPortGet__'
+                $ref: '#/components/schemas/Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectInputGet__'
     patch:
       tags:
       - project
@@ -42,7 +42,7 @@ paths:
               title: Updates
               type: array
               items:
-                $ref: '#/components/schemas/ProjectPort'
+                $ref: '#/components/schemas/ProjectInputUpdate'
         required: true
       responses:
         '200':
@@ -50,7 +50,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectPortGet__'
+                $ref: '#/components/schemas/Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectInputGet__'
   /projects/{project_id}/outputs:
     get:
       tags:
@@ -72,18 +72,62 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectPortGet__'
+                $ref: '#/components/schemas/Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectOutputGet__'
+  /projects/{project_id}/metadata/ports:
+    get:
+      tags:
+      - project
+      summary: List Project Metadata Ports
+      description: New in version *0.12*
+      operationId: list_project_metadata_ports
+      parameters:
+      - required: true
+        schema:
+          title: Project Id
+          type: string
+          format: uuid
+        name: project_id
+        in: path
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Envelope_list_simcore_service_webserver.projects.projects_ports_handlers.ProjectMetadataPortGet__'
 components:
   schemas:
-    Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectPortGet__:
-      title: Envelope[dict[uuid.UUID, simcore_service_webserver.projects.projects_ports_handlers.ProjectPortGet]]
+    Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectInputGet__:
+      title: Envelope[dict[uuid.UUID, simcore_service_webserver.projects.projects_ports_handlers.ProjectInputGet]]
+      type: object
+      properties:
+        data:
+          title: Data
+          type: object
+          additionalProperties:
+            $ref: '#/components/schemas/ProjectInputGet'
+        error:
+          title: Error
+    Envelope_dict_uuid.UUID__simcore_service_webserver.projects.projects_ports_handlers.ProjectOutputGet__:
+      title: Envelope[dict[uuid.UUID, simcore_service_webserver.projects.projects_ports_handlers.ProjectOutputGet]]
       type: object
       properties:
         data:
           title: Data
           type: object
           additionalProperties:
-            $ref: '#/components/schemas/ProjectPortGet'
+            $ref: '#/components/schemas/ProjectOutputGet'
+        error:
+          title: Error
+    Envelope_list_simcore_service_webserver.projects.projects_ports_handlers.ProjectMetadataPortGet__:
+      title: Envelope[list[simcore_service_webserver.projects.projects_ports_handlers.ProjectMetadataPortGet]]
+      type: object
+      properties:
+        data:
+          title: Data
+          type: array
+          items:
+            $ref: '#/components/schemas/ProjectMetadataPortGet'
         error:
           title: Error
     HTTPValidationError:
@@ -95,11 +139,12 @@ components:
           type: array
           items:
             $ref: '#/components/schemas/ValidationError'
-    ProjectPort:
-      title: ProjectPort
+    ProjectInputGet:
+      title: ProjectInputGet
       required:
       - key
       - value
+      - label
       type: object
       properties:
         key:
@@ -111,8 +156,50 @@ components:
         value:
           title: Value
           description: Value assigned to this i/o port
-    ProjectPortGet:
-      title: ProjectPortGet
+        label:
+          title: Label
+          type: string
+    ProjectInputUpdate:
+      title: ProjectInputUpdate
+      required:
+      - key
+      - value
+      type: object
+      properties:
+        key:
+          title: Key
+          type: string
+          description: Project port's unique identifer. Same as the UUID of the associated
+            port node
+          format: uuid
+        value:
+          title: Value
+          description: Value assigned to this i/o port
+    ProjectMetadataPortGet:
+      title: ProjectMetadataPortGet
+      required:
+      - key
+      - kind
+      type: object
+      properties:
+        key:
+          title: Key
+          type: string
+          description: Project port's unique identifer. Same as the UUID of the associated
+            port node
+          format: uuid
+        kind:
+          title: Kind
+          enum:
+          - input
+          - output
+          type: string
+        content_schema:
+          title: Content Schema
+          type: object
+          description: jsonschema for the port's value. SEE https://json-schema.org/understanding-json-schema/
+    ProjectOutputGet:
+      title: ProjectOutputGet
       required:
       - key
       - value
diff --git a/api/specs/webserver/openapi.yaml b/api/specs/webserver/openapi.yaml
index bbc7ac4902e..1f672e860ed 100644
--- a/api/specs/webserver/openapi.yaml
+++ b/api/specs/webserver/openapi.yaml
@@ -1,7 +1,7 @@
 openapi: 3.0.0
 info:
   title: "osparc-simcore web API"
-  version: 0.11.0
+  version: 0.12.0
   description: "API designed for the front-end app"
   contact:
     name: IT'IS Foundation
@@ -240,6 +240,9 @@ paths:
   /projects/{project_id}/outputs:
     $ref: "./openapi-projects-ports.yaml#/paths/~1projects~1{project_id}~1outputs"
 
+  /projects/{project_id}/metadata/ports:
+    $ref: "./openapi-projects-ports.yaml#/paths/~1projects~1{project_id}~1metadata~1ports"
+
   /nodes/{nodeInstanceUUID}/outputUi/{outputKey}:
     $ref: "./openapi-node-v0.0.1.yaml#/paths/~1nodes~1{nodeInstanceUUID}~1outputUi~1{outputKey}"
 
diff --git a/api/specs/webserver/scripts/openapi_projects_ports.py b/api/specs/webserver/scripts/openapi_projects_ports.py
index b084cc47a1c..67389d0432f 100644
--- a/api/specs/webserver/scripts/openapi_projects_ports.py
+++ b/api/specs/webserver/scripts/openapi_projects_ports.py
@@ -1,4 +1,6 @@
-""" Helper script to generate OAS automatically
+""" Helper script to automatically generate OAS
+
+This OAS are the source of truth
 """
 
 # pylint: disable=redefined-outer-name
@@ -15,13 +17,12 @@
 from models_library.projects import ProjectID
 from models_library.projects_nodes import NodeID
 from simcore_service_webserver.projects.projects_ports_handlers import (
-    ProjectPort,
-    ProjectPortGet,
+    ProjectInputGet,
+    ProjectInputUpdate,
+    ProjectMetadataPortGet,
+    ProjectOutputGet,
 )
 
-# TODO: how to ensure this is in sync with projects_ports_handlers.routes ??
-# this is the source of truth.
-
 app = FastAPI(redoc_url=None)
 
 TAGS: list[Union[str, Enum]] = [
@@ -31,7 +32,7 @@
 
 @app.get(
     "/projects/{project_id}/inputs",
-    response_model=Envelope[dict[NodeID, ProjectPortGet]],
+    response_model=Envelope[dict[NodeID, ProjectInputGet]],
     tags=TAGS,
     operation_id="get_project_inputs",
 )
@@ -41,17 +42,19 @@ async def get_project_inputs(project_id: ProjectID):
 
 @app.patch(
     "/projects/{project_id}/inputs",
-    response_model=Envelope[dict[NodeID, ProjectPortGet]],
+    response_model=Envelope[dict[NodeID, ProjectInputGet]],
     tags=TAGS,
     operation_id="update_project_inputs",
 )
-async def update_project_inputs(project_id: ProjectID, updates: list[ProjectPort]):
+async def update_project_inputs(
+    project_id: ProjectID, updates: list[ProjectInputUpdate]
+):
     """New in version *0.10*"""
 
 
 @app.get(
     "/projects/{project_id}/outputs",
-    response_model=Envelope[dict[NodeID, ProjectPortGet]],
+    response_model=Envelope[dict[NodeID, ProjectOutputGet]],
     tags=TAGS,
     operation_id="get_project_outputs",
 )
@@ -59,6 +62,16 @@ async def get_project_outputs(project_id: ProjectID):
     """New in version *0.10*"""
 
 
+@app.get(
+    "/projects/{project_id}/metadata/ports",
+    response_model=Envelope[list[ProjectMetadataPortGet]],
+    tags=TAGS,
+    operation_id="list_project_metadata_ports",
+)
+async def list_project_metadata_ports(project_id: ProjectID):
+    """New in version *0.12*"""
+
+
 if __name__ == "__main__":
     from _common import CURRENT_DIR, create_openapi_specs
 
diff --git a/packages/models-library/src/models_library/utils/services_io.py b/packages/models-library/src/models_library/utils/services_io.py
index f75d5d73732..67225495e49 100644
--- a/packages/models-library/src/models_library/utils/services_io.py
+++ b/packages/models-library/src/models_library/utils/services_io.py
@@ -8,6 +8,7 @@
 from ..services_constants import PROPERTY_TYPE_TO_PYTHON_TYPE_MAP
 
 PortKindStr = Literal["input", "output"]
+JsonSchemaDict = dict[str, Any]
 
 _PROPERTY_TYPE_TO_SCHEMAS = {
     property_type: schema_of(python_type, title=property_type.capitalize())
@@ -36,7 +37,7 @@ def update_schema_doc(schema: dict[str, Any], port: Union[ServiceInput, ServiceO
 
 def get_service_io_json_schema(
     port: Union[ServiceInput, ServiceOutput]
-) -> Optional[dict[str, Any]]:
+) -> Optional[JsonSchemaDict]:
     """Get json-schema for a i/o service
 
     For legacy metadata with property_type = integer, etc ... , it applies a conversion
diff --git a/services/web/server/VERSION b/services/web/server/VERSION
index d9df1bbc0c7..ac454c6a1fc 100644
--- a/services/web/server/VERSION
+++ b/services/web/server/VERSION
@@ -1 +1 @@
-0.11.0
+0.12.0
diff --git a/services/web/server/setup.cfg b/services/web/server/setup.cfg
index d735446b988..88242a31019 100644
--- a/services/web/server/setup.cfg
+++ b/services/web/server/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 0.11.0
+current_version = 0.12.0
 commit = True
 message = services/webserver api version: {current_version} → {new_version}
 tag = False
diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml
index e314a6cbada..f7c700f8d4d 100644
--- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml
+++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml
@@ -1,7 +1,7 @@
 openapi: 3.0.0
 info:
   title: osparc-simcore web API
-  version: 0.11.0
+  version: 0.12.0
   description: API designed for the front-end app
   contact:
     name: IT'IS Foundation
@@ -3124,14 +3124,14 @@ paths:
           content:
             application/json:
               schema:
-                title: 'Envelope[dict[uuid.UUID, simcore_service_webserver.projects.projects_ports_handlers.ProjectPortGet]]'
+                title: 'Envelope[dict[uuid.UUID, simcore_service_webserver.projects.projects_ports_handlers.ProjectInputGet]]'
                 type: object
                 properties:
                   data:
                     title: Data
                     type: object
                     additionalProperties:
-                      title: ProjectPortGet
+                      title: ProjectInputGet
                       required:
                         - key
                         - value
@@ -3172,7 +3172,7 @@ paths:
               title: Updates
               type: array
               items:
-                title: ProjectPort
+                title: ProjectInputUpdate
                 required:
                   - key
                   - value
@@ -3215,7 +3215,84 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/paths/~1projects~1%7Bproject_id%7D~1inputs/get/responses/200/content/application~1json/schema'
+                title: 'Envelope[dict[uuid.UUID, simcore_service_webserver.projects.projects_ports_handlers.ProjectOutputGet]]'
+                type: object
+                properties:
+                  data:
+                    title: Data
+                    type: object
+                    additionalProperties:
+                      title: ProjectOutputGet
+                      required:
+                        - key
+                        - value
+                        - label
+                      type: object
+                      properties:
+                        key:
+                          title: Key
+                          type: string
+                          description: Project port's unique identifer. Same as the UUID of the associated port node
+                          format: uuid
+                        value:
+                          title: Value
+                          description: Value assigned to this i/o port
+                        label:
+                          title: Label
+                          type: string
+                  error:
+                    title: Error
+  '/projects/{project_id}/metadata/ports':
+    get:
+      tags:
+        - project
+      summary: List Project Metadata Ports
+      description: New in version *0.12*
+      operationId: list_project_metadata_ports
+      parameters:
+        - required: true
+          schema:
+            title: Project Id
+            type: string
+            format: uuid
+          name: project_id
+          in: path
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema:
+                title: 'Envelope[list[simcore_service_webserver.projects.projects_ports_handlers.ProjectMetadataPortGet]]'
+                type: object
+                properties:
+                  data:
+                    title: Data
+                    type: array
+                    items:
+                      title: ProjectMetadataPortGet
+                      required:
+                        - key
+                        - kind
+                      type: object
+                      properties:
+                        key:
+                          title: Key
+                          type: string
+                          description: Project port's unique identifer. Same as the UUID of the associated port node
+                          format: uuid
+                        kind:
+                          title: Kind
+                          enum:
+                            - input
+                            - output
+                          type: string
+                        content_schema:
+                          title: Content Schema
+                          type: object
+                          description: 'jsonschema for the port''s value. SEE https://json-schema.org/understanding-json-schema/'
+                  error:
+                    title: Error
   '/nodes/{nodeInstanceUUID}/outputUi/{outputKey}':
     get:
       tags:
diff --git a/services/web/server/src/simcore_service_webserver/projects/_ports.py b/services/web/server/src/simcore_service_webserver/projects/_ports.py
index 0f483eb0eed..a4de97926f8 100644
--- a/services/web/server/src/simcore_service_webserver/projects/_ports.py
+++ b/services/web/server/src/simcore_service_webserver/projects/_ports.py
@@ -8,12 +8,12 @@
     JsonSchemaValidationError,
     jsonschema_validate_data,
 )
-from models_library.utils.services_io import get_service_io_json_schema
+from models_library.utils.services_io import JsonSchemaDict, get_service_io_json_schema
 from pydantic import ValidationError
 
 
 @dataclass(frozen=True)
-class _ProjectPort:
+class ProjectPortData:
     kind: Literal["input", "output"]
     node_id: NodeID
     io_key: str
@@ -23,21 +23,30 @@ class _ProjectPort:
     def key(self):
         return f"{self.node_id}.{self.io_key}"
 
-    def get_schema(self) -> Optional[dict[str, Any]]:
+    def get_schema(self) -> Optional[JsonSchemaDict]:
         node_meta = catalog.get_metadata(self.node.key, self.node.version)
         if self.kind == "input" and node_meta.outputs:
             if input_meta := node_meta.outputs[self.io_key]:
-                return get_service_io_json_schema(input_meta)
+                return self._get_port_schema(input_meta)
         elif self.kind == "output" and node_meta.inputs:
             if output_meta := node_meta.inputs[self.io_key]:
-                return get_service_io_json_schema(output_meta)
+                return self._get_port_schema(output_meta)
         return None
 
+    def _get_port_schema(self, io_meta):
+        schema = get_service_io_json_schema(io_meta)
+        if schema:
+            # uses node label instead of service title
+            # This way it will contain the label the user
+            # gave to the port
+            schema["title"] = self.node.label
+        return schema
 
-def _iter_project_ports(
+
+def iter_project_ports(
     workbench: dict[NodeID, Node],
     filter_kind: Optional[Literal["input", "output"]] = None,
-) -> Iterator[_ProjectPort]:
+) -> Iterator[ProjectPortData]:
     """Iterates the nodes in a workbench that define the input/output ports of a project
 
     - A node in general has inputs and outputs:
@@ -63,7 +72,7 @@ def _iter_project_ports(
             assert list(node.outputs.keys()) == ["out_1"]  # nosec
 
             for output_key in node.outputs.keys():
-                yield _ProjectPort(
+                yield ProjectPortData(
                     kind="input", node_id=node_id, io_key=output_key, node=node
                 )
 
@@ -79,7 +88,7 @@ def _iter_project_ports(
             assert list(node.inputs.keys()) == ["in_1"]  # nosec
 
             for inputs_key in node.inputs.keys():
-                yield _ProjectPort(
+                yield ProjectPortData(
                     kind="output", node_id=node_id, io_key=inputs_key, node=node
                 )
 
@@ -87,7 +96,7 @@ def _iter_project_ports(
 def get_project_inputs(workbench: dict[NodeID, Node]) -> dict[NodeID, Any]:
     """Returns the values assigned to each input node"""
     input_to_value = {}
-    for port in _iter_project_ports(workbench, "input"):
+    for port in iter_project_ports(workbench, "input"):
         input_to_value[port.node_id] = (
             port.node.outputs["out_1"] if port.node.outputs else None
         )
@@ -113,7 +122,7 @@ def set_project_inputs(
         if (node := workbench[node_id]) and node.outputs != output:
             # validates value against jsonschema
             try:
-                port = _ProjectPort(
+                port = ProjectPortData(
                     kind="input", node_id=node_id, io_key="out_1", node=node
                 )
                 if schema := port.get_schema():
@@ -136,7 +145,7 @@ class Config(PortLink.Config):
 def get_project_outputs(workbench: dict[NodeID, Node]) -> dict[NodeID, Any]:
     """Returns values assigned to each output node"""
     output_to_value = {}
-    for port in _iter_project_ports(workbench, "output"):
+    for port in iter_project_ports(workbench, "output"):
         if port.node.inputs:
             try:
                 # Is link?
diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_ports_handlers.py b/services/web/server/src/simcore_service_webserver/projects/projects_ports_handlers.py
index e47db8b9463..539b9c99459 100644
--- a/services/web/server/src/simcore_service_webserver/projects/projects_ports_handlers.py
+++ b/services/web/server/src/simcore_service_webserver/projects/projects_ports_handlers.py
@@ -5,14 +5,15 @@
 
 import functools
 import logging
-from typing import Any
+from typing import Any, Literal, Optional
 
 from aiohttp import web
 from models_library.projects import ProjectID
 from models_library.projects_nodes import Node, NodeID
 from models_library.users import UserID
 from models_library.utils.fastapi_encoders import jsonable_encoder
-from pydantic import BaseModel, Field, parse_obj_as
+from models_library.utils.services_io import JsonSchemaDict
+from pydantic import BaseModel, Extra, Field, parse_obj_as
 from servicelib.aiohttp.requests_validation import (
     parse_request_body_as,
     parse_request_path_parameters_as,
@@ -82,14 +83,30 @@ async def _get_validated_workbench_model(
     return workbench
 
 
-#
+routes = web.RouteTableDef()
+
+
+class _InputSchema:
+    class Config:
+        allow_population_by_field_name = False
+        extra = Extra.forbid
+        allow_mutations = False
+
+
+class _OutputSchema:
+    class Config:
+        allow_population_by_field_name = True
+        extra = Extra.ignore
+        allow_mutations = False
+
+        #
+
+
 # projects/*/inputs COLLECTION -------------------------
 #
 
-routes = web.RouteTableDef()
-
 
-class ProjectPort(BaseModel):
+class _ProjectIOBase(BaseModel):
     key: NodeID = Field(
         ...,
         description="Project port's unique identifer. Same as the UUID of the associated port node",
@@ -97,7 +114,15 @@ class ProjectPort(BaseModel):
     value: Any = Field(..., description="Value assigned to this i/o port")
 
 
-class ProjectPortGet(ProjectPort):
+class ProjectInputUpdate(_InputSchema, _ProjectIOBase):
+    ...
+
+
+class ProjectInputGet(_OutputSchema, _ProjectIOBase):
+    label: str
+
+
+class ProjectOutputGet(_OutputSchema, _ProjectIOBase):
     label: str
 
 
@@ -118,7 +143,7 @@ async def get_project_inputs(request: web.Request) -> web.Response:
 
     return _web_json_response_enveloped(
         data={
-            node_id: ProjectPortGet(
+            node_id: ProjectInputGet(
                 key=node_id, label=workbench[node_id].label, value=value
             )
             for node_id, value in inputs.items()
@@ -134,7 +159,7 @@ async def update_project_inputs(request: web.Request) -> web.Response:
     db: ProjectDBAPI = ProjectDBAPI.get_from_app_context(request.app)
     req_ctx = RequestContext.parse_obj(request)
     path_params = parse_request_path_parameters_as(ProjectPathParams, request)
-    inputs_updates = await parse_request_body_as(list[ProjectPort], request)
+    inputs_updates = await parse_request_body_as(list[ProjectInputUpdate], request)
 
     assert request.app  # nosec
 
@@ -168,7 +193,7 @@ async def update_project_inputs(request: web.Request) -> web.Response:
 
     return _web_json_response_enveloped(
         data={
-            node_id: ProjectPortGet(
+            node_id: ProjectInputGet(
                 key=node_id, label=workbench[node_id].label, value=value
             )
             for node_id, value in inputs.items()
@@ -198,9 +223,55 @@ async def get_project_outputs(request: web.Request) -> web.Response:
 
     return _web_json_response_enveloped(
         data={
-            node_id: ProjectPortGet(
+            node_id: ProjectOutputGet(
                 key=node_id, label=workbench[node_id].label, value=value
             )
             for node_id, value in outputs.items()
         }
     )
+
+
+#
+# projects/*/metadata/ports sub-collection -------------------------
+#
+
+
+class ProjectMetadataPortGet(BaseModel):
+    key: NodeID = Field(
+        ...,
+        description="Project port's unique identifer. Same as the UUID of the associated port node",
+    )
+    kind: Literal["input", "output"]
+    content_schema: Optional[JsonSchemaDict] = Field(
+        None,
+        description="jsonschema for the port's value. SEE https://json-schema.org/understanding-json-schema/",
+    )
+
+
+@routes.get(
+    f"/{VTAG}/projects/{{project_id}}/metadata/ports",
+    name="list_project_metadata_ports",
+)
+@login_required
+@permission_required("project.read")
+@_handle_project_exceptions
+async def list_project_metadata_ports(request: web.Request) -> web.Response:
+    req_ctx = RequestContext.parse_obj(request)
+    path_params = parse_request_path_parameters_as(ProjectPathParams, request)
+
+    assert request.app  # nosec
+
+    workbench = await _get_validated_workbench_model(
+        app=request.app, project_id=path_params.project_id, user_id=req_ctx.user_id
+    )
+
+    return _web_json_response_enveloped(
+        data=[
+            ProjectMetadataPortGet(
+                key=port.node_id,
+                kind=port.kind,
+                content_schema=port.get_schema(),
+            )
+            for port in _ports.iter_project_ports(workbench)
+        ]
+    )
diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects__ports.py b/services/web/server/tests/unit/with_dbs/02/test_projects__ports.py
index c713bbba1ea..726e7c30ba2 100644
--- a/services/web/server/tests/unit/with_dbs/02/test_projects__ports.py
+++ b/services/web/server/tests/unit/with_dbs/02/test_projects__ports.py
@@ -12,9 +12,9 @@
 from pydantic import parse_obj_as
 from simcore_service_webserver.projects._ports import (
     InvalidInputValue,
-    _iter_project_ports,
     get_project_inputs,
     get_project_outputs,
+    iter_project_ports,
     set_project_inputs,
 )
 
@@ -210,7 +210,7 @@ def test_get_project_outputs(workbench: dict[NodeID, Node]):
 
 def test_project_port_get_schema(workbench):
 
-    for port in _iter_project_ports(workbench):
+    for port in iter_project_ports(workbench):
         # eval json-schema
         schema = port.get_schema()
         assert schema
diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py b/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py
index 1e5ca3da3fa..5e52a7cf3e8 100644
--- a/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py
+++ b/services/web/server/tests/unit/with_dbs/02/test_projects_ports_handlers.py
@@ -208,6 +208,66 @@ async def test_io_workflow(
 
     project_id = user_project["uuid"]
 
+    # list_project_metadata_ports
+    expected_url = client.app.router["list_project_metadata_ports"].url_for(
+        project_id=project_id
+    )
+    assert URL(f"/v0/projects/{project_id}/metadata/ports") == expected_url
+
+    resp = await client.get(f"/v0/projects/{project_id}/metadata/ports")
+    ports_meta, error = await assert_status(resp, expected_cls=expected)
+
+    if not error:
+        assert ports_meta == [
+            {
+                "key": "38a0d401-af4b-4ea7-ab4c-5005c712a546",
+                "kind": "input",
+                "content_schema": {
+                    "description": "Parameter of type integer",
+                    "title": "X",
+                    "type": "integer",
+                },
+            },
+            {
+                "key": "fc48252a-9dbb-4e07-bf9a-7af65a18f612",
+                "kind": "input",
+                "content_schema": {
+                    "description": "Parameter of type integer",
+                    "title": "Z",
+                    "type": "integer",
+                },
+            },
+            {
+                "key": "7bf0741f-bae4-410b-b662-fc34b47c27c9",
+                "kind": "input",
+                "content_schema": {
+                    "description": "Parameter of type boolean",
+                    "title": "on",
+                    "type": "boolean",
+                },
+            },
+            {
+                "key": "09fd512e-0768-44ca-81fa-0cecab74ec1a",
+                "kind": "output",
+                "content_schema": {
+                    "default": 0,
+                    "description": "Captures integer values attached to it",
+                    "title": "Random sleep interval_2",
+                    "type": "integer",
+                },
+            },
+            {
+                "key": "76f607b4-8761-4f96-824d-cab670bc45f5",
+                "kind": "output",
+                "content_schema": {
+                    "default": 0,
+                    "description": "Captures integer values attached to it",
+                    "title": "Random sleep interval",
+                    "type": "integer",
+                },
+            },
+        ]
+
     # get_project_inputs
     expected_url = client.app.router["get_project_inputs"].url_for(
         project_id=project_id