# Activities

"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."

**[Activity Streams Specification 1.0](http://activitystrea.ms/specs/json/1.0/)**

## Adding Activities: Basic

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

<admonition type="warning">

As seen above time doesn't have any timezone information but it's always in UTC.

</admonition>

Now, let's show you how to add an activity to a feed using your Stream API client:

<Tabs>

```csharp label="C#"
// Instantiate a feed object
var 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);
```

```ruby label="Ruby"
# Instantiate a feed object
user_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)
```

```python label="Python"
# Instantiate a feed object
user_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)
```

```java label="Java"
// Instantiate a feed object
FlatFeed 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();
```

```go label="Go"
// 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)
	}
```

```php label="PHP"
// 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);
```

```js label="JavaScript"
// 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 side
const user1 = client.feed("user", "1", token);

// Create an activity object
const activity = { actor: "User:1", verb: "pin", object: "Place:42" };

// Add an activity to the feed
await user1.addActivity(activity);
```

</Tabs>

Listed below are the mandatory and recommended fields when adding Activities.

### Fields

| name       | type                           | description                                                                                                                                                                                               | default      | optional |
| ---------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------- |
| actor      | string                         | the actor performing the activity                                                                                                                                                                         | -            |          |
| verb       | string                         | The verb of the activity with a maximum length of 255 bytes                                                                                                                                               | -            |          |
| object     | string                         | The object of the activity                                                                                                                                                                                | -            |          |
| time       | string                         | The time of the activity, iso format (UTC local time). Required to ensure activity uniqueness and also to later [update activities](/activity-feeds/docs/node/v2/adding-activities/) by Time + Foreign ID | Current time | ✓        |
| to         | list                           | [See the documentation on Targeting & "TO" support.](/activity-feeds/docs/node/v2/targeting/)                                                                                                             | -            | ✓        |
| foreign_id | string                         | A unique ID from your application for this activity. IE: pin:1 or like:300. Required to later [update activities](/activity-feeds/docs/node/v2/adding-activities/) by Time + Foreign ID.                  | -            | ✓        |
| \*         | string / list / object / point | Add as many custom fields as needed.                                                                                                                                                                      | -            | ✓        |

### Custom Fields

<Tabs>

```csharp label="C#"
// Create a bit more complex activity
var 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);
```

```js label="JavaScript"
// 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 side
const user1 = client.feed("user", "1", userToken);

// Create a bit more complex activity
const 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);
```

```ruby label="Ruby"
# Create a bit more complex activity
activity_data = {:actor => 'User:1', :verb => 'run', :object => 'Exercise:42',
  :course => {:name => 'Golden Gate park', :distance => 10},
  :participants => ['Thierry', 'Tommaso'],
  :started_at => DateTime.now(),
  :foreign_id => 'run:1',
  :location => {:type => 'point', :coordinates => [37.769722,-122.476944] }
}
activity_response = user_feed_1.add_activity(activity_data)
```

```python label="Python"
import datetime

# Create a bit more complex activity
activity_data = {'actor': 'User:1', 'verb': 'run', 'object': 'Exercise:42',
  'course': {'name': 'Golden Gate park', 'distance': 10},
  'participants': ['Thierry', 'Tommaso'],
  'started_at': datetime.datetime.utcnow(),
  'foreign_id': 'run:1',
  'location': {'type': 'point', 'coordinates': [37.769722,-122.476944] }
}
user_feed_1.add_activity(activity_data)
```

```java label="Java"
// Create a bit more complex activity
Activity activity = Activity.builder()
    .actor("User:1")
    .verb("run")
    .object("Exercise:42")
    .foreignID("run:1")
    .extra(new ImmutableMap.Builder<String, Object>()
        .put("course", new ImmutableMap.Builder<String, Object>()
            .put("name", "Golden Gate park")
            .put("distance", 10)
            .build())
        .put("participants", new String[]{
            "Thierry",
            "Tommaso",
        })
        .put("started_at", LocalDateTime.now())
        .put("location", new ImmutableMap.Builder<String, Object>()
            .put("type", "point")
            .put("coordinates", new double[]{37.769722, -122.476944})
            .build())
        .build())
    .build();
userFeed.addActivity(activity).join();
```

```go label="Go"
// Create a bit more complex activity
	activity := stream.Activity{
		Actor:   "User:1",
		Verb:   "run",
		Object:  "Exercise:42",
		ForeignID: "run:1",
		Extra: map[string]any{
			"course": map[string]any{
				"name":   "Golden Gate park",
				"distance": 10,
			},
			"participants": []string{
				"Thierry",
				"Tommaso",
			},
			"started_at": time.Now(),
		},
	}
	_, err := userFeed.AddActivity(context.TODO(), activity)
	if err != nil {
		panic(err)
	}
```

```php label="PHP"
// Create a bit more complex activity
$now = new DateTime('now');

$data = [
  'actor' => 'User:1',
  'verb' => 'run',
  'object' => 1,
  'course' => ['name'=> 'Golden Gate park', 'distance'=> 10],
  'participants' => ['Thierry', 'Tommaso'],
  'started_at' => $now,
  'foreign_id' => 'run:1',
  'location' => [
    'type'=> 'point',
    'coordinates'=> [
      37.769722, -122.476944,
    ],
  ],
];

$userFeed1->addActivity($data);
```

</Tabs>

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](/activity-feeds/docs/node/v2/enrichment/) by your backend or client.

