diff --git a/doc/RELEASE_NOTES.txt b/doc/RELEASE_NOTES.txt
index 175db19f5ad3c9d63ea8abecc3b1cc3eb66dd3e4..c48e1db6479028998f64b83b8626de08b15ce9d8 100644
--- a/doc/RELEASE_NOTES.txt
+++ b/doc/RELEASE_NOTES.txt
@@ -1,3 +1,17 @@
+Release 1.1 ()
+=============================
+
+- Added the following functionality for managing DAQs:
+  - Max. run time specification causes DAQ to be stopped automatically
+  - Target directory specification causes files to be uploaded into
+    a specific directory relative to experiment root path
+  - Upload data/target directory on exit specify upload of the given
+    directory automatically after DAQ completes
+- Added the following functionality for managing uploads:
+  - Target directory specification causes files to be uploaded into
+    a specific directory relative to experiment root path
+- Introduced sphinx as python API documentation framework
+
 Release 1.0 (01/31/2017)
 =============================
 
diff --git a/doc/sphinx/.svnignore b/doc/sphinx/.svnignore
new file mode 100644
index 0000000000000000000000000000000000000000..9ef96044faba86acfbf59bd0deb4545625df837f
--- /dev/null
+++ b/doc/sphinx/.svnignore
@@ -0,0 +1,2 @@
+build
+
diff --git a/doc/sphinx/source/dm.aps_bss.api.rst b/doc/sphinx/source/dm.aps_bss.api.rst
new file mode 100644
index 0000000000000000000000000000000000000000..00946b37a6dbeaf1c387c384bfb6c322e01a6941
--- /dev/null
+++ b/doc/sphinx/source/dm.aps_bss.api.rst
@@ -0,0 +1,12 @@
+.. automodule:: dm.aps_bss.api
+
+.. currentmodule:: dm.aps_bss.api
+
+ApsBssApi
+---------
+
+.. autoclass:: dm.aps_bss.api.apsBssApi.ApsBssApi()
+    :show-inheritance: 
+    :members:  __init__, listRuns, getCurrentRun, listBeamlineProposals, getBeamlineProposal
+
+
diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst
index d9d39b903dc1dcbabd6c744a2e340a89a62b73ab..d7220f53355315c8812f0e9bc38bea52a622de8b 100644
--- a/doc/sphinx/source/index.rst
+++ b/doc/sphinx/source/index.rst
@@ -13,6 +13,7 @@ The `dm` package contains python APIs for accessing Data Management services.
    :caption: Contents:
 
    dm.daq_web_service.api
+   dm.aps_bss.api
 
 Indices and tables
 ==================
