const response = await client....
const rateLimit = response.metadata.rateLimit;
// the total limit allowed for the resource requested
console.log(rateLimit.rateLimit);
// the remaining limit
console.log(rateLimit.rateLimitRemaining);
// when the current limit will reset - Date
console.log(rateLimit.rateLimitReset);
// or
try {
await client....
} catch (error) {
const rateLimit = error.metadata?.rateLimit;
if (error.metadata?.responseCode === 429) {
// Wait until rate limit resets and then retry
}
}Rate Limits
Stream enforces rate limits to ensure the stability of our service for our customers and to encourage a performant integration. Stream powers user interactions for some of the world's largest apps and has scaling infrastructure to accommodate. Rate limits can be increased at customers' requests and by upgrading to larger usage plans.
Our default rate limits are documented below. Rate limits are higher for Enterprise plans depending on capacity and use case. Talk to your Customer Success Manager for increases.
Every Application has rate limits applied based on a combination of API endpoint and platform: these limits are set on a 1-minute time window. For example, different operations have different limits per endpoint. Likewise, different platforms such as iOS, Android or your server-side infrastructure have independent counters for each API endpoint's rate limit.
Dynamic Rate Limiting: Rate limits may be adjusted based on overall platform load and your application's individual usage patterns for query endpoints. During periods of high demand, the platform may temporarily reduce rate limits to ensure stability and fair resource allocation for all users. Monitor the X-RateLimit-* headers in API responses to track your current limits.
Types of rate limits
There are two kinds of rate limits:
- User Rate Limits: Apply to each user and platform combination and help to prevent a single user from consuming your Application rate limits.
- App rate limits: App rate limits are calculated per endpoint and platform combination for your application.
User Rate Limits
To avoid individual users consuming your entire quota, every single user is limited to at most 60 requests per minute (per API endpoint and platform). When the limit is exceeded, requests from that user and platform will be rejected.
App Rate Limits
Stream supports four different platforms via our official SDKs:
- Server: SDKs that execute on the server including Node, Python, Ruby, Go, C#, PHP, and Java.
- Android: SDKs that execute on an Android device including Kotlin, Java, Flutter, and React Native for Android clients.
- iOS: SDKs that execute on an iOS device including Swift, Flutter, and React Native for iOS clients.
- Web: SDKs that execute in a browser including React, Angular, or vanilla JavaScript clients.
Rate limit quotas are not shared across different platforms. This way if by accident a server-side script hits a rate limit, you will not have any impact on your mobile and web applications. When the limit is hit, all calls from the same app, platform, and endpoint will result in an error with a 429 HTTP status code.
App rate limits are administered both per minute and per second. The per-second limit is equal to the per-minute limit divided by 30 to allow for bursts.
What To Do When You've Hit a Rate Limit
You should always review responses from Stream to watch for error conditions. If you receive 429 status, this means your API request was rate-limited and you will need to retry. We recommend implementing an exponential back-off retry mechanism.
Here are a few things to keep in mind to avoid rate limits on server-side:
Slow down your scripts: This is the most common cause of rate limits. You're running a cronjob or script that runs many API calls in succession. Adding a small timeout in between API calls typically solves the issue.
Use batch endpoints: Batch update endpoints exist for many operations. So instead of making many calls to update one resource each, use the batch endpoint to update multiple resources in one request.
Query only when needed: Sometimes apps will call a query endpoint to see if an entity exists before creating it. Many of Stream's endpoints have an upsert behaviour, so this isn't necessary in most cases.
If rate limits are still a problem, Stream can set higher limits for certain pricing plans:
- For Standard plans, Stream may also raise rate limits in certain instances. An integration review is required to ensure your integration is making optimal use of the default rate limits before any increase will be applied.
- For Enterprise plans, Stream will review your architecture, and set higher rate limits for your production application.
Rate limit headers
API responses include rate limit information in headers.
| Header | Description |
|---|---|
| X-RateLimit-Limit | the total limit allowed for the resource requested (i.e. 5000) |
| X-RateLimit-Remaining | the remaining limit (i.e. 4999) |
| X-RateLimit-Reset | when the current limit will reset (Unix timestamp) |
const response = await client....
const rateLimit = response.metadata.rateLimit;
// the total limit allowed for the resource requested
console.log(rateLimit.rateLimit);
// the remaining limit
console.log(rateLimit.rateLimitRemaining);
// when the current limit will reset - Date
console.log(rateLimit.rateLimitReset);
// or
try {
await client....
} catch (error) {
const rateLimit = error.metadata?.rateLimit;
if (error.metadata?.responseCode === 429) {
// Wait until rate limit resets and then retry
}
}const response = await client....
const rateLimit = response.metadata.rateLimit;
// the total limit allowed for the resource requested
console.log(rateLimit.rateLimit);
// the remaining limit
console.log(rateLimit.rateLimitRemaining);
// when the current limit will reset - Date
console.log(rateLimit.rateLimitReset);
// or
try {
await client....
} catch (error) {
const rateLimit = error.metadata?.rateLimit;
if (error.metadata?.responseCode === 429) {
// Wait until rate limit resets and then retry
}
}const response = await client....
const rateLimit = response.metadata.rateLimit;
// the total limit allowed for the resource requested
console.log(rateLimit.rateLimit);
// the remaining limit
console.log(rateLimit.rateLimitRemaining);
// when the current limit will reset - Date
console.log(rateLimit.rateLimitReset);
// or
try {
await client....
} catch (error) {
const rateLimit = error.metadata?.rateLimit;
if (error.metadata?.responseCode === 429) {
// Wait until rate limit resets and then retry
}
}response, err := client.QueryUsers(ctx, request)
if err != nil {
if streamErr, ok := err.(getstream.StreamError); ok {
if streamErr.StatusCode == 429 {
rateLimit := streamErr.RateLimit
// Wait until time.Unix(rateLimit.Reset, 0) and retry
}
}
log.Fatal(err)
return
}
// Payload
users := response.Data.Users
// Rate limit (same shape on every success response)
rateLimit := response.RateLimitInfo
// rateLimit.Limit - total limit
// rateLimit.Remaining - remaining in window
// rateLimit.Reset - Unix timestamp (seconds); convert to time: time.Unix(rateLimit.Reset, 0)response = client.some_method(...)
# Payload
data = response.data
# Rate limit (method, not property)
rate_limit = response.rate_limit()
if rate_limit:
print(rate_limit.limit) # total limit
print(rate_limit.remaining) # remaining in window
print(rate_limit.reset) # datetime (UTC) when the window resets
try:
response = client.some_method(...)
except StreamAPIException as e:
if e.status_code == 429:
rate_limit = e.rate_limit_info
if rate_limit:
# Wait until rate_limit.reset and retry
passbegin
response = client.feeds.some_method(...)
# Payload is accessible via method_missing on the response
data = response.to_h
rescue GetStreamRuby::APIError => e
# Check if rate limited from error message
# Implement exponential back-off retry
end$response = $feedsClient->someMethod(...);
// Payload
$data = $response->getData();
// Rate limit info from response headers
$limit = $response->getHeader('x-ratelimit-limit'); // total limit
$remaining = $response->getHeader('x-ratelimit-remaining'); // remaining in window
$reset = $response->getHeader('x-ratelimit-reset'); // Unix timestamp when the window resets
try {
$response = $feedsClient->someMethod(...);
} catch (\GetStream\Exceptions\StreamApiException $e) {
if ($e->getStatusCode() === 429) {
// Wait until rate limit resets and retry
}
}try
{
var response = await feedsClient.SomeMethodAsync(...);
// Payload
var data = response.Data;
}
catch (GetStreamApiException e) when (e.StatusCode == 429)
{
// Rate limited - wait and retry with exponential back-off
}var response = client.feeds().someMethod(...).request();
// Payload
var data = response.getData();
// Rate limit info (available on every response)
RateLimit rateLimit = response.getRateLimit();
// rateLimit.getLimit() - total limit
// rateLimit.getRemaining() - remaining in window
// rateLimit.getReset() - Date when the window resets
try {
var response = client.feeds().someMethod(...).request();
} catch (StreamException e) {
if (e.getResponseData() != null && e.getResponseData().getStatusCode() == 429) {
// Wait until rate limit resets and retry
}
}Inspecting rate limits
Stream offers the ability to inspect an App's current rate limit quotas and usage in your App's dashboard. Alternatively, you can also retrieve the API limits for your application using server-side SDKs:
// 1. Get feeds rate limits, server-side platform
const limits = await client.feeds.getFeedsRateLimits({ server_side: true });
// 2. Get feeds rate limits, all platforms
const limits = await client.feeds.getFeedsRateLimits();
// 3. Get feeds rate limits, iOS and Android
const limits = await client.feeds.getFeedsRateLimits({
ios: true,
android: true,
});
// 4. Get feeds rate limits for specific endpoints
const limits = await client.feeds.getFeedsRateLimits({
endpoints: "QueryActivities,AddActivity,GetOrCreateFeed",
});
// Get rate limits (all platforms, common and moderation endpoints)
const rateLimits = await client.getRateLimits();// 1. Get feeds rate limits, server-side platform
limits, _ := client.Feeds().GetFeedsRateLimits(ctx, getstream.WithServerSide())
// 2. Get feeds rate limits, all platforms
limits, _ := client.Feeds().GetFeedsRateLimits(ctx)
// 3. Get feeds rate limits, iOS and Android
limits, _ := client.Feeds().GetFeedsRateLimits(ctx, getstream.WithIOS(), getstream.WithAndroid())
// 4. Get feeds rate limits for specific endpoints
limits, _ := client.Feeds().GetFeedsRateLimits(ctx, getstream.WithEndpoints("QueryActivities", "AddActivity", "GetOrCreateFeed"))
// Get rate limits (all platforms, common and moderation endpoints)
limits, _ = client.GetRateLimits(ctx)// 1. Get feeds rate limits, server-side platform
var limits = client.feeds().getFeedsRateLimits().serverSide(true).request();
// 2. Get feeds rate limits, all platforms
var limits = client.feeds().getFeedsRateLimits().request();
// 3. Get feeds rate limits, iOS and Android
var limits = client.feeds().getFeedsRateLimits().ios(true).android(true).request();
// 4. Get feeds rate limits for specific endpoints
var limits = client.feeds().getFeedsRateLimits().endpoints("QueryActivities", "AddActivity", "GetOrCreateFeed").request();
// Get rate limits (all platforms, common and moderation endpoints)
var rateLimits = client.getRateLimits().request();// 1. Get feeds rate limits, server-side platform
$limits = $client->feeds()->getFeedsRateLimits(['server_side' => true]);
// 2. Get feeds rate limits, all platforms
$limits = $client->feeds()->getFeedsRateLimits();
// 3. Get feeds rate limits, iOS and Android
$limits = $client->feeds()->getFeedsRateLimits(['ios' => true, 'android' => true]);
// 4. Get feeds rate limits for specific endpoints
$limits = $client->feeds()->getFeedsRateLimits(['endpoints' => ['QueryActivities', 'AddActivity', 'GetOrCreateFeed']]);
// Get rate limits (all platforms, common and moderation endpoints)
$limits = $client->getRateLimits();// 1. Get feeds rate limits, server-side platform
var limits = await client.Feeds.GetFeedsRateLimitsAsync(new GetFeedsRateLimitsOptions { ServerSide = true });
// 2. Get feeds rate limits, all platforms
var limits = await client.Feeds.GetFeedsRateLimitsAsync();
// 3. Get feeds rate limits, iOS and Android
var limits = await client.Feeds.GetFeedsRateLimitsAsync(new GetFeedsRateLimitsOptions { Ios = true, Android = true });
// 4. Get feeds rate limits for specific endpoints
var limits = await client.Feeds.GetFeedsRateLimitsAsync(new GetFeedsRateLimitsOptions
{
Endpoints = new[] { "QueryActivities", "AddActivity", "GetOrCreateFeed" }
});
// Get rate limits (all platforms, common and moderation endpoints)
var rateLimits = await client.GetRateLimitsAsync();# 1. Get feeds rate limits, server-side platform
limits = client.feeds.get_feeds_rate_limits(server_side=True)
# 2. Get feeds rate limits, all platforms
limits = client.feeds.get_feeds_rate_limits()
# 3. Get feeds rate limits, iOS and Android
limits = client.feeds.get_feeds_rate_limits(ios=True, android=True)
# 4. Get feeds rate limits for specific endpoints
limits = client.feeds.get_feeds_rate_limits(endpoints=["QueryActivities", "AddActivity", "GetOrCreateFeed"])
# Get rate limits (all platforms, common and moderation endpoints)
limits = client.get_rate_limits()# 1. Get feeds rate limits, server-side platform
limits = client.feeds.get_feeds_rate_limits(server_side: true)
# 2. Get feeds rate limits, all platforms
limits = client.feeds.get_feeds_rate_limits()
# 3. Get feeds rate limits, iOS and Android
limits = client.feeds.get_feeds_rate_limits(ios: true, android: true)
# 4. Get feeds rate limits for specific endpoints
limits = client.feeds.get_feeds_rate_limits(endpoints: ['QueryActivities', 'AddActivity', 'GetOrCreateFeed'])
# Get rate limits (all platforms, common and moderation endpoints)
limits = client.get_rate_limits()Default rate limits by endpoint
Feeds endpoints
| Operation ID | Rate limit (req/min) |
|---|---|
| AcceptFeedMemberInvite | 1,000 |
| AcceptFollow | 1,000 |
| ActivityFeedback | 1,000 |
| AddActivity | 1,000 |
| AddActivityReaction | 1,000 |
| AddBookmark | 1,000 |
| AddComment | 1,000 |
| AddCommentReaction | 1,000 |
| AddCommentsBatch | 300 |
| CastPollVote | 1,000 |
| CreateCollections | 300 |
| CreateFeedGroup | 300 |
| CreateFeedsBatch | 300 |
| CreateFeedView | 300 |
| CreateMembershipLevel | 300 |
| DeleteActivity | 1,000 |
| DeleteActivityReaction | 1,000 |
| DeleteActivities | 300 |
| DeleteBookmark | 1,000 |
| DeleteBookmarkFolder | 1,000 |
| DeleteComment | 1,000 |
| DeleteCommentReaction | 1,000 |
| DeleteFeed | 1,000 |
| DeleteFeedsBatch | 300 |
| DeleteFeedGroup | 300 |
| DeleteFeedUserData | 300 |
| DeleteFeedView | 300 |
| DeleteMembershipLevel | 300 |
| DeletePollVote | 1,000 |
| ExportFeedUserData | 300 |
| Follow | 1,000 |
| FollowBatch | 300 |
| GetActivity | 1,000 |
| GetComment | 1,000 |
| GetCommentReplies | 1,000 |
| GetComments | 1,000 |
| GetFeedGroup | 1,000 |
| GetFeedView | 1,000 |
| GetFeedVisibility | 1,000 |
| GetFeedsRateLimits | 1,000 |
| GetFollowSuggestions | 1,000 |
| GetOrCreateFeed | 10,000 |
| GetOrCreateFeedGroup | 300 |
| GetOrCreateFeedView | 300 |
| GetOrCreateFollows | 1,000 |
| GetOrCreateUnfollows | 1,000 |
| ListFeedGroups | 1,000 |
| ListFeedViews | 1,000 |
| ListFeedVisibilities | 1,000 |
| MarkActivity | 1,000 |
| PinActivity | 1,000 |
| QueryActivities | 1,000 |
| QueryActivityReactions | 1,000 |
| QueryBookmarkFolders | 1,000 |
| QueryBookmarks | 1,000 |
| QueryCommentReactions | 1,000 |
| QueryComments | 1,000 |
| QueryFeedMembers | 1,000 |
| QueryFeeds | 1,000 |
| QueryFollows | 1,000 |
| QueryMembershipLevels | 1,000 |
| QueryFeedsUsageStats | 300 |
| ReadCollections | 300 |
| RejectFeedMemberInvite | 1,000 |
| RejectFollow | 1,000 |
| RestoreActivity | 300 |
| StopWatchingFeed | 1,000 |
| Unfollow | 1,000 |
| UnfollowBatch | 300 |
| UnpinActivity | 1,000 |
| UpdateActivity | 1,000 |
| UpdateActivityPartial | 1,000 |
| UpdateActivitiesPartialBatch | 300 |
| UpdateBookmark | 1,000 |
| UpdateBookmarkFolder | 1,000 |
| UpdateComment | 1,000 |
| UpdateFeed | 1,000 |
| UpdateFeedGroup | 300 |
| UpdateFeedMembers | 1,000 |
| UpdateFeedView | 300 |
| UpdateFeedVisibility | 1,000 |
| UpdateFollow | 1,000 |
| UpdateMembershipLevel | 1,000 |
| UpsertActivities | 300 |
| UpsertCollections | 300 |
Common endpoints
These endpoints are shared among all Stream products:
| Operation ID | Rate limit (req/min) |
|---|---|
| BlockUsers | 300 |
| CheckExternalStorage | 60 |
| CheckPush | 60 |
| CheckSNS | 60 |
| CheckSQS | 300 |
| CreateBlockList | 60 |
| CreateDevice | 300 |
| CreateExternalStorage | 60 |
| CreateGuest | 1,000 |
| CreateImport | 300 |
| CreateImportURL | 300 |
| CreateImportV2Task | 1,000 |
| CreatePoll | 300 |
| CreatePollOption | 300 |
| CreateRole | 60 |
| Connect | 10,000 |
| DeactivateUser | 60 |
| DeactivateUsers | 60 |
| DeleteBlockList | 60 |
| DeleteDevice | 60 |
| DeleteExternalStorage | 60 |
| DeleteFile | 60 |
| DeleteImage | 60 |
| DeleteImportV2Task | 1,000 |
| DeletePoll | 60 |
| DeletePollOption | 60 |
| DeletePushProvider | 60 |
| DeleteRole | 60 |
| DeleteUsers | 6 |
| ExportUser | 60 |
| ExportUsers | 60 |
| GetApp | 10,000 |
| GetBlockList | 60 |
| GetBlockedUsers | 300 |
| GetImport | 300 |
| GetImportV2Task | 1,000 |
| GetOG | 1,000 |
| GetPermission | 60 |
| GetPoll | 1,000 |
| GetPollOption | 1,000 |
| GetPushTemplates | 60 |
| GetRateLimits | 1,000 |
| GetTask | 300 |
| GetUserLiveLocations | 1,000 |
| ListBlockLists | 60 |
| ListDevices | 60 |
| ListExternalStorage | 60 |
| ListImportV2Tasks | 1,000 |
| ListImports | 300 |
| ListPermissions | 60 |
| ListPushProviders | 60 |
| ListRoles | 60 |
| LongPoll | 10,000 |
| QueryPollVotes | 1,000 |
| QueryPolls | 1,000 |
| QueryUsers | 1,000 |
| ReactivateUser | 60 |
| ReactivateUsers | 60 |
| RestoreUsers | 1,000 |
| UnblockUsers | 300 |
| UpdateApp | 60 |
| UpdateBlockList | 60 |
| UpdateExternalStorage | 60 |
| UpdateLiveLocation | 1,000 |
| UpdatePoll | 300 |
| UpdatePollOption | 300 |
| UpdatePollPartial | 300 |
| UpdatePushNotificationPreferences | 300 |
| UpdateUsers | 300 |
| UpdateUsersPartial | 300 |
| UploadFile | 1,000 |
| UploadImage | 1,000 |
| UpsertPushProvider | 60 |
| UpsertPushTemplate | 60 |