diff --git a/src/Clients/IStatsClient.cs b/src/Clients/IStatsClient.cs
new file mode 100644
index 00000000..b5fc42bf
--- /dev/null
+++ b/src/Clients/IStatsClient.cs
@@ -0,0 +1,24 @@
+using System.Threading.Tasks;
+using StreamChat.Models;
+
+namespace StreamChat.Clients
+{
+ ///
+ /// A client that can be used to query team-level usage statistics.
+ ///
+ public interface IStatsClient
+ {
+ ///
+ /// Queries team-level usage statistics from the warehouse database.
+ /// Returns all 16 metrics grouped by team with cursor-based pagination.
+ /// This endpoint is server-side only.
+ /// Date Range Options (mutually exclusive):
+ ///
+ /// - Use 'month' parameter (YYYY-MM format) for monthly aggregated values
+ /// - Use 'start_date'/'end_date' parameters (YYYY-MM-DD format) for daily breakdown
+ /// - If neither provided, defaults to current month (monthly mode)
+ ///
+ ///
+ Task QueryTeamUsageStatsAsync(QueryTeamUsageStatsOptions options = null);
+ }
+}
diff --git a/src/Clients/IStreamClientFactory.cs b/src/Clients/IStreamClientFactory.cs
index eace2a96..20d61e9a 100644
--- a/src/Clients/IStreamClientFactory.cs
+++ b/src/Clients/IStreamClientFactory.cs
@@ -101,5 +101,10 @@ public interface IStreamClientFactory
///
/// https://getstream.io/chat/docs/dotnet-csharp/threads/?language=csharp#filtering-and-sorting-threads
IThreadClient GetThreadClient();
+
+ ///
+ /// Returns an instance. The returned client can be used as a singleton in your application.
+ ///
+ IStatsClient GetStatsClient();
}
}
diff --git a/src/Clients/StatsClient.cs b/src/Clients/StatsClient.cs
new file mode 100644
index 00000000..3d39145b
--- /dev/null
+++ b/src/Clients/StatsClient.cs
@@ -0,0 +1,20 @@
+using System.Net;
+using System.Threading.Tasks;
+using StreamChat.Models;
+using StreamChat.Rest;
+
+namespace StreamChat.Clients
+{
+ public class StatsClient : ClientBase, IStatsClient
+ {
+ internal StatsClient(IRestClient client) : base(client)
+ {
+ }
+
+ public async Task QueryTeamUsageStatsAsync(QueryTeamUsageStatsOptions options = null)
+ => await ExecuteRequestAsync("stats/team_usage",
+ HttpMethod.POST,
+ HttpStatusCode.Created,
+ options ?? new QueryTeamUsageStatsOptions());
+ }
+}
diff --git a/src/Clients/StreamClientFactory.cs b/src/Clients/StreamClientFactory.cs
index d2561242..66d51629 100644
--- a/src/Clients/StreamClientFactory.cs
+++ b/src/Clients/StreamClientFactory.cs
@@ -27,6 +27,7 @@ public class StreamClientFactory : IStreamClientFactory
private readonly ITaskClient _taskClient;
private readonly IModerationClient _moderationClient;
private readonly IThreadClient _threadClient;
+ private readonly IStatsClient _statsClient;
///
/// Initializes a new instance of the class.
@@ -94,6 +95,7 @@ public StreamClientFactory(string apiKey, string apiSecret, Action _appClient;
@@ -112,5 +114,6 @@ public StreamClientFactory(string apiKey, string apiSecret, Action _userClient;
public IModerationClient GetModerationClient() => _moderationClient;
public IThreadClient GetThreadClient() => _threadClient;
+ public IStatsClient GetStatsClient() => _statsClient;
}
}
diff --git a/src/Models/Import.cs b/src/Models/Import.cs
index 46f8c69b..332f9d40 100644
--- a/src/Models/Import.cs
+++ b/src/Models/Import.cs
@@ -25,6 +25,9 @@ public enum ImportState
{
None,
+ [EnumMember(Value = "queued")]
+ Queued,
+
[EnumMember(Value = "uploaded")]
Uploaded,
diff --git a/src/Models/TeamUsageStats.cs b/src/Models/TeamUsageStats.cs
new file mode 100644
index 00000000..5756b5dc
--- /dev/null
+++ b/src/Models/TeamUsageStats.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace StreamChat.Models
+{
+ ///
+ /// Represents a metric value for a specific date.
+ ///
+ public class DailyValue
+ {
+ /// Date in YYYY-MM-DD format.
+ [JsonProperty("date")]
+ public string Date { get; set; }
+
+ /// Metric value for this date.
+ [JsonProperty("value")]
+ public long Value { get; set; }
+ }
+
+ ///
+ /// Statistics for a single metric with optional daily breakdown.
+ ///
+ public class MetricStats
+ {
+ /// Per-day values (only present in daily mode).
+ [JsonProperty("daily")]
+ public List Daily { get; set; }
+
+ /// Aggregated total value.
+ [JsonProperty("total")]
+ public long Total { get; set; }
+ }
+
+ ///
+ /// Team-level usage statistics for multi-tenant apps.
+ ///
+ public class TeamUsageStats
+ {
+ /// Team identifier (empty string for users not assigned to any team).
+ [JsonProperty("team")]
+ public string Team { get; set; }
+
+ // Daily activity metrics (total = SUM of daily values)
+
+ /// Daily active users.
+ [JsonProperty("users_daily")]
+ public MetricStats UsersDaily { get; set; }
+
+ /// Daily messages sent.
+ [JsonProperty("messages_daily")]
+ public MetricStats MessagesDaily { get; set; }
+
+ /// Daily translations.
+ [JsonProperty("translations_daily")]
+ public MetricStats TranslationsDaily { get; set; }
+
+ /// Daily image moderations.
+ [JsonProperty("image_moderations_daily")]
+ public MetricStats ImageModerationsDaily { get; set; }
+
+ // Peak metrics (total = MAX of daily values)
+
+ /// Peak concurrent users.
+ [JsonProperty("concurrent_users")]
+ public MetricStats ConcurrentUsers { get; set; }
+
+ /// Peak concurrent connections.
+ [JsonProperty("concurrent_connections")]
+ public MetricStats ConcurrentConnections { get; set; }
+
+ // Rolling/cumulative metrics (total = LATEST daily value)
+
+ /// Total users.
+ [JsonProperty("users_total")]
+ public MetricStats UsersTotal { get; set; }
+
+ /// Users active in last 24 hours.
+ [JsonProperty("users_last_24_hours")]
+ public MetricStats UsersLast24Hours { get; set; }
+
+ /// MAU - users active in last 30 days.
+ [JsonProperty("users_last_30_days")]
+ public MetricStats UsersLast30Days { get; set; }
+
+ /// Users active this month.
+ [JsonProperty("users_month_to_date")]
+ public MetricStats UsersMonthToDate { get; set; }
+
+ /// Engaged MAU.
+ [JsonProperty("users_engaged_last_30_days")]
+ public MetricStats UsersEngagedLast30Days { get; set; }
+
+ /// Engaged users this month.
+ [JsonProperty("users_engaged_month_to_date")]
+ public MetricStats UsersEngagedMonthToDate { get; set; }
+
+ /// Total messages.
+ [JsonProperty("messages_total")]
+ public MetricStats MessagesTotal { get; set; }
+
+ /// Messages in last 24 hours.
+ [JsonProperty("messages_last_24_hours")]
+ public MetricStats MessagesLast24Hours { get; set; }
+
+ /// Messages in last 30 days.
+ [JsonProperty("messages_last_30_days")]
+ public MetricStats MessagesLast30Days { get; set; }
+
+ /// Messages this month.
+ [JsonProperty("messages_month_to_date")]
+ public MetricStats MessagesMonthToDate { get; set; }
+ }
+
+ ///
+ /// Options for querying team usage stats.
+ ///
+ public class QueryTeamUsageStatsOptions
+ {
+ ///
+ /// Month in YYYY-MM format (e.g., '2026-01'). Mutually exclusive with start_date/end_date.
+ /// Returns aggregated monthly values.
+ ///
+ [JsonProperty("month")]
+ public string Month { get; set; }
+
+ ///
+ /// Start date in YYYY-MM-DD format. Used with end_date for custom date range. Returns daily breakdown.
+ ///
+ [JsonProperty("start_date")]
+ public string StartDate { get; set; }
+
+ ///
+ /// End date in YYYY-MM-DD format. Used with start_date for custom date range. Returns daily breakdown.
+ ///
+ [JsonProperty("end_date")]
+ public string EndDate { get; set; }
+
+ /// Maximum number of teams to return per page (default: 30, max: 30).
+ [JsonProperty("limit")]
+ public int? Limit { get; set; }
+
+ /// Cursor for pagination to fetch next page of teams.
+ [JsonProperty("next")]
+ public string Next { get; set; }
+ }
+
+ ///
+ /// Response from querying team usage stats.
+ ///
+ public class QueryTeamUsageStatsResponse : ApiResponse
+ {
+ /// Array of team usage statistics.
+ [JsonProperty("teams")]
+ public List Teams { get; set; }
+
+ /// Cursor for pagination to fetch next page.
+ [JsonProperty("next")]
+ public string Next { get; set; }
+ }
+}
diff --git a/tests/StatsClientTests.cs b/tests/StatsClientTests.cs
new file mode 100644
index 00000000..54ef646f
--- /dev/null
+++ b/tests/StatsClientTests.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Threading.Tasks;
+using FluentAssertions;
+using NUnit.Framework;
+using StreamChat.Models;
+
+namespace StreamChatTests
+{
+ /// Tests for
+ ///
+ /// The tests follow arrange-act-assert pattern divided by empty lines.
+ /// Please make sure to follow the pattern to keep the consistency.
+ ///
+ [TestFixture]
+ public class StatsClientTests : TestBase
+ {
+ [Test]
+ public async Task TestQueryTeamUsageStatsDefault()
+ {
+ var response = await _statsClient.QueryTeamUsageStatsAsync();
+
+ response.Teams.Should().NotBeNull();
+ }
+
+ [Test]
+ public async Task TestQueryTeamUsageStatsWithMonth()
+ {
+ var currentMonth = DateTime.UtcNow.ToString("yyyy-MM");
+
+ var response = await _statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
+ {
+ Month = currentMonth,
+ });
+
+ response.Teams.Should().NotBeNull();
+ }
+
+ [Test]
+ public async Task TestQueryTeamUsageStatsWithDateRange()
+ {
+ var endDate = DateTime.UtcNow;
+ var startDate = endDate.AddDays(-7);
+
+ var response = await _statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
+ {
+ StartDate = startDate.ToString("yyyy-MM-dd"),
+ EndDate = endDate.ToString("yyyy-MM-dd"),
+ });
+
+ response.Teams.Should().NotBeNull();
+ }
+
+ [Test]
+ public async Task TestQueryTeamUsageStatsWithPagination()
+ {
+ var response = await _statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
+ {
+ Limit = 10,
+ });
+
+ response.Teams.Should().NotBeNull();
+
+ // If there's a next cursor, fetch the next page
+ if (!string.IsNullOrEmpty(response.Next))
+ {
+ var nextResponse = await _statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
+ {
+ Limit = 10,
+ Next = response.Next,
+ });
+
+ nextResponse.Teams.Should().NotBeNull();
+ }
+ }
+
+ [Test]
+ public async Task TestQueryTeamUsageStatsResponseStructure()
+ {
+ // Query last year to maximize chance of getting data
+ var endDate = DateTime.UtcNow;
+ var startDate = endDate.AddDays(-365);
+
+ var response = await _statsClient.QueryTeamUsageStatsAsync(new QueryTeamUsageStatsOptions
+ {
+ StartDate = startDate.ToString("yyyy-MM-dd"),
+ EndDate = endDate.ToString("yyyy-MM-dd"),
+ });
+
+ response.Teams.Should().NotBeNull();
+
+ if (response.Teams.Count > 0)
+ {
+ var team = response.Teams[0];
+
+ // Verify team identifier
+ team.Team.Should().NotBeNull();
+
+ // Verify daily activity metrics
+ team.UsersDaily.Should().NotBeNull();
+ team.MessagesDaily.Should().NotBeNull();
+ team.TranslationsDaily.Should().NotBeNull();
+ team.ImageModerationsDaily.Should().NotBeNull();
+
+ // Verify peak metrics
+ team.ConcurrentUsers.Should().NotBeNull();
+ team.ConcurrentConnections.Should().NotBeNull();
+
+ // Verify rolling/cumulative metrics
+ team.UsersTotal.Should().NotBeNull();
+ team.UsersLast24Hours.Should().NotBeNull();
+ team.UsersLast30Days.Should().NotBeNull();
+ team.UsersMonthToDate.Should().NotBeNull();
+ team.UsersEngagedLast30Days.Should().NotBeNull();
+ team.UsersEngagedMonthToDate.Should().NotBeNull();
+ team.MessagesTotal.Should().NotBeNull();
+ team.MessagesLast24Hours.Should().NotBeNull();
+ team.MessagesLast30Days.Should().NotBeNull();
+ team.MessagesMonthToDate.Should().NotBeNull();
+
+ // Verify metric structure
+ team.UsersDaily.Total.Should().BeGreaterOrEqualTo(0);
+ }
+ }
+ }
+}
diff --git a/tests/TestBase.cs b/tests/TestBase.cs
index fb8f52fd..d4d79628 100644
--- a/tests/TestBase.cs
+++ b/tests/TestBase.cs
@@ -26,6 +26,7 @@ public abstract class TestBase
protected static readonly ITaskClient _taskClient = TestClientFactory.GetTaskClient();
protected static readonly IModerationClient _moderationClient = TestClientFactory.GetModerationClient();
protected static readonly IThreadClient _threadClient = TestClientFactory.GetThreadClient();
+ protected static readonly IStatsClient _statsClient = TestClientFactory.GetStatsClient();
private readonly List _testChannels = new List();
private readonly List _testChannelTypes = new List();
diff --git a/tests/TestClientFactory.cs b/tests/TestClientFactory.cs
index d3b92932..9b74a266 100644
--- a/tests/TestClientFactory.cs
+++ b/tests/TestClientFactory.cs
@@ -26,5 +26,6 @@ public static class TestClientFactory
public static ITaskClient GetTaskClient() => _clientFactory.GetTaskClient();
public static IModerationClient GetModerationClient() => _clientFactory.GetModerationClient();
public static IThreadClient GetThreadClient() => _clientFactory.GetThreadClient();
+ public static IStatsClient GetStatsClient() => _clientFactory.GetStatsClient();
}
}