# Multi-Tenant & Teams

Many apps that add chat have customers of their own. If you're building something like Slack, or a SaaS application like InVision you want to make sure that one customer can't read the messages of another customer. Stream Chat can be configured in multi-tenant mode so that users are organized in separated teams that cannot interact with each other.

## Teams

Stream Chat has the concept of teams for users and channels. The purpose of teams is to provide a simple way to separate different groups of users and channels within a single application.

If a user belongs to a team, the API will ensure that such user will only be able to connect to channels from the same team. Features such as user search are limited so that a user can only search for users from the same team by default.

<admonition type="info">

In legacy permission system users can never access users nor channels from other teams. In [Permissions V2](/chat/docs/<framework>/chat_permission_policies/) it is possible to alter this behavior using multi-tenant permissions.

</admonition>

When enabling multi-tenant mode all user requests will always ensure that the request applies to a team the user belongs to. For instance, if a user from team "blue" tries to delete a message that was created on a channel from team "red" the API will return an error. If user doesn't have team set, it will only have access to users and channels that don't have team.

## Enable Teams for your application

In order to use Teams, your application must have multi-tenant mode enabled. You can ensure your app is in multi-tenant mode by calling the Application Settings endpoint.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const client = new StreamChat("{{ api_key }}", "{{ api_secret }}");

