diff --git a/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java b/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..db0582182beb61df521b689b894acd0da3f3dd93
--- /dev/null
+++ b/src/main/java/org/nrg/xapi/rest/data/InvestigatorsApi.java
@@ -0,0 +1,207 @@
+package org.nrg.xapi.rest.data;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.commons.lang3.StringUtils;
+import org.nrg.framework.annotations.XapiRestController;
+import org.nrg.xdat.bean.XnatInvestigatordataBean;
+import org.nrg.xdat.model.XnatInvestigatordataI;
+import org.nrg.xdat.om.XnatInvestigatordata;
+import org.nrg.xdat.rest.AbstractXapiRestController;
+import org.nrg.xdat.security.services.RoleHolder;
+import org.nrg.xdat.security.services.UserManagementServiceI;
+import org.nrg.xft.XFTItem;
+import org.nrg.xft.event.EventUtils;
+import org.nrg.xft.security.UserI;
+import org.nrg.xft.utils.SaveItemHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.web.bind.annotation.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@Api(description = "XNAT Data Investigators API")
+@XapiRestController
+@RequestMapping(value = "/investigators")
+public class InvestigatorsApi extends AbstractXapiRestController {
+    @Autowired
+    public InvestigatorsApi(final UserManagementServiceI userManagementService, final RoleHolder roleHolder, final JdbcTemplate jdbcTemplate) {
+        super(userManagementService, roleHolder);
+        _jdbcTemplate = jdbcTemplate;
+    }
+
+    @ApiOperation(value = "Get list of investigators.", notes = "The investigators function returns a list of all investigators configured in the XNAT system.", response = XnatInvestigatordataI.class, responseContainer = "List")
+    @ApiResponses({@ApiResponse(code = 200, message = "Returns a list of all of the currently configured investigators."),
+                   @ApiResponse(code = 500, message = "An unexpected or unknown error occurred")})
+    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+    @ResponseBody
+    public ResponseEntity<List<XnatInvestigatordataI>> getInvestigators() {
+        return new ResponseEntity<>(_jdbcTemplate.query(PI_QUERY + PI_ORDERBY, PI_ROWMAPPER), HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Gets the requested investigator.", notes = "Returns the investigator with the specified ID.", response = XnatInvestigatordataI.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Returns the requested investigator."),
+                   @ApiResponse(code = 404, message = "The requested investigator wasn't found."),
+                   @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")})
+    @RequestMapping(value = "{investigatorId}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+    @ResponseBody
+    public ResponseEntity<XnatInvestigatordataI> getInvestigator(@PathVariable("investigatorId") final int investigatorId) {
+        final XnatInvestigatordataI investigator = getXnatInvestigator(investigatorId);
+        if (investigator == null) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+        return new ResponseEntity<>(investigator, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Creates a new investigator from the submitted attributes.", notes = "Returns the newly created investigator with the submitted attributes.", response = XnatInvestigatordataI.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Returns the newly created investigator."),
+                   @ApiResponse(code = 403, message = "Insufficient privileges to create the submitted investigator."),
+                   @ApiResponse(code = 404, message = "The requested investigator wasn't found."),
+                   @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")})
+    @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST)
+    @ResponseBody
+    public ResponseEntity<XnatInvestigatordataI> createInvestigator(@RequestBody final XnatInvestigatordataBean investigator) throws Exception {
+        if (StringUtils.isBlank(investigator.getFirstname()) || StringUtils.isBlank(investigator.getLastname())) {
+            _log.error("User {} tried to create investigator without a first or last name.", getSessionUser().getUsername());
+            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+        }
+        final UserI user = getSessionUser();
+        final XFTItem item = XFTItem.NewItem(XnatInvestigatordata.SCHEMA_ELEMENT_NAME, user);
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".title", investigator.getTitle());
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".firstname", investigator.getFirstname());
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".lastname", investigator.getLastname());
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".department", investigator.getDepartment());
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".institution", investigator.getInstitution());
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".email", investigator.getEmail());
+        item.setProperty(XnatInvestigatordata.SCHEMA_ELEMENT_NAME + ".phone", investigator.getPhone());
+        if (!SaveItemHelper.authorizedSave(item, user, false, false, EventUtils.newEventInstance(EventUtils.CATEGORY.DATA, EventUtils.TYPE.REST, EventUtils.CREATE_INVESTTGATOR))) {
+            _log.error("Failed to create a new investigator for user {}. Check the logs for possible errors or exceptions.", user.getUsername());
+            return new ResponseEntity<>(HttpStatus.CONFLICT);
+        }
+        return new ResponseEntity<>(getXnatInvestigator(investigator.getFirstname(), investigator.getLastname()), HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Updates the requested investigator from the submitted attributes.", notes = "Returns the updated investigator.", response = XnatInvestigatordataI.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Returns the updated investigator."),
+                   @ApiResponse(code = 304, message = "The requested investigator is the same as the submitted investigator."),
+                   @ApiResponse(code = 403, message = "Insufficient privileges to edit the requested investigator."),
+                   @ApiResponse(code = 404, message = "The requested investigator wasn't found."),
+                   @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")})
+    @RequestMapping(value = "{investigatorId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PUT)
+    @ResponseBody
+    public ResponseEntity<XnatInvestigatordataI> updateInvestigator(@PathVariable("investigatorId") final int investigatorId, @RequestBody final XnatInvestigatordataBean investigator) throws Exception {
+        final UserI user = getSessionUser();
+        final XnatInvestigatordata existing = XnatInvestigatordata.getXnatInvestigatordatasByXnatInvestigatordataId(investigatorId, user, false);
+        if (existing == null) {
+            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+        }
+
+        boolean isDirty = false;
+        // Only update fields that are actually included in the submitted data and differ from the original source.
+        if (StringUtils.isNotBlank(investigator.getTitle()) && !StringUtils.equals(investigator.getTitle(), existing.getTitle())) {
+            existing.setTitle(investigator.getTitle());
+            isDirty = true;
+        }
+        if (StringUtils.isNotBlank(investigator.getFirstname()) && !StringUtils.equals(investigator.getFirstname(), existing.getFirstname())) {
+            existing.setFirstname(investigator.getFirstname());
+            isDirty = true;
+        }
+        if (StringUtils.isNotBlank(investigator.getLastname()) && !StringUtils.equals(investigator.getLastname(), existing.getLastname())) {
+            existing.setLastname(investigator.getLastname());
+            isDirty = true;
+        }
+        if (StringUtils.isNotBlank(investigator.getDepartment()) && !StringUtils.equals(investigator.getDepartment(), existing.getDepartment())) {
+            existing.setDepartment(investigator.getDepartment());
+            isDirty = true;
+        }
+        if (StringUtils.isNotBlank(investigator.getInstitution()) && !StringUtils.equals(investigator.getInstitution(), existing.getInstitution())) {
+            existing.setInstitution(investigator.getInstitution());
+            isDirty = true;
+        }
+        if (StringUtils.isNotBlank(investigator.getEmail()) && !StringUtils.equals(investigator.getEmail(), existing.getEmail())) {
+            existing.setEmail(investigator.getEmail());
+            isDirty = true;
+        }
+        if (StringUtils.isNotBlank(investigator.getPhone()) && !StringUtils.equals(investigator.getPhone(), existing.getPhone())) {
+            existing.setPhone(investigator.getPhone());
+            isDirty = true;
+        }
+
+        if (isDirty) {
+            if (!SaveItemHelper.authorizedSave(existing, user, false, false, EventUtils.newEventInstance(EventUtils.CATEGORY.DATA, EventUtils.TYPE.REST, EventUtils.MODIFY_INVESTTGATOR))) {
+                _log.error("Failed to save the investigator with ID {}. Check the logs for possible errors or exceptions.");
+                return new ResponseEntity<>(HttpStatus.CONFLICT);
+            }
+            return new ResponseEntity<>(getXnatInvestigator(investigatorId), HttpStatus.OK);
+        }
+
+        return new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
+    }
+
+    @ApiOperation(value = "Deletes the requested investigator.", notes = "Returns true if the requested investigator was successfully deleted. Returns false otherwise.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Returns true to indicate the requested investigator was successfully deleted."),
+                   @ApiResponse(code = 404, message = "The requested investigator wasn't found."),
+                   @ApiResponse(code = 500, message = "An unexpected or unknown error occurred.")})
+    @RequestMapping(value = "{investigatorId}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.DELETE)
+    @ResponseBody
+    public ResponseEntity<Boolean> deleteInvestigator(@PathVariable("investigatorId") final int investigatorId) throws Exception {
+        final UserI user = getSessionUser();
+        final XnatInvestigatordata investigator = XnatInvestigatordata.getXnatInvestigatordatasByXnatInvestigatordataId(investigatorId, user, false);
+        if (investigator == null) {
+            return new ResponseEntity<>(false, HttpStatus.NOT_FOUND);
+        }
+        SaveItemHelper.authorizedDelete(investigator.getItem(), user, EventUtils.newEventInstance(EventUtils.CATEGORY.DATA, EventUtils.TYPE.REST, EventUtils.REMOVE_INVESTTGATOR));
+        return new ResponseEntity<>(true, HttpStatus.OK);
+    }
+
+    private XnatInvestigatordataI getXnatInvestigator(final int investigatorId) {
+        final List<XnatInvestigatordataI> investigators = _jdbcTemplate.query(PI_QUERY + PI_BY_ID, new Object[]{investigatorId}, PI_ROWMAPPER);
+        if (investigators.size() == 0) {
+            return null;
+        }
+        return investigators.get(0);
+    }
+
+    private XnatInvestigatordataI getXnatInvestigator(final String firstname, final String lastname) {
+        final List<XnatInvestigatordataI> investigators = _jdbcTemplate.query(PI_QUERY + PI_BY_NAME, new Object[]{firstname, lastname}, PI_ROWMAPPER);
+        if (investigators.size() == 0) {
+            return null;
+        }
+        return investigators.get(0);
+    }
+
+    private static final Logger _log = LoggerFactory.getLogger(InvestigatorsApi.class);
+
+    // TODO: InvestigatorListResource uses a query that adds the PI's login. XnatInvestigatordataBean has no setter for login, so leaving it off for now.
+    private static final String                           PI_QUERY     = "SELECT DISTINCT ON (lastname, firstname) xnat_investigatorData_id, title, firstname, lastname, institution, department, email, phone FROM xnat_investigatorData ";
+    private static final String                           PI_ORDERBY   = "ORDER BY lastname, firstname";
+    private static final String                           PI_BY_ID     = "WHERE xnat_investigatordata_id = ?";
+    private static final String                           PI_BY_NAME   = "WHERE firstname = ? AND lastname = ?";
+    private final static RowMapper<XnatInvestigatordataI> PI_ROWMAPPER = new RowMapper<XnatInvestigatordataI>() {
+        @Override
+        public XnatInvestigatordataI mapRow(final ResultSet resultSet, final int i) throws SQLException {
+            return new XnatInvestigatordataBean() {{
+                setXnatInvestigatordataId(resultSet.getInt("xnat_investigatordata_id"));
+                setTitle(resultSet.getString("title"));
+                setFirstname(resultSet.getString("firstname"));
+                setLastname(resultSet.getString("lastname"));
+                setDepartment(resultSet.getString("department"));
+                setInstitution(resultSet.getString("institution"));
+                setEmail(resultSet.getString("email"));
+                setPhone(resultSet.getString("phone"));
+            }};
+        }
+    };
+
+    private final JdbcTemplate _jdbcTemplate;
+}
diff --git a/src/main/webapp/xdat-templates/navigations/NoMenuTop.vm b/src/main/webapp/xdat-templates/navigations/NoMenuTop.vm
index 5bd9c6c9c43f09e4115574b86486e9df10230083..7d9a179485e6a19d8e188c9d44867d81ca20209d 100644
--- a/src/main/webapp/xdat-templates/navigations/NoMenuTop.vm
+++ b/src/main/webapp/xdat-templates/navigations/NoMenuTop.vm
@@ -23,7 +23,7 @@ $page.setBgColor($ui.bgcolor)
             </script>
         #else
             #set($username = $user.getUsername())
