diff --git a/nomad/app/v1/models/graph/__main__.py b/nomad/app/v1/models/graph/__main__.py
deleted file mode 100644
index 30ebdc1e7e9164ea056a8a05482fd5c12c17bda5..0000000000000000000000000000000000000000
--- a/nomad/app/v1/models/graph/__main__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import sys
-
-module_prefix = 'nomad.app.v1.models.graph'
-
-setattr(sys.modules[f'{module_prefix}.utils'], 'ref_prefix', '#/definitions')
-model = getattr(sys.modules[f'{module_prefix}.graph_models'], sys.argv[1])
-print(model.schema_json(indent=2))
diff --git a/nomad/app/v1/models/graph/graph_models.py b/nomad/app/v1/models/graph/graph_models.py
index ff4b2151c21999bbcf866d2c235e589bbba52142..f9eaac851f050b923dbe983b357fd86d555fa2b0 100644
--- a/nomad/app/v1/models/graph/graph_models.py
+++ b/nomad/app/v1/models/graph/graph_models.py
@@ -184,6 +184,7 @@ class GraphUploads(BaseModel):
 
 class GraphEntryMetadata(BaseModel, extra=Extra.allow):
     entry: GraphEntry
+    m_children: Any
 
 
 class SearchRequestOptions(BaseModel):
diff --git a/nomad/app/v1/models/graph/utils.py b/nomad/app/v1/models/graph/utils.py
index b32a291b08aa81ee806fbaab6fa4687bd607123a..bd26f6fc09925f3f459046da28092cd9c0f3fdba 100644
--- a/nomad/app/v1/models/graph/utils.py
+++ b/nomad/app/v1/models/graph/utils.py
@@ -52,6 +52,7 @@ import sys
 ref_prefix = '#/components/schemas'
 request_suffix = 'Request'
 response_suffix = 'Response'
+graph_model_export = False
 
 
 class _DictModel(BaseModel):
@@ -110,6 +111,14 @@ class _DictModel(BaseModel):
                 if value_type == Literal['*']:
                     types.append({'enum': ['*'], 'type': 'string'})
                 else:
+                    # This forces all model names to be unique. Pydandic
+                    # replaces non unique model names with qualified names.
+                    # We are just using the plain name here. Unfortunately,
+                    # there is no way to get the "long_model_name" map from pydantic
+                    # to put the right names here. Therefore, in the presence
+                    # of non-unique names, we are using the wrong referenes.
+                    # Things depending on the openapi schema will cause issues.
+                    # i.e. https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR/-/issues/1958
                     types.append({'$ref': f'{ref_prefix}/{value_type.__name__}'})
 
             if 'properties' in schema:
@@ -196,6 +205,13 @@ def _generate_model(
 
     # We need to populate a forward ref for the model in the ns use it in recursion cases.
     result_model_name = f'{source_model.__name__}{suffix}'
+    if (
+        graph_model_export
+        and result_model_name.startswith('Graph')
+        and result_model_name not in ['GraphRequest', 'GraphResponse']
+    ):
+        result_model_name = result_model_name[5:]
+
     is_ns_origin = len(ns) == 0
     if result_model_name not in ns:
         ns[result_model_name] = ForwardRef(result_model_name)
@@ -307,6 +323,7 @@ def _generate_model(
     assert (
         getattr(sys.modules[source_model.__module__], result_model_name, result_model)
         == result_model
+        or graph_model_export
     ), f'Model class with name {result_model_name} already exists.'
     setattr(sys.modules[source_model.__module__], result_model_name, result_model)
 
diff --git a/nomad/app/v1/routers/entries.py b/nomad/app/v1/routers/entries.py
index beae96be75da9ff752b7353755305d7e2cfb4f4a..c921c0edbc4dbea73f04fe2245936da63b662c55 100644
--- a/nomad/app/v1/routers/entries.py
+++ b/nomad/app/v1/routers/entries.py
@@ -329,6 +329,14 @@ class ArchiveChange(BaseModel):
     new_value: Any
     action: ArchiveChangeAction = ArchiveChangeAction.upsert
 
+    class Config:
+        @staticmethod
+        def schema_extra(schema, model) -> None:
+            # Removing the title from the Any typed new_value field
+            # makes this a proper JSON schema "any" instead of a named
+            # empty type.
+            del schema['properties']['new_value']['title']
+
 
 class EntryEdit(BaseModel):
     changes: List[ArchiveChange]
@@ -1485,9 +1493,10 @@ async def post_entry_edit(
     #   - no checks yet, we simply assume that the raw file and the changes
     #     agree on the schema
     #   - no handling of concurrent changes yet
-    for change in reversed(data.changes):
+    for change in data.changes:
         path = change.path.split('/')
         section_data = archive_data
+        next_key = to_key(path[0])
 
         for path_index, path_segment in enumerate(path[:-1]):
             # Usually all keys are str and indicate either a quantity or
diff --git a/nomad/cli/dev.py b/nomad/cli/dev.py
index 9aaadbbf6108b4872a909fbbb56b49e48ef30fb1..9bf52df4bf4db658be37d55e52d7ebf056d39a70 100644
--- a/nomad/cli/dev.py
+++ b/nomad/cli/dev.py
@@ -78,6 +78,36 @@ def gui_qa(skip_tests: bool):
     sys.exit(ret_code)
 
 
+@dev.command(help='Export an API model in JSON schema.')
+@click.argument('model')
+def api_model(model):
+    import importlib
+
+    if model in [
+        'nomad.app.v1.models.graph.GraphRequest',
+        'nomad.app.v1.models.graph.GraphResponse',
+    ]:
+        from nomad.app.v1.models.graph.utils import (
+            generate_request_model,
+            generate_response_model,
+        )
+        from nomad.app.v1.models.graph.graph_models import Graph
+
+        sys.modules['nomad.app.v1.models.graph.utils'].ref_prefix = '#/definitions'
+        sys.modules['nomad.app.v1.models.graph.utils'].graph_model_export = True
+
+        if model == 'nomad.app.v1.models.graph.GraphRequest':
+            model = generate_request_model(Graph)
+        else:
+            model = generate_response_model(Graph)
+        print(model.schema_json(indent=2))
+    else:
+        pkg, cls = model.rsplit('.', 1)
+        importlib.import_module(pkg)
+        model = getattr(sys.modules[pkg], cls)
+        print(model.schema_json(indent=2))
+
+
 def get_gui_artifacts_js() -> str:
     from nomad.datamodel import all_metainfo_packages
     from nomad.parsing.parsers import code_metadata
diff --git a/nomad/parsing/parser.py b/nomad/parsing/parser.py
index 680826748397e0214787bc9864f228a158b618ae..7e95da9792f2155e97e8bb1a0ed79b2b344c474b 100644
--- a/nomad/parsing/parser.py
+++ b/nomad/parsing/parser.py
@@ -527,9 +527,11 @@ class ArchiveParser(MatchingParser):
 
         if metadata_data is not None:
             self.domain = metadata_data.get('domain')
-            # Setting metadata in this way is not supported (any more)
-            if entry_name := metadata_data.get('entry_name', None):
-                archive.metadata.entry_name = entry_name
+            for quantity_name in ['entry_name', 'references', 'comment']:
+                quantity = EntryMetadata.m_def.all_quantities[quantity_name]
+                if value := metadata_data.get(quantity_name, None):
+                    archive.metadata.m_set(quantity, value)
+
             del archive_data[EntryArchive.metadata.name]
 
         # ensure that definitions are parsed first to make them available for the
diff --git a/tests/app/v1/routers/test_entries_archive_edit.py b/tests/app/v1/routers/test_entries_archive_edit.py
index 557e0bf6eca9e3ebccbad3e7d89c9eabb0fd44b6..8a2e90107dbffd9b62c3d1d9677d5627e4ed7559 100644
--- a/tests/app/v1/routers/test_entries_archive_edit.py
+++ b/tests/app/v1/routers/test_entries_archive_edit.py
@@ -67,8 +67,8 @@ from tests.test_files import create_test_upload_files
         pytest.param(
             {
                 'changes': [
-                    {'path': 'data/sub', 'action': 'remove'},
                     {'path': 'data/sub/name', 'new_value': 'NewName'},
+                    {'path': 'data/sub', 'action': 'remove'},
                 ]
             },
             {
@@ -82,14 +82,40 @@ from tests.test_files import create_test_upload_files
         pytest.param(
             {
                 'changes': [
-                    {'path': 'data/sub/1', 'action': 'remove'},
                     {'path': 'data/sub/1/name', 'new_value': 'NewName'},
+                    {'path': 'data/sub/1', 'action': 'remove'},
                 ]
             },
             {'data': {'name': 'TestName', 'sub': [None]}},
             'user1',
             id='remove-repeated-sub-section',
         ),
+        pytest.param(
+            {
+                'changes': [
+                    {'path': 'data/sub/0', 'action': 'upsert', 'new_value': {}},
+                    {
+                        'path': 'data/sub/0/name',
+                        'action': 'upsert',
+                        'new_value': 'NewName1',
+                    },
+                    {'path': 'data/sub/1', 'action': 'upsert', 'new_value': {}},
+                    {
+                        'path': 'data/sub/1/name',
+                        'action': 'upsert',
+                        'new_value': 'NewName2',
+                    },
+                ]
+            },
+            {
+                'data': {
+                    'name': 'TestName',
+                    'sub': [{'name': 'NewName1'}, {'name': 'NewName2'}],
+                }
+            },
+            'user1',
+            id='add-multiple-repeated-sub-section',
+        ),
     ],
 )
 def test_post_entry_edit(