<admonition type="info">

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` .

</admonition>

<admonition type="info">

In one add call, at max 100 activities can be included.

</admonition>

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.

### Add Activity Response Data

When an activity is successfully added, the Stream API includes activity id in the serialized JSON response, like so:

<Tabs>

```json label="JSON"
{
  "id": "ef696c12-69ab-11e4-8080-80003644b625",
  "actor": "User:1",
  "course": {
    "distance": 10,
    "name": "Golden Gate Park"
  },
  "object": "Exercise:42",
  "participants": ["Thierry", "Tommaso"],
  "started_at": "2014-11-11T15:06:16+01:00",
  "target": null,
  "time": "2014-11-11T14:06:30.494",
  "verb": "run"
}
```

</Tabs>

## Retrieving Activities

The example below shows how to retrieve the Activities in a feed:

<Tabs>

```csharp label="C#"
// Get activities from 5 to 10
var result = await userFeed1.GetActivitiesAsync(5, 5);

# Get the 5 activities added after lastActivity
var 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"));
```

```js label="JavaScript"
// Get activities from 5 to 10

user1
  .get({ limit: 5, offset: 5 })
  .then(activitiesSuccess)
  .catch(activitiesError);

// Get the 5 activities added after lastActivity

user1
  .get({ limit: 5, id_lt: lastActivity.id })
  .then(activitiesSuccess)
  .catch(activitiesError);

// Get the 5 activities added before lastActivity

user1
  .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 collections

user1
  .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);
}
```

```ruby label="Ruby"
# Get activities from 5 to 10
result = user_feed_1.get(:limit=>5, :offset=>5)

# Get the 5 activities added after last_activity
result = user_feed_1.get(:limit=>5, :id_lt=>last_activity.id)

# Get the 5 activities added before last_activity
result = 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')
```

```python label="Python"
# Get activities from 5 to 10
result = user_feed_1.get(limit=5, offset=5)

# Get the 5 activities added after last_activity
result = user_feed_1.get(limit=5, id_lt=last_activity.id)

# Get the 5 activities added before last_activity
result = 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 collections
result = user_feed_1.get(limit=5, enrich=True, reactions={"counts": True, "recent": True})
```

```java label="Java"
// Get 3rd page with 20 activities
response = 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 collections
response = userFeed.getEnrichedActivities(new EnrichmentFlags().withOwnReactions().withRecentReactions().withReactionCounts()).join();
```

```go label="Go"
// 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)
	}
