# Rate limits

Stream applies rate limits to protect both your application and our infrastructure. Rate limits prevent:

- Integration issues or abuse from degrading your app's performance (excessive API calls trigger client-side events)
- Resource consumption beyond what is provisioned for your plan
- Common integration mistakes, such as opening multiple WebSocket connections per user

Rate limits work the same way across every Stream product. They are applied per **API endpoint** and **platform** on a 1-minute window. Different platforms (iOS, Android, Web, Server) have independent counters for each endpoint.

<Admonition type="info">

If 6,000 iOS users and 6,000 Android users connect within one minute, no rate limit is triggered. The 10,000/minute connect limit applies independently to each platform.

</Admonition>

<Admonition type="info">

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

</Admonition>

## Types of Rate Limits

### User Rate Limits

Each user is limited to **60 requests per minute** per endpoint and platform. This prevents a single user from consuming your entire application quota. Your server is not subject to user rate limits.

### App Rate Limits

App rate limits apply per endpoint and platform combination. Stream supports four platforms:

| Platform | SDKs                                  |
| -------- | ------------------------------------- |
| Server   | Node, Python, Ruby, Go, C#, PHP, Java |
| Android  | Kotlin, Java, Flutter, React Native   |
| iOS      | Swift, Flutter, React Native          |
| Web      | React, Angular, JavaScript            |

Rate limits are not shared across platforms. If a server-side script hits a rate limit, your mobile and web applications are unaffected.

App rate limits are enforced both per minute and per second. The per-second limit equals the per-minute limit divided by 30 to allow for bursts.

When a rate limit is exceeded, all calls from the same app, platform, and endpoint return HTTP status `429`.

## Rate Limit Headers

All API responses include rate limit information in headers.

| Header                | Description                                        |
| --------------------- | -------------------------------------------------- |
| X-RateLimit-Limit     | Total limit for the requested resource (e.g. 5000) |
| X-RateLimit-Remaining | Remaining requests in current window (e.g. 4999)   |
| X-RateLimit-Reset     | When the current window resets (Unix timestamp)    |

## Inspecting Rate Limits

Check your current rate limit quotas and usage in your app's dashboard, or via the API.

<Tabs>

```js label="Node.js"
// Server-side platform only
const limits = await client.getRateLimits({ serverSide: true });

// All platforms
const limits = await client.getRateLimits();

// Specific platforms
const limits = await client.getRateLimits({ ios: true, android: true });

// Specific endpoints
const limits = await client.getRateLimits({
  endpoints: ["QueryUsers", "UpdateUsers"],
});
```

```python label="Python"
# Server-side platform only
limits = client.get_rate_limits(server_side=True)

# All platforms
limits = client.get_rate_limits()

# Specific platforms
limits = client.get_rate_limits(ios=True, android=True)

# Specific endpoints
limits = client.get_rate_limits(endpoints=["QueryUsers", "UpdateUsers"])
```

```ruby label="Ruby"
require 'getstream_ruby'

# Server-side platform only
limits = client.common.get_rate_limits(true)

# All platforms
limits = client.common.get_rate_limits

# Specific platforms
limits = client.common.get_rate_limits(nil, true, true)

# Specific endpoints
limits = client.common.get_rate_limits(nil, nil, nil, nil, 'QueryUsers,UpdateUsers')
```

```php label="PHP"
// Server-side platform only
$response = $client->getRateLimits(true, false, false, false, '');

// All platforms
$response = $client->getRateLimits(false, false, false, false, '');
```

```go label="Go"
// Server-side platform only
limits, _ := client.GetRateLimits(ctx, &getstream.GetRateLimitsRequest{
  ServerSide: getstream.PtrTo(true),
})

// All platforms
limits, _ = client.GetRateLimits(ctx, &getstream.GetRateLimitsRequest{})

// Specific platforms
limits, _ = client.GetRateLimits(ctx, &getstream.GetRateLimitsRequest{
  Ios:     getstream.PtrTo(true),
  Android: getstream.PtrTo(true),
})

// Specific endpoints
limits, _ = client.GetRateLimits(ctx, &getstream.GetRateLimitsRequest{
  Endpoints: getstream.PtrTo("QueryUsers,UpdateUsers"),
})
```

