diff --git a/src/bp-core/bp-core-classes.php b/src/bp-core/bp-core-classes.php
index fb24188..eed9bbd 100644
--- a/src/bp-core/bp-core-classes.php
+++ b/src/bp-core/bp-core-classes.php
@@ -2407,3 +2407,208 @@ function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
 		$output .= '<input type="hidden" class="menu-item-xfn" name="menu-item[' . $possible_object_id . '][menu-item-xfn]" value="'. esc_attr( $item->xfn ) .'" />';
 	}
 }
+
+/**
+ * Base class for the BuddyPress Suggestions API.
+ *
+ * Originally built to power BuddyPress' at-mentions suggestions, it's flexible enough to be used
+ * for similar kinds of future core requirements, or those desired by third-party developers.
+ *
+ * To implement a new suggestions service, create a new class that extends this one, and update
+ * the list of default services in {@link bp_core_get_suggestions()}. If you're building a plugin,
+ * it's recommend that you use the `bp_suggestions_services` filter to do this. :)
+ *
+ * While the implementation of the query logic is left to you, it should be as quick and efficient
+ * as possible. When implementing the abstract methods in this class, pay close attention to the
+ * recommendations provided in the phpDoc blocks, particularly the expected return types.
+ *
+ * @since BuddyPress (2.1.0)
+ */
+abstract class BP_Suggestions {
+
+	/**
+	 * Default arguments common to all suggestions services.
+	 * 
+	 * If your custom service requires further defaults, add them here.
+	 *
+	 * @since BuddyPress (2.1.0)
+	 * @var array
+	 */
+	protected $default_args = array(
+		'limit' => 16,
+		'term'  => '',
+		'type'  => '',
+	);
+
+	/**
+	 * Holds the arguments for the query (about to made to the suggestions service).
+	 *
+	 * This includes `$default_args`, as well as the user-supplied values.
+	 *
+	 * @since BuddyPress (2.1.0)
+	 * @var array
+	 */
+	protected $args = array(
+	);
+
+
+	/**
+	 * Constructor.
+	 *
+	 * @param array $args Optional. If set, used as the parameters for the suggestions service query.
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function __construct( array $args = array() ) {
+		if ( ! empty( $args ) ) {
+			$this->set_query( $args );
+		}
+	}
+
+	/**
+	 * Set the parameters for the suggestions service query.
+	 *
+	 * @param array $args {
+	 *     @type int $limit Maximum number of results to display. Optional, default: 16.
+	 *     @type string $type The name of the suggestion service to use for the request. Mandatory.
+	 *     @type string $term The suggestion service will try to find results that contain this string.
+	 *           Mandatory.
+	 * }
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function set_query( array $args = array() ) {
+		$this->args = wp_parse_args( $args, $this->default_args );
+	}
+
+	/**
+	 * Validate and sanitise the parameters for the suggestion service query.
+	 *
+	 * Be sure to call this class' version of this method when implementing it in your own service.
+	 * If validation fails, you must return a WP_Error object.
+	 *
+	 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function validate() {
+		$this->args['limit'] = absint( $this->args['limit'] );
+		$this->args['term']  = trim( sanitize_text_field( $this->args['term'] ) );
+		$this->args          = apply_filters( 'bp_suggestions_args', $this->args, $this );
+
+
+		// Check for invalid or missing mandatory parameters.
+		if ( ! $this->args['limit'] || ! $this->args['term'] ) {
+			return new WP_Error( 'missing_parameter' );
+		}
+
+		// Check for blocked users (e.g. deleted accounts, or spammers).
+		if ( is_user_logged_in() && ! bp_is_user_active( get_current_user_id() ) ) {
+			return new WP_Error( 'invalid_user' );
+		}
+
+		return apply_filters( 'bp_suggestions_validate_args', true, $this );
+	}
+
+	/**
+	 * Find and return a list of suggestions that match the query.
+	 *
+	 * The return type is important. If no matches are found, an empty array must be returned.
+	 * Matches must be returned as objects in an array.
+	 *
+	 * The object format for each match must be: { 'ID': string, 'image': string, 'name': string }
+	 * For example: { 'ID': 'admin', 'image': 'http://example.com/logo.png', 'name': 'Name Surname' }
+	 *
+	 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
+	 * @since BuddyPress (2.1.0)
+	 */
+	abstract public function get_suggestions();
+}
+
+/**
+ * Adds support for user at-mentions to the Suggestions API.
+ *
+ * This class is in the Core component because it's required by a class in the Groups component,
+ * and Groups is loaded before Members (alphabetical order).
+ *
+ * @since BuddyPress (2.1.0)
+ */
+class BP_Members_Suggestions extends BP_Suggestions {
+
+	/**
+	 * Default arguments for this suggestions service.
+	 *
+	 * @since BuddyPress (2.1.0)
+	 * @var array $args {
+	 *     @type int $limit Maximum number of results to display. Default: 16.
+	 *     @type bool $only_friends If true, only match the current user's friends. Default: false.
+	 *     @type string $term The suggestion service will try to find results that contain this string.
+	 *           Mandatory.
+	 * }
+	 */
+	protected $default_args = array(
+		'limit'        => 16,
+		'only_friends' => false,
+		'term'         => '',
+		'type'         => '',
+	);
+
+
+	/**
+	 * Validate and sanitise the parameters for the suggestion service query.
+	 *
+	 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function validate() {
+		$this->args['only_friends'] = (bool) $this->args['only_friends'];
+		$this->args                 = apply_filters( 'bp_members_suggestions_args', $this->args, $this );
+
+		// Check for invalid or missing mandatory parameters.
+		if ( $this->args['only_friends'] && ( ! bp_is_active( 'friends' ) || ! is_user_logged_in() ) ) {
+			return new WP_Error( 'missing_requirement' );
+		}
+
+		return apply_filters( 'bp_members_suggestions_validate_args', parent::validate(), $this );
+	}
+
+	/**
+	 * Find and return a list of username suggestions that match the query.
+	 *
+	 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function get_suggestions() {
+		$user_query = array(
+			'count_total'     => '',  // Prevents total count
+			'populate_extras' => false,
+			'type'            => 'alphabetical',
+
+			'page'            => 1,
+			'per_page'        => $this->args['limit'],
+			'search_terms'    => $this->args['term'],
+		);
+
+		// Only return matches of friends of this user.
+		if ( $this->args['only_friends'] && is_user_logged_in() ) {
+			$user_query['user_id'] = get_current_user_id();
+		}
+
+		$user_query = apply_filters( 'bp_members_suggestions_query_args', $user_query, $this );
+		if ( is_wp_error( $user_query ) ) {
+			return $user_query;
+		}
+
+
+		$user_query = new BP_User_Query( $user_query );
+		$results    = array();
+
+		foreach ( $user_query->results as $user ) {
+			$result        = new stdClass();
+			$result->ID    = $user->user_nicename;
+			$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
+			$result->name  = bp_core_get_user_displayname( $user->ID );
+
+			$results[] = $result;
+		}
+
+		return apply_filters( 'bp_members_suggestions_get_suggestions', $results, $this );
+	}
+}
\ No newline at end of file
diff --git a/src/bp-core/bp-core-functions.php b/src/bp-core/bp-core-functions.php
index 9e46100..c22b882 100644
--- a/src/bp-core/bp-core-functions.php
+++ b/src/bp-core/bp-core-functions.php
@@ -1921,3 +1921,53 @@ function bp_nav_menu_get_item_url( $slug ) {
 
 	return $nav_item_url;
 }
+
+/** Suggestions***************************************************************/
+
+/**
+ * BuddyPress Suggestions API for types of at-mentions.
+ *
+ * This is used to power BuddyPress' at-mentions suggestions, but it is flexible enough to be used
+ * for similar kinds of future requirements, or those implemented by third-party developers.
+ *
+ * @param array $args
+ * @return array|WP_Error Array of results. If there were any problems, returns a WP_Error object.
+ * @since BuddyPress (2.1.0)
+ */
+function bp_core_get_suggestions( $args ) {
+	$args = wp_parse_args( $args );
+
+	if ( ! $args['type'] ) {
+		return new WP_Error( 'missing_parameter' );
+	}
+
+	// Members @name suggestions.
+	if ( $args['type'] === 'members' ) {
+		$class = 'BP_Members_Suggestions';
+
+		// Members @name suggestions for users in a specific Group.
+		if ( isset( $args['group_id'] ) ) {
+			$class = 'BP_Groups_Member_Suggestions';
+		}
+
+	} else {
+		// If you've built a custom suggestions service, use this to tell BP the name of your class.
+		$class = apply_filters( 'bp_suggestions_services', '', $args );
+	}
+
+	if ( ! $class || ! class_exists( $class ) ) {
+		return new WP_Error( 'missing_parameter' );
+	}
+
+
+	$suggestions = new $class( $args );
+	$validation  = $suggestions->validate();
+
+	if ( is_wp_error( $validation ) ) {
+		$retval = $validation;
+	} else {
+		$retval = $suggestions->get_suggestions();
+	}
+
+	return apply_filters( 'bp_core_get_suggestions', $retval, $args );
+}
\ No newline at end of file
diff --git a/src/bp-friends/bp-friends-filters.php b/src/bp-friends/bp-friends-filters.php
index 6ada0e7..d91386a 100644
--- a/src/bp-friends/bp-friends-filters.php
+++ b/src/bp-friends/bp-friends-filters.php
@@ -49,4 +49,4 @@ function bp_friends_filter_user_query_populate_extras( BP_User_Query $user_query
 		}
 	}
 }