```

```php label="PHP"
// 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);
```

</Tabs>

### Parameters

| name             | type    | description                                                                                                                           | default | optional |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| limit            | integer | The number of Activities to retrieve (max: 100)                                                                                       | 25      | ✓        |
| id_gte           | string  | Filter the feed on ids greater than or equal to the given value                                                                       | -       | ✓        |
| id_gt            | string  | Filter the feed on ids greater than the given value                                                                                   | -       | ✓        |
| id_lte           | string  | Filter the feed on ids smaller than or equal to the given value                                                                       | -       | ✓        |
| id_lt            | string  | Filter the feed on ids smaller than the given value                                                                                   | -       | ✓        |
| offset           | integer | The offset                                                                                                                            | 0       | ✓        |
| ranking          | string  | The custom ranking formula used to sort the feed, must be defined in the dashboard                                                    | -       | ✓        |
| enrich           | boolean | When using collections, you can request Stream to enrich activities to include them                                                   | false   | ✓        |
| reactions.own    | boolean | Include reactions added by current user to all activities (see [reaction](/activity-feeds/docs/node/v2/reactions-introduction/) docs) | false   | ✓        |
| reactions.recent | boolean | Include recent reactions to activities (see [reaction](/activity-feeds/docs/node/v2/reactions-introduction/) docs)                    | false   | ✓        |
| reaction.counts  | boolean | Include reaction counts to activities (see [reaction](/activity-feeds/docs/node/v2/reactions-introduction/) docs)                     | false   | ✓        |
| reaction.kinds   | array   | Filter reactions with given kinds (support differs by SDKs, request if missing)                                                       | -       | ✓        |

Activity reads returns at most 100 Activities. Requests with a limit greater than 100 are automatically capped.

<admonition type="info">

The maximum depth of activities that can be retrived is 1000

</admonition>

### Pagination

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.

<admonition type="info">

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.

</admonition>

<admonition type="warning">

Sorting using a [custom ranking](/activity-feeds/docs/node/v2/custom-ranking/) formula is only available on [paid plans](https://getstream.io/pricing/#feeds).

</admonition>

<admonition type="warning">

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.

</admonition>

### Retrieve Feed Response Data

When a feed is successfully retrieved, the Stream API returns the following payload:

<Tabs>

```js label="JavaScript"
{
 results: [],
 next: '/api/v1.0/feed/<< feed_group >>/<< feed_id >>/?api_key=<< api_key >>&id_lt=<< next_activity_id >>&limit=<< activity_limit >>'
}
```

</Tabs>

The format for results array depends on the type of feed associated with the notification. When a [Flat](/activity-feeds/docs/node/v2/flat-feeds/) Feed is retrieved, the array contains Activities. Whereas when an [Aggregated](/activity-feeds/docs/node/v2/creating-feeds/) or [Notification](/activity-feeds/docs/node/v2/creating-feeds/) 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.

## Removing Activities

There are two ways to remove an activity:

- **Activity Id**  - found in the serialized response from server

- **Foreign Id** - optionally specified when adding an activity

Have a look at the section on [Using Foreign IDs.](/activity-feeds/docs/node/v2/adding-activities/)

<Tabs>

```csharp label="C#"
// Remove an activity by its id
await feed.RemoveActivityAsync("e561de8f-00f1-11e4-b400-0cc47a024be0");

// Remove activities by their foreign_id
await feed.RemoveActivityAsync("run:1", true);
```

```js label="JavaScript"
// Remove an activity by its id
user1.removeActivity("ACTIVITY_ID");

// Remove activities foreign_id 'run:1'
user1.removeActivity({ foreignId: "run:1" });
```

```ruby label="Ruby"
# Remove an activity by its id
user_feed_1.remove_activity('ACTIVITY_ID')

# Remove activities with foreign_id 'run:1'
user_feed_1.remove_activity('run:1', foreign_id=true)
```

```python label="Python"
# Remove an activity by its id
response = 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"]
```

```java label="Java"
// Remove an activity by its id
userFeed.removeActivityByID("e561de8f-00f1-11e4-b400-0cc47a024be0").join();

// Remove activities with foreign_id 'run:1'
userFeed.removeActivityByForeignID("run:1").join();
```

```go label="Go"
// 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)
	}
```

```php label="PHP"
// 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)
```

</Tabs>

<admonition type="info">

When you remove by foreign_id field,  **all**  activities in the feed with the provided foreign_id will be removed.

</admonition>

## Updating Activities

Activities that have both  `foreign_id`  and  `time`  defined can be updated via the APIs. Changes to activities immediately show up on every feed.

<Tabs>

```csharp label="C#"
var activity = new Activity("1", "like", "3")
{
  Time = DateTime.Now,
  ForeignId = "like:3"
};
activity.SetData("popularity", 100);