```csharp label="C#"
// Server-side platform only
var limits = await client.GetRateLimitsAsync(new { server_side = "true" });

// All platforms
var limits = await client.GetRateLimitsAsync();

// Specific platforms
var limits = await client.GetRateLimitsAsync(new { ios = "true", android = "true" });

// Specific endpoints
var limits = await client.GetRateLimitsAsync(new { endpoints = "QueryUsers,UpdateUsers" });
```

```java label="Java"
// Server-side platform only
var limits = client.getRateLimits(GetRateLimitsRequest.builder().ServerSide(true).build()).execute();

// All platforms
var limits = client.getRateLimits(GetRateLimitsRequest.builder().build()).execute();

// Specific platforms
var limits = client.getRateLimits(GetRateLimitsRequest.builder().Ios(true).Android(true).build()).execute();

// Specific endpoints
var limits = client.getRateLimits(GetRateLimitsRequest.builder().Endpoints("QueryUsers,UpdateUsers").build()).execute();
```

</Tabs>

The response includes the 1-minute limit, remaining quota, and window reset timestamp.

## Handling Rate Limit Errors

When you receive a `429` status code, implement exponential back-off retry logic. Use the `X-RateLimit-Reset` header to determine when to retry.

### Catching 429s in backend SDKs

The Go, Python, Java, PHP, Ruby, and .NET SDKs surface 429s as a dedicated rate-limit exception that carries the parsed `Retry-After` header. Catch the rate-limit subclass first if you want to react to throttling, then fall through to the broader API-error catch for other 4xx/5xx responses. Each SDK documents its exception class hierarchy for this purpose. In Node.js, check the response code and rate limit information on the error's metadata instead.

<Tabs>

```js label="Node.js"
try {
  await client.queryUsers(...);
} catch (error) {
  const rateLimit = error.metadata?.rateLimit;
  if (error.metadata?.responseCode === 429) {
    // Wait until rateLimit.rateLimitReset, then retry
  }
}
```

```python label="Python"
from getstream.exceptions import StreamRateLimitException

try:
    client.query_users(...)
except StreamRateLimitException as e:
    # e.retry_after is a timedelta (None when the header is absent)
    if e.retry_after:
        time.sleep(e.retry_after.total_seconds())
```

```ruby label="Ruby"
begin
  client.query_users(...)
rescue GetStreamRuby::RateLimitError => e
  sleep(e.retry_after || 1)
end
```

```php label="PHP"
use GetStream\Exceptions\StreamRateLimitException;

try {
    $client->queryUsers(...);
} catch (StreamRateLimitException $e) {
    // $e->getRetryAfter() is ?int (seconds)
    sleep($e->getRetryAfter() ?? 1);
}
```

```go label="Go"
import (
    "errors"
    "github.com/GetStream/getstream-go/v4"
)

_, err := client.QueryUsers(ctx, ...)
if errors.Is(err, getstream.ErrRateLimited) {
    var streamErr *getstream.StreamError
    errors.As(err, &streamErr)
    time.Sleep(streamErr.RetryAfter)
}
```

```csharp label="C#"
try
{
    await client.QueryUsersAsync(...);
}
catch (GetStreamRateLimitException ex)
{
    // ex.RetryAfter is a TimeSpan? (null when the header is absent)
    await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(1));
}
```

```java label="Java"
import io.getstream.exceptions.StreamRateLimitException;

try {
    client.queryUsers(...);
} catch (StreamRateLimitException e) {
    if (e.getRetryAfter() != null) {
        Thread.sleep(e.getRetryAfter().toMillis());
    }
}
```

</Tabs>

`Retry-After` parsing supports both forms documented in RFC 7231 §7.1.3: an integer number of seconds (`Retry-After: 30`) or an HTTP-date (`Retry-After: Fri, 31 Dec 2026 23:59:59 GMT`). Past dates clamp to zero. If the header is absent or unparseable, the exception's `retryAfter` field is `null`/`None`/zero so your code does not have to guard against parse errors.

### Automatic retries

Instead of catching `429`s yourself, you can let the SDK retry them for you. Automatic retries are opt-in and disabled by default; enable them when you construct the client and set the attempt budget and the maximum wait between attempts.

<Tabs>

```python label="Python"
from getstream import Stream, RetryConfig

client = Stream(
    api_key="your_api_key",
    api_secret="your_api_secret",
    retry=RetryConfig(enabled=True, max_attempts=3, max_backoff=30.0),
)
```

