<img src="https://github.com/GetStream/stream-go2/raw/v8.9.0/assets/logo.svg" width="50%" height="50%">
Official Go API client for Stream Feeds, a service for building applications with activity feeds.
<a href="https://getstream.io/activity-feeds/docs/"><strong>Explore the docs »</strong></a>
<a href="https://github.com/GetStream/stream-go2/issues">Report Bug</a>
·
<a href="https://github.com/GetStream/stream-go2/issues">Request Feature</a>
🚨 Breaking change in v7.0 <
From version 7.0.0, all methods' first argument is
context.Context. The reason is that we internally changedhttp.NewRequest()tohttp.NewRequestWithContext()so that it's easier to handle cancellations, timeouts and deadlines for our users.
stream-go2 is a Go client for Stream Feeds API.
You can sign up for a Stream account at our Get Started page.
You can use this library to access chat API endpoints server-side.
For the client-side integrations (web and mobile) have a look at the JavaScript, iOS and Android SDK libraries (docs).
💡 Note: this is a library for the Feeds product. The Chat SDKs can be found here.
to targetsGet the client:
$ go get github.com/GetStream/stream-go2/v8
For
v4, usegithub.com/GetStream/stream-go2/v4but updating to the last version is highly recommended.
import stream "github.com/GetStream/stream-go2/v8"
key := "YOUR_API_KEY"
secret := "YOUR_API_SECRET"
client, err := stream.New(key, secret)
if err != nil {
// ...
}
You can pass additional options when creating a client using the available ClientOption functions:
client, err := stream.NewClient(key, secret,
stream.WithAPIRegion("us-east"),
stream.WithAPIVersion("1.0"),
stream.WithTimeout(5 * time.Second),
...,
)
You can also create a client using environment variables:
client, err := stream.NewFromEnv()
Available environment variables:
STREAM_API_KEYSTREAM_API_SECRETSTREAM_API_REGIONSTREAM_API_VERSIONAPI has different rate limits for each distinct endpoint and this information is returned to the client in response headers and SDK parses headers into Rate type.
This info is provided to the user in 2 ways:
* in responses; resp, _ := feed.GetActivities; resp.Rate.
* in errors; if request doesn't succeed then error is a type of APIError and headers are accessible via err.(APIError).Rate.
Create a flat feed from slug and user ID:
flat, err := client.FlatFeed("user", "123")
Create an aggregated feed from slug and user ID:
aggr, err := client.AggregatedFeed("aggregated", "123")
Create a notification feed from slug and user ID:
notif, err := client.NotificationFeed("notification", "123")
Flat, aggregated, and notification feeds implement the Feed interface methods.
In the snippets below, feed indicates any kind of feed, while flat, aggregated, and notification are used
to indicate that only that kind of feed has certain methods or can perform certain operations.
resp, err := flat.GetActivities()
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
fmt.Println("Rate:", resp.Rate)
fmt.Println("Next:", resp.Next)
fmt.Println("Activities:")
for _, activity := range resp.Results {
fmt.Println(activity)
}
You can retrieve flat feeds with custom ranking, using the dedicated method:
resp, err := flat.GetActivitiesWithRanking(ctx, "popularity")
if err != nil {
// ...
}
resp, err := aggregated.GetActivities(ctx)
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
fmt.Println("Rate:", resp.Rate)
fmt.Println("Next:", resp.Next)
fmt.Println("Groups:")
for _, group := range resp.Results {
fmt.Println("Group:", group.Name, "ID:", group.ID, "Verb:", group.Verb)
fmt.Println("Activities:", group.ActivityCount, "Actors:", group.ActorCount)
for _, activity := range group.Activities {
// ...
}
}
resp, err := notification.GetActivities(ctx)
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
fmt.Println("Rate:", resp.Rate)
fmt.Println("Next:", resp.Next)
fmt.Println("Unseen:", resp.Unseen, "Unread:", resp.Unread)
fmt.Println("Groups:")
for _, group := range resp.Results {
fmt.Println("Group:", group.Group, "ID:", group.ID, "Verb:", group.Verb)
fmt.Println("Seen:", group.IsSeen, "Read:", group.IsRead)
fmt.Println("Activities:", group.ActivityCount, "Actors:", group.ActorCount)
for _, activity := range group.Activities {
// ...
}
}
You can pass supported options and filters when retrieving activities:
resp, err := flat.GetActivities(
ctx,
stream.WithActivitiesIDGTE("f505b3fb-a212-11e7-..."),
stream.WithActivitiesLimit(5),
...,
)
Add a single activity:
resp, err := feed.AddActivity(ctx, stream.Activity{Actor: "bob", ...})
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
fmt.Println("Rate:", resp.Rate)
fmt.Println("Activity:", resp.Activity) // resp wraps the stream.Activity type
Add multiple activities:
a1 := stream.Activity{Actor: "bob", ...}
a2 := stream.Activity{Actor: "john", ...}
a3 := stream.Activity{Actor: "alice", ...}
resp, err := feed.AddActivities(ctx, a1, a2, a3)
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
fmt.Println("Rate:", resp.Rate)
fmt.Println("Activities:")
for _, activity := range resp.Activities {
fmt.Println(activity)
}
_, err := feed.UpdateActivities(ctx, a1, a2, ...)
if err != nil {
// ...
}
You can partial update activities identified either by ID:
changesetA := stream.NewUpdateActivityRequestByID("f505b3fb-a212-11e7-...", map[string]any{"key": "new-value"}, []string{"removed", "keys"})
changesetB := stream.NewUpdateActivityRequestByID("f707b3fb-a212-11e7-...", map[string]any{"key": "new-value"}, []string{"removed", "keys"})
resp, err := client.PartialUpdateActivities(ctx, changesetA, changesetB)
if err != nil {
// ...
}
or by a ForeignID and timestamp pair:
changesetA := stream.NewUpdateActivityRequestByForeignID("dothings:1", stream.Time{...}, map[string]any{"key": "new-value"}, []string{"removed", "keys"})
changesetB := stream.NewUpdateActivityRequestByForeignID("dothings:2", stream.Time{...}, map[string]any{"key": "new-value"}, []string{"removed", "keys"})
resp, err := client.PartialUpdateActivities(ctx, changesetA, changesetB)
if err != nil {
// ...
}
You can either remove activities by ID or ForeignID:
_, err := feed.RemoveActivityByID(ctx, "f505b3fb-a212-11e7-...")
if err != nil {
// ...
}
_, err := feed.RemoveActivityByForeignID(ctx, "bob:123")
if err != nil {
// ...
}
_, err := feed.Follow(ctx, anotherFeed)
if err != nil {
// ...
}
Beware that it's possible to follow only flat feeds.
You can pass options to the Follow method. For example:
_, err := feed.Follow(ctx,
anotherFeed,
stream.WithFollowFeedActivityCopyLimit(15),
...,
)
Get the feeds that a feed is following:
resp, err := feed.GetFollowing(ctx)
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
for _, followed := range resp.Results {
fmt.Println(followed.FeedID, followed.TargetID)
}
You can pass options to GetFollowing:
resp, err := feed.GetFollowing(
ctx,
stream.WithFollowingLimit(5),
...,
)
resp, err := flat.GetFollowers(ctx)
if err != nil {
// ...
}
fmt.Println("Duration:", resp.Duration)
fmt.Println("Rate:", resp.Rate)
for _, follower := range resp.Results {
fmt.Println(follower.FeedID, follower.TargetID)
}
Note: this is only possible for FlatFeed types.
You can pass options to GetFollowers:
resp, err := feed.GetFollowing(
ctx,
stream.WithFollowersLimit(5),
...,
)
_, err := flat.Unfollow(ctx, anotherFeed)
if err != nil {
// ...
}
You can pass options to Unfollow:
_, err := flat.Unfollow(ctx,
anotherFeed,
stream.WithUnfollowKeepHistory(true),
...,
)
to targetsRemove all old targets and set new ones (replace):
newTargets := []stream.Feed{f1, f2}
_, err := feed.UpdateToTargets(ctx, activity, stream.WithToTargetsNew(newTargets...))
if err != nil {
// ...
}
Add some targets and remove some others:
add := []stream.Feed{target1, target2}
remove := []stream.Feed{oldTarget1, oldTarget2}
_, err := feed.UpdateToTargets(
ctx,
activity,
stream.WithToTargetsAdd(add),
stream.WithToTargetsRemove(remove),
)
if err != nil {
// ...
}
Note: you can't mix stream.WithToTargetsNew with stream.WithToTargetsAdd or stream.WithToTargetsRemove.
You can add the same activities to multiple feeds at once with the (*Client).AddToMany method (docs):
_, err := client.AddToMany(ctx,
activity, feed1, feed2, ...,
)
if err != nil {
// ...
}
You can create multiple follow relationships at once with the (*Client).FollowMany method (docs):
relationships := []stream.FollowRelationship{
stream.NewFollowRelationship(source, target),
...,
}
_, err := client.FollowMany(ctx, relationships)
if err != nil {
// ...
}
You can get a token suitable for client-side real-time feed updates as:
// Read+Write token
token := feed.RealtimeToken(false)
// Read-only token
readonlyToken := feed.RealtimeToken(true)
If your app is enabled for analytics collection you can use the Go client to track events. The main documentation for the analytics features is available in our Docs page.
You can obtain a specialized Analytics client (*stream.AnalyticsClient) from a regular client, which you can use to track events:
// Create the client
analytics := client.Analytics()
Engagement events can be tracked with the TrackEngagement method of AnalyticsClient. It accepts any number of EngagementEvents.
Events' syntax is not checked by the client, so be sure to follow our documentation about it.
Events are simple maps, but the stream package offers handy helpers to populate such events easily.
// Create the event
event := stream.EngagementEvent{}.
WithLabel("click").
WithForeignID("event:1234").
WithUserData(stream.NewUserData().String("john")).
WithFeatures(
stream.NewEventFeature("color", "blue"),
stream.NewEventFeature("shape", "rectangle"),
).
WithLocation("homepage")
// Track the event(s)
_, err := analytics.TrackEngagement(ctx, event)
if err != nil {
// ...
}
Impression events can be tracked with the TrackImpression method of AnalyticsClient (syntax docs):
```go // Create the impression events imp := stream.ImpressionEventData{}. WithF
$ claude mcp add stream-go2 \
-- python -m otcore.mcp_server <graph>