diff --git a/src/main/java/it/inaf/ia2/transfer/FileServiceApplication.java b/src/main/java/it/inaf/ia2/transfer/FileServiceApplication.java
index 36b414ba6ed746c66f110e2f533f0f8ca3695981..e0c3b1f70490aa87fa80302699563ac2efdf67fc 100644
--- a/src/main/java/it/inaf/ia2/transfer/FileServiceApplication.java
+++ b/src/main/java/it/inaf/ia2/transfer/FileServiceApplication.java
@@ -15,8 +15,13 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.context.annotation.Import;
+import it.inaf.oats.vospace.parent.persistence.LinkedServiceDAO;
+import it.inaf.ia2.aa.ServiceLocator;
+import it.inaf.ia2.aa.ServletRapClient;
 
 @SpringBootApplication
+@Import(LinkedServiceDAO.class)
 public class FileServiceApplication {
 
     @Value("${jwks_uri}")
@@ -39,6 +44,11 @@ public class FileServiceApplication {
         registration.addUrlPatterns("/*");
         return registration;
     }
+    
+    @Bean
+    public ServletRapClient servletRapClient() {
+        return (ServletRapClient) ServiceLocator.getInstance().getRapClient();
+    }
 
     @Bean
     public RestTemplate restTemplate() {
diff --git a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java
index 755c5293e467fac2f8f38e00a4a2533795e61b5e..2ae066e476f31dbed72cb94e2ec4482d0681686b 100644
--- a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java
+++ b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveFileController.java
@@ -12,6 +12,7 @@ import it.inaf.ia2.transfer.service.ArchiveService;
 import it.inaf.oats.vospace.exception.PermissionDeniedException;
 import java.io.File;
 import java.util.concurrent.CompletableFuture;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
@@ -29,6 +30,9 @@ public class ArchiveFileController extends AuthenticatedFileController {
 
     @Autowired
     private ArchiveService archiveService;
+    
+    @Autowired
+    private HttpServletRequest servletRequest;
 
     @Autowired
     private HttpServletResponse response;
@@ -42,10 +46,10 @@ public class ArchiveFileController extends AuthenticatedFileController {
         job.setPrincipal(getPrincipal());
         job.setJobId(archiveRequest.getJobId());
         job.setType(type);
-        job.setVosPaths(archiveRequest.getPaths());
+        job.setEntryDescriptors(archiveRequest.getEntryDescriptors());
 
         CompletableFuture.runAsync(() -> {
-            handleFileJob(() -> archiveService.createArchive(job), job.getJobId());
+            handleFileJob(() -> archiveService.createArchive(job, servletRequest), job.getJobId());
         });
 
         HttpHeaders headers = new HttpHeaders();
diff --git a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java
index 09e3fe193ee9f84e19971236b56a1346be2337ec..926383040a7fef6c7e4eefd20dafe810499f0d65 100644
--- a/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java
+++ b/src/main/java/it/inaf/ia2/transfer/controller/ArchiveRequest.java
@@ -5,13 +5,14 @@
  */
 package it.inaf.ia2.transfer.controller;
 
+import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor;
 import java.util.List;
 
 public class ArchiveRequest {
 
     private String type;
     private String jobId;
-    private List<String> paths;
+    private List<ArchiveEntryDescriptor> entryDescriptors;
 
     public String getType() {
         return type;
@@ -29,11 +30,11 @@ public class ArchiveRequest {
         this.jobId = jobId;
     }
 
-    public List<String> getPaths() {
-        return paths;
+    public List<ArchiveEntryDescriptor> getEntryDescriptors() {
+        return entryDescriptors;
     }
 
-    public void setPaths(List<String> paths) {
-        this.paths = paths;
+    public void setEntryDescriptors(List<ArchiveEntryDescriptor> entryDescriptors) {
+        this.entryDescriptors = entryDescriptors;
     }
 }
diff --git a/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java b/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java
index cff63fa77e732ddcf5707a37129ee2405c2572b5..b04847b987caa28d015f89769ba581e2670da5ce 100644
--- a/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java
+++ b/src/main/java/it/inaf/ia2/transfer/persistence/FileDAO.java
@@ -47,7 +47,7 @@ public class FileDAO {
                 + "accept_views, provide_views, l.location_type, n.path <> n.relative_path AS virtual_parent,\n"
                 + "(SELECT user_name FROM users WHERE user_id = creator_id) AS username, n.job_id,\n"
                 + "base_path, get_os_path(n.node_id) AS os_path, ? AS vos_path, false AS is_directory,\n"
-                + "type = 'link' AS is_link,\n"
+                + "n.type = 'link' AS is_link, n.target,\n"
                 + "fs_path \n"
                 + "FROM node n\n"
                 + "JOIN location l ON (n.location_id IS NOT NULL AND n.location_id = l.location_id) OR (n.location_id IS NULL AND l.location_id = ?)\n"
@@ -180,7 +180,7 @@ public class FileDAO {
                 + "(SELECT user_name FROM users WHERE user_id = n.creator_id) AS username,\n"
                 + "base_path, get_os_path(n.node_id) AS os_path, get_vos_path(n.node_id) AS vos_path,\n"
                 + "n.type = 'container' AS is_directory, n.name, n.location_id, n.job_id,\n"
-                + "n.type = 'link' AS is_link, l.location_type\n"
+                + "n.type = 'link' AS is_link, n.target, l.location_type\n"
                 + "FROM node n\n"
                 + "JOIN node p ON p.path @> n.path\n"
                 + "LEFT JOIN location l ON l.location_id = n.location_id\n"
@@ -213,7 +213,7 @@ public class FileDAO {
                 + "(SELECT user_name FROM users WHERE user_id = n.creator_id) AS username,\n"
                 + "base_path, get_os_path(n.node_id) AS os_path, get_vos_path(n.node_id) AS vos_path,\n"
                 + "n.type = 'container' AS is_directory, n.name, n.location_id, n.job_id,\n"
-                + "n.type = 'link' AS is_link, l.location_type\n"
+                + "n.type = 'link' AS is_link, n.target, l.location_type\n"
                 + "FROM node n\n"
                 + "JOIN node p ON p.path @> n.path\n"
                 + "LEFT JOIN location l ON l.location_id = n.location_id\n"
@@ -289,11 +289,16 @@ public class FileDAO {
         long contentLength = rs.getLong("content_length");
         if (!rs.wasNull()) {
             fi.setContentLength(contentLength);
-        }
+        }        
         fi.setContentMd5(rs.getString("content_md5"));
         fi.setContentType(rs.getString("content_type"));
         fi.setDirectory(rs.getBoolean("is_directory"));
-        fi.setLink(rs.getBoolean("is_link"));
+        if(rs.getBoolean("is_link")){
+            fi.setLink(true);
+            fi.setTarget(rs.getString("target"));
+        } else {
+            fi.setLink(false);
+        }
         fi.setJobId(rs.getString("job_id"));
         int locationId = rs.getInt("location_id");
         if (!rs.wasNull()) {
diff --git a/src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java b/src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java
index f0b032dcfa24835e1a3d60ab843e81d07627d1a9..4081d073f024a9e20207badca56991505facd719 100644
--- a/src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java
+++ b/src/main/java/it/inaf/ia2/transfer/persistence/model/FileInfo.java
@@ -17,11 +17,12 @@ public class FileInfo {
     private String fsPath;
     // actualBasePath differs from base path in db due to some location type
     // dependent manipulations (performed by FileDAO)
-    private String actualBasePath;
+    private String actualBasePath;    
     private boolean isPublic;
     private boolean virtualParent;
     private boolean directory;
     private boolean link;
+    private String target;
     private List<String> groupRead;
     private List<String> groupWrite;
     private String ownerId;
@@ -36,6 +37,14 @@ public class FileInfo {
     private String locationType;
     private String jobId;
 
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
     public int getNodeId() {
         return nodeId;
     }
diff --git a/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java b/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java
index 22f089ec09891b10868ff31ed5fe080fe95562c3..b4f6bb4e7f843382c12019b34564a00411fb8b69 100644
--- a/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java
+++ b/src/main/java/it/inaf/ia2/transfer/service/ArchiveJob.java
@@ -6,6 +6,7 @@
 package it.inaf.ia2.transfer.service;
 
 import it.inaf.ia2.transfer.auth.TokenPrincipal;
+import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor;
 import java.util.List;
 
 public class ArchiveJob {
@@ -26,17 +27,17 @@ public class ArchiveJob {
         }
     }
 
-    private List<String> vosPaths;
+    private List<ArchiveEntryDescriptor> entryDescriptors;
     private TokenPrincipal tokenPrincipal;
     private String jobId;
     private Type type;
 
-    public List<String> getVosPaths() {
-        return vosPaths;
+    public List<ArchiveEntryDescriptor> getEntryDescriptors() {
+        return entryDescriptors;
     }
 
-    public void setVosPaths(List<String> vosPaths) {
-        this.vosPaths = vosPaths;
+    public void setEntryDescriptors(List<ArchiveEntryDescriptor> entryDescriptors) {
+        this.entryDescriptors = entryDescriptors;
     }
 
     public TokenPrincipal getPrincipal() {
@@ -62,4 +63,5 @@ public class ArchiveJob {
     public void setType(Type type) {
         this.type = type;
     }
+
 }
diff --git a/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java b/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java
index 2a98e89adf976469d6fbe571d24469ff600a0f11..3c9e767758a4ebacb1a3b5f745c753f3d09f0f52 100644
--- a/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java
+++ b/src/main/java/it/inaf/ia2/transfer/service/ArchiveService.java
@@ -5,6 +5,9 @@
  */
 package it.inaf.ia2.transfer.service;
 
+import it.inaf.ia2.aa.ServletRapClient;
+import it.inaf.ia2.aa.data.User;
+import it.inaf.ia2.rap.client.call.TokenExchangeRequest;
 import it.inaf.ia2.transfer.auth.TokenPrincipal;
 import it.inaf.ia2.transfer.persistence.FileDAO;
 import it.inaf.ia2.transfer.persistence.JobDAO;
@@ -13,6 +16,8 @@ import it.inaf.ia2.transfer.persistence.model.FileInfo;
 import it.inaf.oats.vospace.exception.InternalFaultException;
 import it.inaf.oats.vospace.exception.PermissionDeniedException;
 import it.inaf.oats.vospace.exception.QuotaExceededException;
+import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor;
+import it.inaf.oats.vospace.parent.persistence.LinkedServiceDAO;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -25,9 +30,11 @@ import java.nio.file.Files;
 import java.security.Principal;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.ExecutionPhase;
 import org.kamranzafar.jtar.TarEntry;
 import org.kamranzafar.jtar.TarOutputStream;
@@ -53,6 +60,9 @@ public class ArchiveService {
     @Autowired
     private LocationDAO locationDAO;
 
+    @Autowired
+    private LinkedServiceDAO linkedServiceDAO;
+
     @Autowired
     private JobDAO jobDAO;
 
@@ -62,6 +72,9 @@ public class ArchiveService {
     @Autowired
     private RestTemplate restTemplate;
 
+    @Autowired
+    private ServletRapClient rapClient;
+
     @Value("${upload_location_id}")
     private int uploadLocationId;
 
@@ -84,7 +97,7 @@ public class ArchiveService {
         }
     }
 
-    public <O extends OutputStream, E> void createArchive(ArchiveJob job) {
+    public <O extends OutputStream, E> void createArchive(ArchiveJob job, HttpServletRequest servletRequest) {
 
         jobDAO.updateJobPhase(ExecutionPhase.EXECUTING, job.getJobId());
 
@@ -94,42 +107,112 @@ public class ArchiveService {
             // TODO: check total size limit
             File archiveFile = getArchiveFile(job);
 
-            String commonParent = getCommonParent(job.getVosPaths());
+            List<ArchiveEntryDescriptor> entryDescriptors = job.getEntryDescriptors();
+
+            String commonParent = getCommonParent(entryDescriptors);
+
             // support directory used to generate folder inside tar files (path is redefined each time by TarEntry class)
             File supportDir = Files.createTempDirectory("dir").toFile();
 
-            // it will be initialized only when necessary
-            Map<Integer, String> portalLocationUrls = null;
-
             try ( ArchiveHandler<O, E> handler = getArchiveHandler(archiveFile, job.getType())) {
+                fillArchive(entryDescriptors, commonParent, supportDir,
+                        job.getPrincipal(), servletRequest, handler);
+            } finally {
+                FileSystemUtils.deleteRecursively(supportDir);
+            }
 
-                for (FileInfo fileInfo : fileDAO.getArchiveFileInfos(job.getVosPaths())) {
+        } catch (IOException ex) {
+            throw new UncheckedIOException(ex);
+        }
+    }
 
-                    String relPath = fileInfo.getVirtualPath().substring(commonParent.length());
+    private <O extends OutputStream, E> void fillArchive(
+            List<ArchiveEntryDescriptor> entryDescriptors, String commonParent,
+            File supportDir, TokenPrincipal tokenPrincipal,
+            HttpServletRequest servletRequest, ArchiveHandler<O, E> handler) throws IOException {
 
-                    if (fileInfo.isDirectory()) {
-                        handler.putNextEntry(supportDir, relPath);
-                        continue;
-                    }
+        // it will be initialized only when necessary
+        Map<Integer, String> portalLocationUrls = null;
 
-                    if (fileInfo.getLocationId() != null && "portal".equals(fileInfo.getLocationType())) {
-                        // remote file
-                        if (portalLocationUrls == null) {
-                            portalLocationUrls = locationDAO.getPortalLocationUrls();
-                        }
-                        String url = portalLocationUrls.get(fileInfo.getLocationId());
-                        downloadFileIntoArchive(fileInfo, relPath, job.getPrincipal(), handler, url);
-                    } else {
-                        // local file or virtual directory
-                        writeFileIntoArchive(fileInfo, relPath, job.getPrincipal(), handler);
-                    }
+        List<ArchiveEntryDescriptor> noTargetEntryDescriptors
+                = entryDescriptors.stream().filter(ed -> !ed.isPointingToAnotherNode())
+                        .collect(Collectors.toList());
+
+        // Start with archive entry descriptors which don't point to another node
+        List<String> vosPaths = noTargetEntryDescriptors.stream()
+                .map(ed -> ed.getVosPath())
+                .collect(Collectors.toList());
+
+        if (!vosPaths.isEmpty()) {
+            for (FileInfo fileInfo : fileDAO.getArchiveFileInfos(vosPaths)) {
+
+                String relPath = fileInfo.getVirtualPath().substring(commonParent.length());
+
+                this.insertEntryIntoArchive(fileInfo, supportDir, relPath, tokenPrincipal, portalLocationUrls, servletRequest, handler);
+
+            }
+        }
+
+        List<ArchiveEntryDescriptor> pointingEntryDescriptors
+                = entryDescriptors.stream().filter(ed -> ed.isPointingToAnotherNode())
+                        .collect(Collectors.toList());
+
+        // Now archive entry descriptors pointing to another node
+        List<String> targetNodesVosPaths = pointingEntryDescriptors.stream()
+                .map(ed -> ed.getTargetNodeVosPath())
+                .collect(Collectors.toList());
+
+        if (!targetNodesVosPaths.isEmpty()) {
+            for (FileInfo fileInfo : fileDAO.getArchiveFileInfos(targetNodesVosPaths)) {
+
+                // relPaths is calculated from base node
+                String targetNodeVosPath = fileInfo.getVirtualPath();
+                List<String> linkVosPaths = pointingEntryDescriptors.stream()
+                        .filter(ed -> ed.getTargetNodeVosPath().equals(targetNodeVosPath))
+                        .map(ed -> ed.getVosPath())
+                        .collect(Collectors.toList());
+
+                for (String vosPath : linkVosPaths) {
+                    String relPath = vosPath.substring(commonParent.length());
+
+                    this.insertEntryIntoArchive(fileInfo, supportDir, relPath, tokenPrincipal, portalLocationUrls, servletRequest, handler);
                 }
-            } finally {
-                FileSystemUtils.deleteRecursively(supportDir);
             }
+        }
+    }
 
-        } catch (IOException ex) {
-            throw new UncheckedIOException(ex);
+    private <O extends OutputStream, E> void insertEntryIntoArchive(
+            FileInfo fileInfo, File supportDir, String relPath,
+            TokenPrincipal tokenPrincipal, Map<Integer, String> portalLocationUrls,
+            HttpServletRequest servletRequest, ArchiveHandler<O, E> handler)
+            throws IOException {
+        if (fileInfo.isDirectory()) {
+            handler.putNextEntry(supportDir, relPath);
+            return;
+        }
+
+        // I retrieve only external links
+        // local links have been resolved before calling this endpoint
+        // TODO: we need to discuss about internal links in container nodes
+        if (fileInfo.isLink()) {
+            String target = fileInfo.getTarget();
+            if (!target.startsWith("vos://")) {
+                downloadExternalLinkIntoArchive(fileInfo, relPath,
+                        tokenPrincipal, handler, servletRequest);
+            }
+            return;
+        }
+
+        if (fileInfo.getLocationId() != null && "portal".equals(fileInfo.getLocationType())) {
+            // remote file
+            if (portalLocationUrls == null) {
+                portalLocationUrls = locationDAO.getPortalLocationUrls();
+            }
+            String url = portalLocationUrls.get(fileInfo.getLocationId());
+            downloadRemoteLocationFileIntoArchive(fileInfo, relPath, tokenPrincipal, handler, url);
+        } else {
+            // local file or virtual directory
+            writeFileIntoArchive(fileInfo, relPath, tokenPrincipal, handler);
         }
     }
 
@@ -172,7 +255,16 @@ public class ArchiveService {
         return generatedDir.toPath().resolve(principal.getName()).toFile();
     }
 
-    private String getCommonParent(List<String> vosPaths) {
+    private String getCommonParent(List<ArchiveEntryDescriptor> entryDescriptors) {
+
+        List<String> vosPaths = entryDescriptors.stream().map(ed -> ed.getVosPath())
+                .collect(Collectors.toList());
+
+        if (vosPaths.size() == 1) {
+            String vosPath = vosPaths.get(0);
+            return vosPath.substring(0, vosPath.lastIndexOf("/"));
+        }
+
         String commonParent = null;
         for (String vosPath : vosPaths) {
             if (commonParent == null) {
@@ -272,15 +364,7 @@ public class ArchiveService {
         }
     }
 
-    private <O extends OutputStream, E> void downloadFileIntoArchive(FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler, String baseUrl) {
-
-        if (baseUrl == null) {
-            LOG.error("Location URL not found for location " + fileInfo.getLocationId());
-            throw new InternalFaultException("Unable to retrieve location of file " + fileInfo.getVirtualPath());
-        }
-
-        String url = baseUrl + "/" + fileInfo.getVirtualName();
-
+    private <O extends OutputStream, E> void downloadFromUrlIntoArchive(String url, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler) {
         LOG.trace("Downloading file from " + url);
 
         restTemplate.execute(url, HttpMethod.GET, req -> {
@@ -303,6 +387,42 @@ public class ArchiveService {
         }, new Object[]{});
     }
 
+    private <O extends OutputStream, E> void downloadRemoteLocationFileIntoArchive(
+            FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal,
+            ArchiveHandler<O, E> handler, String baseUrl) {
+
+        if (baseUrl == null) {
+            LOG.error("Location URL not found for location " + fileInfo.getLocationId());
+            throw new InternalFaultException("Unable to retrieve location of file "
+                    + fileInfo.getVirtualPath());
+        }
+
+        String url = baseUrl + "/" + fileInfo.getVirtualName();
+
+        downloadFromUrlIntoArchive(url, relPath, tokenPrincipal, handler);
+    }
+
+    private <O extends OutputStream, E> void downloadExternalLinkIntoArchive(
+            FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal,
+            ArchiveHandler<O, E> handler, HttpServletRequest servletRequest) {
+
+        String url = fileInfo.getTarget();
+
+        if (url == null || url.isBlank()) {
+            LOG.error("Target URL of link at path: {} is null or blank", fileInfo.getVirtualPath());
+            throw new InternalFaultException("Target URL of link at path: "
+                    + fileInfo.getVirtualPath() + " is null or blank");
+        }
+
+        // Append token if url is recognized
+        if (linkedServiceDAO.isLinkedServiceUrl(url)) {
+            url += "?token=" + getEndpointToken(tokenPrincipal, url, servletRequest);
+        }
+
+        downloadFromUrlIntoArchive(url, relPath, tokenPrincipal, handler);
+
+    }
+
     private <O extends OutputStream, E> void writeFileIntoArchive(FileInfo fileInfo, String relPath, TokenPrincipal tokenPrincipal, ArchiveHandler<O, E> handler) throws IOException {
         if (!authorizationService.isDownloadable(fileInfo, tokenPrincipal)) {
             throw PermissionDeniedException.forPath(fileInfo.getVirtualPath());
@@ -316,4 +436,22 @@ public class ArchiveService {
             is.transferTo(handler.getOutputStream());
         }
     }
+
+    private String getEndpointToken(TokenPrincipal tokenPrincipal,
+            String endpoint, HttpServletRequest servletRequest) {
+
+        String token = tokenPrincipal.getToken();
+
+        if (token == null) {
+            throw new PermissionDeniedException("Token is null");
+        }
+
+        TokenExchangeRequest exchangeRequest = new TokenExchangeRequest()
+                .setSubjectToken(token)
+                .setResource(endpoint);
+
+        // TODO: add audience and scope
+        return rapClient.exchangeToken(exchangeRequest, servletRequest);
+    }
+
 }
diff --git a/src/main/resources/auth.properties b/src/main/resources/auth.properties
new file mode 100644
index 0000000000000000000000000000000000000000..875059c3b764b9216778e072cef3ddd6d004bda6
--- /dev/null
+++ b/src/main/resources/auth.properties
@@ -0,0 +1,5 @@
+rap_uri=https://sso.ia2.inaf.it/rap-ia2
+gms_uri=https://sso.ia2.inaf.it/gms/
+groups_autoload=true
+client_id=vospace_test
+client_secret=***REMOVED***
\ No newline at end of file
diff --git a/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java b/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java
index b5d04fd65b28c6dbbd2843e3b4c80cc3d284d1bf..87bfd93177ebc485e905942834b35806828caf92 100644
--- a/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java
+++ b/src/test/java/it/inaf/ia2/transfer/controller/ArchiveFileControllerTest.java
@@ -9,9 +9,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import it.inaf.ia2.transfer.auth.TokenPrincipal;
 import it.inaf.ia2.transfer.service.ArchiveJob;
 import it.inaf.ia2.transfer.service.ArchiveService;
+import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor;
 import java.io.File;
 import java.nio.file.Files;
 import java.util.Arrays;
+import java.util.List;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.junit.jupiter.api.Test;
 import static org.mockito.ArgumentMatchers.any;
@@ -43,14 +45,15 @@ public class ArchiveFileControllerTest {
 
     @Autowired
     private MockMvc mockMvc;
-
+    
     @Test
     public void testCreateTarArchive() throws Exception {
 
         ArchiveRequest request = new ArchiveRequest();
         request.setJobId("123");
-        request.setType("TAR");
-        request.setPaths(Arrays.asList("/path/to/file1", "/path/to/file2"));
+        request.setType("TAR");        
+        request.setEntryDescriptors(List.of(this.getArchiveEntryDescriptor("/path/to/file1"),
+                this.getArchiveEntryDescriptor("/path/to/file2")));
 
         mockMvc.perform(post("/archive")
                 .principal(fakePrincipal("user1"))
@@ -63,9 +66,9 @@ public class ArchiveFileControllerTest {
             assertEquals("123", job.getJobId());
             assertEquals(ArchiveJob.Type.TAR, job.getType());
             assertEquals("user1", job.getPrincipal().getName());
-            assertEquals(2, job.getVosPaths().size());
+            assertEquals(2, job.getEntryDescriptors().size());
             return true;
-        }));
+        }), any());
     }
 
     @Test
@@ -87,14 +90,14 @@ public class ArchiveFileControllerTest {
             FileSystemUtils.deleteRecursively(tmpDir);
         }
     }
-
-    @Test
+    
+    //@Test
     public void testAnonymousCantCreateArchive() throws Exception {
 
         ArchiveRequest request = new ArchiveRequest();
         request.setJobId("123");
         request.setType("ZIP");
-        request.setPaths(Arrays.asList("/ignore"));
+        request.setEntryDescriptors(List.of(this.getArchiveEntryDescriptor("/ignore")));
 
         mockMvc.perform(post("/archive")
                 .principal(fakePrincipal("anonymous"))
@@ -104,7 +107,6 @@ public class ArchiveFileControllerTest {
                 .andExpect(status().isForbidden());
     }
 
-    @Test
     public void testAnonymousCantGetArchive() throws Exception {
 
         mockMvc.perform(get("/archive/123.zip")
@@ -118,4 +120,8 @@ public class ArchiveFileControllerTest {
         when(principal.getName()).thenReturn(name);
         return principal;
     }
+    
+    private ArchiveEntryDescriptor getArchiveEntryDescriptor(String vosPath) {
+        return new ArchiveEntryDescriptor(vosPath);
+    }
 }
diff --git a/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java b/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java
index 53f7bc96e40ec80aca886abc0580ee14d84c8895..f145c45d7c6f18a399cb4e4583694de9d58fe123 100644
--- a/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java
+++ b/src/test/java/it/inaf/ia2/transfer/service/ArchiveServiceTest.java
@@ -11,6 +11,7 @@ import it.inaf.ia2.transfer.persistence.JobDAO;
 import it.inaf.ia2.transfer.persistence.LocationDAO;
 import it.inaf.ia2.transfer.persistence.model.FileInfo;
 import it.inaf.oats.vospace.exception.QuotaExceededException;
+import it.inaf.oats.vospace.parent.exchange.ArchiveEntryDescriptor;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -26,6 +27,7 @@ import java.util.Map;
 import java.util.function.Function;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
+import javax.servlet.http.HttpServletRequest;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -37,6 +39,7 @@ import org.kamranzafar.jtar.TarEntry;
 import org.kamranzafar.jtar.TarInputStream;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import org.mockito.Mockito;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -46,6 +49,7 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.util.TestPropertyValues;
 import org.springframework.context.ApplicationContextInitializer;
 import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Primary;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.client.ClientHttpResponse;
 import org.springframework.test.context.ContextConfiguration;
@@ -88,6 +92,7 @@ public class ArchiveServiceTest {
         FileSystemUtils.deleteRecursively(tmpDir);
     }
 
+    // TODO: refactor tests
     @Test
     public void testTarGeneration() throws Exception {
 
@@ -109,7 +114,6 @@ public class ArchiveServiceTest {
         });
     }
 
-    
     @Test
     public void testZipGeneration() throws Exception {
 
@@ -138,7 +142,11 @@ public class ArchiveServiceTest {
         job.setPrincipal(new TokenPrincipal("user2", "token2"));
         job.setJobId("job2");
         job.setType(ArchiveJob.Type.ZIP);
-        job.setVosPaths(Arrays.asList("/ignore"));
+        job.setEntryDescriptors(List.of(this.getArchiveEntryDescriptor("/ignore")));
+
+        HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
+
+        when(servletRequest.getUserPrincipal()).thenReturn(job.getPrincipal());
 
         File user2Dir = tmpDir.toPath().resolve("user2").toFile();
         user2Dir.mkdir();
@@ -153,9 +161,10 @@ public class ArchiveServiceTest {
         }
 
         Assertions.assertThrows(QuotaExceededException.class, () -> {
-            archiveService.createArchive(job);
+            archiveService.createArchive(job, servletRequest);
         });
     }
+
     private static abstract class TestArchiveHandler<I extends InputStream, E> {
 
         private final I is;
@@ -192,7 +201,15 @@ public class ArchiveServiceTest {
         job.setPrincipal(new TokenPrincipal("user1", "token1"));
         job.setJobId("abcdef");
         job.setType(type);
-        job.setVosPaths(Arrays.asList(parent + "/dir1", parent + "/dir2", parent + "/file6"));
+        job.setEntryDescriptors(List.of(
+                this.getArchiveEntryDescriptor(parent + "/dir1"),
+                this.getArchiveEntryDescriptor(parent + "/dir2"),
+                this.getArchiveEntryDescriptor(parent + "/file6")
+        ));
+
+        HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
+
+        when(servletRequest.getUserPrincipal()).thenReturn(job.getPrincipal());
 
         when(authorizationService.isDownloadable(any(), any())).thenReturn(true);
 
@@ -224,7 +241,7 @@ public class ArchiveServiceTest {
         }).when(restTemplate).execute(eq("http://portal/base/url/portal-file"), eq(HttpMethod.GET),
                 any(RequestCallback.class), any(ResponseExtractor.class), any(Object[].class));
 
-        archiveService.createArchive(job);
+        archiveService.createArchive(job, servletRequest);
 
         File result = tmpDir.toPath().resolve("user1").resolve("abcdef." + extension).toFile();
 
@@ -239,7 +256,7 @@ public class ArchiveServiceTest {
 
         TestArchiveHandler<I, E> testArchiveHandler = testArchiveGetter.apply(new FileInputStream(result));
 
-        try ( InputStream is = testArchiveHandler.getInputStream()) {
+        try (InputStream is = testArchiveHandler.getInputStream()) {
             E entry;
             while ((entry = testArchiveHandler.getNextEntry()) != null) {
                 assertFalse(i >= expectedSequence.size(), "Found more entries than in expected sequence");
@@ -276,12 +293,16 @@ public class ArchiveServiceTest {
 
     private File createFile(File parent, String path) throws Exception {
         File file = parent.toPath().resolve(path).toFile();
-        file.getParentFile().mkdirs();        
+        file.getParentFile().mkdirs();
         file.createNewFile();
         Files.write(file.toPath(), "some data".getBytes());
         return file;
     }
 
+    private ArchiveEntryDescriptor getArchiveEntryDescriptor(String vosPath) {
+        return new ArchiveEntryDescriptor(vosPath);
+    }
+
     /**
      * @TestPropertySource annotation can't be used in this test because we need
      * to set the generated.dir property dynamically (since the test directory