Activity metrics let you track custom counters like views, clicks, and impressions for activities. These metrics are stored on the activity and can be used in ranking expressions to build engagement-driven feeds.
Activity metrics require a paid Feeds plan. Free-tier applications cannot use this feature. Paid (self-service) plans get the default metrics listed below. Enterprise plans can additionally configure up to 10 custom metrics per app.
Track one or more metric events in a single request. Each event specifies an activity, a metric name, and an optional delta (defaults to 1). Events are independently rate-limited per user per activity per metric.
All paid plans include the following built-in metrics:
Metric
Max per hour per user per activity
views
10
clicks
5
impressions
50
These defaults are always available. Enterprise apps can override their rate limits or disable them by setting the limit to 0 via custom metrics configuration.
Metric names must be alphanumeric with underscores or dashes only, and a maximum of 64 characters (e.g. views, link_clicks, video-watch-time).
Enterprise plans can configure up to 10 custom metrics per app using the updateApp API. Custom metrics are merged on top of the defaults:
Add a new metric: Include it in activity_metrics_config with a rate limit value.
Override a default's rate limit: Include the default metric name with a new limit value.
Disable a default metric: Set its value to 0.
Unmentioned defaults are preserved: Only metrics you explicitly include in the config are affected.
// Add custom metrics and override the default "views" limitawait client.updateApp({ activity_metrics_config: { shares: 5, // new custom metric: 5 per hour per user per activity saves: 10, // new custom metric: 10 per hour views: 20, // override default views limit from 10 to 20 impressions: 0, // disable the default impressions metric },});
// Add custom metrics and override the default "views" limit_, err = client.UpdateApp(ctx, &getstream.UpdateAppRequest{ ActivityMetricsConfig: map[string]int{ "shares": 5, // new custom metric: 5 per hour per user per activity "saves": 10, // new custom metric: 10 per hour "views": 20, // override default views limit from 10 to 20 "impressions": 0, // disable the default impressions metric },})if err != nil { log.Fatal("Error updating app:", err)}
// Add custom metrics and override the default "views" limitUpdateAppRequest request = UpdateAppRequest.builder() .activityMetricsConfig(Map.of( "shares", 5, // new custom metric: 5 per hour per user per activity "saves", 10, // new custom metric: 10 per hour "views", 20, // override default views limit from 10 to 20 "impressions", 0 // disable the default impressions metric )) .build();common.updateApp(request).execute();
// Add custom metrics and override the default "views" limit$client->updateApp(new GeneratedModels\UpdateAppRequest( activityMetricsConfig: [ 'shares' => 5, // new custom metric: 5 per hour per user per activity 'saves' => 10, // new custom metric: 10 per hour 'views' => 20, // override default views limit from 10 to 20 'impressions' => 0, // disable the default impressions metric ]));
// Add custom metrics and override the default "views" limitawait client.UpdateAppAsync(new UpdateAppRequest{ ActivityMetricsConfig = new Dictionary<string, int> { ["shares"] = 5, // new custom metric: 5 per hour per user per activity ["saves"] = 10, // new custom metric: 10 per hour ["views"] = 20, // override default views limit from 10 to 20 ["impressions"] = 0, // disable the default impressions metric }});
# Add custom metrics and override the default "views" limitclient.update_app( activity_metrics_config={ "shares": 5, # new custom metric: 5 per hour per user per activity "saves": 10, # new custom metric: 10 per hour "views": 20, # override default views limit from 10 to 20 "impressions": 0, # disable the default impressions metric })
# Add custom metrics and override the default "views" limitclient.common.update_app( GetStream::Generated::Models::UpdateAppRequest.new( activity_metrics_config: { shares: 5, # new custom metric: 5 per hour per user per activity saves: 10, # new custom metric: 10 per hour views: 20, # override default views limit from 10 to 20 impressions: 0 # disable the default impressions metric } ))
// Add custom metrics and override the default "views" limit (server-side)await client.updateApp({ activity_metrics_config: { shares: 5, // new custom metric: 5 per hour per user per activity saves: 10, // new custom metric: 10 per hour views: 20, // override default views limit from 10 to 20 impressions: 0, // disable the default impressions metric },});
The resulting effective config for this app would be:
Metric
Limit
Source
views
20
overridden from default (10)
clicks
5
default (unchanged)
impressions
—
disabled by custom config
shares
5
custom
saves
10
custom
You can read back the current custom config via getApp:
Custom metrics configuration is available on Enterprise plans only. Paid self-service plans use the default metrics. The activity_metrics_config returned by getApp only contains your custom overrides — default metrics that have not been overridden are not listed.
You can track up to 100 events in a single request. Events can target different activities and different metrics. Each event is independently rate-limited.
Each metric is rate-limited per user (or IP) per activity per metric within a 1-hour sliding window. When the limit is exceeded, the event is rejected and allowed is set to false in the response — this is not an error, just a signal that the event was not counted.
Always check the allowed field in the response:
const response = await client.trackActivityMetrics({ events: [{ activity_id: "activity_123", metric: "views" }],});for (const result of response.results) { if (!result.allowed) { console.log(`Rate limited: ${result.activity_id}/${result.metric}`); }}
$response = $feedsClient->trackActivityMetrics( new GeneratedModels\TrackActivityMetricsRequest( events: [ new GeneratedModels\TrackActivityMetricsEvent( activityID: 'activity_123', metric: 'views' ), ], userID: 'user_456' ));foreach ($response->getData()->results as $result) { if (!$result->allowed) { printf("Rate limited: %s/%s\n", $result->activityID, $result->metric); }}
var response = await _feedsV3Client.TrackActivityMetricsAsync( new TrackActivityMetricsRequest { Events = new List<TrackActivityMetricsEvent> { new TrackActivityMetricsEvent { ActivityID = "activity_123", Metric = "views" }, }, UserID = "user_456" });foreach (var result in response.Data!.Results) { if (!result.Allowed) { Console.WriteLine($"Rate limited: {result.ActivityID}/{result.Metric}"); }}
response = client.feeds.track_activity_metrics( events=[TrackActivityMetricsEvent(activity_id="activity_123", metric="views")], user_id="user_456",)for result in response.data.results: if not result.allowed: print(f"Rate limited: {result.activity_id}/{result.metric}")
When using metrics in ranking expressions, you must provide defaults for each metric. Activities without any tracked metrics need default values for the expression to evaluate correctly.