-            #if($username)
+            #if($username != "" && $username != "guest")
                 ## use default timout value from web.xml as the starting text in the "timeLeft" element
                 <span id="user_info">Logged in as: &nbsp;<a href="$link.setPage("XDATScreen_UpdateUser.vm")">$!username</a> <b>|</b> <a href="$link.setAction("LogoutUser")">Logout</a></span>
                 <script>
diff --git a/src/main/webapp/xdat-templates/navigations/NoninteractiveTop.vm b/src/main/webapp/xdat-templates/navigations/NoninteractiveTop.vm
index 32ba1e26e50f929389c044d0b936f3a93f89945d..25fd66fc6db0c8384c1a041de2f78ec62c76e661 100644
--- a/src/main/webapp/xdat-templates/navigations/NoninteractiveTop.vm
+++ b/src/main/webapp/xdat-templates/navigations/NoninteractiveTop.vm
@@ -18,7 +18,7 @@ $page.setBgColor($ui.bgcolor)
             <span id="user_info">Logged in as: <span style="color:red;">Guest</span>
         #else
             #set($username = $user.getUsername())
-            #if($username)
+            #if($username != "" && $username != "guest")
                 ## use default timout value from web.xml as the starting text in the "timeLeft" element
                 <span id="user_info">Logged in as: &nbsp;<i>$!username</i> <b>|</b> <a href="$link.setAction("LogoutUser")">Logout</a></span>
                 <script>
diff --git a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
index f5adc98afaa50ca9e62ed888867739e6e861595a..ee1b596e04ecb5a2507d31f18a484b155651d7e1 100644
--- a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
+++ b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
@@ -35,7 +35,7 @@
             </script>
         #else
             #set($username = $user.getUsername())
-            #if($username)
+            #if($username != "" && $username != "guest")
                 ## use default timeout value from web.xml as the starting text in the "#timeLeft" element
                 <span id="user_info">Logged in as: &nbsp;<a href="$link.setPage("XDATScreen_UpdateUser.vm")">$!user.getUsername()</a> <b>|</b><span class="tip_icon" style="margin-right:3px;left:2px;top:3px;">
                     <span class="tip shadowed" style="top:20px;z-index:10000;white-space:normal;left:-150px;width:300px;background-color:#ffc;">Your XNAT session will auto-logout after a certain period of inactivity. You can reset that timer without reloading the page by clicking "renew."</span>