diff --git a/migration/19.12/1912.sql b/migration/19.12/1912.sql
index 6b08fbb66a5a29f6550477aa50f0a8f5b4a82e0e..67d69b3f5abf748c59ace9cda3460da82445ff13 100644
--- a/migration/19.12/1912.sql
+++ b/migration/19.12/1912.sql
@@ -357,6 +357,7 @@ ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_wkstation CHARACTER VARY
 ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_batch CHARACTER VARYING (50) DEFAULT NULL::character varying;
 ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS scan_postmark CHARACTER VARYING (50) DEFAULT NULL::character varying;
 ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS custom_fields jsonb;
+ALTER TABLE res_letterbox ADD COLUMN IF NOT EXISTS linked_resources jsonb NOT NULL DEFAULT '[]';
 
 
 /* USERGROUP_CONTENT */
diff --git a/rest/index.php b/rest/index.php
index 4397f71049112798e168041857f6caedd66152e6..59ca6acbec5fc1e1db16a63aee1ac687a1d73397 100755
--- a/rest/index.php
+++ b/rest/index.php
@@ -351,6 +351,8 @@ $app->get('/resources/{resId}/listInstance', \Entity\controllers\ListInstanceCon
 $app->get('/resources/{resId}/visaCircuit', \Entity\controllers\ListInstanceController::class . ':getVisaCircuitByResId');
 $app->get('/resources/{resId}/opinionCircuit', \Entity\controllers\ListInstanceController::class . ':getOpinionCircuitByResId');
 $app->get('/resources/{resId}/availableCircuits', \Entity\controllers\ListTemplateController::class . ':getAvailableCircuitsByResId');
+$app->post('/resources/{resId}/linkedResources', \Resource\controllers\ResController::class . ':linkResources');
+$app->delete('/resources/{resId}/linkedResources/{id}', \Resource\controllers\ResController::class . ':unlinkResources');
 $app->get('/res/{resId}/acknowledgementReceipt/{id}', \AcknowledgementReceipt\controllers\AcknowledgementReceiptController::class . ':getAcknowledgementReceipt');
 $app->put('/res/resource/status', \Resource\controllers\ResController::class . ':updateStatus');
 $app->post('/res/list', \Resource\controllers\ResController::class . ':getList');
diff --git a/sql/structure.sql b/sql/structure.sql
index 15989b07b7579eb052703c5fd9997d8ce40843c8..d30d02eae5a67d61a8b89bcb076ff25928f09320 100755
--- a/sql/structure.sql
+++ b/sql/structure.sql
@@ -977,6 +977,7 @@ CREATE TABLE res_letterbox
   flag_alarm2 char(1) default 'N'::character varying,
   model_id integer NOT NULL,
   custom_fields jsonb,
+  linked_resources jsonb NOT NULL DEFAULT '[]',
   CONSTRAINT res_letterbox_pkey PRIMARY KEY  (res_id)
 )
 WITH (OIDS=FALSE);
diff --git a/src/app/resource/controllers/ResController.php b/src/app/resource/controllers/ResController.php
index d4a8b982a09fd14f20fd49e8da27cf10622e95aa..4be8a219b7762c699f5085a572443493b6a00ff7 100755
--- a/src/app/resource/controllers/ResController.php
+++ b/src/app/resource/controllers/ResController.php
@@ -674,6 +674,68 @@ class ResController
         return $response->withJson(['isAllowed' => true]);
     }
 
+    public function linkResources(Request $request, Response $response, array $args)
+    {
+        if (!Validator::intVal()->validate($args['resId']) || !ResController::hasRightByResId(['resId' => [$args['resId']], 'userId' => $GLOBALS['id']])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Resource out of perimeter']);
+        }
+
+        $body = $request->getParsedBody();
+
+        if (!Validator::arrayType()->notEmpty()->validate($body['linkedResources'])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Body linkedResources is empty or not an array']);
+        }
+
+        if (!ResController::hasRightByResId(['resId' => $body['linkedResources'], 'userId' => $GLOBALS['id']])) {
+            return ['errors' => 'Body linkedResources out of perimeter'];
+        }
+
+        $resource = ResModel::getById(['resId' => $args['resId'], 'select' => ['linked_resources']]);
+        $linkedResources = json_decode($resource['linked_resources'], true);
+        $linkedResources = array_merge($linkedResources, $body['linkedResources']);
+        $linkedResources = array_unique($linkedResources);
+        foreach ($linkedResources as $key => $value) {
+            $linkedResources[$key] = (string)$value;
+        }
+
+        ResModel::update([
+            'set'       => ['linked_resources' => json_encode($linkedResources)],
+            'where'     => ['res_id = ?'],
+            'data'      => [$args['resId']]
+        ]);
+        ResModel::update([
+            'postSet'   => ['linked_resources' => "jsonb_insert(linked_resources, '{0}', '\"{$args['resId']}\"')"],
+            'where'     => ['res_id in (?)', "(linked_resources @> ?) = false"],
+            'data'      => [$body['linkedResources'], "\"{$args['resId']}\""]
+        ]);
+
+        return $response->withStatus(204);
+    }
+
+    public function unlinkResources(Request $request, Response $response, array $args)
+    {
+        if (!Validator::intVal()->validate($args['resId']) || !ResController::hasRightByResId(['resId' => [$args['resId']], 'userId' => $GLOBALS['id']])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Resource out of perimeter']);
+        }
+
+        if (!Validator::intVal()->validate($args['id']) || !ResController::hasRightByResId(['resId' => [$args['id']], 'userId' => $GLOBALS['id']])) {
+            return ['errors' => 'Resource to unlink out of perimeter'];
+        }
+
+        ResModel::update([
+            'postSet'   => ['linked_resources' => "linked_resources - '{$args['id']}'"],
+            'where'     => ['res_id = ?'],
+            'data'      => [$args['resId']]
+        ]);
+        ResModel::update([
+            'postSet'   => ['linked_resources' => "linked_resources - '{$args['resId']}'"],
+            'where'     => ['res_id = ?'],
+            'data'      => [$args['id']]
+        ]);
+
+        return $response->withStatus(204);
+    }
+
     public static function getEncodedDocument(array $aArgs)
     {
         ValidatorModel::notEmpty($aArgs, ['resId']);
@@ -995,6 +1057,12 @@ class ResController
             }
         }
 
+        if (!empty($body['linkedResources'])) {
+            if (!ResController::hasRightByResId(['resId' => [$body['linkedResources']], 'userId' => $GLOBALS['id']])) {
+                return ['errors' => 'Body linkedResources out of perimeter'];
+            }
+        }
+
         return true;
     }
 
diff --git a/src/app/resource/controllers/StoreController.php b/src/app/resource/controllers/StoreController.php
index 25fd8385f4bec923c4a1df031872f8af45c84e97..084e2d42e8859fd83195ffce61c5c8271ef790b8 100755
--- a/src/app/resource/controllers/StoreController.php
+++ b/src/app/resource/controllers/StoreController.php
@@ -203,6 +203,7 @@ class StoreController
             'barcode'               => $args['barcode'] ?? null,
             'origin'                => $args['origin'] ?? null,
             'custom_fields'         => !empty($args['customFields']) ? json_encode($args['customFields']) : null,
+            'linked_resources'      => !empty($args['linkedResources']) ? json_encode($args['linkedResources']) : null,
             'external_id'           => $externalId,
             'creation_date'         => 'CURRENT_TIMESTAMP'
         ];