// first time the activity is added
await userFeed1.AddActivityAsync(activity);

// update the popularity value for the activity
activity.SetData("popularity", 10);

// send the update to the APIs
await client.Batch.UpdateActivityAsync(activity);
```

```js label="JavaScript"
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 added
user1.addActivity(activity);

// update the popularity value for the activity
activity.popularity = 10;

// send the update to the APIs
client.updateActivities([activity]);
```

```ruby label="Ruby"
activity = {
 :actor => "1",
 :verb => "like",
 :object => "3",
 :time => DateTime.now,
 :foreign_id => "like:3",
 :popularity => 100
}

# first time the activity is added
user_feed_1.add_activity(activity)

# update the popularity value for the activity
activity[:popularity] = 10

# send the update to the APIs
client.update_activities([activity])
```

```python label="Python"
activity = {
  "actor": "1",
  "verb":"like",
  "object": "3",
  "time": datetime.datetime.utcnow(),
  "foreign_id": "like:3",
  "popularity": 100
}

# first time the activity is added
user_feed_1.add_activity(activity)

# update the popularity value for the activity
activity['popularity'] = 10

# send the update to the APIs
client.update_activities([activity])
```

```java label="Java"
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 added
userFeed.addActivity(activity).join();

// update the popularity value for the activity
activity = Activity.builder()
    .fromActivity(activity)
    .extraField("popularity", 10)
    .build();

client.batch().updateActivities(activity);
```

```go label="Go"
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)
	}
```

```php label="PHP"
$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);
```

</Tabs>

<admonition type="info">

When you update an activity, you must include the following fields both when adding and updating the activity: `time` & `foreign_id`

</admonition>

<admonition type="info">

updateActivities can only be used server-side.

</admonition>

<admonition type="warning">

It is not possible to update more than 100 activities per request with this method.

</admonition>

<admonition type="warning">

When updating an activity, any changes to the  `to`  field are ignored.

</admonition>

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.

## Activity Partial Update

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).

<Tabs>

```csharp label="C#"
// partial update by activity ID
var 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 ID
var foreignIdTime = new ForeignIdTime("product:123", DateTime.Parse("2016-11-10T13:20:00.000000"));
await client.ActivityPartialUpdate(null, foreignIDTime, set, unset);
```

```js label="JavaScript"
// partial update by activity ID
client.activityPartialUpdate({
 id: '54a60c1e-4ee3-494b-a1e3-50c06acb5ed4',
 set: {
  'product.price': 19.99,
  'shares': {
   'facebook': '...',
   'twitter': '...'
  },
 },
 unset: [
  'daily_likes',
  'popularity'
 ]
})

// partial update by foreign ID
client.activityPartialUpdate({
 foreign_id: 'product:123',
 time: '2016-11-10T13:20:00.000000',
 set: {
  ...
 },
 unset: [
  ...
 ]
})
```

```python label="Python"
# partial update by activity ID
client.activity_partial_update(
 id='54a60c1e-4ee3-494b-a1e3-50c06acb5ed4',
 set={
  'product.price': 19.99,
  'shares': {
   'facebook': '...',
   'twitter': '...'
  },
 },
 unset=[
  'daily_likes',
  'popularity'
 ]
)

# partial update by foreign ID
client.activity_partial_update(
 foreign_id='product:123',
 time='2016-11-10T13:20:00.000000',
 set={ ... },
 unset=[ ... ],
)
```

```ruby label="Ruby"
# partial update by activity ID
client.activity_partial_update(
 id: '54a60c1e-4ee3-494b-a1e3-50c06acb5ed4',
 set: {
  'product.price': 19.99,
  'shares': {
   'facebook': '...',
   'twitter': '...'
  },
 },
 unset: [
  'daily_likes',
  'popularity'
 ]
)

# partial update by foreign ID
client.activity_partial_update(
 foreign_id: 'product:123',
 time: '2016-11-10T13:20:00.000000',
 set: {
  ...
 },
 unset: [
  ...
 ]
)
```

```php label="PHP"
$set = [
  "product.name" => "boots",
  "product.price" => 7.99,
  "popularity" => 1000,
  "foo" => ["bar" => ["baz" => "qux"]],
];
$unset = ["product.color"];