-add_filter( 'bp_user_query_populate_extras', 'bp_friends_filter_user_query_populate_extras', 4, 2 );
+add_filter( 'bp_user_query_populate_extras', 'bp_friends_filter_user_query_populate_extras', 4, 2 );
\ No newline at end of file
diff --git a/src/bp-friends/bp-friends-functions.php b/src/bp-friends/bp-friends-functions.php
index 5f797c7..a7fddff 100644
--- a/src/bp-friends/bp-friends-functions.php
+++ b/src/bp-friends/bp-friends-functions.php
@@ -565,4 +565,4 @@ function friends_remove_data( $user_id ) {
 }
 add_action( 'wpmu_delete_user',  'friends_remove_data' );
 add_action( 'delete_user',       'friends_remove_data' );
-add_action( 'bp_make_spam_user', 'friends_remove_data' );
+add_action( 'bp_make_spam_user', 'friends_remove_data' );
\ No newline at end of file
diff --git a/src/bp-groups/bp-groups-admin.php b/src/bp-groups/bp-groups-admin.php
index 46e6a94..899b41f 100644
--- a/src/bp-groups/bp-groups-admin.php
+++ b/src/bp-groups/bp-groups-admin.php
@@ -984,41 +984,38 @@ function bp_groups_admin_get_usernames_from_ids( $user_ids = array() ) {
 function bp_groups_admin_autocomplete_handler() {
 
 	// Bail if user user shouldn't be here, or is a large network
-	if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) )
+	if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) ) {
 		wp_die( -1 );
+	}
+
+	$term     = isset( $_GET['term'] )     ? sanitize_text_field( $_GET['term'] ) : '';
+	$group_id = isset( $_GET['group_id'] ) ? absint( $_GET['group_id'] )          : 0;
 
