diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
index 4ca0620f12984d9a3716eac56c4a291864033d2c..9cfa265bca80dbb957c26008ee923795a4f04fbd 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
@@ -6,6 +6,7 @@ import it.inaf.ia2.vospace.ui.VOSpaceException;
 import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringWriter;
 import java.io.UncheckedIOException;
 import java.net.ConnectException;
 import java.net.URI;
@@ -20,7 +21,9 @@ import java.util.function.Function;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import javax.xml.bind.JAXB;
+import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.vospace.v2.Node;
+import net.ivoa.xml.vospace.v2.Transfer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -42,7 +45,7 @@ public class VOSpaceClient {
     private final ForkJoinPool jaxbExecutor;
 
     @Autowired
-    private HttpServletRequest servletRequest;
+    protected HttpServletRequest servletRequest;
 
     public VOSpaceClient(@Value("${vospace-backend-url}") String backendUrl) {
         if (backendUrl.endsWith("/")) {
@@ -65,7 +68,18 @@ public class VOSpaceClient {
                 .header("Accept", useJson ? "application/json" : "text/xml")
                 .build();
 
-        return call(request, BodyHandlers.ofInputStream(), 200, res -> parseJson(res, Node.class));
+        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class));
+    }
+
+    public JobSummary startTransferJob(Transfer transfer) {
+
+        HttpRequest request = getRequest("/transfers?PHASE=RUN")
+                .header("Accept", useJson ? "application/json" : "text/xml")
+                .header("Content-Type", useJson ? "application/json" : "text/xml")
+                .POST(HttpRequest.BodyPublishers.ofString(marshal(transfer)))
+                .build();
+
+        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, JobSummary.class));
     }
 
     private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, Function<T, U> responseHandler) {
@@ -116,7 +130,7 @@ public class VOSpaceClient {
         return null;
     }
 
-    private <T> T parseJson(InputStream in, Class<T> type) {
+    private <T> T unmarshal(InputStream in, Class<T> type) {
         try {
             if (useJson) {
                 return MAPPER.readValue(in, type);
@@ -129,6 +143,21 @@ public class VOSpaceClient {
         }
     }
 
+    private String marshal(Object data) {
+        try {
+            if (useJson) {
+                return MAPPER.writeValueAsString(data);
+            } else {
+                try ( StringWriter sw = new StringWriter()) {
+                    JAXB.marshal(data, sw);
+                    return sw.toString();
+                }
+            }
+        } catch (IOException ex) {
+            throw new UncheckedIOException(ex);
+        }
+    }
+
     private static <T> void logServerError(HttpRequest request, HttpResponse<T> response) {
         if (response.body() instanceof String) {
             logServerErrorString(request, (HttpResponse<String>) response);
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ef1a00529f3f16fc1936c1b69417ef5a20f3498
--- /dev/null
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/JobController.java
@@ -0,0 +1,57 @@
+package it.inaf.ia2.vospace.ui.controller;
+
+import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
+import it.inaf.ia2.vospace.ui.data.Job;
+import java.util.ArrayList;
+import java.util.List;
+import net.ivoa.xml.uws.v1.ExecutionPhase;
+import net.ivoa.xml.uws.v1.JobSummary;
+import net.ivoa.xml.vospace.v2.Protocol;
+import net.ivoa.xml.vospace.v2.Transfer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class JobController {
+
+    @Value("${vospace-authority}")
+    private String authority;
+
+    @Autowired
+    private VOSpaceClient client;
+
+    @PostMapping(value = "/recall", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<Job> startRecallFromTapeJob(@RequestBody List<String> paths) {
+
+        if (paths.size() != 1) {
+            throw new UnsupportedOperationException();
+        }
+
+        Transfer transfer = new Transfer();
+        transfer.setDirection("pullToVoSpace");
+        transfer.setTarget("vos://" + authority + paths.get(0));
+        Protocol protocol = new Protocol();
+        protocol.setUri("ia2:tape-recall");
+        transfer.getProtocol().add(protocol);
+
+        JobSummary job = client.startTransferJob(transfer);
+
+        if (job.getPhase() == ExecutionPhase.QUEUED) {
+            return ResponseEntity.ok(new Job(job));
+        }
+        // TODO: proper handling
+        throw new RuntimeException("Error while executing job " + job.getJobId() + ". Job phase is " + job.getPhase() + ". QUEUED expected");
+    }
+
+    @GetMapping(value = "/jobs", produces = MediaType.APPLICATION_JSON_VALUE)
+    public List<Job> getJobs() {
+        // TODO
+        return new ArrayList<>();
+    }
+}
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ea2b8c35e809939fe0a1545a057fdc228ba6bd3
--- /dev/null
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/data/Job.java
@@ -0,0 +1,43 @@
+package it.inaf.ia2.vospace.ui.data;
+
+import net.ivoa.xml.uws.v1.ExecutionPhase;
+import net.ivoa.xml.uws.v1.JobSummary;
+
+public class Job {
+
+    private String id;
+    private ExecutionPhase phase;
+    private boolean read;
+
+    public Job() {
+    }
+
+    public Job(JobSummary job) {
+        this.id = job.getJobId();
+        this.phase = job.getPhase();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public ExecutionPhase getPhase() {
+        return phase;
+    }
+
+    public void setPhase(ExecutionPhase phase) {
+        this.phase = phase;
+    }
+
+    public boolean isRead() {
+        return read;
+    }
+
+    public void setRead(boolean read) {
+        this.read = read;
+    }
+}
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
index 04beff899a6c774dffaceb72a6af006a9cfdc614..91b0b821147597c14c765d4b7025b682bd29e4b7 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesService.java
@@ -85,6 +85,6 @@ public class NodesService {
             return true;
         }
         // TODO: check user group
-        return false;
+        return true; // temporary always true
     }
 }
diff --git a/vospace-ui-backend/src/main/resources/application.properties b/vospace-ui-backend/src/main/resources/application.properties
index 1b0cb7d4cba4d692e30b91ff7dc704fc1f00894f..200742e505cddb0523c6902b22b03389cbfa00b7 100644
--- a/vospace-ui-backend/src/main/resources/application.properties
+++ b/vospace-ui-backend/src/main/resources/application.properties
@@ -2,8 +2,10 @@ server.port=8085
 
 vospace-backend-url=http://localhost:8083/vospace
 vospace-authority=example.com!vospace
-use-json=true
+use-json=false
 
 # For development only:
 spring.profiles.active=dev
 cors.allowed.origin=http://localhost:8080
+
+logging.level.it.inaf=TRACE
diff --git a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
index 2149009f110b16194beeb4c79eed3c1780ca8f15..107d040e5670f38066f54559e01938eb32818b63 100644
--- a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
+++ b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
@@ -8,6 +8,7 @@ import java.net.http.HttpClient;
 import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
 import java.util.concurrent.CompletableFuture;
+import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.vospace.v2.ContainerNode;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.junit.jupiter.api.BeforeEach;
@@ -40,6 +41,8 @@ public class VOSpaceClientTest {
             staticMock.when(HttpClient::newBuilder).thenReturn(builder);
             voSpaceClient = new VOSpaceClient("http://localhost/vospace");
         }
+
+        voSpaceClient.servletRequest = mock(HttpServletRequest.class);
     }
 
     @Test
diff --git a/vospace-ui-frontend/src/api/server/index.js b/vospace-ui-frontend/src/api/server/index.js
index 0c5e4a98868a220971c8a51cfae674cc78c26e5f..077a7c036464d6f746b794b21a3f71dc4e190274 100644
--- a/vospace-ui-frontend/src/api/server/index.js
+++ b/vospace-ui-frontend/src/api/server/index.js
@@ -72,5 +72,17 @@ export default {
         'Cache-Control': 'no-cache'
       }
     }, false, false);
+  },
+  startRecallFromTapeJob(paths) {
+    let url = BASE_API_URL + 'recall';
+    return apiRequest({
+      method: 'POST',
+      url: url,
+      withCredentials: true,
+      headers: {
+        'Cache-Control': 'no-cache'
+      },
+      data: paths
+    });
   }
 }
diff --git a/vospace-ui-frontend/src/components/Main.vue b/vospace-ui-frontend/src/components/Main.vue
index 99455ce8e06b6278aa070c3346a215bcb068ef22..56c9563dea312fabf8ccd2844fe2d634acde0ffc 100644
--- a/vospace-ui-frontend/src/components/Main.vue
+++ b/vospace-ui-frontend/src/components/Main.vue
@@ -57,7 +57,7 @@ export default {
       return items;
     },
     tapeButtonEnabled() {
-      return this.$store.state.tapeButtonEnabled;
+      return this.$store.state.tapeButtonEnabled || true; // temporary always true
     }
   },
   created() {
diff --git a/vospace-ui-frontend/src/store.js b/vospace-ui-frontend/src/store.js
index af99d795d10fa71e9a309188a963e985d841304c..a4cb6813b50103fcf8a361a9f1da25330d0a626a 100644
--- a/vospace-ui-frontend/src/store.js
+++ b/vospace-ui-frontend/src/store.js
@@ -62,7 +62,7 @@ export default new Vuex.Store({
       commit('setTapeButtonEnabled', document.querySelectorAll('#nodes input.tape:checked').length > 0);
     },
     startRecallFromTapeJob({ commit }) {
-      let tapeCheckboxes = document.querySelectorAll('#nodes input.tape:checked');
+      let tapeCheckboxes = document.querySelectorAll('#nodes input:checked'); // temporary: it should be input.tape
       let paths = [];
       for (let i = 0; i < tapeCheckboxes.length; i++) {
         paths.push(tapeCheckboxes[i].getAttribute('data-node'));