diff --git a/src/main/java/it/inaf/oats/vospace/TransferController.java b/src/main/java/it/inaf/oats/vospace/TransferController.java
index 5169f98621c79be4333e43066c7388128013036b..330e1dcb4068cb3bcfba69dce5b3216a38f65e34 100644
--- a/src/main/java/it/inaf/oats/vospace/TransferController.java
+++ b/src/main/java/it/inaf/oats/vospace/TransferController.java
@@ -1,7 +1,6 @@
 package it.inaf.oats.vospace;
 
 import it.inaf.ia2.aa.data.User;
-import it.inaf.oats.vospace.datamodel.NodeProperties;
 import it.inaf.oats.vospace.persistence.JobDAO;
 import java.util.Optional;
 import java.util.UUID;
@@ -10,7 +9,6 @@ import net.ivoa.xml.uws.v1.ExecutionPhase;
 import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.uws.v1.Jobs;
 import net.ivoa.xml.vospace.v2.Transfer;
-import net.ivoa.xml.vospace.v2.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
@@ -24,8 +22,10 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
-import java.util.stream.Collectors;
+import net.ivoa.xml.vospace.v2.Protocol;
 
 @RestController
 public class TransferController {
@@ -70,6 +70,41 @@ public class TransferController {
         return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
     }
 
+    @GetMapping("/synctrans")
+    public ResponseEntity<?> syncTransferUrlParamsMode(@RequestParam("TARGET") String target,
+            @RequestParam("DIRECTION") String direction, @RequestParam("PROTOCOL") String protocolUris, User principal) {
+
+        Transfer transfer = new Transfer();
+        transfer.setTarget(target);
+        transfer.setDirection(direction);
+
+        // CADC client sends multiple protocol parameters and Spring join them using a comma separator.
+        // Some values seems to be duplicated, so a set is used to avoid the duplication
+        for (String protocolUri : new HashSet<>(Arrays.asList(protocolUris.split(",")))) {
+            Protocol protocol = new Protocol();
+            protocol.setUri(protocolUri);
+            transfer.getProtocols().add(protocol);
+        }
+
+        JobSummary jobSummary = newJobSummary(transfer, principal);
+        jobService.createSyncJobResult(jobSummary);
+
+        if (jobSummary.getErrorSummary() != null) {
+            // TODO: decide how to hanlde HTTP error codes
+            // If an error occurs with the synchronous convenience mode where the preferred endpoint
+            // is immediately returned as a redirect, the error information is returned directly in 
+            // the response body with the associated HTTP status code. 
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                    .body(jobSummary.getErrorSummary().getMessage());
+        }
+        
+        // Behaves as if REQUEST=redirect was set, for compatibility with CADC client
+        String endpoint = transfer.getProtocols().get(0).getEndpoint();
+        HttpHeaders headers = new HttpHeaders();
+        headers.set("Location", endpoint);
+        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
+    }
+
     @GetMapping(value = "/transfers/{jobId}", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
     public ResponseEntity<JobSummary> getJob(@PathVariable("jobId") String jobId) {
         return jobDAO.getJob(jobId).map(j -> ResponseEntity.ok(j)).orElse(ResponseEntity.notFound().build());
@@ -147,7 +182,7 @@ public class TransferController {
 
     private JobSummary newJobSummary(Transfer transfer, User principal) {
         String jobId = UUID.randomUUID().toString().replace("-", "");
-                
+
         JobSummary jobSummary = new JobSummary();
         jobSummary.setJobId(jobId);
         jobSummary.setOwnerId(principal.getName());
diff --git a/src/main/java/it/inaf/oats/vospace/UriService.java b/src/main/java/it/inaf/oats/vospace/UriService.java
index ab05851c5a4a3f90434c044a1ba2629e0fa08b0f..fdbb39f1c4c12dedcb2387f23310a5d16da39712 100644
--- a/src/main/java/it/inaf/oats/vospace/UriService.java
+++ b/src/main/java/it/inaf/oats/vospace/UriService.java
@@ -8,7 +8,7 @@ import it.inaf.oats.vospace.datamodel.NodeProperties;
 import it.inaf.oats.vospace.datamodel.NodeUtils;
 import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
 import it.inaf.oats.vospace.exception.InternalFaultException;
-import it.inaf.oats.vospace.exception.InvalidURIException;
+import it.inaf.oats.vospace.exception.InvalidArgumentException;
 import it.inaf.oats.vospace.exception.NodeNotFoundException;
 import it.inaf.oats.vospace.exception.PermissionDeniedException;
 import it.inaf.oats.vospace.exception.ProtocolNotSupportedException;
@@ -22,6 +22,7 @@ import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.uws.v1.ResultReference;
@@ -67,28 +68,55 @@ public class UriService {
 
         job.setResults(results);
         // Moved phase setting to caller method for ERROR management
-        // job.setPhase(ExecutionPhase.COMPLETED);
     }
 
+    /**
+     * Sets the endpoint value for all valid protocols (protocol negotiation).
+     */
     public void setSyncTransferEndpoints(JobSummary job) {
 
         Transfer transfer = getTransfer(job);
 
-        Protocol protocol = transfer.getProtocols().get(0);
+        if (transfer.getProtocols().isEmpty()) {
+            // At least one protocol is expected from client
+            throw new InvalidArgumentException("Transfer contains no protocols");
+        }
+
+        JobService.JobType jobType = JobType.valueOf(transfer.getDirection());
+
+        List<String> validProtocolUris = new ArrayList<>();
+        switch (jobType) {
+            case pullFromVoSpace:
+                validProtocolUris.add("ivo://ivoa.net/vospace/core#httpget");
+                break;
+            case pushToVoSpace:
+                validProtocolUris.add("ivo://ivoa.net/vospace/core#httpput");
+                break;
+        }
+
+        List<Protocol> validProtocols
+                = transfer.getProtocols().stream()
+                        .filter(protocol -> validProtocolUris.contains(protocol.getUri()))
+                        .collect(Collectors.toList());
 
-        if (!"ivo://ivoa.net/vospace/core#httpget".equals(protocol.getUri())
-                && !"ivo://ivoa.net/vospace/core#httpput".equals(protocol.getUri())) {
-            // throw new IllegalStateException("Unsupported protocol " + protocol.getUri());
+        if (validProtocols.isEmpty()) {
+            Protocol protocol = transfer.getProtocols().get(0);
             throw new ProtocolNotSupportedException(protocol.getUri());
         }
-        protocol.setEndpoint(getEndpoint(job, transfer));
+
+        String endpoint = getEndpoint(job, transfer);
+        validProtocols.stream().forEach(p -> p.setEndpoint(endpoint));
+
+        // Returns modified transfer containing only valid protocols
+        transfer.getProtocols().clear();
+        transfer.getProtocols().addAll(validProtocols);
     }
 
     private Node getEndpointNode(String relativePath,
             JobService.JobType jobType,
             User user) {
         Optional<Node> optNode = nodeDao.listNode(relativePath);
-        if (optNode.isPresent()) {            
+        if (optNode.isPresent()) {
             return optNode.get();
         } else {
             switch (jobType) {
@@ -98,11 +126,10 @@ public class UriService {
                 case pullToVoSpace:
                     DataNode newNode = new DataNode();
                     newNode.setUri(relativePath);
-                    return createNodeController.createNode(newNode, user);                     
+                    return createNodeController.createNode(newNode, user);
                 default:
                     throw new InternalFaultException("No supported job direction specified");
             }
-
         }
     }
 
@@ -203,13 +230,13 @@ public class UriService {
 
         List<Object> jobPayload = job.getJobInfo().getAny();
         if (jobPayload.isEmpty()) {
-            throw new IllegalStateException("Empty job payload for job " + job.getJobId());
+            throw new InternalFaultException("Empty job payload for job " + job.getJobId());
         }
         if (jobPayload.size() > 1) {
-            throw new IllegalStateException("Multiple objects in job payload not supported");
+            throw new InternalFaultException("Multiple objects in job payload not supported");
         }
         if (!(jobPayload.get(0) instanceof Transfer)) {
-            throw new IllegalStateException(jobPayload.get(0).getClass().getCanonicalName()
+            throw new InternalFaultException(jobPayload.get(0).getClass().getCanonicalName()
                     + " not supported as job payload. Job id: " + job.getJobId());
         }
 
diff --git a/src/main/java/it/inaf/oats/vospace/exception/InvalidArgumentException.java b/src/main/java/it/inaf/oats/vospace/exception/InvalidArgumentException.java
new file mode 100644
index 0000000000000000000000000000000000000000..d24af645b56c8f1fb4680a18eea85f20027d6d65
--- /dev/null
+++ b/src/main/java/it/inaf/oats/vospace/exception/InvalidArgumentException.java
@@ -0,0 +1,13 @@
+package it.inaf.oats.vospace.exception;
+
+import net.ivoa.xml.uws.v1.ErrorSummaryFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class InvalidArgumentException extends VoSpaceErrorSummarizableException {
+
+    public InvalidArgumentException(String message) {
+        super(message, ErrorSummaryFactory.VOSpaceFault.NODE_NOT_FOUND);
+    }
+}
diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
index 4a47a4783912e6606bea7b7dbeb96b25b94196dd..e59b314915acde93caff690554f3177d30d281d1 100644
--- a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
+++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
@@ -142,7 +142,7 @@ public class TransferControllerTest {
         testPullToVoSpace("/portalnode", getResourceFileContent("pullToVoSpace-portal.xml"));
 
         verify(nodeDao, times(1)).setNodeLocation(eq("/portalnode"), eq(2), eq("lbcr.20130512.060722.fits.gz"));
-        
+
         verify(jobDao, times(2)).updateJob(argThat(j -> {
             assertTrue(j.getResults().get(0).getHref().startsWith("http://archive.lbto.org"));
             assertEquals(ExecutionPhase.COMPLETED, j.getPhase());
@@ -226,17 +226,17 @@ public class TransferControllerTest {
         property.setUri("ivo://ivoa.net/vospace/core#publicread");
         property.setValue("true");
         node.getProperties().add(property);
-        
+
         Property ownerProp = new Property();
         ownerProp.setUri(NodeProperties.CREATOR_URI);
         ownerProp.setValue("user1");
         node.getProperties().add(ownerProp);
-        
+
         Property groupProp = new Property();
         groupProp.setUri(NodeProperties.GROUP_WRITE_URI);
         groupProp.setValue("group1");
         node.getProperties().add(groupProp);
-        
+
         return node;
     }
 
@@ -280,6 +280,22 @@ public class TransferControllerTest {
                 .andReturn().getResponse().getContentAsString();
     }
 
+    @Test
+    public void testSyncTransferUrlParamsMode() throws Exception {
+
+        Node node = mockPublicDataNode();
+        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));
+
+        mockMvc.perform(get("/synctrans")
+                .header("Authorization", "Bearer user1_token")
+                .param("TARGET", "vos://example.com!vospace/mynode")
+                .param("DIRECTION", "pullFromVoSpace")
+                // testing duplicated protocol (CADC client)
+                .param("PROTOCOL", "ivo://ivoa.net/vospace/core#httpget")
+                .param("PROTOCOL", "ivo://ivoa.net/vospace/core#httpget"))
+                .andExpect(status().is3xxRedirection());
+    }
+
     private Jobs getFakeJobs() {
         Jobs jobs = new Jobs();
         jobs.setVersion("1.1");
diff --git a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
index cef14ed89336bb45457dcac622d0921cbed9e0c3..9fbe034100bd5e073cccc335011d0ef7a79e6b92 100644
--- a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
+++ b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
@@ -3,6 +3,8 @@ package it.inaf.oats.vospace;
 import it.inaf.ia2.aa.ServletRapClient;
 import it.inaf.ia2.aa.data.User;
 import it.inaf.oats.vospace.datamodel.NodeProperties;
+import it.inaf.oats.vospace.exception.InvalidArgumentException;
+import it.inaf.oats.vospace.exception.ProtocolNotSupportedException;
 import it.inaf.oats.vospace.persistence.LocationDAO;
 import it.inaf.oats.vospace.persistence.NodeDAO;
 import it.inaf.oats.vospace.persistence.model.Location;
@@ -14,8 +16,10 @@ import net.ivoa.xml.vospace.v2.ContainerNode;
 import net.ivoa.xml.vospace.v2.DataNode;
 import net.ivoa.xml.vospace.v2.Node;
 import net.ivoa.xml.vospace.v2.Property;
+import net.ivoa.xml.vospace.v2.Protocol;
 import net.ivoa.xml.vospace.v2.Transfer;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import static org.mockito.ArgumentMatchers.any;
@@ -44,10 +48,10 @@ public class UriServiceTest {
 
     @MockBean
     private LocationDAO locationDAO;
-    
+
     @MockBean
     private ServletRapClient rapClient;
-    
+
     @MockBean
     private CreateNodeController createNodeController;
 
@@ -66,10 +70,13 @@ public class UriServiceTest {
         @Bean
         @Primary
         public HttpServletRequest servletRequest() {
-            return mock(HttpServletRequest.class);
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            User user = new User().setUserId("anonymous");
+            when(request.getUserPrincipal()).thenReturn(user);
+            return request;
         }
     }
-    
+
     @BeforeEach
     public void init() {
         Location location = new Location();
@@ -102,7 +109,7 @@ public class UriServiceTest {
         creator.setUri(NodeProperties.CREATOR_URI);
         creator.setValue("user1");
         node.getProperties().add(creator);
-        
+
         Property readgroup = new Property();
         readgroup.setUri(NodeProperties.GROUP_READ_URI);
         readgroup.setValue("group1");
@@ -128,40 +135,39 @@ public class UriServiceTest {
 
         assertEquals("http://file-service/mydata1?jobId=job-id&token=<new-token>", job.getResults().get(0).getHref());
     }
-    
+
     @Test
     public void pushToNonexistentNode() {
-        
+
         ContainerNode node = new ContainerNode();
         node.setUri("vos://example.com!vospace/mydata1");
         Property creator = new Property();
         creator.setUri(NodeProperties.CREATOR_URI);
         creator.setValue("user1");
         node.getProperties().add(creator);
-        
+
         Property readgroup = new Property();
         readgroup.setUri(NodeProperties.GROUP_READ_URI);
         readgroup.setValue("group1");
         node.getProperties().add(readgroup);
-        
+
         DataNode dnode = new DataNode();
         dnode.setUri("vos://example.com!vospace/mydata1/mydata2");
         dnode.getProperties().add(creator);
         dnode.getProperties().add(readgroup);
-        
+
         Property writegroup = new Property();
         writegroup.setUri(NodeProperties.GROUP_WRITE_URI);
         writegroup.setValue("group1");
         dnode.getProperties().add(writegroup);
-                
+
         when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
         when(nodeDAO.listNode(eq("/mydata1/mydata2"))).thenReturn(Optional.empty());
-               
-        
+
         User user = mock(User.class);
         when(user.getAccessToken()).thenReturn("<token>");
         when(user.getName()).thenReturn("user1");
-        
+
         when(servletRequest.getUserPrincipal()).thenReturn(user);
 
         when(rapClient.exchangeToken(argThat(req -> {
@@ -172,14 +178,14 @@ public class UriServiceTest {
 
         JobSummary job = getPushToVoSpaceJob();
         Transfer tr = uriService.getTransfer(job);
-        
+
         when(createNodeController.createNode(any(), eq(user))).thenReturn(dnode);
-        
+
         uriService.setTransferJobResult(job, tr);
-        
+
         verify(createNodeController, times(1)).createNode(any(), eq(user));
-        
-        assertEquals("http://file-service/mydata1/mydata2?jobId=job-id2&token=<new-token>", job.getResults().get(0).getHref());    
+
+        assertEquals("http://file-service/mydata1/mydata2?jobId=job-id2&token=<new-token>", job.getResults().get(0).getHref());
     }
 
     @Test
@@ -197,7 +203,131 @@ public class UriServiceTest {
 
         verify(nodeDAO).setNodeLocation(eq("/test/f1/lbtfile.fits"), eq(5), eq("lbtfile.fits"));
     }
-    
+
+    @Test
+    public void testSetSyncTransferEndpointsPullFromVoSpace() {
+
+        mockPublicNode();
+
+        Transfer transfer = new Transfer();
+        transfer.setTarget("vos://example.com!vospace/mydata1");
+        transfer.setDirection("pullFromVoSpace");
+
+        Protocol protocol1 = new Protocol();
+        protocol1.setUri("ivo://ivoa.net/vospace/core#httpget");
+        transfer.getProtocols().add(protocol1);
+        Protocol protocol2 = new Protocol();
+
+        protocol2.setUri("invalid_protocol");
+        transfer.getProtocols().add(protocol2);
+
+        JobSummary job = new JobSummary();
+        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
+        jobInfo.getAny().add(transfer);
+        job.setJobInfo(jobInfo);
+
+        assertEquals(2, transfer.getProtocols().size());
+
+        uriService.setSyncTransferEndpoints(job);
+
+        // invalid protocol is removed
+        assertEquals(1, transfer.getProtocols().size());
+        assertEquals("ivo://ivoa.net/vospace/core#httpget", transfer.getProtocols().get(0).getUri());
+    }
+
+    @Test
+    public void testSetSyncTransferEndpointsPushToVoSpace() {
+
+        Node node = mockPublicNode();
+
+        Property creator = new Property();
+        creator.setUri(NodeProperties.CREATOR_URI);
+        creator.setValue("user1");
+
+        node.getProperties().add(creator);
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("user1");
+        when(servletRequest.getUserPrincipal()).thenReturn(user);
+
+        Transfer transfer = new Transfer();
+        transfer.setTarget("vos://example.com!vospace/mydata1");
+        transfer.setDirection("pushToVoSpace");
+
+        Protocol protocol1 = new Protocol();
+        protocol1.setUri("ivo://ivoa.net/vospace/core#httpput");
+        transfer.getProtocols().add(protocol1);
+        Protocol protocol2 = new Protocol();
+
+        protocol2.setUri("invalid_protocol");
+        transfer.getProtocols().add(protocol2);
+
+        JobSummary job = new JobSummary();
+        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
+        jobInfo.getAny().add(transfer);
+        job.setJobInfo(jobInfo);
+
+        assertEquals(2, transfer.getProtocols().size());
+
+        uriService.setSyncTransferEndpoints(job);
+
+        // invalid protocol is removed
+        assertEquals(1, transfer.getProtocols().size());
+        assertEquals("ivo://ivoa.net/vospace/core#httpput", transfer.getProtocols().get(0).getUri());
+    }
+
+    @Test
+    public void testSetSyncTransferEndpointsUnsupportedProtocol() {
+
+        Transfer transfer = new Transfer();
+        transfer.setTarget("vos://example.com!vospace/mydata1");
+        transfer.setDirection("pullFromVoSpace");
+
+        Protocol protocol = new Protocol();
+        protocol.setUri("invalid_protocol");
+        transfer.getProtocols().add(protocol);
+
+        JobSummary job = new JobSummary();
+        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
+        jobInfo.getAny().add(transfer);
+        job.setJobInfo(jobInfo);
+
+        try {
+            uriService.setSyncTransferEndpoints(job);
+            fail("Expected ProtocolNotSupportedException");
+        } catch (ProtocolNotSupportedException ex) {
+        }
+    }
+
+    @Test
+    public void testSetSyncTransferEndpointsNoProtocols() {
+
+        Transfer transfer = new Transfer();
+        transfer.setTarget("vos://example.com!vospace/mydata1");
+        transfer.setDirection("pullFromVoSpace");
+
+        JobSummary job = new JobSummary();
+        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
+        jobInfo.getAny().add(transfer);
+        job.setJobInfo(jobInfo);
+
+        try {
+            uriService.setSyncTransferEndpoints(job);
+            fail("Expected InvalidArgumentException");
+        } catch (InvalidArgumentException ex) {
+        }
+    }
+
+    private Node mockPublicNode() {
+        DataNode node = new DataNode();
+        node.setUri("vos://example.com!vospace/mydata1");
+        Property publicProperty = new Property();
+        publicProperty.setUri(NodeProperties.PUBLIC_READ_URI);
+        publicProperty.setValue(String.valueOf(true));
+        node.getProperties().add(publicProperty);
+        when(nodeDAO.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
+        return node;
+    }
+
     private JobSummary getJob() {
 
         Transfer transfer = new Transfer();
@@ -214,7 +344,7 @@ public class UriServiceTest {
 
         return job;
     }
-    
+
     private JobSummary getPushToVoSpaceJob() {
         Transfer transfer = new Transfer();
         transfer.setTarget("vos://example.com!vospace/mydata1/mydata2");
@@ -228,6 +358,6 @@ public class UriServiceTest {
 
         job.setJobInfo(jobInfo);
 
-        return job;        
+        return job;
     }
 }