$this->client->doPartialActivityUpdate($activity['id'], null, null, $set, $unset);
// OR
$this->client->doPartialActivityUpdate(null, $activity['foreign_id'], $activity['time'], $set, $unset);
```

```java label="Java"
// prepare the set operations
Map<string, object=""> set = new ImmutableMap.Builder<string, object="">()
    .put("product.price", 19.99)
    .put("shares", new ImmutableMap.Builder<string, object="">()
        .put("facebook", "...")
        .put("twitter", "...")
        .build())
    .build();
// prepare the unset operations
String[] unset = new String[] { "daily_likes", "popularity" };

// partial update by activity ID
String id = "54a60c1e-4ee3-494b-a1e3-50c06acb5ed4";
client.updateActivityByID(id, set, unset).join();

// partial update by foreign ID
String foreignID = "product:123";
Date timestamp = new Date();
client.updateActivityByForeignID(foreignID, timestamp, set, unset).join();</string,></string,></string,>
```

```go label="Go"
// partial update by activity ID

	// prepare the set operations
	set := map[string]any{
		"product.price": 19.99,
		"shares": map[string]any{
			"facebook": "...",
			"twitter": "...",
		},
	}
	// prepare the unset operations
	unset := []string{"daily_likes", "popularity"}

	id := "54a60c1e-4ee3-494b-a1e3-50c06acb5ed4"
	_, err := client.UpdateActivityByID(context.TODO(), id, set, unset)
	if err != nil {
		panic(err)
	}

	// partial update by foreign ID
	foreignID := "product:123"
	timestamp := stream.Time{ /*...*/ }
	_, err := client.UpdateActivityByForeignID(context.TODO(), foreignID, timestamp, set, unset)
	if err != nil {
		panic(err)
	}
```

</Tabs>

### Parameters

| name       | type   | description                               | default | optional |
| ---------- | ------ | ----------------------------------------- | ------- | -------- |
| id         | string | The target activity ID                    | -       | ✓        |
| foreign_id | string | The target activity foreign ID            | -       | ✓        |
| time       | string | The target activity timestamp             | -       | ✓        |
| set        | object | The set operations, max 25 top level keys | -       | ✓        |
| unset      | list   | The unset operations. max 25              | -       | ✓        |

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:

<Tabs>

```js label="JavaScript"
{
  "id": "...", "actor": "...", "object": "...", "verb": "...", "foreign_id": "...",
  "product": {
    "sku": 12345,
    "price": {
      "eur": 7.99,
      "usd": 9.99
    },
    "name": "shoes",
    "popularity": 9000
  }
}
```

</Tabs>

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:

<Tabs>

```js label="JavaScript"
{
  "set": {
    "product.price.eur": 8.99,
    "product.price.gbp": 6.99,
    "product.available": {
      "blue": 100,
      "black": 300
    },
    "facebook_page": "..."
  },
  "unset": [
    "product.price.usd",
    "popularity"
  ]
}
```

</Tabs>

<admonition type="info">

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`

</admonition>

<admonition type="info">

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.

</admonition>

<admonition type="info">

activityPartialUpdate can only be used server-side.

</admonition>

## Batching Partial Updates

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:

<Tabs>

```python label="Python"
# Update by ID
client.activities_partial_update([
  {
   id: "ACTIVITY_ID",
   set: {
    "key": "new-value"
   },
   unset: [ "removed", "keys" ]
  },
  {
   id: "ACTIVITY_ID",
   set: {
    "foo": "bar",
    "baz": "quux"
   },
   unset: [ "removed", "keys" ]
  }
 ])

# Update by foreign ID and timestamp
client.activities_partial_update([
 {
  foreign_id: "stroll:1",
  time: '2016-11-10T13:20:00.000000',
  set: {
   "key": "new-value"
  },
  unset: [ "removed", "keys" ]
 },
 {
  foreign_id: "stroll:2",
  time: '2017-11-10T13:20:00.000000',
  set: {
   "foo": "bar",
   "baz": "quux"
  },
  unset: [ "removed", "keys" ]
 }
])
```

