"In its simplest form, an activity consists of an actor, a verb, and an object. It tells the story of a person performing an action on or with an object."
Adding an activity in its simplest form means passing an object with the following basic properties:
Actor
Verb
Object
Recommended:
Foreign Id
Time
Here's an example:
“Erik is pinning Hawaii to his Places to Visit board.”
Let's break the example down:
Actor : "Eric" (User:1)
Verb : "pin"
Object : "Hawaii" (Place:42)
Foreign Id : "Eric's board activity" (Activity:1)
Time : 2017-07-01T20:30:45.123
As seen above time doesn't have any timezone information but it's always in UTC.
Now, let's show you how to add an activity to a feed using your Stream API client:
// Instantiate a feed objectvar userFeed1 = client.Feed("user", "1");// Add an activity to the feed, where actor, object and target are references to objects (`Eric`, `Hawaii`, `Places to Visit`)var activity = new Activity("User:1", "pin", "Place:42");await userFeed1.AddActivityAsync(activity);
# Instantiate a feed objectuser_feed_1 = client.feed('user', '1')# Add an activity to the feed, where actor, object and target are references to objects (`Eric`, `Hawaii`, `Places to Visit`)activity_data = {:actor => "User:1", :verb => "pin", :object => "Place:42"}activity_response = user_feed_1.add_activity(activity_data)
# Instantiate a feed objectuser_feed_1 = client.feed('user', '1')# Add an activity to the feed, where actor, object and target are references to objects (`Eric`, `Hawaii`, `Places to Visit`)activity_data = {"actor": "User:1", "verb": "pin", "object": "Place:42"}activity_response = user_feed_1.add_activity(activity_data)
// Instantiate a feed objectFlatFeed userFeed = client.flatFeed("user", "1");// Add an activity to the feed, where actor, object and target// are references to objects (`Eric`, `Hawaii`, `Places to Visit`)Activity activity = Activity.builder() .actor("User:1") .verb("pin") .object("Place:42") .target("Board:1") .build();userFeed.addActivity(activity).get();
// Instantiate a feed object userFeed, err := client.FlatFeed("user", "1") if err != nil { panic(err) } // Add an activity to the feed, where actor, object and verb are references to objects (`Eric`, `Hawaii`, `Places to Visit`) activity := stream.Activity{ Actor: "User:1", Verb: "pin", Object: "Place:42", } _, err := userFeed.AddActivity(context.TODO(), activity) if err != nil { panic(err) }
// Instantiate a feed object$userFeed1 = $client->feed('user', '1');// Add an activity to the feed, where actor, object and target are references to objects (`Eric`, `Hawaii`, `Places to Visit`)$data = [ "actor"=>"User:1", "verb"=>"pin", "object"=>"Place:42"];$userFeed1->addActivity($data);
// Server-side: Instantiate a feed using feed group 'user' and user id '1'const user1 = client.feed("user", "1");// Client-side: Instantiate a feed for feed group 'user', user id '1'// and a security token generated server sideconst user1 = client.feed("user", "1", token);// Create an activity objectconst activity = { actor: "User:1", verb: "pin", object: "Place:42" };// Add an activity to the feedawait user1.addActivity(activity);
Listed below are the mandatory and recommended fields when adding Activities.
// Create a bit more complex activityvar activity = new Activity("User:1", "run", "Exercise:42"){ ForeignId = "run:1"};var course = new Dictionary<string, object>();course["name"] = "Shevlin Park";course["distance"] = 10;var participants = new[] { "Thierry", "Tommaso" };var location = new Dictionary<string, object>();location.Add("type", "point");location.Add("coordinates", new[] {37.769722F, -122.476944F});activity.SetData("location", location);activity.SetData("course", course);activity.SetData("participants", participants);
// Server-side: Instantiate a feed using feed class 'user' and user id '1'const user1 = client.feed("user", "1");// Client-side: Instantiate a feed for feed group 'user', user id '1'// and a security token generated server sideconst user1 = client.feed("user", "1", userToken);// Create a bit more complex activityconst activity = { actor: "User:1", verb: "run", object: "Exercise:42", course: { name: "Golden Gate park", distance: 10 }, participants: ["Thierry", "Tommaso"], started_at: new Date(), foreign_id: "run:1", location: { type: "point", coordinates: [37.769722, -122.476944] },};await user1.addActivity(activity);
In the above example, the course , location , participants and started_at fields will be stored with the Activity and included whenever the Activity is retrieved.
For performance reasons, activities are limited in size (10KB) and must not contain blob/binary data (e.g. base64 encoded images). Use references and identifiers to facilitate Activity enrichment by your backend or client.
These reserved words must not be used as field names: activity_id , activity , analytics , extra_context , id , is_read , is_seen , origin , score , site_id , to .
In one add call, at max 100 activities can be included.
Foreign IDs
The example above also specified a foreign_id .
The foreign id is a unique identifier for the activity that can be stored and used within the app. Making use of the the foreign id field is highly recommended as it is needed in order to update Activities.
The example below shows how to retrieve the Activities in a feed:
// Get activities from 5 to 10var result = await userFeed1.GetActivitiesAsync(5, 5);# Get the 5 activities added after lastActivityvar result = await userFeed1.GetActivitiesAsync(0, 5, FeedFilter.Where().IdLessThan(lastActivity.id));// Get activities sorted by rank (Ranked Feeds Enabled):var flatActivities = await userFeed1.GetFlatActivitiesAsync(GetOptions.Default.WithLimit(5).WithRanking("popularity"));
// Get activities from 5 to 10user1 .get({ limit: 5, offset: 5 }) .then(activitiesSuccess) .catch(activitiesError);// Get the 5 activities added after lastActivityuser1 .get({ limit: 5, id_lt: lastActivity.id }) .then(activitiesSuccess) .catch(activitiesError);// Get the 5 activities added before lastActivityuser1 .get({ limit: 5, id_gt: lastActivity.id }) .then(activitiesSuccess) .catch(activitiesError);// Get activities sorted by rank (Ranked Feeds Enabled)user1 .get({ limit: 20, ranking: "popularity" }) .then(activitiesSuccess) .catch(activitiesError);// Get the 5 activities and enrich them with reactions and collectionsuser1 .get({ limit: 20, enrich: true, reactions: { own: true, counts: true, recent: true }, }) .then(activitiesSuccess) .catch(activitiesError);function activitiesSuccess(successData) { console.log(successData);}function activitiesError(errorData) { console.log(errorData);}
# Get activities from 5 to 10result = user_feed_1.get(:limit=>5, :offset=>5)# Get the 5 activities added after last_activityresult = user_feed_1.get(:limit=>5, :id_lt=>last_activity.id)# Get the 5 activities added before last_activityresult = user_feed_1.get(:limit=>5, :id_gt=>last_activity.id)# Get activities sorted by rank (Ranked Feeds Enabled):result = user_feed_1.get(:limit=>5, :ranking=>'popularity')
# Get activities from 5 to 10result = user_feed_1.get(limit=5, offset=5)# Get the 5 activities added after last_activityresult = user_feed_1.get(limit=5, id_lt=last_activity.id)# Get the 5 activities added before last_activityresult = user_feed_1.get(limit=5, id_gt=last_activity.id)# Get activities sorted by rank (Ranked Feeds Enabled):result = user_feed_1.get(limit=5, ranking="popularity")# Get first 5 activities and enrich them with reactions and collectionsresult = user_feed_1.get(limit=5, enrich=True, reactions={"counts": True, "recent": True})
// Get 3rd page with 20 activitiesresponse = userFeed.feed.getActivities(new Limit(20), new Offset(40)); // Get 5 activities with id less than the given UUID (Faster - Recommended!)response = userFeed.getActivities(new Filter().idLessThan("e561de8f-00f1-11e4-b400-0cc47a024be0").limit(5)).join();// Get activities from 5 to 10 (Pagination-based - Slower)response = userFeed.getActivities(new Pagination().offset(0).limit(5)).join();// Get activities sorted by rank (Ranked Feeds Enabled):response = userFeed.getActivities(new Pagination().limit(5), "popularity").join();// Get activities and enrich them with reactions and collectionsresponse = userFeed.getEnrichedActivities(new EnrichmentFlags().withOwnReactions().withRecentReactions().withReactionCounts()).join();
// Get activities f rom 5 to 10 resp, err := userFeed.GetActivities( context.TODO(), stream.WithActivitiesLimit(5), stream.WithActivitiesOffset(5), ) if err != nil { panic(err) } // Get the 5 activities added after lastActivity resp, err = userFeed.GetActivities( context.TODO(), stream.WithActivitiesLimit(5), stream.WithActivitiesIDLT(lastActivity.ID), ) if err != nil { panic(err) } // Get activities sorted by rank (Ranked Feeds Enabled): resp, err = userFeed.GetActivitiesWithRanking( context.TODO(), "popularity", stream.WithActivitiesLimit(5), ) if err != nil { panic(err) }
// Get activities from 5 to 10$results = $userFeed1->getActivities(5, 5);# Get the 5 activities added after last_activity$options = ['id_lt' => $last_activity_id];$results = $userFeed1->getActivities(0, 5, $options);# Get the 5 activities added before last_activity$options = ['id_gt' => $last_activity_id];$results = $userFeed1->getActivities(0, 5, $options);// Get 5 enriched activities - using limit, offset, enrich - and sorted by rank (Ranked Feeds Enabled):$options = ['ranking' => 'popularity'];$results = $userFeed1->getActivities(0, 5, $options, $enrich=true);
The recommended way to paginate feeds is with offset and limit parameters. Such approach makes for simpler code and it works for all types of feeds.
When using id_lte to paginate an aggregated feed, use the ID of the group that is returned from the API. Using an ID of an individual activity within the group will not work and result in an error.
While paginating, view is cached to have a consistent scrolling. If you want fresh updated page, pass refresh=true query parameter into feed read call.
The format for results array depends on the type of feed associated with the notification. When a Flat Feed is retrieved, the array contains Activities. Whereas when an Aggregated or Notification Feed is retrieved, the array contains Activity Groups.
The 'next' property in the response contains a URL that may be used to retrieve the next page of activities within the feed.
// Remove an activity by its idawait feed.RemoveActivityAsync("e561de8f-00f1-11e4-b400-0cc47a024be0");// Remove activities by their foreign_idawait feed.RemoveActivityAsync("run:1", true);
// Remove an activity by its iduser1.removeActivity("ACTIVITY_ID");// Remove activities foreign_id 'run:1'user1.removeActivity({ foreignId: "run:1" });
# Remove an activity by its iduser_feed_1.remove_activity('ACTIVITY_ID')# Remove activities with foreign_id 'run:1'user_feed_1.remove_activity('run:1', foreign_id=true)
# Remove an activity by its idresponse = user_feed_1.remove_activity("e561de8f-00f1-11e4-b400-0cc47a024be0")removed_activity_id = response["removed"]# Remove activities with foreign_id 'run:1'response = user_feed_1.remove_activity(foreign_id='run:1')removed_foreign_id = response["removed"]
// Remove an activity by its iduserFeed.removeActivityByID("e561de8f-00f1-11e4-b400-0cc47a024be0").join();// Remove activities with foreign_id 'run:1'userFeed.removeActivityByForeignID("run:1").join();
// Remove an activity by its id _, err := userFeed.RemoveActivityByID(context.TODO(), "e561de8f-00f1-11e4-b400-0cc47a024be0") if err != nil { panic(err) } // Remove activities with foreign_id 'run:1' _, err = userFeed.RemoveActivityByForeignID(context.TODO(), "run:1") if err != nil { panic(err) }
// Remove an activity by its id$userFeed1->removeActivity('e561de8f-00f1-11e4-b400-0cc47a024be0');// Remove activities with foreign_id 'run:1'$userFeed1->removeActivity('run:1', true)
When you remove by foreign_id field, all activities in the feed with the provided foreign_id will be removed.
Activities that have both foreign_id and time defined can be updated via the APIs. Changes to activities immediately show up on every feed.
var activity = new Activity("1", "like", "3"){ Time = DateTime.Now, ForeignId = "like:3"};activity.SetData("popularity", 100);// first time the activity is addedawait userFeed1.AddActivityAsync(activity);// update the popularity value for the activityactivity.SetData("popularity", 10);// send the update to the APIsawait client.Batch.UpdateActivityAsync(activity);
const now = new Date();const activity = { actor: "1", verb: "like", object: "3", time: now.toISOString(), foreign_id: "like:3", popularity: 100,};// first time the activity is addeduser1.addActivity(activity);// update the popularity value for the activityactivity.popularity = 10;// send the update to the APIsclient.updateActivities([activity]);
activity = { :actor => "1", :verb => "like", :object => "3", :time => DateTime.now, :foreign_id => "like:3", :popularity => 100}# first time the activity is addeduser_feed_1.add_activity(activity)# update the popularity value for the activityactivity[:popularity] = 10# send the update to the APIsclient.update_activities([activity])
activity = { "actor": "1", "verb":"like", "object": "3", "time": datetime.datetime.utcnow(), "foreign_id": "like:3", "popularity": 100}# first time the activity is addeduser_feed_1.add_activity(activity)# update the popularity value for the activityactivity['popularity'] = 10# send the update to the APIsclient.update_activities([activity])
Activity activity = Activity.builder() .actor("1") .verb("like") .object("3") .time(new Date()) .foreignID("like:3") .extraField("popularity", 100) .build();// first time the activity is addeduserFeed.addActivity(activity).join();// update the popularity value for the activityactivity = Activity.builder() .fromActivity(activity) .extraField("popularity", 10) .build();client.batch().updateActivities(activity);
activity := stream.Activity{ Actor: "User:1", Verb: "like", Object: "Object:3", Time: stream.Time{time.Now()}, ForeignID: "like:3", Extra: map[string]any{ "popularity": 100, }, } // first time the activity is added _, err := userFeed.AddActivity(context.TODO(), activity) if err != nil { panic(err) } // update the popularity value for the activity activity.Extra["popularity"] = 10 // send the update to the APIs _, err = client.UpdateActivities(context.TODO(), activity) if err != nil { panic(err) }
$now = new DateTime('now');$activities = [];$activity = [ 'actor' => 1, 'verb' => 'tweet', 'object' => 1, 'time' => $now->format(DateTime::ISO8601), 'foreign_id' => 'batch1', 'popularity' => 100,];// add the activity to a feed$userFeed1->addActivity(activity);// change a field of the activity data$activity['popularity'] = 10;$activities = [$activity];// update the activity$client->updateActivities($activities);
When you update an activity, you must include the following fields both when adding and updating the activity: time & foreign_id
updateActivities can only be used server-side.
It is not possible to update more than 100 activities per request with this method.
When updating an activity, any changes to the to field are ignored.
This API method works particularly well in combination with the ranked feeds. You can, for instance, issue an update if an activity is promoted or not and use the ranked feeds to show it higher in the feed. Similarly, you could update the like and comment counts and use a ranking method based on popularity to sort the activities.
It is possible to update only a part of an activity with the partial update request. You can think of it as a quick "patching operation".
The activity to update can be selected by its ID or Foreign ID and Time combination.
A set and an unset params can be provided to add, modify, or remove attributes to/from the target activity. The set and unset params can be used separately or combined together (see below).
// partial update by activity IDvar set = new Dictionary<string, object>();set.Add("product.price", 19.99);set.Add("product.price", new Dictionary<string, string="">() { {"facebook", "..."}, {"twitter", "..."} });var unset = new[]{"daily_likes", "popularity"};await client.ActivityPartialUpdateAsync("54a60c1e-4ee3-494b-a1e3-50c06acb5ed4", null, set, unset);// partial update by foreign IDvar foreignIdTime = new ForeignIdTime("product:123", DateTime.Parse("2016-11-10T13:20:00.000000"));await client.ActivityPartialUpdate(null, foreignIDTime, set, unset);
The set object contains the insertion updates for the target fields, where the keys are the activity fields to update and the values are the new ones to assign. If the target field does not exist, it's created with the given content. If the target field exists, its content is replaced.
It is possible to quickly reference nested elements using the dotted notation ( father.child. ... ), but in this case the whole hierarchy is required to exist and to be valid.
For example, if the target activity looks like the following:
It is possible to update the product's EUR price with the key "product.price.eur" , or even create a new field with the key "product.price.gbp" , but it's not possible to reference a non-existing hierarchy like "product.colors.blue" .
The unset field contains a list of key strings that will be removed from the activity's payload. They must exist, and, if referenced with the dotted notation, their hierarchy must be valid.
The set and unset fields can be combined in the same request, but they must not be conflicting with each other (they must not have operations referencing the same keys or hierarchies).
The following example shows a valid combination of set and unset operations which will update the above example activity:
Note: it is not possible to include the following reserved fields in a partial update request (set or unset): id , actor , verb , object , time , target , foreign_id , to , origin
The size of an activity's payload must be less than 10KB after all set and unset operations are applied. The size is based on the size of the JSON-encoded activity.
activityPartialUpdate can only be used server-side.
It is also possible to partially update activities in batches. The same individual activity restrictions are applied, and there's a limit of 100 activities per batch.
Activities in each batch can be identified either by their ID or by the ForeignID and Time combination:
Stream handles uniqueness based on the foreign_id and time fields. If you want to be able to update your activities you need to specify both of these fields.
Uniqueness is enforced on the combination of time and foreign_id . See the example below for an overview:
var firstActivity = new Activity("1", "like", "3"){ Time = DateTime.UtcNow, ForeignId = "like:3"};var firstActivityID = (await userFeed1.AddActivityAsync(firstActivity)).Id;var secondActivity = new Activity("1", "like", "3"){ Time = DateTime.UtcNow, ForeignId = "like:3"};secondActivity.SetData("extra", "extra_value");var secondActivityID = (await userFeed1.AddActivityAsync(secondActivity)).Id;// The unique combination of foreign_id and time ensure that both// activities are unique and therefore the first_activity_id != second_activity_i
from datetime import datetimefirst_activity = { "actor": "1", "verb":"like", "object": "3", "time":datetime.utcnow(), "foreign_id": "like:3"}first_activity_id = user_feed_1.add_activity(first_activity)['id']second_activity = { "actor": "1", "verb":"like", "object": "3", "time": datetime.utcnow(), "extra": "extra_value", "foreign_id": "like:3"}second_activity_id = user_feed_1.add_activity(second_activity)['id']# The unique combination of foreign_id and time ensure that both# activities are unique and therefore the first_activity_id != second_activity_id
first_activity = { :actor => "1", :verb => "like", :object => "4", :time => DateTime.now, :foreign_id => "like:3"}first_activity_id = user_feed_1.add_activity(first_activity)['id']second_activity = { :actor => "1", :verb => "like", :object => "4", :time => DateTime.now, :foreign_id => "like:3"}second_activity_id = user_feed_1.add_activity(second_activity)['id']# The unique combination of foreign_id and time ensure that both# activities are unique and therefore the first_activity_id != second_activity_id
Activity firstActivity = userFeed.addActivity(Activity.builder() .actor("1") .verb("like") .object("3") .time(new Date()) .foreignID("like:3") .build()).join();Activity secondActivity = userFeed.addActivity(Activity.builder() .actor("1") .verb("like") .object("3") .time(new Date()) .extraField("extra", "extra_value") .foreignID("like:3") .build()).join();// The unique combination of foreign_id and time ensure that both// activities are unique and therefore the firstActivity ID != secondActivity ID
now := time.Now() firstActivity, err := userFeed.AddActivity(context.TODO(), stream.Activity{ Actor: "1", Verb: "like", Object: "3", Time: stream.Time{now}, ForeignID: "like:3", }) if err != nil { panic(err) } secondActivity, err := userFeed.AddActivity(context.TODO(), stream.Activity{ Actor: "1", Verb: "like", Object: "3", Time: stream.Time{now}, Extra: map[string]interface{}{ "extra": "extra_value", }, ForeignID: "like:3", }) if err != nil { panic(err) } // foreign ID and time are the same for both activities // hence only one activity is created and first and second IDs are equal // firstActivity.ID == secondActivity.ID
The unique combination of foreign_id and time ensure that both activities are unique and therefore the first_activity_id!=second_activity_id .
By default, activity upsert by this uniqueness guarantee is enabled and it can be disabled per application in the server side by contacting to support. Additionally, it's also disabled if a request is using a client side authentication or has disable_activity_upsert query parameter.
Since the Feeds API doesn't require users there's no user-to-user blocking. However, in order to stay GDPR compliant there's support for discarding actors when reading a feed. This leaves it up to the customer to keep track of which users have blocked other users.
# userA has blocked userB and userCfeed = client.feed("user", "userA")results = feed.get(discard_actors=",".join(["userB", "userC"]))
It's possible to have the Feeds API automatically hide activities older than X days/months/years. This is an app-level setting, please contact support if you wish to have it enabled.