```ruby label="Ruby"
require 'getstream_ruby'

client = GetStreamRuby::Client.new(
  api_key: 'your_api_key',
  api_secret: 'your_api_secret',
  retry_config: GetStreamRuby::RetryConfig.new(
    enabled: true,
    max_attempts: 3,
    max_backoff: 30.0
  )
)
```

```php label="PHP"
use GetStream\ClientBuilder;
use GetStream\Http\RetryConfig;

$client = (new ClientBuilder())
    ->apiKey('your_api_key')
    ->apiSecret('your_api_secret')
    ->retry(new RetryConfig(enabled: true, maxAttempts: 3, maxBackoff: 30.0))
    ->build();
```

```go label="Go"
import (
    "time"

    stream "github.com/GetStream/getstream-go/v4"
)

client, err := stream.NewClient("your_api_key", "your_api_secret",
    stream.WithRetry(stream.RetryConfig{
        Enabled:     true,
        MaxAttempts: 3,
        MaxBackoff:  30 * time.Second,
    }),
)
```

```csharp label="C#"
using GetStream;

var client = new StreamClient(new StreamOptions
{
    ApiKey = "your_api_key",
    ApiSecret = "your_api_secret",
    Retry = new RetryConfig
    {
        Enabled = true,
        MaxAttempts = 3,
        MaxBackoff = TimeSpan.FromSeconds(30),
    },
});
```

```java label="Java"
import io.getstream.services.framework.StreamSDKClient;
import io.getstream.services.framework.StreamClientOptions;
import io.getstream.services.framework.RetryConfig;
import java.time.Duration;

RetryConfig retry = new RetryConfig()
    .setEnabled(true)
    .setMaxAttempts(3)
    .setMaxBackoff(Duration.ofSeconds(30));

StreamSDKClient client = new StreamSDKClient(
    "your_api_key", "your_api_secret",
    new StreamClientOptions().setRetry(retry));
```

</Tabs>

When enabled, the SDK retries only idempotent `GET` and `HEAD` requests, and only when the response is a `429` that the backend has not marked unrecoverable, or a transport-level failure (connection reset, timeout, DNS, or TLS). Writes (`POST`, `PUT`, `PATCH`, `DELETE`), `5xx` responses, and other `4xx` responses are never retried. `maxAttempts` counts the initial request, so the default of `3` means one request plus up to two retries. Between attempts the SDK waits for the `Retry-After` header when present (clamped to `maxBackoff`), otherwise a full-jitter exponential backoff capped at `maxBackoff`. If every attempt fails, the last error is surfaced unchanged, so the exception handling above still applies.

## Avoiding Rate Limits

1. **Add delays to scripts** - The most common cause of rate limits. Add timeouts between successive API calls in batch scripts or cronjobs.

2. **Use batch endpoints** - Instead of 100 individual calls, use batch endpoints to update multiple resources in one request.

3. **Check client-side rendering logic** - Infinite pagination bugs or other client-side issues can trigger excessive API calls.

4. **Avoid redundant queries** - Many of Stream's endpoints have upsert behavior. Do not query to check whether an entity exists before creating it.

Each product's rate limits page lists practices specific to that product, such as [livestream chat](https://getstream.io/chat/docs/node/rate-limits/) messaging volume.

## Requesting Higher Limits

- **Standard plans** - Stream may increase limits after reviewing your integration to confirm optimal usage of default limits.
- **Enterprise plans** - Stream reviews your architecture and sets appropriate limits for your production application.

## Default Limits by Endpoint

Default limits for endpoints shared by all Stream products are listed below. Product endpoints are documented with each product:

- [Chat rate limits](https://getstream.io/chat/docs/node/rate-limits/)
- [Video rate limits](https://getstream.io/video/docs/api/rate-limits/)
- [Feeds rate limits](https://getstream.io/activity-feeds/docs/node/rate-limits/)

### Common endpoints

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

<Admonition type="info">

All endpoints also enforce the user rate limit of 60 requests per minute per user. Rate limits can be adjusted based on your use case and plan.

</Admonition>


---

This page was last updated at 2026-08-07T13:10:45.471Z.

For the most recent version of this documentation, visit [https://getstream.io/docs/platform/rate-limits/](https://getstream.io/docs/platform/rate-limits/).