Skip to content

Rate Limits

Rate limit mechanics are shared by every Stream product and documented on Rate limits: how limits are counted, the X-RateLimit-* headers, inspecting your quotas, catching 429s, and enabling automatic retries in the server SDKs. This page lists the chat endpoint limits and chat-specific practices.

Rate Limits by Endpoint

Default rate limits for self-serve plans (per minute, per platform).

API Request Limit/min
Connect 10,000
Get or Create Channel 10,000
Get App 10,000
Mark All Read 10,000
Mark Read 10,000
Query Channels 10,000
Send Event 10,000
Create Guest 1,000
Delete Message 1,000
Delete Reaction 1,000
Get Message 1,000
Get Reactions 1,000
Get Replies 1,000
Query Users 1,000
Run Message Action 1,000
Send Message 1,000
Send Reaction 1,000
Stop Watching Channel 1,000
Update Message 1,000
Upload File 1,000
Upload Image 1,000
Ban 300
Create Device 300
Edit Users 300
Flag 300
Hide Channel 300
Mute 300
Query Members 300
Search 300
Show Channel 300
Unban 300
Unflag 300
Unmute 300
Update Channel 300
Update Users 300
Update Users (Partial) 300
Activate User 60
Check Push 60
Create Channel Type 60
Deactivate User 60
Delete Channel 60
Delete Channel Type 60
Delete Device 60
Delete File 60
Delete User 60
Export Channel 60
Export User 60
Get Channel Type 60
List Channel Types 60
List Devices 60
Truncate Channel 60
Update App 60
Update Channel Type 60
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.

Avoiding Rate Limits in Chat

In addition to the general practices, chat integrations should:

  1. Avoid redundant queries - Channel creation is an upsert operation. Do not call QueryChannels to check if a channel exists before creating it. See Query Channels.

  2. Use one WebSocket per user - Multiple WebSocket connections per user cause performance issues, billing problems, and unexpected behavior. See Initialization & Users.

  3. Follow livestream best practices - High-volume messaging scenarios require additional optimization. See Livestream Best Practices.

Handling 429s in the Unity SDK

The Unity SDK surfaces rate limits and other API errors through StreamApiException. The exception classes are documented in Error handling, and the full list of codes in API error codes:

// Example 1 — single catch + switch on the numeric error code.
try
{
    await channel.SendNewMessageAsync("Hello");
}
catch (StreamApiException ex)
{
    // ex.Code / ex.StatusCode / ex.ErrorMessage / ex.ExceptionFields / ex.MoreInfo
    // are all available for inspection. See the API Error Codes docs page
    // for the full list.
    switch (ex.Code)
    {
        case StreamApiException.RateLimitErrorStreamCode:
            // HTTP 429 — back off with exponential delay before retrying.
            await Task.Delay(TimeSpan.FromSeconds(1));
            break;
        case StreamApiException.CooldownErrorStreamCode:
            // HTTP 403 — slow-mode cooldown. Gate the send UI for `channel.Cooldown` seconds.
            break;
        case StreamApiException.PermissionDeniedErrorStreamCode:
            // HTTP 403 — user lacks permission. Hide / disable the control.
            break;
        default:
            Debug.LogError($"Stream API error {ex.Code} (HTTP {ex.StatusCode}): {ex.ErrorMessage}");
            break;
    }
}

// Alternatively, use the `when` syntax combined with our dedicated
// error-checking extensions.
try
{
    await channel.SendNewMessageAsync("Hello");
}
catch (StreamApiException ex) when (ex.IsRateLimitExceededError())
{
    // HTTP 429 / Stream code 9 — back off before retrying.
    await Task.Delay(TimeSpan.FromSeconds(1));
}

// Most common error cases have a dedicated extension method on StreamApiException.
// Pick the helper that matches your branch instead of comparing `ex.Code` by hand:
//   IsRateLimitExceededError       (429 / 9)     — back off and retry
//   IsCooldownError                (403 / 60)    — gate UI for `channel.Cooldown` seconds
//   IsPermissionDeniedError        (403 / 17)    — hide the UI control
//   IsNoAccessToChannelsError      (403 / 70)    — drop the channel locally
//   IsAppSuspendedError            (403 / 99)    — show service-unavailable UI
//   IsAuthenticationError          (401 / 5)     — send the user back to sign-in
//   IsTokenExpiredError            (401 / 40)    — refresh the token
//   IsTokenError                   (401 / 40-43) — any token-related failure
//   IsDoesNotExistError            (404 / 16)    — refresh the local view
//   IsInputError                   (400 / 4)     — inspect `ex.ExceptionFields`
//   IsMessageTooLongError          (400 / 20)    — show character-limit error
//   IsMessageModerationFailedError (400 / 73)    — show "filtered" UI
//   IsPayloadTooBigError           (413 / 22)    — ask for a smaller file
//   IsInternalSystemError          (500 / -1)    — retry, then surface a transient-failure UI
//
// The general guidance: wrap a call in try/catch where you want to react
// specifically (rate-limit back-off, cooldown UI, validation feedback,
// permission gating). For everything else, let the exception propagate to
// your global error handler — every SDK call already throws StreamApiException
// on server-rejected requests, so a single boundary catch is enough.