diff --git a/src/main/java/it/inaf/oats/vospace/TransferController.java b/src/main/java/it/inaf/oats/vospace/TransferController.java
index 330e1dcb4068cb3bcfba69dce5b3216a38f65e34..292ff53474bc3345a784aa9bc87d0560bb6b1762 100644
--- a/src/main/java/it/inaf/oats/vospace/TransferController.java
+++ b/src/main/java/it/inaf/oats/vospace/TransferController.java
@@ -25,6 +25,7 @@ import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import net.ivoa.xml.uws.v1.ErrorSummary;
 import net.ivoa.xml.vospace.v2.Protocol;
 
 @RestController
@@ -97,7 +98,7 @@ public class TransferController {
             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();
@@ -110,6 +111,24 @@ public class TransferController {
         return jobDAO.getJob(jobId).map(j -> ResponseEntity.ok(j)).orElse(ResponseEntity.notFound().build());
     }
 
+    @GetMapping(value = "/transfers/{jobId}/error", produces = {MediaType.TEXT_PLAIN_VALUE})
+    public ResponseEntity<String> getJobError(@PathVariable("jobId") String jobId) {
+        return jobDAO.getJob(jobId).map(j -> {
+            if (j.getPhase().equals(ExecutionPhase.ERROR)) {
+
+                ErrorSummary e = j.getErrorSummary();
+
+                if (e.isHasDetail()) {
+                    return ResponseEntity.ok(e.getDetailMessage());
+                } else {
+                    return ResponseEntity.ok("No error details available");
+                }
+            } else {
+                return ResponseEntity.ok("Job is not in ERROR phase");
+            }
+        }).orElse(ResponseEntity.notFound().build());
+    }
+
     @PostMapping(value = "/transfers/{jobId}/phase")
     public ResponseEntity<?> setJobPhase(@PathVariable("jobId") String jobId, @RequestParam("PHASE") String phase, User principal) {
 
diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
index 85fce66cc1981d25098af3a53fc204aa766f11c6..ecbd244686e729e468434bc8c331377c0f22c14d 100644
--- a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
+++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
@@ -3,6 +3,8 @@ package it.inaf.oats.vospace;
 import it.inaf.ia2.aa.data.User;
 import static it.inaf.oats.vospace.VOSpaceXmlTestUtil.loadDocument;
 import it.inaf.oats.vospace.datamodel.NodeProperties;
+import it.inaf.oats.vospace.exception.ErrorSummaryFactory;
+import it.inaf.oats.vospace.exception.PermissionDeniedException;
 import it.inaf.oats.vospace.persistence.JobDAO;
 import it.inaf.oats.vospace.persistence.LocationDAO;
 import it.inaf.oats.vospace.persistence.NodeDAO;
@@ -46,6 +48,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 import org.w3c.dom.Document;
 import java.util.List;
+import net.ivoa.xml.uws.v1.ErrorSummary;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.BeforeEach;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -258,6 +261,54 @@ public class TransferControllerTest {
 
         verify(jobDao, times(1)).getJob(eq("123"));
     }
+    
+    @Test
+    public void testErrorEndpoint() throws Exception {        
+        JobSummary job = new JobSummary();
+        job.setJobId("123");
+        job.setPhase(ExecutionPhase.EXECUTING);
+        ErrorSummary e = ErrorSummaryFactory.newErrorSummary(
+                new PermissionDeniedException("/pippo1/pippo2")
+        );
+        job.setErrorSummary(e);          
+
+        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
+                
+        String response = mockMvc.perform(get("/transfers/123/error")
+                .accept(MediaType.TEXT_PLAIN_VALUE))
+                .andDo(print())
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+        
+        assertEquals("Job is not in ERROR phase", response);
+        
+        job.setPhase(ExecutionPhase.ERROR);
+        
+        response = mockMvc.perform(get("/transfers/123/error")
+                .accept(MediaType.TEXT_PLAIN_VALUE))
+                .andDo(print())
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+        
+        assertEquals(e.getDetailMessage(), response);
+        
+        e.setHasDetail(false);        
+        
+        response = mockMvc.perform(get("/transfers/123/error")
+                .accept(MediaType.TEXT_PLAIN_VALUE))
+                .andDo(print())
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+        
+        assertEquals("No error details available", response);
+        
+        when(jobDao.getJob(eq("124"))).thenReturn(Optional.ofNullable(null));
+        
+        mockMvc.perform(get("/transfers/124/error")
+                .accept(MediaType.TEXT_PLAIN_VALUE))
+                .andDo(print())
+                .andExpect(status().is4xxClientError());        
+    }
 
     @Test
     public void testGetJobs() throws Exception {