Follow and Unfollow

Follow

The source feed should have a group that has “following” activity selector enabled, for example the built-in timeline group. The target feed should have a group that has “current” activity selector enabled, for example the built-in user group.

# Follow a user
response = self.client.feeds.follow(
    source=f"{self.USER_FEED_TYPE}:{self.test_user_id}",
    target=f"{self.USER_FEED_TYPE}:{self.test_user_id_2}",
)

Trying to follow a feed that is already followed will result in an error. You can also use the getOrCreateFollows endpoint that provides an idempotent batch follow mechanism.

Unfollow

When unfollowing a feed, all previous activities of that feed are removed from the timeline.

Trying to unfollow a feed that is not followed, will result in an error. You can also use the getOrCreateUnfollows endpoint that provides an idempotent batch unfollow mechanism.

unfollow_response = self.client.feeds.unfollow(
    f"{self.USER_FEED_TYPE}:{self.test_user_id}",
    f"{self.USER_FEED_TYPE}:{self.test_user_id_2}",
)

Querying Follows

# Query follows
response = self.client.feeds.query_follows(
    limit=10,
    filter={
        "source_feed": f"{self.USER_FEED_TYPE}:{self.test_user_id}"
    }
)

Follows Queryable Built-In Fields

nametypedescriptionsupported operationsexample
source_feedstring or list of stringsThe feed ID that is following$in, $eq{ source_feed: { $eq: 'messaging:general' } }
target_feedstring or list of stringsThe feed ID being followed$in, $eq{ target_feed: { $in: [ 'sports:news', 'tech:updates' ] } }
statusstring or list of stringsThe follow status$in, $eq{ status: { $in: [ 'accepted', 'pending', 'rejected' ] } }
created_atstring, must be formatted as an RFC3339 timestampThe time the follow relationship was created$eq, $gt, $gte, $lt, $lte{ created_at: { $gte: '2023-12-04T09:30:20.45Z' } }

Follow Requests

Some apps require the user’s approval for following them.

# Sara needs to configure the feed with visibility = followers for enabling follow requests
feeds.get_or_create_feed(
    feed_group="user",
    feed_id="sara",
    user_id="sara",
    data={"visibility": "followers"}
)

# Adam requesting to follow the feed
feeds.get_or_create_feed(
    feed_group="timeline",
    feed_id="adam",
    user_id="adam"
)

# Create follow request from adamTimeline to saraFeed
follow_response = feeds.follow(
    source="timeline:adam",
    target="user:sara"
)
print(follow_response["follow"]["status"])  # pending

# Sara accepting
feeds.accept_follow(
    source="timeline:adam",
    target="user:sara",
    follower_role="feed_member"  # optional
)

# or rejecting the request
feeds.reject_follow(
    source="timeline:adam",
    target="user:sara"
)

Push Preferences on Follow

Understanding the difference between push_preference, skip_push and create_notification_activity:

When following a feed, you can set push_preference to control push notifications for future activities from that feed:

  • all - Receive push notifications for all activities from the followed feed
  • none (default) - Don’t receive push notifications for activities from the followed feed

The skip_push controls whether the follow action itself triggers a notification.

The create_notification_activity controls whether the follow action creates an activity on the source feed author’s notification feed.

Note: You usually don’t want to set skip_push and create_notification_activity true at the same time, for more information see the Push Overview page

// Scenario 1: Follow a user and receive notifications for their future activities
await timeline.follow("user:alice", {
  push_preference: "all", // You'll get push notifications for Alice's future posts
});

// Scenario 2: Follow a user but don't get notifications for their activities
await timeline.follow("user:bob", {
  push_preference: "none", // You won't get push notifications for Bob's future posts
});

// Scenario 3: Follow a user silently
await timeline.follow("user:charlie", {
  skip_push: true, // Charlie won't get a "you have a new follower" notification
  push_preference: "all", // But you'll still get notifications for Charlie's future posts
});

// Scenario 4: Silent follow with no future notifications
await timeline.follow("user:diana", {
  skip_push: true, // Diana won't know you followed her
  push_preference: "none", // And you won't get notifications for her posts
});

// Scenario 5: Follow a user and create notification activity for Charile
await timeline.follow("user:charlie", {
  skip_push: true, // Charlie won't get a "you have a new follower" notification
  create_notification_activity: true, // Charlie's notification feed will have a new activity
  push_preference: "all", // But you'll still get notifications for Charlie's future posts
});

Built-in fields of follows

FollowResponse

