diff --git a/src/ApiPlatform/Resources/Module/UpgradeModule.php b/src/ApiPlatform/Resources/Module/UpgradeModule.php
new file mode 100644
index 0000000..bd3ff14
--- /dev/null
+++ b/src/ApiPlatform/Resources/Module/UpgradeModule.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright since 2007 PrestaShop SA and Contributors
+ * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
+ *
+ * NOTICE OF LICENSE
+ *
+ * This source file is subject to the Academic Free License version 3.0
+ * that is bundled with this package in the file LICENSE.md.
+ * It is also available through the world-wide-web at this URL:
+ * https://opensource.org/licenses/AFL-3.0
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@prestashop.com so we can send you a copy immediately.
+ *
+ * @author    PrestaShop SA and Contributors <contact@prestashop.com>
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license   https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
+ */
+
+declare(strict_types=1);
+
+namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\Module;
+
+use ApiPlatform\Metadata\ApiResource;
+use PrestaShop\PrestaShop\Core\Domain\Module\Command\UpgradeModuleCommand;
+use PrestaShop\PrestaShop\Core\Domain\Module\Query\GetModuleInfos;
+use PrestaShopBundle\ApiPlatform\Metadata\CQRSUpdate;
+
+#[ApiResource(
+    operations: [
+        new CQRSUpdate(
+            uriTemplate: '/module/{technicalName}/upgrade',
+            CQRSCommand: UpgradeModuleCommand::class,
+            CQRSQuery: GetModuleInfos::class,
+            scopes: [
+                'module_write',
+            ],
+        ),
+    ],
+)]
+class UpgradeModule extends Module
+{
+}
diff --git a/tests/Integration/ApiPlatform/ModuleEndpointTest.php b/tests/Integration/ApiPlatform/ModuleEndpointTest.php
index 9a7e431..bd70554 100644
--- a/tests/Integration/ApiPlatform/ModuleEndpointTest.php
+++ b/tests/Integration/ApiPlatform/ModuleEndpointTest.php
@@ -89,6 +89,11 @@ public function getProtectedEndpoints(): iterable
             'multipart/form-data',
         ];
 
+        yield 'upgrade' => [
+            'PUT',
+            '/module/{technicalName}/upgrade',
+        ];
+
         yield 'uninstall module' => [
             'PUT',
             '/module/{technicalName}/uninstall',
@@ -646,6 +651,93 @@ public function testBulkUninstallModule()
         }
     }
 
+    public function testUpgradeModule(): void
+    {
+        $bearerToken = $this->getBearerToken(['module_write']);
+
+        // Upload Zip from GitHub with version 2.1.2 the module is present but not installed
+        $response = static::createClient()->request('POST', '/module/upload-source', [
+            'auth_bearer' => $bearerToken,
+            'json' => [
+                'source' => 'https://github.com/PrestaShop/dashproducts/releases/download/v2.1.2/dashproducts.zip',
+            ],
+        ]);
+        self::assertResponseStatusCodeSame(201);
+        $decodedResponse = json_decode($response->getContent(), true);
+        $this->assertNotFalse($decodedResponse);
+
+        // The returned response and the GET infos response should be identical
+        $module212 = [
+            'moduleId' => null,
+            'technicalName' => 'dashproducts',
+            'moduleVersion' => '2.1.2',
+            // Module is simply uploaded not installed
+            'installedVersion' => null,
+            'enabled' => false,
+            'installed' => false,
+        ];
+        $this->assertEquals($module212, $decodedResponse);
+        $this->assertEquals($module212, $this->getModuleInfos($module212['technicalName']));
+
+        // Now we install the module
+        $response = static::createClient()->request('PUT', sprintf('/module/%s/install', $module212['technicalName']), [
+            'auth_bearer' => $bearerToken,
+            // We must define a JSON body even if it is empty, we need to search how to make this optional
+            'json' => [
+            ],
+        ]);
+        self::assertResponseStatusCodeSame(200);
+        $decodedResponse = json_decode($response->getContent(), true);
+        $this->assertNotFalse($decodedResponse);
+
+        // The ID is dynamic, so we fetch it after creation
+        $this->assertArrayHasKey('moduleId', $decodedResponse);
+        $module212['moduleId'] = $decodedResponse['moduleId'];
+        $module212['installed'] = true;
+        $module212['enabled'] = true;
+        $module212['installedVersion'] = '2.1.2';
+        $this->assertEquals($module212, $decodedResponse);
+        $this->assertEquals($module212, $this->getModuleInfos($module212['technicalName']));
+
+        // Now upload the source for version 2.1.3, the module version is updated but not the installed one
+        $response = static::createClient()->request('POST', '/module/upload-source', [
+            'auth_bearer' => $bearerToken,
+            'json' => [
+                'source' => 'https://github.com/PrestaShop/dashproducts/releases/download/v2.1.3/dashproducts.zip',
+            ],
+        ]);
+        self::assertResponseStatusCodeSame(201);
+        $decodedResponse = json_decode($response->getContent(), true);
+        $this->assertNotFalse($decodedResponse);
+
+        $module213 = [
+            'moduleId' => $module212['moduleId'],
+            'technicalName' => 'dashproducts',
+            'moduleVersion' => '2.1.3',
+            // Module is simply uploaded not installed
+            'installedVersion' => '2.1.2',
+            'enabled' => true,
+            'installed' => true,
+        ];
+        $this->assertEquals($module213, $decodedResponse);
+        $this->assertEquals($module213, $this->getModuleInfos($module213['technicalName']));
+
+        $response = static::createClient()->request('PUT', sprintf('/module/%s/upgrade', $module212['technicalName']), [
+            'auth_bearer' => $bearerToken,
+            // We must define a JSON body even if it is empty, we need to search how to make this optional
+            'json' => [
+            ],
+        ]);
+        self::assertResponseStatusCodeSame(200);
+        $decodedResponse = json_decode($response->getContent(), true);
+        $this->assertNotFalse($decodedResponse);
+
+        // Check response from status upgrade request
+        $module213['installedVersion'] = '2.1.3';
+        $this->assertEquals($module213, $decodedResponse);
+        $this->assertEquals($module213, $this->getModuleInfos($module213['technicalName']));
+    }
+
     private function getModuleInfos(string $technicalName): array
     {
         $bearerToken = $this->getBearerToken(['module_read']);