```go label="Go"
changesetA := stream.NewUpdateActivityRequestByID(
		"907a8010-1d84-11e9-9457-9cb6d0925edd",
		map[string]any{
			"key": "new-value",
		},
		[]string{"removed", "keys"},
	)
	changesetB := stream.NewUpdateActivityRequestByID(
		"de88ed3c-1d98-11e9-9457-9cb6d0925edd",
		map[string]any{
			"foo": "bar",
			"baz": "quux",
		},
		[]string{"removed", "keys"},
	)
	changesetC := stream.NewUpdateActivityRequestByForeignID(
		"stroll:1",
		stream.Time{ /*...*/ },
		map[string]any{
			"key": "new-value",
		},
		[]string{"removed", "keys"},
	)
	changesetD := stream.NewUpdateActivityRequestByForeignID(
		"stroll:2",
		stream.Time{ /*...*/ },
		map[string]any{
			"foo": "bar",
			"baz": "quux",
		},
		[]string{"removed", "keys"},
	)
	_, err := client.PartialUpdateActivities(context.TODO(), changesetA, changesetB)
	if err != nil {
		panic(err)
	}
	_, err := client.PartialUpdateActivities(context.TODO(), changesetC, changesetD)
	if err != nil {
		panic(err)
	}
```

```php label="PHP"
$payload = [
  [
    "id" => $act1["id"],
    "set" => ["popularity" => 999],
    "unset" => ['new'],
  ],
  [
    "id" => $act2["id"],
    "set" => ["popularity" => 5],
  ],
];
$this->client->batchPartialActivityUpdate($payload);
```

</Tabs>

## Uniqueness & Foreign IDs

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:

<Tabs>

```csharp label="C#"
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
```

```js label="JavaScript"
const first_activity = {
 actor: 1,
 verb: 'add',
 object: '1',
 foreign_id: 'activity_1'
 time: new Date().toISOString()
};

// Add activity to activity feed:
const { id: first_activity_id } = await user_feed_1.addActivity(first_activity);

const second_activity = {
 actor: 1,
 verb: 'add',
 object: '1',
 foreign_id: 'activity_2'
 time: new Date().toISOString()
};

const { id: second_activity_id } = await user_feed_1.addActivity(second_activity);
```

```python label="Python"
from datetime import datetime

first_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
```

```ruby label="Ruby"
first_activity = {
 :actor =&gt; "1",
 :verb =&gt; "like",
 :object =&gt; "4",
 :time =&gt; DateTime.now,
 :foreign_id =&gt; "like:3"
}
first_activity_id = user_feed_1.add_activity(first_activity)['id']

second_activity = {
 :actor =&gt; "1",
 :verb =&gt; "like",
 :object =&gt; "4",
 :time =&gt; DateTime.now,
 :foreign_id =&gt; "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
```

```java label="Java"
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
```

```go label="Go"
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
```

</Tabs>

<admonition type="warning">

The unique combination of `foreign_id` and time ensure that both activities are unique and therefore the `first_activity_id` `!=` `second_activity_id` .

</admonition>

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.

## GDPR-compliant actor discarding

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.

<Tabs>

```python label="Python"
# userA has blocked userB and userC
feed = client.feed("user", "userA")
results = feed.get(discard_actors=",".join(["userB", "userC"]))
```

```text label="None"
curl --location 'https://eu-west-api.stream-io-api.com/api/v1.0/enrich/feed/feed_group/feed_id?api_key=APIKEY&limit=25&offset=0&withRecentReactions=true&recentReactionsLimit=25&discard_actors=userA%2CSU%3AuserB' \
--header 'Stream-Auth-Type: jwt' \
--header 'Authorization: TOKEN' \
--header 'Content-Type: application/json' \
--data ''
```

</Tabs>

<admonition type="info">

This a recent feature, if your SDK of choice is not listed please [contact support](https://getstream.io/contact/support/)

</admonition>

## Automatic Activity Hiding

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](https://getstream.io/contact/support/) if you wish to have it enabled.


---

This page was last updated at 2026-05-22T16:31:48.561Z.

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/php/v2/adding-activities/](https://getstream.io/activity-feeds/docs/php/v2/adding-activities/).