diff --git a/src/python/dm/aps_bss/__init__.py b/src/python/dm/aps_bss/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/python/dm/aps_bss/api/__init__.py b/src/python/dm/aps_bss/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/python/dm/aps_bss/api/apsBssApi.py b/src/python/dm/aps_bss/api/apsBssApi.py
new file mode 100755
index 0000000000000000000000000000000000000000..7a9f8e63ca84c287975ba9cdc26e622bf489490c
--- /dev/null
+++ b/src/python/dm/aps_bss/api/apsBssApi.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+
+import os
+from dm.common.exceptions.configurationError import ConfigurationError
+from dm.common.api.dmApi import DmApi
+from dm.aps_bss.impl.bssClient import BssClient
+
+class ApsBssApi(DmApi):
+    ''' Data Management API for APS Beamline Scheduling System. '''
+    def __init__(self, beamlineName=None, username=None, password=None):
+        '''
+        Constructor.
+
+        :param beamlineName: beamline name (if not provided, environment variable DM_BEAMLINE_NAME must be set)
+        :type beamlineName: str
+
+        :param username: BSS username (if not provided, environment variable DM_BSS_LOGIN_FILE must be set)
+        :type username: str
+
+        :param password: BSS password (if not provided, environment variable DM_BSS_LOGIN_FILE must be set)
+        :type password: str
+
+        :raises ConfigurationError: in case beamline name or username/password are not provided, and corresponding environment variables are not set
+
+        Note that environment variable DM_BSS_LOGIN_FILE should contain 
+        path to a file containing BSS username and password as a single line in 
+        the '<username>|<password>' form.
+
+        >>> api = ApsBssApi(beamlineName='1-ID-B,C,E', username='dm', password='XYZ')
+        '''
+        DmApi.__init__(self)
+        if not username or not password:
+            loginFile = os.environ.get('DM_BSS_LOGIN_FILE')
+            if loginFile:
+                try:
+                    # Assume form <username>|<password>
+                    tokenList = open(loginFile).readline().split('|')
+                    if len(tokenList) == 2:
+                        username = tokenList[0].strip()
+                        password = tokenList[1].strip()
+                except Exception, ex:
+                    raise ConfigurationError('Could not determine username/password from BSS login file: %s' % str(ex))
+        if not username or not password:
+            raise ConfigurationError('Username and/or password were not provided, and DM_BSS_LOGIN_FILE is not set.')
+
+        if not beamlineName:
+            beamlineName = os.environ.get('DM_BEAMLINE_NAME')
+        if not beamlineName:
+            raise ConfigurationError('Beamline name was not provided, and DM_BEAMLINE_NAME is not set.')
+
+        self.beamlineName = beamlineName
+        self.bssClient = BssClient(username, password)
+
+    @DmApi.execute2
+    def listRuns(self):
+        '''
+        List all available runs.
+
+        :returns: list of RunInfo objects
+
+        :raises DmException: for any error
+
+        >> runs = api.listRuns()
+        >> for run in runs:
+        >>     print run['name']
+        '''
+        return self.bssClient.listRuns()
+
+    @DmApi.execute2
+    def getCurrentRun(self):
+        '''
+        Find current run.
+
+        :returns: RunInfo object
+
+        :raises DmException: for any error
+
+        >> run = api.getCurrentRun()
+        '''
+        return self.bssClient.getCurrentRun()
+
+    @DmApi.execute2
+    def listBeamlineProposals(self, runName=None):
+        '''
+        List beamline proposals for a given run.
+
+        :param runName: run name (if not provided, current run name will be used)
+        :type runName: str
+
+        :returns: list of ProposalInfo objects
+
+        :raises DmException: for any error
+
+        >> proposals = api.listBeamlineProposals()
+        >> for proposal in proposals:
+        >>     print proposal['title']
+        '''
+        if not runName:
+            runName = self.getCurrentRun()['name']
+        return self.bssClient.listBeamlineProposals(self.beamlineName, runName)
+
+    @DmApi.execute2
+    def getBeamlineProposal(self, proposalId, runName=None):
+        '''
+        Get beamline proposal with a given id.
+
+        :param porposalId: proposal id
+        :type runName: str
+
+        :param runName: run name (if not provided, current run name will be used)
+        :type runName: str
+
+        :returns: ProposalInfo object
+
+        :raises ObjectNotFound: if proposal with the given id does not exist
+
+        :raises DmException: for any other error
+
+        >> proposal = api.getBeamlineProposal(42096)
+        >> for experimenter in proposal['experimenters']:
+        >>     print experimenter['badge'], experimenter['lastName'] 
+        '''
+        if not runName:
+            runName = self.getCurrentRun()['name']
+        return self.bssClient.getBeamlineProposal(proposalId, self.beamlineName, runName)
+
+#######################################################################
+# Testing.
+if __name__ == '__main__':
+    api = ApsBssApi('1-ID-B,C,E')
+    print 'RUNS', api.listRuns()
+    print 'CURRRENT RUN: ', api.getCurrentRun()
+    print 'PROPOSALS: ', api.listBeamlineProposals()
+    print
+    print 'FIND PROPOSAL: ', api.getBeamlineProposal(42096)
+    print 'FIND PROPOSAL: ', api.getBeamlineProposal(52096)
+
+
diff --git a/src/python/dm/aps_bss/impl/__init__.py b/src/python/dm/aps_bss/impl/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/python/dm/aps_bss/impl/bssClient.py b/src/python/dm/aps_bss/impl/bssClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5224e4c377b5ac3888f1581acace377206474bd
--- /dev/null
+++ b/src/python/dm/aps_bss/impl/bssClient.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+
+import datetime
+
+from suds.wsse import Security
+from suds.wsse import UsernameToken
+from suds.client import Client
+
+from dm.common.exceptions.objectNotFound import ObjectNotFound
+from dm.common.objects.runInfo import RunInfo
+from dm.common.objects.beamlineInfo import BeamlineInfo
+from dm.common.objects.proposalInfo import ProposalInfo
+from dm.common.objects.apsUserInfo import ApsUserInfo
+from dm.common.exceptions.dmException import DmException
+from dm.common.utility.loggingManager import LoggingManager
+
+class BssClient:
+
+    WSDL_URL = 'https://schedule.aps.anl.gov/beamschedds/springws'
+    CACHE_DURATION_IN_SECONDS = 10
+
+    def __init__(self, username, password):
+        self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__)
+        self.__configure(username, password)
+
+    def __configure(self, username, password):
+        beamlineScheduleServiceUrl = self.WSDL_URL + '/beamlineScheduleService/beamlineScheduleWebService.wsdl'
+        runScheduleServiceUrl = self.WSDL_URL + '/runScheduleService/runScheduleWebService.wsdl'
+
+        try:
+            self.runScheduleServiceClient = Client(runScheduleServiceUrl)
+            self.runScheduleServiceClient.options.cache.setduration(seconds=self.CACHE_DURATION_IN_SECONDS)
+            self.setSoapHeader(self.runScheduleServiceClient, username, password)
+        
+            self.beamlineScheduleServiceClient = Client(beamlineScheduleServiceUrl)
+            self.beamlineScheduleServiceClient.options.cache.setduration(seconds=self.CACHE_DURATION_IN_SECONDS)
+            self.setSoapHeader(self.beamlineScheduleServiceClient, username, password)
+        except Exception, ex:
+            self.logger.error('Cannot open BSS connection: %s' % str(ex))
+            raise DmException(exception=ex)
+         
+    @classmethod
+    def setSoapHeader(cls, client, username, password):
+        security = Security()
+        token = UsernameToken(username, password)
+        token.setcreated()
+        security.tokens.append(token)
+        client.set_options(wsse=security)
+
+    def listRuns(self):
+        ''' Return list of all runs. '''
+        result = self.runScheduleServiceClient.service.findAllRuns()
+        runArray = result.run
+        runs = []
+        for run in runArray:
+            runs.append(RunInfo({'name' : run.runName, 'startTime' : run.startTime, 'endTime' : run.endTime}))
+        return runs
+
+    def listRunsBetweenDates(self, startDate, endDate):
+        ''' Find list of runs between given startDate and endDate. '''
+        result = self.runScheduleServiceClient.service.findAllRuns()
+        runArray = result.run
+        runs = []
+        for run in runArray:
+            if run.startTime >= startDate and run.endTime <= endDate:
+                runs.append(RunInfo({'name' : run.runName, 'startTime' : run.startTime, 'endTime' : run.endTime}))
+        return runs
+
+    def getRunForDates(self, startDate, endDate):
+        ''' Find run that spans given startDate and endDate. '''
+        result = self.runScheduleServiceClient.service.findAllRuns()
+        runArray = result.run
+        for run in runArray:
+            if startDate >= run.startTime and endDate <= run.endTime:
+                return RunInfo({'name' : run.runName, 'startTime' : run.startTime, 'endTime' : run.endTime})
+        raise ObjectNotFound('No run found.')
+
+    def getCurrentRun(self):
+        ''' Find current run. '''
+        now = datetime.datetime.now()
+        return self.getRunForDates(now, now)
+
+    def listBeamlines(self):
+        ''' Find list of all beamlines. '''
+        result = self.beamlineScheduleServiceClient.service.findAllBeamlines()
+        beamlineArray = result.beamline
+        beamlines = []
+        for beamline in beamlineArray:
+            beamlines.append(BeamlineInfo({'name' : beamline.beamlineName, 'id' : beamline.id}))
+        return beamlines
+
+    def createProposalInfo(self, proposal):
+        experimenterArray = proposal.experimenters.experimenter
+        experimenters = []
+        for experimenter in experimenterArray:
+            user = ApsUserInfo({
+                'id' : experimenter.id,
+                'badge' : experimenter.badge,
+                'email' : experimenter.email,
+                'firstName' : experimenter.firstName,
+                'instId' : experimenter.instId,
+                'institution' : experimenter.institution,
+                'lastName' : experimenter.lastName
+            })
+            if hasattr(experimenter, 'piFlag'):
+                user['piFlag'] = experimenter.piFlag
+
+            experimenters.append(user)
+        proposalInfo = ProposalInfo({
+            'title' : proposal.proposalTitle,
+            'id' : proposal.id,
+            'experimenters' : experimenters
+        })
+        return proposalInfo
+
+    def listBeamlineProposals(self, beamlineName, runName):
+        ''' Find beamline schedule for a given beamlineName and runName. '''
+        schedule = self.beamlineScheduleServiceClient.service.findBeamlineSchedule(beamlineName, runName)
+        activitiesArray = schedule.activities.activity
+        proposals = []
+        for activity in activitiesArray:
+            if hasattr(activity, 'beamtimeRequest'):
+                proposal = activity.beamtimeRequest.proposal
+                proposals.append(self.createProposalInfo(proposal))
+        return proposals
+
+    def getBeamlineProposal(self, proposalId, beamlineName, runName):
+        ''' Find proposal with a given id, beamlineName and runName. '''
+        schedule = self.beamlineScheduleServiceClient.service.findBeamlineSchedule(beamlineName, runName)
+        activitiesArray = schedule.activities.activity
+        proposals = []
+        for activity in activitiesArray:
+            if hasattr(activity, 'beamtimeRequest'):
+                proposal = activity.beamtimeRequest.proposal
+                if proposal.id == proposalId:
+                    return self.createProposalInfo(proposal)
+        raise ObjectNotFound('Proposal with id %s does not exist (beamline: %s; run: %s).' % (proposalId, beamlineName, runName))
+
+if __name__ == '__main__':
+    bss = BssClient('DMADMIN', 'A2Fxew@11:76am')
+    now = datetime.datetime.now()
+    days = datetime.timedelta(days=180)
+    before = now - days
+    print before, now
+    runNames = bss.getRunsBetweenDates(before, now)
+    print 'RUNS: ', runNames 
+    print 'CURRENT RUN: ', bss.getCurrentRun()
+    print bss.listBeamlines()
+    print bss.listBeamlineProposals('1-ID-B,C,E', '2017-1')
+    print
+    print bss.getBeamlineProposal(51190, '1-ID-B,C,E', '2017-1')
+    print
+    print bss.getBeamlineProposal(48258, '1-ID-B,C,E', '2017-1')
+
+
diff --git a/src/python/dm/daq_web_service/api/experimentDaqApi.py b/src/python/dm/daq_web_service/api/experimentDaqApi.py
index f0e0b00d36ce3d295031fecde34bb1064c3e200e..db32234b4cf0a32ceb1e7846c05c67231ed975ae 100755
--- a/src/python/dm/daq_web_service/api/experimentDaqApi.py
+++ b/src/python/dm/daq_web_service/api/experimentDaqApi.py
@@ -15,7 +15,7 @@ from daqRestApi import DaqRestApi
 
 class ExperimentDaqApi(DaqRestApi):
     ''' 
-    This class is used to access experiment interface provided by the
+    Data Management API for accessing experiment interface provided by the
     DAQ service on a DM experiment station.
     '''