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 RequestLimit/min
Connect10,000
Get or Create Channel10,000
Get App10,000
Mark All Read10,000
Mark Read10,000
Query Channels10,000
Send Event10,000
Create Guest1,000
Delete Message1,000
Delete Reaction1,000
Get Message1,000
Get Reactions1,000
Get Replies1,000
Query Users1,000
Run Message Action1,000
Send Message1,000
Send Reaction1,000
Stop Watching Channel1,000
Update Message1,000
Upload File1,000
Upload Image1,000
Ban300
Create Device300
Edit Users300
Flag300
Hide Channel300
Mute300
Query Members300
Search300
Show Channel300
Unban300
Unflag300
Unmute300
Update Channel300
Update Users300
Update Users (Partial)300
Activate User60
Check Push60
Create Channel Type60
Deactivate User60
Delete Channel60
Delete Channel Type60
Delete Device60
Delete File60
Delete User60
Export Channel60
Export User60
Get Channel Type60
List Channel Types60
List Devices60
Truncate Channel60
Update App60
Update Channel Type60

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.