NameTypeDescriptionConstraints
created_atnumberWhen the follow relationship was createdRequired
customobjectCustom data for the follow relationship-
follower_rolestringRole of the follower (source user) in the follow relationshipRequired
push_preferencestring (all, none)Push preference for notificationsRequired
request_accepted_atnumberWhen the follow request was accepted-
request_rejected_atnumberWhen the follow request was rejected-
source_feedFeedResponseSource feed objectRequired
statusstring (accepted, pending, rejected)Status of the follow relationshipRequired
target_feedFeedResponseTarget feed objectRequired
updated_atnumberWhen the follow relationship was last updatedRequired

Follow Suggestions

Stream provides intelligent follow suggestions to help users discover feeds they might want to follow based on their activity and social graph.

# Get follow suggestions for a user
suggestions = client.feeds.get_follow_suggestions(
    feed_group_id="user",
    limit=10,
    user_id="john"
)

print(f"Algorithm used: {suggestions.algorithm_used}")
print(f"Duration: {suggestions.duration}")

for suggestion in suggestions.suggestions:
    print(f"Suggested feed: {suggestion.fid}")
    print(f"Name: {suggestion.name}")
    print(f"Description: {suggestion.description}")
    print(f"Follower count: {suggestion.follower_count}")
    print(f"Recommendation score: {suggestion.recommendation_score}")
    print(f"Reason: {suggestion.reason}")
    print(f"Algorithm scores: {suggestion.algorithm_scores}")

Response Fields

The follow suggestions response includes:

  • suggestions: Array of suggested feeds to follow
    • feed: Feed identifier
    • name: Feed name
    • description: Feed description
    • visibility: Feed visibility setting
    • member_count: Number of members
    • follower_count: Number of followers
    • following_count: Number of feeds this feed follows
    • created_at: When the feed was created
    • updated_at: When the feed was last updated
    • recommendation_score: Combined recommendation score (0-1)
    • reason: Human-readable reason for the suggestion
    • algorithm_scores: Individual algorithm scores
  • algorithm_used: The algorithm used to generate suggestions
  • duration: Request processing time

Algorithm Types

Stream’s follow suggestions use a sophisticated multi-algorithm approach with weighted scoring:

  • popularity (Weight: 0.3): Based on follower count and engagement

    • Calculates normalized follower count relative to the most popular feed in your app
    • Score = min(follower_count / max_follower_count, 1.0)
    • Helps surface trending and popular content
  • friend-of-friend (Weight: 0.7): Based on social connections and mutual follows

    • Analyzes how many of your followed feeds also follow the suggested feed
    • Score = mutual_follows / your_total_follows
    • Leverages social proof and network effects
  • combined: Uses multiple algorithms with weighted scoring

    • Final score = (popularity_score × 0.3 + friend_of_friend_score × 0.7) / total_weight
    • Provides balanced recommendations combining popularity and social relevance

Note: Additional algorithms will be added in future releases to provide even more sophisticated recommendations.

Scoring System

The recommendation system uses a sophisticated scoring mechanism:

  1. Individual Algorithm Scores: Each algorithm calculates a score from 0.0 to 1.0
  2. Weighted Combination: Scores are combined using configurable weights
  3. Normalization: Final scores are normalized to ensure fair comparison
  4. Filtering: Only feeds with positive combined scores are included
  5. Ranking: Results are sorted by combined score in descending order

Features

  • Excludes feeds already followed by the user
  • Excludes user’s own feeds
  • Sophisticated algorithm to find feeds to follow

Batch follow & unfollow

getOrCreateFollows/getOrCreateUnfollows endpoints allow creating a maximum of 100 follow/unfollow at once.

These are idempotent endpoints (as opposed to follow and unfollow), trying to follow/unfollow a feed that’s already/not yet followed won’t cause errors.

# Batch create follows
response = client.feeds.get_or_create_follows(
    follows=[
        {
            "source": "timeline:john",
            "target": "user:tom",
            # Optional
            "push_preference": "all",
            "custom": {
                "reason": "investment"
            }
        },
        {
            "source": "timeline:john",
            "target": "stock:apple"
        }
    ]
)

print(f"Created follows: {len(response['created'])}")
print(f"Total follows: {len(response['follows'])}")

# Batch remove follows
unfollow_response = client.feeds.get_or_create_unfollows(
    follows=[
        {
            "source": "timeline:john",
            "target": "user:tom"
        }
    ]
)

print(f"Follows that were removed: {len(unfollow_response['follows'])}")
© Getstream.io, Inc. All Rights Reserved.