-	$return = array();
+	if ( ! $term || ! $group_id ) {
+		wp_die( -1 );
+	}
 
-	// Exclude current group members
-	$group_id = isset( $_GET['group_id'] ) ? wp_parse_id_list( $_GET['group_id'] ) : array();
-	$group_member_query = new BP_Group_Member_Query( array(
-		'group_id'        => $group_id,
-		'per_page'        => 0, // show all
-		'group_role'      => array( 'member', 'mod', 'admin', ),
-		'populate_extras' => false,
-		'count_total'     => false,
+	$suggestions = bp_core_get_suggestions( array(
+		'group_id' => -$group_id,  // A negative value will exclude this group's members from the suggestions.
+		'limit'    => 10,
+		'term'     => $term,
+		'type'     => 'members',
 	) );
 
-	$group_members = ! empty( $group_member_query->results ) ? wp_list_pluck( $group_member_query->results, 'ID' ) : array();
+	$matches = array();
 
-	$terms = isset( $_GET['term'] ) ? $_GET['term'] : '';
-	$users = bp_core_get_users( array(
-		'type'            => 'alphabetical',
-		'search_terms'    => $terms,
-		'exclude'         => $group_members,
-		'per_page'        => 10,
-		'populate_extras' => false
-	) );
+	if ( $suggestions && ! is_wp_error( $suggestions ) ) {
+		foreach ( $suggestions as $user ) {
 
-	foreach ( (array) $users['users'] as $user ) {
-		$return[] = array(
-			/* translators: 1: user_login, 2: user_email */
-			'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), bp_is_username_compatibility_mode() ? $user->user_login : $user->user_nicename, $user->user_email ),
-			'value' => $user->user_nicename,
-		);
+			$matches[] = array(
+				// translators: 1: user_login, 2: user_email
+				'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), $user->name, $user->ID ),
+				'value' => $user->ID,
+			);
+		}
 	}
 
-	wp_die( json_encode( $return ) );
+	wp_die( json_encode( $matches ) );
 }
 add_action( 'wp_ajax_bp_group_admin_member_autocomplete', 'bp_groups_admin_autocomplete_handler' );
 
diff --git a/src/bp-groups/bp-groups-classes.php b/src/bp-groups/bp-groups-classes.php
index 2290660..916cadf 100644
--- a/src/bp-groups/bp-groups-classes.php
+++ b/src/bp-groups/bp-groups-classes.php
@@ -4076,3 +4076,133 @@ function bp_register_group_extension( $group_extension_class = '' ) {
 		add_action( "admin_init", array( &$extension, "_register" ) );
 	' ), 11 );
 }
+
+/**
+ * Adds support for user at-mentions (for users in a specific Group) to the Suggestions API.
+ *
+ * @since BuddyPress (2.1.0)
+ */
+class BP_Groups_Member_Suggestions extends BP_Members_Suggestions {
+
+	/**
+	 * Default arguments for this suggestions service.
+	 *
+	 * @since BuddyPress (2.1.0)
+	 * @var array $args {
+	 *     @type int $group_id Positive integers will restrict the search to members in that group.
+	 *           Negative integers will restrict the search to members in every other group.
+	 *     @type int $limit Maximum number of results to display. Default: 16.
+	 *     @type bool $only_friends If true, only match the current user's friends. Default: false.
+	 *     @type string $term The suggestion service will try to find results that contain this string.
+	 *           Mandatory.
+	 * }
+	 */
+	protected $default_args = array(
+		'group_id'     => 0,
+		'limit'        => 16,
+		'only_friends' => false,
+		'term'         => '',
+		'type'         => '',
+	);
+
+
+	/**
+	 * Validate and sanitise the parameters for the suggestion service query.
+	 *
+	 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function validate() {
+		$this->args['group_id'] = (int) $this->args['group_id'];
+		$this->args             = apply_filters( 'bp_groups_member_suggestions_args', $this->args, $this );
+
+		// Check for invalid or missing mandatory parameters.
+		if ( ! $this->args['group_id'] || ! bp_is_active( 'groups' ) ) {
+			return new WP_Error( 'missing_requirement' );
+		}
+
+		// Check that the specified group_id exists, and that the current user can access it.
+		$the_group = groups_get_group( array(
+			'group_id'        => absint( $this->args['group_id'] ),
+			'populate_extras' => true,
+		) );
+
+		if ( $the_group->id === 0 || ! $the_group->user_has_access ) {
+			return new WP_Error( 'access_denied' );
+		}
+
+		return apply_filters( 'bp_groups_member_suggestions_validate_args', parent::validate(), $this );
+	}
+
+	/**
+	 * Find and return a list of username suggestions that match the query.
+	 *
+	 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
+	 * @since BuddyPress (2.1.0)
+	 */
+	public function get_suggestions() {
+		$user_query = array(
+			'count_total'     => '',  // Prevents total count
+			'populate_extras' => false,
+			'type'            => 'alphabetical',
+
+			'group_role'      => array( 'admin', 'member', 'mod' ),
+			'page'            => 1,
+			'per_page'        => $this->args['limit'],
+			'search_terms'    => $this->args['term'],
+		);
+
+		// Only return matches of friends of this user.
+		if ( $this->args['only_friends'] && is_user_logged_in() ) {
+			$user_query['user_id'] = get_current_user_id();
+		}
+
+		// Positive Group IDs will restrict the search to members in that group.
+		if ( $this->args['group_id'] > 0 ) {
+			$user_query['group_id'] = $this->args['group_id'];
+
+		// Negative Group IDs will restrict the search to members in every other group.
+		} else {
+			$group_query = array(
+				'count_total'     => '',  // Prevents total count
+				'populate_extras' => false,
+				'type'            => 'alphabetical',
+
+				'group_id'        => absint( $this->args['group_id'] ),
+				'group_role'      => array( 'admin', 'member', 'mod' ),
+				'page'            => 1,
+			);
+			$group_users = new BP_Group_Member_Query( $group_query );
+
+			if ( $group_users->results ) {
+				$user_query['exclude'] = wp_list_pluck( $group_users->results, 'ID' );
+			} else {
+				$user_query['include'] = array( 0 );
+			}
+		}
+
+		$user_query = apply_filters( 'bp_groups_member_suggestions_query_args', $user_query, $this );
+		if ( is_wp_error( $user_query ) ) {
+			return $user_query;
+		}
+
+
+		if ( isset( $user_query['group_id'] ) ) {
+			$user_query = new BP_Group_Member_Query( $user_query );
+		} else {
+			$user_query = new BP_User_Query( $user_query );
+		}
+
+		$results = array();
+		foreach ( $user_query->results as $user ) {
+			$result        = new stdClass();
+			$result->ID    = $user->user_nicename;
+			$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
+			$result->name  = bp_core_get_user_displayname( $user->ID );
+
+			$results[] = $result;
+		}
+
+		return apply_filters( 'bp_groups_member_suggestions_get_suggestions', $results, $this );
+	}
+}
\ No newline at end of file
diff --git a/src/bp-templates/bp-legacy/buddypress-functions.php b/src/bp-templates/bp-legacy/buddypress-functions.php
index 784b2dc..455b60e 100644
--- a/src/bp-templates/bp-legacy/buddypress-functions.php
+++ b/src/bp-templates/bp-legacy/buddypress-functions.php
@@ -1383,70 +1383,34 @@ function bp_legacy_theme_ajax_messages_delete() {
  * @return string HTML.
  */
 function bp_legacy_theme_ajax_messages_autocomplete_results() {
-
+	$limit = isset( $_GET['limit'] ) ? absint( $_GET['limit'] )          : (int) apply_filters( 'bp_autocomplete_max_results', 10 );
+	$term  = isset( $_GET['q'] )     ? sanitize_text_field( $_GET['q'] ) : '';
+	
 	// Include everyone in the autocomplete, or just friends?
-	if ( bp_is_current_component( bp_get_messages_slug() ) )
-		$autocomplete_all = buddypress()->messages->autocomplete_all;
-
-	$pag_page     = 1;
-	$limit        = (int) $_GET['limit'] ? $_GET['limit'] : apply_filters( 'bp_autocomplete_max_results', 10 );
-	$search_terms = isset( $_GET['q'] ) ? $_GET['q'] : '';
-
-	$user_query_args = array(
-		'search_terms' => $search_terms,
-		'page'         => intval( $pag_page ),
-		'per_page'     => intval( $limit ),
-	);
-
-	// If only matching against friends, get an $include param for
-	// BP_User_Query
-	if ( ! $autocomplete_all && bp_is_active( 'friends' ) ) {
-		$include = BP_Friends_Friendship::get_friend_user_ids( bp_loggedin_user_id() );
-
-		// Ensure zero matches if no friends are found
-		if ( empty( $include ) ) {
-			$include = array( 0 );
-		}
-
-		$user_query_args['include'] = $include;
+	if ( bp_is_current_component( bp_get_messages_slug() ) ) {
+		$only_friends = ( buddypress()->messages->autocomplete_all === false );
+	} else {
+		$only_friends = true;
 	}
 
-	$user_query = new BP_User_Query( $user_query_args );
-
-	// Backward compatibility - if a plugin is expecting a legacy
-	// filter, pass the IDs through the filter and requery (groan)
-	if ( has_filter( 'bp_core_autocomplete_ids' ) || has_filter( 'bp_friends_autocomplete_ids' ) ) {
-		$found_user_ids = wp_list_pluck( $user_query->results, 'ID' );
-
-		if ( $autocomplete_all ) {
-			$found_user_ids = apply_filters( 'bp_core_autocomplete_ids', $found_user_ids );
-		} else {
-			$found_user_ids = apply_filters( 'bp_friends_autocomplete_ids', $found_user_ids );
-		}
-
-		if ( empty( $found_user_ids ) ) {
-			$found_user_ids = array( 0 );
-		}
-
-		// Repopulate the $user_query variable
-		$user_query = new BP_User_Query( array(
-			'include' => $found_user_ids,
-		) );
-	}
+	$suggestions = bp_core_get_suggestions( array(
+		'limit'        => $limit,
+		'only_friends' => $only_friends,
+		'term'         => $term,
+		'type'         => 'members',
+	) );
 
-	if ( ! empty( $user_query->results ) ) {
-		foreach ( $user_query->results as $user ) {
-			if ( bp_is_username_compatibility_mode() ) {
-				// Sanitize for spaces. Use urlencode() rather
-				// than rawurlencode() because %20 breaks JS
-				$username = urlencode( $user->user_login );
-			} else {
-				$username = $user->user_nicename;
-			}
+	if ( $suggestions && ! is_wp_error( $suggestions ) ) {
+		foreach ( $suggestions as $user ) {
 
 			// Note that the final line break acts as a delimiter for the
 			// autocomplete javascript and thus should not be removed
-			echo '<span id="link-' . esc_attr( $username ) . '" href="' . bp_core_get_user_domain( $user->ID ) . '"></span>' . bp_core_fetch_avatar( array( 'item_id' => $user->ID, 'type' => 'thumb', 'width' => 15, 'height' => 15, 'alt' => $user->display_name ) ) . ' &nbsp;' . bp_core_get_user_displayname( $user->ID ) . ' (' . esc_html( $username ) . ')' . "\n";
+			printf( '<span id="%s" href="#"></span><img src="%s" style="width: 15px"> &nbsp; %s (%s)' . "\n",
+				esc_attr( 'link-' . $user->ID ),
+				esc_url( $user->image ),
+				esc_html( $user->name ),
+				esc_html( $user->ID )
+			);
 		}
 	}
 
diff --git a/tests/phpunit/testcases/apis/suggestions-nonauth.php b/tests/phpunit/testcases/apis/suggestions-nonauth.php
new file mode 100644
index 0000000..a16fb8e
--- /dev/null
+++ b/tests/phpunit/testcases/apis/suggestions-nonauth.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Suggestions API tests specifically for non-authenticated (anonymous) users.
+ *
+ * @group api
+ * @group suggestions
+ */
+class BP_Tests_Suggestions_Non_Authenticated extends BP_UnitTestCase {
+	protected $group_ids    = array();
+	protected $group_slugs  = array();
+	protected $user_ids     = array();
+
+	public function setUp() {
+		parent::setUp();
+
+		$users = array(
+			// user_login, display_name
+			array( 'aardvark',    'Bob Smith' ),
+			array( 'alpaca red',  'William Quinn' ),
+			array( 'cat',         'Lauren Curtis' ),
+			array( 'caterpillar', 'Eldon Burrows' ),
+			array( 'dog green',   'Reece Thornton' ),
+			array( 'pig',         'Joshua Barton' ),
+			array( 'rabbit blue', 'Amber Hooper' ),
+			array( 'smith',       'Robert Bar' ),
+			array( 'snake',       'Eleanor Moore' ),
+			array( 'xylo',        'Silver McFadden' ),
+			array( 'zoom',        'Lisa Smithy' ),
+		);
+
+		// Create some dummy users.
+		foreach( $users as $user ) {
+			$this->user_ids[ $user[0] ] = $this->create_user( array(
+				'display_name' => $user[1],
+				'user_login'   => $user[0],
+			) );
+		}
+
+		$this->group_slugs['hidden']  = 'the-maw';
+		$this->group_slugs['public']  = 'the-great-journey';
+		$this->group_slugs['private'] = 'tsavo-highway';
+
+		// Create dummy groups.
+		$this->group_ids['hidden'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['hidden'],
+			'status'     => 'hidden',
+		) );
+		$this->group_ids['public'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['public'],
+			'status'     => 'public',
+		) );
+		$this->group_ids['private'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['private'],
+			'status'     => 'private',
+		) );
+
+		// Add dummy users to dummy hidden groups.
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['pig'] );
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['alpaca red'] );
+
+		// Add dummy users to dummy public groups.
+		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['alpaca red'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['cat'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['smith'] );
+
+		// Add dummy users to dummy private groups.
+		groups_join_group( $this->group_ids['private'], $this->user_ids['cat'] );
+		groups_join_group( $this->group_ids['private'], $this->user_ids['caterpillar'] );
+	}
+
+
+	/**
+	 * Tests below this point are expected to fail.
+	 */
+
+	public function test_suggestions_with_type_members_and_only_friends() {
+		// only_friends requires authenticated requests
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_and_only_friends() {
+		// only_friends requires authenticated requests
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id'     => $this->group_ids['public'],
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_private() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoom
+	}
+
+	public function test_suggestions_with_type_groupmembers_private_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );  // no access to group.
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );  // no access to group.
+	}
+}
\ No newline at end of file
diff --git a/tests/phpunit/testcases/apis/suggestions.php b/tests/phpunit/testcases/apis/suggestions.php
new file mode 100644
index 0000000..c551a33
--- /dev/null
+++ b/tests/phpunit/testcases/apis/suggestions.php
@@ -0,0 +1,479 @@
+<?php
+/**
+ * Suggestions API tests for authenticated (logged in) users.
+ *
+ * @group api
+ * @group suggestions
+ */
+class BP_Tests_Suggestions_Authenticated extends BP_UnitTestCase {
+	protected $current_user = null;
+	protected $group_ids    = array();
+	protected $group_slugs  = array();
+	protected $old_user_id  = 0;
+	protected $user_ids     = array();
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->old_user_id  = get_current_user_id();
+		$this->current_user = $this->create_user( array(
+			'display_name' => 'Katie Parker',
+			'user_login'   => 'admin',
+		) );
+
+		$this->set_current_user( $this->current_user );
+
+		$users = array(
+			// user_login, display_name
+			array( 'aardvark',    'Bob Smith' ),
+			array( 'alpaca red',  'William Quinn' ),
+			array( 'cat',         'Lauren Curtis' ),
+			array( 'caterpillar', 'Eldon Burrows' ),
+			array( 'dog green',   'Reece Thornton' ),
+			array( 'pig',         'Joshua Barton' ),
+			array( 'rabbit blue', 'Amber Hooper' ),
+			array( 'smith',       'Robert Bar' ),
+			array( 'snake',       'Eleanor Moore' ),
+			array( 'xylo',        'Silver McFadden' ),
+			array( 'zoom',        'Lisa Smithy' ),
+		);
+
+		// Create some dummy users.
+		foreach ( $users as $user ) {
+			$this->user_ids[ $user[0] ] = $this->create_user( array(
+				'display_name' => $user[1],
+				'user_login'   => $user[0],
+			) );
+		}
+
+		// Create some dummy friendships.
+		friends_add_friend( $this->current_user, $this->user_ids['aardvark'], true );
+		friends_add_friend( $this->current_user, $this->user_ids['cat'], true );
+		friends_add_friend( $this->current_user, $this->user_ids['caterpillar'], true );
+		friends_add_friend( $this->current_user, $this->user_ids['pig'], true );
+
+		$this->group_slugs['hidden']  = 'the-maw';
+		$this->group_slugs['public']  = 'the-great-journey';
+		$this->group_slugs['private'] = 'tsavo-highway';
+
+		// Create dummy groups.
+		$this->group_ids['hidden'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['hidden'],
+			'status'     => 'hidden',
+		) );
+		$this->group_ids['public'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['public'],
+			'status'     => 'public',
+		) );
+		$this->group_ids['private'] = $this->factory->group->create( array(
+			'creator_id' => $this->user_ids['xylo'],
+			'slug'       => $this->group_slugs['private'],
+			'status'     => 'private',
+		) );
+
+		// Add dummy users to dummy hidden groups.
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['pig'] );
+		groups_join_group( $this->group_ids['hidden'], $this->user_ids['alpaca red'] );
+
+		// Add dummy users to dummy public groups.
+		groups_join_group( $this->group_ids['public'], $this->current_user );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['aardvark'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['alpaca red'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['cat'] );
+		groups_join_group( $this->group_ids['public'], $this->user_ids['smith'] );
+
+		// Add dummy users to dummy private groups.
+		groups_join_group( $this->group_ids['private'], $this->user_ids['cat'] );
+		groups_join_group( $this->group_ids['private'], $this->user_ids['caterpillar'] );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_user_id );
+	}
+
+
+	public function test_suggestions_with_type_members() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 3, count( $suggestions ) );  // aardvark, smith, zoom.
+	}
+
+	public function test_suggestions_with_type_members_and_limit() {
+		$suggestions = bp_core_get_suggestions( array(
+			'limit' => 2,
+			'type'  => 'members',
+			'term'  => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // two of: aardvark, smith, zoom.
+	}
+
+	public function test_suggestions_with_type_members_and_only_friends() {
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'cat',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar.
+	}
+
+	public function test_suggestions_with_type_members_and_term_as_displayname() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'aardvark',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_members_and_term_as_usernicename() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'eleanor',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // snake.
+	}
+
+	public function test_suggestions_with_term_as_current_user() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'katie',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );
+		$this->assertSame( 'admin', $suggestions[0]->ID );
+	}
+
+
+	public function test_suggestions_with_type_groupmembers_public() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_limit() {
+		$suggestions = bp_core_get_suggestions( array(
+			'limit'    => 1,
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // one of: aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_only_friends() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id'     => $this->group_ids['public'],
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_term_as_displayname() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'aardvark',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_term_as_usernicename() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'robert',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_as_id() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden() {
+		// current_user isn't a member of the hidden group
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// "alpaca red" is in the hidden group
+		$this->set_current_user( $this->user_ids['alpaca red'] );
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // pig
+	}
+
+	public function test_suggestions_with_type_groupmembers_private() {
+		// current_user isn't a member of the private group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// "caterpillar" is in the private group
+		$this->set_current_user( $this->user_ids['caterpillar'] );
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar
+	}
+
+
+	public function test_suggestions_with_type_groupmembers_public_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoom
+	}
+
+	public function test_suggestions_with_type_groupmembers_private_and_exclude_group_from_results() {
+		// current_user isn't a member of the private group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+
+		$this->set_current_user( $this->user_ids['caterpillar'] );
+
+		// "cat" is in the private group, so won't show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEmpty( $suggestions );
+
+		// "zoo" is not the private group, so will show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'zoo',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoo
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group_from_results() {
+		// current_user isn't a member of the hidden group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => $this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+
+		$this->set_current_user( $this->user_ids['alpaca red'] );
+
+		// "alpaca red" is in the hidden group, so won't show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'alpaca red',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEmpty( $suggestions );
+
+		// "zoo" is not the hidden group, so will show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -$this->group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'zoo',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoo
+	}
+
+
+	/**
+	 * These next tests check the format of the response from the Suggestions API.
+	 */
+
+	public function test_suggestions_response_no_matches() {
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => 'abcdefghijklmnopqrstuvwxyz',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertInternalType( 'array', $suggestions );
+		$this->assertEmpty( $suggestions );
+	}
+
+	public function test_suggestions_response_single_match() {
+		$suggestion = bp_core_get_suggestions( array(
+			'term' => 'zoom',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestion ) );
+		$this->assertInternalType( 'array', $suggestion );
+		$this->assertNotEmpty( $suggestion );
+
+		$suggestion = array_shift( $suggestion );
+
+		$this->assertInternalType( 'object', $suggestion );
+		$this->assertAttributeNotEmpty( 'image', $suggestion );
+		$this->assertAttributeNotEmpty( 'ID', $suggestion );
+		$this->assertAttributeNotEmpty( 'name', $suggestion );
+	}
+
+	public function test_suggestions_response_multiple_matches() {
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => 'cat',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertInternalType( 'array', $suggestions );
+		$this->assertNotEmpty( $suggestions );
+
+		foreach ( $suggestions as $suggestion ) {
+			$this->assertInternalType( 'object', $suggestion );
+			$this->assertAttributeNotEmpty( 'image', $suggestion );
+			$this->assertAttributeNotEmpty( 'ID', $suggestion );
+			$this->assertAttributeNotEmpty( 'name', $suggestion );
+		}
+	}
+
+	public function test_suggestions_term_is_case_insensitive() {
+		$lowercase = bp_core_get_suggestions( array(
+			'term' => 'lisa',
+			'type' => 'members',
+		) );
+		$this->assertFalse( is_wp_error( $lowercase ) );
+		$this->assertEquals( 1, count( $lowercase ) );
+
+		$uppercase = bp_core_get_suggestions( array(
+			'term' => 'LISA',
+			'type' => 'members',
+		) );
+		$this->assertFalse( is_wp_error( $uppercase ) );
+		$this->assertEquals( 1, count( $uppercase ) );
+
+		$this->assertSame( $lowercase[0]->ID, $uppercase[0]->ID );
+		$this->assertSame( 'zoom', $lowercase[0]->ID );
+	}
+
+	public function test_suggestions_response_property_types() {
+		$suggestion = bp_core_get_suggestions( array(
+			'term' => 'zoom',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestion ) );
+		$this->assertInternalType( 'array', $suggestion );
+		$this->assertNotEmpty( $suggestion );
+
+		$suggestion = array_shift( $suggestion );
+
+		$this->assertInternalType( 'object', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'image', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'ID', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'name', $suggestion );
+	}
+
+
+	/**
+	 * Tests below this point are expected to fail.
+	 */
+
+	public function test_suggestions_with_bad_type() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'fake_type',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_and_bad_group_ids() {
+		// group_ids can't be a group slug.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => 'fake-group-slug',
+			'type'     => 'members',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_bad_term() {
+		// a non-empty term is mandatory
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => '',
+			'type' => 'members',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+}
\ No newline at end of file