// Enable multi-tenant in app settings.
await client.updateAppSettings({
  multi_tenant_enabled: true,
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
client.updateApp(UpdateAppRequest.builder()
    .multiTenantEnabled(true)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream import Stream

client = Stream(api_key="{{ api_key }}", api_secret="{{ api_secret }}")
client.update_app(multi_tenant_enabled=True)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.common.update_app(Models::UpdateAppRequest.new(multi_tenant_enabled: true))
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->updateApp(new Models\UpdateAppRequest(multiTenantEnabled: true));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await client.UpdateAppAsync(new UpdateAppRequest { MultiTenantEnabled = true });
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
client.UpdateApp(ctx, &getstream.UpdateAppRequest{
  MultiTenantEnabled: getstream.PtrTo(true),
})
```

</codetabs-item>

</codetabs>

<admonition type="info">

You only need to activate multi-tenancy once per application.

</admonition>

<admonition type="warning">

Do not turn off multitenancy on an application without very careful consideration as this will turn off teams checking which gives users the ability to access all channels and messages across all teams.

Make sure to activate multi-tenancy before using teams.

</admonition>

## User teams

When using teams, users must be created from your back-end and specify which teams they are a member of.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// creates or updates a user from backend to be part of the "red" and "blue" teams
client.upsertUser({ id, teams: ["red", "blue"] });
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Partial updating a user to be part of red and blue team
client.updateUsersPartial(UpdateUsersPartialRequest.builder()
    .users(List.of(UpdateUserPartialRequest.builder()
        .id(user.getId())
        .set(Map.of("teams", List.of("red", "blue")))
        .build()))
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import UpdateUserPartialRequest

client.update_users_partial(users=[
    UpdateUserPartialRequest(id=user_id, set={"teams": ["red", "blue"]})
])
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.common.update_users_partial(Models::UpdateUsersPartialRequest.new(
  users: [Models::UpdateUserPartialRequest.new(
    id: user_id,
    set: { 'teams' => ['red', 'blue'] }
  )]
))
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->updateUsersPartial(new Models\UpdateUsersPartialRequest(
    users: [new Models\UpdateUserPartialRequest(
        id: $userId,
        set: (object)["teams" => ["red", "blue"]],
    )],
));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await client.UpdateUsersPartialAsync(new UpdateUsersPartialRequest
{
    Users = new List<UpdateUserPartialRequest>
    {
        new UpdateUserPartialRequest
        {
            ID = user.ID,
            Set = new Dictionary<string, object> { { "teams", new[] { "red", "blue" } } },
        },
    },
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.UpdateUsersPartial(ctx, &getstream.UpdateUsersPartialRequest{
  Users: []getstream.UpdateUserPartialRequest{
    {
      ID: user.ID,
      Set: map[string]any{
        "teams": []string{"red", "blue"},
      },
    },
  },
})
```

</codetabs-item>

</codetabs>

<admonition type="info">

A user can be a member of a maximum of 250 teams. Team name is limited to 100 bytes

</admonition>

<admonition type="warning">

User teams are included in all User object payloads. We recommend to have short team names to reduce response payload sizes

</admonition>

<admonition type="warning">

In Permissions v1, user teams can only be changed using server-side auth. This ensures users can't change their own team membership. In Permissions v2 it is possible to update user teams from client-side if `UpdateUserTeam` action is granted to the user

</admonition>

## Channel team

Channels can be associated with a team. Users can create channels client-side but if their user is part of a team, they will have to specify a team or the request will be rejected with an error.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Creates the red-general channel for the red team
client.createChannel(
  channelType = "messaging",
  channelId = "red-general",
  memberIds = listOf("user1", "user2"),
  extraData = mapOf("team" to "red")
).enqueue { result ->
  if (result is Result.Success) {
    val channel = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// Creates the channel red-general for team red
client.channel("messaging", "red-general", { team: "red" }).create();
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
import StreamChat

/// 1: Create a `ChannelId` that represents the channel you want to create.
let channelId = ChannelId(type: .messaging, id: "general")

/// 2: Use the `ChatClient` to create a `ChatChannelController` with the `ChannelId`.
let channelController = try chatClient.channelController(
  createChannelWithId: channelId,
  name: "Channel Name",
  imageURL: nil,
  team: "red",
  extraData: .defaultValue
)

/// 3: Call `ChatChannelController.synchronize` to create the channel.
channelController.synchronize { error in
  if let error = error {
    /// 4: Handle possible errors
    print(error)
  }
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// Creates the channel red-general for team red
final channel = await client.channel(
 'messaging',
 id: 'red-general',
 extraData: {'team': 'red'},
).create();
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import ChannelInput

channel = client.chat.channel("messaging", "red-general")
channel.get_or_create(data=ChannelInput(created_by_id=user_id, team="red"))
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
const FChannelProperties Properties{
  TEXT("messaging"),   // Type
  TEXT("red-general"),  // Id
  TEXT("red"),      // Team

};
Client->CreateChannel(
  Properties,
  [](UChatChannel* Channel)
  {
    // Channel created
  });
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.get_or_create_channel('messaging', 'red-general', Models::ChannelGetOrCreateRequest.new(
  data: Models::ChannelInput.new(
    team: 'red',
    created_by_id: user_id
  )
))
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->getOrCreateChannel("messaging", "red-general", new Models\ChannelGetOrCreateRequest(
    data: new Models\ChannelInput(
        team: "red",
        createdByID: $userId,
    ),
));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await chat.GetOrCreateChannelAsync("messaging", "red-general", new ChannelGetOrCreateRequest
{
    Data = new ChannelInput
    {
        Team = "red",
        CreatedByID = user.ID,
    },
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
channel := client.Chat().Channel("messaging", "red-general")
resp, err := channel.GetOrCreate(ctx, &getstream.GetOrCreateChannelRequest{
  Data: &getstream.ChannelInput{
    CreatedByID: getstream.PtrTo(owner.ID),
    Team:        getstream.PtrTo("red"),
  },
})
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Creates the red-general channel for the red team
Map<String, Object> extraData = new HashMap<>();
List<String> memberIds = new LinkedList<>();
extraData.put("team", "red");
client.createChannel("messaging", "red-general", memberIds, extraData).enqueue(result -> {
  if (result.isSuccess()) {
    Channel channel = result.data();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

</codetabs>

<admonition type="info">

Channel teams allows you to ensure proper permission checking for a multi tenant application. Keep in mind that you will still need to enforce that channel IDs are unique. A very effective approach is to include the team name as a prefix to avoid collisions. (ie. "red-general" and "blue-general" instead of just "general")

</admonition>

## User Search

By default the user search will only return results from teams that user is a part of. API injects filter `{teams: {$in: ["red", "blue"]}}` for every request that doesn't already contain filter for `teams` field. If you want to query users from all teams, you have to provide empty filter like this: `{teams:{}}` . For server-side requests, this filter does not apply.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// search for users with name Jordan that are part of the same team as authorized user
val filter = Filters.eq("name", "Jordan")

// search for users with name Nick in all teams
val filter = Filters.and(
    Filters.eq("name", "Nick"),
    Filters.eq("teams", emptyMap<String, String>())
)

// search for users with name Dan in subset of teams
val filter = Filters.and(
    Filters.eq("name", "Dan"),
    Filters.`in`("teams", listOf("red", "blue")),
)

client.queryUsers(QueryUsersRequest(filter, offset = 0, limit = 1)).enqueue { result ->
    if (result is Result.Success) {
        val users: List<User> = result.value
    } else {
        // Handle Result.Failure
    }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// search for users with name Jordan that are part of the same team as authorized user
client.queryUsers({
  name: { $eq: "Jordan" },
});

// search for users with name Nick in all teams
client.queryUsers({
  $and: [{ name: { $eq: "Nick" } }, { teams: {} }],
});

// search for users with name Dan in subset of teams
client.queryUsers({
  $and: [{ name: { $eq: "Nick" } }, { teams: { $in: ["red", "blue"] } }],
});

// search for users with name Tom that don't have any team assigned
client.queryUsers({
  $and: [{ name: { $eq: "Tom" } }, { teams: { $eq: null } }],
});
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
import StreamChat

// search for users with name Jordan that are part of the same team as authorized user
let controller = chatClient.userListController(
  query: .init(filter: .eq("name", value: "Jordan"))
)

controller.synchronize { error in
  if let error = error {
    // handle error
    print(error)
  } else {
    // access users
    print(controller.users)
  }
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// search for users with name Jordan that are part of the same team as authorized user
await client.queryUsers(
 filter: Filter.equal('name', 'Jordan'),
);

// search for users with name Nick in all teams
await client.queryUsers(
 filter: Filter.and([
  Filter.equal('name', 'Nick'),
  Filter.raw(value: {'teams': {}}),
 ]),
);

// search for users with name Dan in subset of teams
await client.queryUsers(
 filter: Filter.and([
  Filter.equal('name', 'Nick'),
  Filter.in_('teams', ['red', 'blue']),
 ]),
);

// search for users with name Tom that don't have any team assigned
await client.queryUsers(
 filter: Filter.and([
  Filter.equal('name', 'Tom'),
  Filter.raw(value: {
   'teams': {'\$eq': null},
  }),
 ]),
);
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// search for users with name Jordan that are part of the same team as the authorized user
Client->QueryUsers(FFilter::Equal(TEXT("name"), TEXT("Jordan")));

// search for users with name Nick in all teams
Client->QueryUsers(FFilter::And({
  FFilter::Equal(TEXT("name"), TEXT("Nick")),
  FFilter::Empty(TEXT("teams")),
}));

// search for users with name Dan in subset of teams
Client->QueryUsers(FFilter::And({
  FFilter::Equal(TEXT("name"), TEXT("Nick")),
  FFilter::In(TEXT("teams"), {TEXT("red"), TEXT("blue")}),
}));

// search for users with name Tom that don't have any team assigned
Client->QueryUsers(FFilter::And({
  FFilter::Equal(TEXT("name"), TEXT("Tom")),
  FFilter::Equal(TEXT("teams"), nullptr),
}));
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Server side usage, it searches all teams implicitly
client.common.query_users(
  Models::QueryUsersPayload.new(
    filter_conditions: { 'name' => { '$eq' => 'Nick' } }
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// Server side usage, it searches all teams implicitly
$response = $client->queryUsers(new Models\QueryUsersPayload(
    filterConditions: (object)["name" => (object)['$eq' => "Nick"]],
));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Server side usage, it searches all teams implicitly
await client.QueryUsersAsync(new QueryUsersPayload
{
    FilterConditions = new Dictionary<string, object>
    {
        { "name", new Dictionary<string, string> { { "$eq", "Nick" } } },
    },
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Server side usage, it searches all teams implicitly
resp, err := client.QueryUsers(ctx, &getstream.QueryUsersRequest{
  Payload: &getstream.QueryUsersPayload{
    FilterConditions: map[string]any{
      "name": map[string]string{"$eq": "Nick"},
    },
  },
})
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Search for users with the name Jordan that are part of the red team
FilterObject filter = Filters.and(
  Filters.eq("name", "Jordan"),
  Filters.contains("teams", "red")
);

int offset = 0;
int limit = 1;
client.queryUsers(new QueryUsersRequest(filter, offset, limit)).enqueue(result -> {
  if (result.isSuccess()) {
    List<User> users = result.data();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

</codetabs>

<admonition type="info">

Users that cannot be displayed to the current user due to lack of permissions will be omitted from response.

</admonition>

## Query Channels

When using multi-tenant, the query channels endpoint will only return channels that match the query **and** are on the same team as the user. API injects filter `{team: {$in: [<user_teams>]}}` for every request that doesn't already contain filter for `team` field. If you want to query channels from all teams, you have to provide empty filter like this: `{team:{}}` . For server-side requests, this filter does not apply.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// query all channels from teams that user is a part of
val filter = Filters.and(
    Filters.eq("type", "messaging"),
    Filters.`in`("members", listOf(userId)),
)

// query all channels from all teams
val filter = Filters.eq("team", emptyMap<Any, Any>())

client.queryChannels(QueryChannelsRequest(filter = filter, limit = 25)).enqueue()
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// query all channels from teams that user is a part of
await client.queryChannels({});

// query all channels from all teams
await client.queryChannels({ team: {} });

// query all channels with no teams
await client.queryChannels({ team: { $eq: null } });
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// query all channels from teams that user is a part of
await client.queryChannels(filter: Filter.empty());

// query all channels from all teams
await client.queryChannels(filter: Filter.custom(key: 'team', value: {}));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// query all channels from teams that user is a part of
chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(Map.of())
    .build()).execute();

// query all channels from all teams
chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(Map.of("team", Map.of()))
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// query all channels from teams that user is a part of
Client->QueryChannels();

// query all channels from all teams
Client->QueryChannels(FFilter::Empty(TEXT("team")));
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# query all channels from teams that user is a part of
client.chat.query_channels(Models::QueryChannelsRequest.new(
  filter_conditions: {}
))

# query all channels from all teams
client.chat.query_channels(Models::QueryChannelsRequest.new(
  filter_conditions: { 'team' => {} }
))
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// query all channels from teams that user is a part of
$response = $client->queryChannels(new Models\QueryChannelsRequest(
    filterConditions: (object)[],
));

// query all channels from all teams
$response = $client->queryChannels(new Models\QueryChannelsRequest(
    filterConditions: (object)["team" => (object)[]],
));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// query all channels from teams that user is a part of
await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    FilterConditions = new Dictionary<string, object>(),
});

// query all channels from all teams
await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    FilterConditions = new Dictionary<string, object>
    {
        { "team", new Dictionary<string, string>() },
    },
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
  FilterConditions: map[string]any{},
})

// query all channels from all teams
resp, err = client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
  FilterConditions: map[string]any{
    "team": map[string]string{},
  },
})
```

</codetabs-item>

</codetabs>

<admonition type="info">

In case if response contains channels that user cannot access, an access error will be returned.

</admonition>

## Team based roles

By default a user will be assigned only 1 role (ie. `user`, `admin`, etc.). If you would like to have different roles depending on the the team the user is part of, you can do so by specifying a separate role per team. This team based role is applicable only on channels that belong to that team. Let's imagine user Jane, she's a user with role `user` throughout the application, however on team `red` we would like to give her elevated permissions and give her the `admin` role.
We can do this by updating the user as follows:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```javascript
await client.upsertUser({
  id: "Jane",
  role: "user",
  teams: ["red", "blue"],
  teams_role: {
    red: "admin",
    blue: "user",
  },
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
response, err := client.UpdateUsers(ctx, &getstream.UpdateUsersRequest{
  Users: map[string]getstream.UserRequest{
    "Jane": {
      ID:    "Jane",
      Role:  getstream.PtrTo("user"),
      Teams: []string{"red", "blue"},
      Custom: map[string]any{
        "teams_role": map[string]string{
          "red":  "admin",
          "blue": "user",
        },
      },
    },
  },
})
```

</codetabs-item>

<codetabs-item value="php" label="Php">

```php
$client->updateUsers(new Models\UpdateUsersRequest(
    users: ["Jane" => new Models\UserRequest(
        id: "Jane",
        role: "user",
        teams: ["red", "blue"],
        custom: (object)[
            "teams_role" => (object)["red" => "admin", "blue" => "user"],
        ],
    )],
));
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import UserRequest

response = client.upsert_users(
    UserRequest(
        id="Jane",
        role="user",
        teams=["red", "blue"],
        teams_role={
            "red": "admin",
            "blue": "user",
        },
    )
)
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
var response = client.updateUsers(UpdateUsersRequest.builder()
    .users(Map.of("Jane", UserRequest.builder()
        .id("Jane")
        .role("user")
        .teams(List.of("red", "blue"))
        .teamsRole(Map.of("red", "admin", "blue", "user"))
        .build()))
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

response = client.common.update_users(Models::UpdateUsersRequest.new(
  users: {
    'Jane' => Models::UserRequest.new(
      id: 'Jane',
      role: 'user',
      teams: ['red', 'blue'],
      custom: { 'teams_role' => { 'red' => 'admin', 'blue' => 'user' } }
    )
  }
))
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await client.UpdateUsersAsync(new UpdateUsersRequest
{
    Users = new Dictionary<string, UserRequest>
    {
        {
            "Jane", new UserRequest
            {
                ID = "Jane",
                Role = "user",
                Teams = new List<string> { "red", "blue" },
                TeamsRole = new Dictionary<string, string>
                {
                    { "red", "admin" },
                    { "blue", "user" },
                },
            }
        },
    },
});
```

</codetabs-item>

</codetabs>

If no team based role is set for a team, the system uses the role of the user.
For example, user Janet is a member of teams `red`, `blue` and `orange`. She has role `user` and team based roles `{ "red": "admin", "blue": "user" }`:

- On team red, she will have `admin` level permissions. This means that on channels that belong to team red, she will have admin level permissions.
- On channels from team blue, she has `user` level permissions.
- On channels from team orange, she also has `user` level permissions (because no team role was assigned for this team).

Please be aware team based roles will only work when multitenancy is enabled.

## Multi-Tenant Permissions

In tables below you will find default permission grants for builtin roles that designed for multi-tenant applications. They are useful for [multi-tenant applications](/chat/docs/<framework>/multi_tenant_chat/) only.

By default, for multi-tenant applications, all objects (users, channels, and messages) must belong to the same team to be able to interact. These multi-tenant permissions enable overriding that behavior, so that certain users can have permissions to interact with objects on any team

### Scope `video:livestream`

| Permission ID |
| ------------- |

### Scope `video:development`

| Permission ID |
| ------------- |

### Scope `.app`

| Permission ID               | global_moderator | global_admin |
| --------------------------- | ---------------- | ------------ |
| flag-user-any-team          | ✅               | ✅           |
| mute-user-any-team          | ✅               | ✅           |
| read-flag-reports-any-team  | ✅               | ✅           |
| search-user-any-team        | ✅               | ✅           |
| update-flag-report-any-team | ✅               | ✅           |
| update-user-owner           | ✅               | ✅           |

### Scope `video:audio_room`

| Permission ID |
| ------------- |

### Scope `video:default`

| Permission ID |
| ------------- |

### Scope `messaging`

| Permission ID                          | global_moderator | global_admin |
| -------------------------------------- | ---------------- | ------------ |
| add-links-any-team                     | ✅               | ✅           |
| ban-channel-member-any-team            | ✅               | ✅           |
| ban-user-any-team                      | ✅               | ✅           |
| create-call-any-team                   | ✅               | ✅           |
| create-channel-any-team                | ✅               | ✅           |
| create-message-any-team                | ✅               | ✅           |
| create-attachment-any-team             | ✅               | ✅           |
| create-mention-any-team                | ✅               | ✅           |
| create-reaction-any-team               | ✅               | ✅           |
| create-system-message-any-team         | ✅               | ✅           |
| delete-attachment-any-team             | ✅               | ✅           |
| delete-channel-any-team                | ✖️               | ✅           |
| delete-channel-owner-any-team          | ✅               | ✖️           |
| delete-message-any-team                | ✅               | ✅           |
| delete-reaction-any-team               | ✅               | ✅           |
| flag-message-any-team                  | ✅               | ✅           |
| join-call-any-team                     | ✅               | ✅           |
| mute-channel-any-team                  | ✅               | ✅           |
| pin-message-any-team                   | ✅               | ✅           |
| read-channel-any-team                  | ✅               | ✅           |
| read-channel-members-any-team          | ✅               | ✅           |
| read-message-flags-any-team            | ✅               | ✅           |
| recreate-channel-any-team              | ✖️               | ✅           |
| recreate-channel-owner-any-team        | ✅               | ✖️           |
| remove-own-channel-membership-any-team | ✅               | ✅           |
| run-message-action-any-team            | ✅               | ✅           |
| send-custom-event-any-team             | ✅               | ✅           |
| skip-channel-cooldown-any-team         | ✅               | ✅           |
| skip-message-moderation-any-team       | ✅               | ✅           |
| truncate-channel-any-team              | ✖️               | ✅           |
| truncate-channel-owner-any-team        | ✅               | ✖️           |
| unblock-message-any-team               | ✅               | ✅           |
| update-channel-any-team                | ✅               | ✅           |
| update-channel-cooldown-any-team       | ✅               | ✅           |
| update-channel-frozen-any-team         | ✅               | ✅           |
| update-channel-members-any-team        | ✅               | ✅           |
| update-message-any-team                | ✅               | ✅           |
| upload-attachment-any-team             | ✅               | ✅           |

### Scope `livestream`

| Permission ID                          | global_moderator | global_admin |
| -------------------------------------- | ---------------- | ------------ |
| add-links-any-team                     | ✅               | ✅           |
| ban-channel-member-any-team            | ✅               | ✅           |
| ban-user-any-team                      | ✅               | ✅           |
| create-call-any-team                   | ✅               | ✅           |
| create-channel-any-team                | ✅               | ✅           |
| create-message-any-team                | ✅               | ✅           |
| create-attachment-any-team             | ✅               | ✅           |
| create-mention-any-team                | ✅               | ✅           |
| create-reaction-any-team               | ✅               | ✅           |
| create-system-message-any-team         | ✅               | ✅           |
| delete-attachment-any-team             | ✅               | ✅           |
| delete-channel-any-team                | ✖️               | ✅           |
| delete-message-any-team                | ✅               | ✅           |
| delete-reaction-any-team               | ✅               | ✅           |
| flag-message-any-team                  | ✅               | ✅           |
| join-call-any-team                     | ✅               | ✅           |
| mute-channel-any-team                  | ✅               | ✅           |
| pin-message-any-team                   | ✅               | ✅           |
| read-channel-any-team                  | ✅               | ✅           |
| read-channel-members-any-team          | ✅               | ✅           |
| read-message-flags-any-team            | ✅               | ✅           |
| recreate-channel-any-team              | ✖️               | ✅           |
| remove-own-channel-membership-any-team | ✖️               | ✅           |
| run-message-action-any-team            | ✅               | ✅           |
| send-custom-event-any-team             | ✅               | ✅           |
| skip-channel-cooldown-any-team         | ✅               | ✅           |
| skip-message-moderation-any-team       | ✅               | ✅           |
| truncate-channel-any-team              | ✖️               | ✅           |
| unblock-message-any-team               | ✅               | ✅           |
| update-channel-any-team                | ✖️               | ✅           |
| update-channel-cooldown-any-team       | ✅               | ✅           |
| update-channel-frozen-any-team         | ✅               | ✅           |
| update-channel-members-any-team        | ✖️               | ✅           |
| update-message-any-team                | ✅               | ✅           |
| upload-attachment-any-team             | ✅               | ✅           |

### Scope `team`

| Permission ID                          | global_moderator | global_admin |
| -------------------------------------- | ---------------- | ------------ |
| add-links-any-team                     | ✅               | ✅           |
| ban-channel-member-any-team            | ✅               | ✅           |
| ban-user-any-team                      | ✅               | ✅           |
| create-call-any-team                   | ✅               | ✅           |
| create-channel-any-team                | ✅               | ✅           |
| create-message-any-team                | ✅               | ✅           |
| create-attachment-any-team             | ✅               | ✅           |
| create-mention-any-team                | ✅               | ✅           |
| create-reaction-any-team               | ✅               | ✅           |
| create-system-message-any-team         | ✅               | ✅           |
| delete-attachment-any-team             | ✅               | ✅           |
| delete-channel-any-team                | ✖️               | ✅           |
| delete-channel-owner-any-team          | ✅               | ✖️           |
| delete-message-any-team                | ✅               | ✅           |
| delete-reaction-any-team               | ✅               | ✅           |
| flag-message-any-team                  | ✅               | ✅           |
| join-call-any-team                     | ✅               | ✅           |
| mute-channel-any-team                  | ✅               | ✅           |
| pin-message-any-team                   | ✅               | ✅           |
| read-channel-any-team                  | ✅               | ✅           |
| read-channel-members-any-team          | ✅               | ✅           |
| read-message-flags-any-team            | ✅               | ✅           |
| recreate-channel-any-team              | ✖️               | ✅           |
| recreate-channel-owner-any-team        | ✅               | ✖️           |
| remove-own-channel-membership-any-team | ✅               | ✅           |
| run-message-action-any-team            | ✅               | ✅           |
| send-custom-event-any-team             | ✅               | ✅           |
| skip-channel-cooldown-any-team         | ✅               | ✅           |
| skip-message-moderation-any-team       | ✅               | ✅           |
| truncate-channel-any-team              | ✖️               | ✅           |
| truncate-channel-owner-any-team        | ✅               | ✖️           |
| unblock-message-any-team               | ✅               | ✅           |
| update-channel-any-team                | ✅               | ✅           |
| update-channel-cooldown-any-team       | ✅               | ✅           |
| update-channel-frozen-any-team         | ✅               | ✅           |
| update-channel-members-any-team        | ✅               | ✅           |
| update-message-any-team                | ✅               | ✅           |
| upload-attachment-any-team             | ✅               | ✅           |

### Scope `commerce`

| Permission ID                          | global_moderator | global_admin |
| -------------------------------------- | ---------------- | ------------ |
| add-links-any-team                     | ✅               | ✅           |
| ban-channel-member-any-team            | ✅               | ✅           |
| ban-user-any-team                      | ✅               | ✅           |
| create-call-any-team                   | ✅               | ✅           |
| create-channel-any-team                | ✅               | ✅           |
| create-message-any-team                | ✅               | ✅           |
| create-attachment-any-team             | ✅               | ✅           |
| create-mention-any-team                | ✅               | ✅           |
| create-reaction-any-team               | ✅               | ✅           |
| create-system-message-any-team         | ✅               | ✅           |
| delete-attachment-any-team             | ✅               | ✅           |
| delete-channel-any-team                | ✖️               | ✅           |
| delete-message-any-team                | ✅               | ✅           |
| delete-reaction-any-team               | ✅               | ✅           |
| flag-message-any-team                  | ✅               | ✅           |
| join-call-any-team                     | ✅               | ✅           |
| mute-channel-any-team                  | ✅               | ✅           |
| pin-message-any-team                   | ✅               | ✅           |
| read-channel-any-team                  | ✅               | ✅           |
| read-channel-members-any-team          | ✅               | ✅           |
| read-message-flags-any-team            | ✅               | ✅           |
| recreate-channel-any-team              | ✖️               | ✅           |
| remove-own-channel-membership-any-team | ✅               | ✅           |
| run-message-action-any-team            | ✅               | ✅           |
| send-custom-event-any-team             | ✅               | ✅           |
| skip-channel-cooldown-any-team         | ✅               | ✅           |
| skip-message-moderation-any-team       | ✅               | ✅           |
| truncate-channel-any-team              | ✖️               | ✅           |
| unblock-message-any-team               | ✅               | ✅           |
| update-channel-any-team                | ✅               | ✅           |
| update-channel-cooldown-any-team       | ✅               | ✅           |
| update-channel-frozen-any-team         | ✅               | ✅           |
| update-channel-members-any-team        | ✅               | ✅           |
| update-message-any-team                | ✅               | ✅           |
| upload-attachment-any-team             | ✅               | ✅           |

### Scope `gaming`

| Permission ID                          | global_moderator | global_admin |
| -------------------------------------- | ---------------- | ------------ |
| add-links-any-team                     | ✅               | ✅           |
| ban-channel-member-any-team            | ✅               | ✅           |
| ban-user-any-team                      | ✅               | ✅           |
| create-call-any-team                   | ✅               | ✅           |
| create-channel-any-team                | ✖️               | ✅           |
| create-message-any-team                | ✅               | ✅           |
| create-attachment-any-team             | ✅               | ✅           |
| create-mention-any-team                | ✅               | ✅           |
| create-reaction-any-team               | ✅               | ✅           |
| create-system-message-any-team         | ✅               | ✅           |
| delete-attachment-any-team             | ✅               | ✅           |
| delete-channel-any-team                | ✖️               | ✅           |
| delete-message-any-team                | ✅               | ✅           |
| delete-reaction-any-team               | ✅               | ✅           |
| flag-message-any-team                  | ✅               | ✅           |
| join-call-any-team                     | ✅               | ✅           |
| mute-channel-any-team                  | ✅               | ✅           |
| pin-message-any-team                   | ✅               | ✅           |
| read-channel-any-team                  | ✅               | ✅           |
| read-channel-members-any-team          | ✅               | ✅           |
| read-message-flags-any-team            | ✅               | ✅           |
| recreate-channel-any-team              | ✖️               | ✅           |
| remove-own-channel-membership-any-team | ✅               | ✅           |
| run-message-action-any-team            | ✅               | ✅           |
| send-custom-event-any-team             | ✅               | ✅           |
| skip-channel-cooldown-any-team         | ✅               | ✅           |
| skip-message-moderation-any-team       | ✅               | ✅           |
| truncate-channel-any-team              | ✖️               | ✅           |
| unblock-message-any-team               | ✅               | ✅           |
| update-channel-any-team                | ✖️               | ✅           |
| update-channel-cooldown-any-team       | ✅               | ✅           |
| update-channel-frozen-any-team         | ✅               | ✅           |
| update-channel-members-any-team        | ✖️               | ✅           |
| update-message-any-team                | ✅               | ✅           |
| upload-attachment-any-team             | ✅               | ✅           |

## Team Usage Statistics

For multi-tenant applications, you can query usage statistics broken down by team. This is useful for billing, monitoring, and analytics purposes. The API returns detailed metrics for each team including user counts, message volumes, and activity patterns.

### Querying Team Usage Stats

Use the `queryTeamUsageStats` method to retrieve usage statistics. You can query by month or by a custom date range.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// Query current month's stats
const response = await client.queryTeamUsageStats();

// Query specific month
const response = await client.queryTeamUsageStats({ month: "2024-01" });

// Query date range
const response = await client.queryTeamUsageStats({
  start_date: "2024-01-01",
  end_date: "2024-01-31",
});

// With pagination
const response = await client.queryTeamUsageStats({ limit: 10 });
if (response.next) {
  const nextPage = await client.queryTeamUsageStats({
    limit: 10,
    next: response.next,
  });
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# Query current month's stats
response = client.chat.query_team_usage_stats()

# Query specific month
response = client.chat.query_team_usage_stats(month="2024-01")

# Query date range
response = client.chat.query_team_usage_stats(
    start_date="2024-01-01",
    end_date="2024-01-31"
)

# With pagination
response = client.chat.query_team_usage_stats(limit=10)
if response.data.next:
    next_page = client.chat.query_team_usage_stats(limit=10, next=response.data.next)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Query current month's stats
response = client.chat.query_team_usage_stats(Models::QueryTeamUsageStatsRequest.new)

# Query specific month
response = client.chat.query_team_usage_stats(Models::QueryTeamUsageStatsRequest.new(
  month: '2024-01'
))

# Query date range
response = client.chat.query_team_usage_stats(Models::QueryTeamUsageStatsRequest.new(
  start_date: '2024-01-01',
  end_date: '2024-01-31'
))

# With pagination
response = client.chat.query_team_usage_stats(Models::QueryTeamUsageStatsRequest.new(limit: 10))
if response.next
  next_page = client.chat.query_team_usage_stats(Models::QueryTeamUsageStatsRequest.new(
    limit: 10,
    next: response.next
  ))
end
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// Query current month's stats
$response = $client->queryTeamUsageStats(new Models\QueryTeamUsageStatsRequest());
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Query current month's stats
var response = chat.queryTeamUsageStats(QueryTeamUsageStatsRequest.builder()
    .build()).execute().getData();

// Query specific month
var monthlyResponse = chat.queryTeamUsageStats(QueryTeamUsageStatsRequest.builder()
    .month("2024-01")
    .build()).execute().getData();

// Query date range
var rangeResponse = chat.queryTeamUsageStats(QueryTeamUsageStatsRequest.builder()
    .startDate("2024-01-01")
    .endDate("2024-01-31")
    .build()).execute().getData();

// With pagination
var pageResponse = chat.queryTeamUsageStats(QueryTeamUsageStatsRequest.builder()
    .limit(10)
    .build()).execute().getData();
if (pageResponse.getNext() != null) {
    var nextPage = chat.queryTeamUsageStats(QueryTeamUsageStatsRequest.builder()
        .limit(10)
        .next(pageResponse.getNext())
        .build()).execute().getData();
}
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Query current month's stats
var response = await statsClient.QueryTeamUsageStatsAsync();

// Query specific month
var response = await statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
{
    Month = "2024-01"
});

// Query date range
var response = await statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
{
    StartDate = "2024-01-01",
    EndDate = "2024-01-31"
});

// With pagination
var response = await statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
{
    Limit = 10
});
if (!string.IsNullOrEmpty(response.Next))
{
    var nextPage = await statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
    {
        Limit = 10,
        Next = response.Next
    });
}
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Query current month's stats
response, err := client.Chat().QueryTeamUsageStats(ctx, &getstream.QueryTeamUsageStatsRequest{})

// Query specific month
response, err = client.Chat().QueryTeamUsageStats(ctx, &getstream.QueryTeamUsageStatsRequest{
    Month: getstream.PtrTo("2024-01"),
})

// Query date range
response, err = client.Chat().QueryTeamUsageStats(ctx, &getstream.QueryTeamUsageStatsRequest{
    StartDate: getstream.PtrTo("2024-01-01"),
    EndDate:   getstream.PtrTo("2024-01-31"),
})

// With pagination
response, err = client.Chat().QueryTeamUsageStats(ctx, &getstream.QueryTeamUsageStatsRequest{
    Limit: getstream.PtrTo(10),
})
if response.Data.Next != nil && *response.Data.Next != "" {
    nextPage, err := client.Chat().QueryTeamUsageStats(ctx, &getstream.QueryTeamUsageStatsRequest{
        Limit: getstream.PtrTo(10),
        Next:  response.Data.Next,
    })
    _ = nextPage
    _ = err
}
```

</codetabs-item>

</codetabs>

### Available Metrics

The response includes statistics for each team with the following metrics:

| Metric                        | Description                            |
| ----------------------------- | -------------------------------------- |
| `users_daily`                 | Number of unique users active per day  |
| `messages_daily`              | Number of messages sent per day        |
| `translations_daily`          | Number of message translations per day |
| `image_moderations_daily`     | Number of images moderated per day     |
| `concurrent_users`            | Peak concurrent users                  |
| `concurrent_connections`      | Peak concurrent connections            |
| `users_total`                 | Total number of users                  |
| `users_last_24_hours`         | Users active in the last 24 hours      |
| `users_last_30_days`          | Users active in the last 30 days       |
| `users_month_to_date`         | Users active month to date             |
| `users_engaged_last_30_days`  | Engaged users in the last 30 days      |
| `users_engaged_month_to_date` | Engaged users month to date            |
| `messages_total`              | Total number of messages               |
| `messages_last_24_hours`      | Messages sent in the last 24 hours     |
| `messages_last_30_days`       | Messages sent in the last 30 days      |
| `messages_month_to_date`      | Messages sent month to date            |

<admonition type="info">

This API requires server-side authentication. It cannot be called from client-side SDKs.

</admonition>

<admonition type="info">

Use the `month` parameter (format: `YYYY-MM`) for monthly reports, or `start_date` and `end_date` (format: `YYYY-MM-DD`) for custom date ranges. If no parameters are provided, the current month's statistics are returned.

</admonition>

### Metric Attribution

Message metrics (`messages_*`) are attributed based on the channel's `team` field (`channel.team`), while user metrics (`users_*`) are attributed based on the user's `teams` array (`user.teams`). This means you may see messages under a team even when `users_*` metrics are zero for that team, if messages were sent in channels belonging to that team by users who are not members of that team.

### Empty Team

The API returns a row for `team=""` (empty string) which represents users and messages that are not assigned to any team. This includes messages in channels without a team set and users without any team membership.

### Response Modes

The response shape differs based on query mode:

**Monthly mode** (using `month` parameter): Returns only the total/aggregated values for each metric. Daily breakdown arrays are omitted.

**Daily mode** (using `start_date` and `end_date`): Returns both daily breakdown arrays and aggregated totals. The aggregation method depends on the metric type:

- **SUM**: Daily activity metrics (`users_daily`, `messages_daily`, `translations_daily`, `image_moderations_daily`) - totals are summed across the date range
- **MAX**: Peak metrics (`concurrent_users`, `concurrent_connections`) - totals reflect the maximum value observed
- **LATEST**: Rolling/cumulative metrics (`users_total`, `users_last_24_hours`, `users_last_30_days`, `users_month_to_date`, `users_engaged_last_30_days`, `users_engaged_month_to_date`, `messages_total`, `messages_last_24_hours`, `messages_last_30_days`, `messages_month_to_date`) - totals reflect the most recent value

### Pagination

Results are paginated in lexicographic order by team name. The `next` cursor in the response is a base64-encoded team ID. Use this cursor value in subsequent requests to fetch the next page of results. The `limit` parameter is capped at 30 teams per request.

### Date Range Validation

When using custom date ranges, the following validations apply:

- `end_date` must be greater than or equal to `start_date`
- The date range cannot exceed 365 days


---

This page was last updated at 2026-03-13T13:16:37.225Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/node/multi_tenant_chat/](https://getstream.io/chat/docs/node/multi_tenant_chat/).