diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
index f3380ddf4b2d7328d7c611e5101cfd3b1faf0fc2..7b6e495de7bc6a142c584448f48f1fe27d98f90a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
@@ -66,6 +66,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDatabaseInstanceDAO;
 import ch.systemsx.cisd.openbis.generic.shared.ICommonServer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.Translator;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
@@ -121,7 +122,7 @@ import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
 public class GeneralInformationService extends AbstractServer<IGeneralInformationService> implements
         IGeneralInformationService
 {
-    public static final int MINOR_VERSION = 21;
+    public static final int MINOR_VERSION = 22;
 
     @Resource(name = ch.systemsx.cisd.openbis.generic.shared.ResourceNames.COMMON_SERVER)
     private ICommonServer commonServer;
@@ -1083,4 +1084,37 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
         return result;
     }
 
+    @Override
+    public List<Attachment> listAttachmentsForProject(String sessionToken, Project project,
+            boolean allVersions)
+    {
+        final ProjectIdentifier identifier =
+                new ProjectIdentifier(project.getSpaceCode(), project.getCode());
+        final ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project info =
+                commonServer.getProjectInfo(sessionToken, identifier);
+        final List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments =
+                commonServer.listProjectAttachments(sessionToken, new TechId(info.getId()));
+        return Translator.translateAttachments(attachments, allVersions);
+    }
+
+    @Override
+    public List<Attachment> listAttachmentsForExperiment(String sessionToken,
+            Experiment experiment, boolean allVersions)
+    {
+        final List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments =
+                commonServer
+                        .listExperimentAttachments(sessionToken, new TechId(experiment.getId()));
+        return Translator.translateAttachments(attachments, allVersions);
+    }
+
+    @Override
+    public List<Attachment> listAttachmentsForSample(String sessionToken, Sample sample,
+            boolean allVersions)
+    {
+        final List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments =
+                commonServer
+                        .listSampleAttachments(sessionToken, new TechId(sample.getId()));
+        return Translator.translateAttachments(attachments, allVersions);
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
index 31172b3ef92f3b875b981a4dce3323abb0cffff1..d5c998d4eb14930fb21031a8e4190803d9cfeb79 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
@@ -26,6 +26,7 @@ import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.openbis.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.shared.AbstractServerLogger;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType.VocabularyTerm;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
@@ -162,7 +163,8 @@ class GeneralInformationServiceLogger extends AbstractServerLogger implements
     public List<Experiment> listExperiments(String sessionToken, List<Project> projects,
             String experimentType)
     {
-        logAccess(sessionToken, "list-experiments", "EXP_TYPE(%s)", experimentType);
+        logAccess(sessionToken, "list-experiments", "PROJECTS(%s)", "EXP_TYPE(%s)",
+                abbreviate(projects), experimentType);
         return null;
     }
 
@@ -385,4 +387,31 @@ class GeneralInformationServiceLogger extends AbstractServerLogger implements
         return null;
     }
 
+    @Override
+    public List<Attachment> listAttachmentsForProject(String sessionToken, Project project,
+            boolean allVersions)
+    {
+        logAccess(sessionToken, "listAttachmentsForProject", "PROJECT(%s)", "ALL_VERSIONS(%s)",
+                project, allVersions);
+        return null;
+    }
+
+    @Override
+    public List<Attachment> listAttachmentsForExperiment(String sessionToken,
+            Experiment experiment, boolean allVersions)
+    {
+        logAccess(sessionToken, "listAttachmentsForExperiment", "EXPERIMENT(%s)",
+                "ALL_VERSIONS(%s)", experiment, allVersions);
+        return null;
+    }
+
+    @Override
+    public List<Attachment> listAttachmentsForSample(String sessionToken, Sample sample,
+            boolean allVersions)
+    {
+        logAccess(sessionToken, "listAttachmentsForSample", "SAMPLE(%s)", "ALL_VERSIONS(%s)",
+                sample, allVersions);
+        return null;
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java
index 8d580f1c17dbc0e67a087ec8999435a7d2826a98..6f0b80966f72f0c45e2bac17ccc057bd4b322cfc 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/Translator.java
@@ -28,6 +28,9 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.lang.ObjectUtils;
+
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType.ControlledVocabularyPropertyTypeInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
@@ -596,4 +599,56 @@ public class Translator
         }
         return list;
     }
+
+    public static List<Attachment> translateAttachments(
+            List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment> attachments,
+            boolean allVersions)
+    {
+        Collections.sort(attachments,
+                new Comparator<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment>()
+                    {
+                        @Override
+                        public int compare(
+                                ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment o1,
+                                ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment o2)
+                        {
+                            final int fileNameComp = o1.getFileName().compareTo(o2.getFileName());
+                            // Newest version first.
+                            return (fileNameComp == 0) ? (o2.getVersion() - o1.getVersion())
+                                    : fileNameComp;
+                        }
+                    });
+        final List<Attachment> list = new ArrayList<Attachment>(attachments.size());
+        String lastFilenameSeen = null;
+        for (ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment attachment : attachments)
+        {
+            // The newest version will be first. If allVersions == false, skip all the older
+            // versions.
+            if (allVersions == false
+                    && ObjectUtils.equals(lastFilenameSeen, attachment.getFileName()))
+            {
+                continue;
+            }
+            list.add(translate(attachment));
+            lastFilenameSeen = attachment.getFileName();
+        }
+        return list;
+    }
+
+    public static Attachment translate(
+            ch.systemsx.cisd.openbis.generic.shared.basic.dto.Attachment attachment)
+    {
+        final Attachment.AttachmentInitializer initializer = new Attachment.AttachmentInitializer();
+        initializer.setFileName(attachment.getFileName());
+        initializer.setVersion(attachment.getVersion());
+        initializer.setTitle(attachment.getTitle());
+        initializer.setDescription(attachment.getDescription());
+        initializer.setRegistrationDate(attachment.getRegistrationDate());
+        initializer.setUserId(attachment.getRegistrator().getUserId());
+        initializer.setUserEmail(attachment.getRegistrator().getEmail());
+        initializer.setUserFirstName(attachment.getRegistrator().getFirstName());
+        initializer.setUserLastName(attachment.getRegistrator().getLastName());
+        initializer.setPermLink(attachment.getPermlink());
+        return new Attachment(initializer);
+    }
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/remoteapitest/api/v1/GeneralInformationServiceJsonApiTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/remoteapitest/api/v1/GeneralInformationServiceJsonApiTest.java
index c4bbdbbf07107371b81332efbe9e7de71661e1e2..4ecd00e238b93a8c02a7e6a3b9901964ac69434c 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/remoteapitest/api/v1/GeneralInformationServiceJsonApiTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/remoteapitest/api/v1/GeneralInformationServiceJsonApiTest.java
@@ -39,6 +39,7 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.common.string.ToStringComparator;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationChangingService;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
@@ -783,4 +784,95 @@ public class GeneralInformationServiceJsonApiTest extends RemoteApiTestCase
         return null;
     }
 
+    @Test
+    public void testListAttachmentsForExperimentAllVersions()
+    {
+        final List<Experiment> experiments =
+                generalInformationService.listExperiments(sessionToken,
+                        Collections.singletonList("/CISD/NEMO/EXP1"));
+        assertEquals(1, experiments.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        experiments.get(0),
+                        true);
+        assertEquals(4, attachments.size());
+        int version = 4;
+        for (Attachment a : attachments)
+        {
+            assertEquals("exampleExperiments.txt", a.getFileName());
+            assertEquals(version, a.getVersion());
+            assertEquals("", a.getTitle());
+            assertEquals("", a.getDescription());
+            assertTrue(a.getRegistrationDate().getTime() > 0);
+            assertEquals("test", a.getUserId());
+            assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
+            assertNotNull(a.getUserFirstName());
+            assertNotNull(a.getUserLastName());
+            assertEquals(
+                    String.format(
+                            "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=exampleExperiments.txt&version=%d&entity=EXPERIMENT&permId=200811050951882-1028",
+                            version),
+                    a.getPermLink());
+            --version;
+        }
+    }
+
+    @Test
+    public void testListAttachmentsForExperimentLatestVersion()
+    {
+        final List<Experiment> experiments =
+                generalInformationService.listExperiments(sessionToken,
+                        Collections.singletonList("/CISD/NEMO/EXP1"));
+        assertEquals(1, experiments.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        experiments.get(0), false);
+        assertEquals(1, attachments.size());
+        final Attachment a = attachments.get(0);
+        assertEquals("exampleExperiments.txt", a.getFileName());
+        assertEquals(4, a.getVersion());
+        assertEquals("", a.getTitle());
+        assertEquals("", a.getDescription());
+        assertTrue(a.getRegistrationDate().getTime() > 0);
+        assertEquals("test", a.getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
+        assertNotNull(a.getUserFirstName());
+        assertNotNull(a.getUserLastName());
+        assertEquals(
+                "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=exampleExperiments.txt&version=4&entity=EXPERIMENT&permId=200811050951882-1028",
+                a.getPermLink());
+    }
+
+    public void testListAttachmentsForSample()
+    {
+        SearchCriteria searchCriteria = new SearchCriteria();
+        searchCriteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE,
+                "3VCP6"));
+
+        final List<Sample> samples =
+                generalInformationService.searchForSamples(sessionToken, searchCriteria);
+        assertEquals(1, samples.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForSample(sessionToken, samples.get(0),
+                        true);
+        assertEquals(1, attachments.size());
+
+        final Attachment a = attachments.get(0);
+        assertEquals("sampleHistory.txt", a.getFileName());
+        assertEquals("", a.getTitle());
+        assertEquals("", a.getDescription());
+        assertEquals(1, a.getVersion());
+        assertTrue(a.getRegistrationDate().getTime() > 0);
+        assertEquals("test", a.getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
+        assertNotNull(a.getUserFirstName());
+        assertNotNull(a.getUserLastName());
+        assertEquals(
+                "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=sampleHistory.txt&version=1&entity=SAMPLE&permId=200811050946559-980",
+                a.getPermLink());
+    }
+
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java
index 388df743eef71bff144c84a782fcd877a4f0b944..87c4c90824a88769bb2a509f219ab9a8a54791d0 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/api/v1/GeneralInformationServiceTest.java
@@ -40,6 +40,7 @@ import org.testng.annotations.Test;
 
 import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Attachment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
@@ -1661,6 +1662,97 @@ public class GeneralInformationServiceTest extends SystemTestCase
         assertEquals("TEST_METAPROJECTS", metaprojectAssignments.getMetaproject().getName());
     }
 
+    @Test
+    public void testListAttachmentsForExperimentAllVersions()
+    {
+        final List<Experiment> experiments =
+                generalInformationService.listExperiments(sessionToken,
+                        Collections.singletonList("/CISD/NEMO/EXP1"));
+        assertEquals(1, experiments.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        experiments.get(0),
+                        true);
+        assertEquals(4, attachments.size());
+        int version = 4;
+        for (Attachment a : attachments)
+        {
+            assertEquals("exampleExperiments.txt", a.getFileName());
+            assertEquals(version, a.getVersion());
+            assertEquals("", a.getTitle());
+            assertEquals("", a.getDescription());
+            assertTrue(a.getRegistrationDate().getTime() > 0);
+            assertEquals("test", a.getUserId());
+            assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
+            assertNotNull(a.getUserFirstName());
+            assertNotNull(a.getUserLastName());
+            assertEquals(
+                    String.format(
+                            "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=exampleExperiments.txt&version=%d&entity=EXPERIMENT&permId=200811050951882-1028",
+                            version),
+                    a.getPermLink());
+            --version;
+        }
+    }
+
+    @Test
+    public void testListAttachmentsForExperimentLatestVersion()
+    {
+        final List<Experiment> experiments =
+                generalInformationService.listExperiments(sessionToken,
+                        Collections.singletonList("/CISD/NEMO/EXP1"));
+        assertEquals(1, experiments.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForExperiment(sessionToken,
+                        experiments.get(0), false);
+        assertEquals(1, attachments.size());
+        final Attachment a = attachments.get(0);
+        assertEquals("exampleExperiments.txt", a.getFileName());
+        assertEquals(4, a.getVersion());
+        assertEquals("", a.getTitle());
+        assertEquals("", a.getDescription());
+        assertTrue(a.getRegistrationDate().getTime() > 0);
+        assertEquals("test", a.getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
+        assertNotNull(a.getUserFirstName());
+        assertNotNull(a.getUserLastName());
+        assertEquals(
+                "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=exampleExperiments.txt&version=4&entity=EXPERIMENT&permId=200811050951882-1028",
+                a.getPermLink());
+    }
+
+    public void testListAttachmentsForSample()
+    {
+        SearchCriteria searchCriteria = new SearchCriteria();
+        searchCriteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE,
+                "3VCP6"));
+
+        final List<Sample> samples =
+                generalInformationService.searchForSamples(sessionToken, searchCriteria);
+        assertEquals(1, samples.size());
+
+        final List<Attachment> attachments =
+                generalInformationService.listAttachmentsForSample(sessionToken, samples.get(0),
+                        true);
+        assertEquals(1, attachments.size());
+
+        final Attachment a = attachments.get(0);
+        assertEquals("sampleHistory.txt", a.getFileName());
+        assertEquals("", a.getTitle());
+        assertEquals("", a.getDescription());
+        assertEquals(1, a.getVersion());
+        assertTrue(a.getRegistrationDate().getTime() > 0);
+        assertEquals("test", a.getUserId());
+        assertEquals("franz-josef.elmer@systemsx.ch", a.getUserEmail());
+        assertNotNull(a.getUserFirstName());
+        assertNotNull(a.getUserLastName());
+        assertEquals(
+                "http://localhost/openbis/index.html?viewMode=SIMPLE#action=DOWNLOAD_ATTACHMENT&file=sampleHistory.txt&version=1&entity=SAMPLE&permId=200811050946559-980",
+                a.getPermLink());
+    }
+
     private void sortDataSets(List<DataSet> dataSets)
     {
         Collections.sort(dataSets, new Comparator<DataSet>()