# Teams

**URL:** https://heroiclabs.com/docs/hiro/unity/teams/
**Keywords:** teams, hiro
**Categories:** hiro, unity, teams

---


# Teams

The Teams API reference for Unity provides a complete catalog of functions for implementing collaborative team features. All documented functions share common authentication patterns, operate within team-scoped contexts, and maintain consistent parameter structures for managing team resources, competitive events, and shared progression systems. Learn more in the [Teams concept guide](../../concepts/teams/).

## Teams API

The core Teams API is used to create teams, manage membership, handle join requests, and send chat messages.

### Core Function Reference

| Function                                                  | Description                                             |
| :-------------------------------------------------------- | :------------------------------------------------------ |
| [TeamsSystem](#initialization)                            | Initializes the teams system                            |
| [RefreshAsync](#refresh-the-teams-system)                 | Refreshes the teams system with latest data from server |
| [CreateTeamAsync](#create-a-team)                         | Creates a new team                                      |
| [ListTeamsAsync](#list-available-teams)                   | Lists available teams by location                       |
| [SearchTeamsAsync](#search-for-teams)                     | Searches for teams by name/shortcode                    |
| [JoinTeamAsync](#join-a-team)                             | Joins a team (by ID or Team object)                     |
| [GetTeamMembersAsync](#get-team-members-from-other-teams) | Gets members of specified team                          |
| [ApproveJoinRequestAsync](#approve-join-requests)         | Approves a single join request                          |
| [ApproveJoinRequestsAsync](#approve-join-requests)        | Approves multiple join requests                         |
| [RejectJoinRequestAsync](#reject-join-requests)           | Rejects a single join request                           |
| [RejectJoinRequestsAsync](#reject-join-requests)          | Rejects multiple join requests                          |
| [PromoteUsersAsync](#promote-team-members)                | Promotes team members to admin                          |
| [DemoteUsersAsync](#demote-team-members)                  | Demotes team members from admin                         |
| [KickUsersAsync](#kick-team-members)                      | Kicks users from a team                                 |
| [LeaveTeamAsync](#leave-team)                             | Leaves a team (by ID or Team object)                    |
| [DeleteTeamAsync](#delete-team)                           | Deletes a team                                          |
| [SendChatMessageAsync](#send-chat-message)                | Sends a chat message to team                            |

{{< note "important" "CancellationToken" >}}
Unless stated otherwise, all functions accept an optional `CancellationToken` which may be used to cancel the request.
{{< / note >}}

### Initialization

The teams system relies on the [Nakama System](../getting-started/nakama-system) and an `ILogger`, both must be passed in as dependencies via the constructor.

```csharp
var teamsSystem = new TeamsSystem(logger, nakamaSystem);
systems.Add(teamsSystem);
```

### Subscribe to changes

You can listen for changes in the teams system so that you can respond appropriately, such as updating the UI, by implementing the `IObserver` pattern, or use the `SystemObserver<T>` type which handles it for you.

```csharp
var disposer = SystemObserver<TeamsSystem>.Create(teamsSystem, system => {
    Instance.Logger.Info($"System updated.");

    // Update UI elements etc as necessary here...
});
```

### Refresh the teams system

To ensure the teams system has the latest information from Nakama you can refresh it.

```csharp
await teamsSystem.RefreshAsync();
```

### Get current team

Access the user's current team and role information.

**Properties**

- `Team` (`ITeam`): The user's current team, or `null` if not in a team
- `IsAdmin` (`bool`): Whether the user is an admin of their current team
- `IsSuperAdmin` (`bool`): Whether the user is the superadmin (owner) of their current team

**Example**

```csharp
var team = teamsSystem.Team;
Debug.Log($"Current Team: {team.Name}. Total members: {team.EdgeCount}");

// Check user's role in the team
var isAdmin = teamsSystem.IsAdmin;
var isSuperAdmin = teamsSystem.IsSuperAdmin;
```

### Get current team members

Access the members of the user's current team.

**Properties**

- `TeamMembers` (`IDictionary<string, IApiGroupUser>`): Dictionary of team members keyed by user ID

**Example**

```csharp
foreach (var teamMemberKvp in teamsSystem.TeamMembers)
{
    var teamMember = teamMemberKvp.Value;
    var online = teamMember.User.Online ? "Online" : "Offline";
    Debug.Log($"{teamMember.User.Username} ({online})");
}
```

### Get team members from other teams

Get the team members in other teams by passing the team id or a team object. You can optionally filter by membership state and limit the number of results.

```csharp
// Get all members (up to default limit of 100)
var memberList = await teamsSystem.GetTeamMembersAsync("<teamId>");
var memberList = await teamsSystem.GetTeamMembersAsync(team);

// Filter by state and limit results
// State values: 0 = Superadmin, 1 = Admin, 2 = Member, 3 = Join request
var adminList = await teamsSystem.GetTeamMembersAsync("<teamId>", state: 1, limit: 50);
var memberList = await teamsSystem.GetTeamMembersAsync(team, state: 2, limit: 25);
```

### Get chat history

Get the chat history for the user's team.

```csharp
foreach (var message in teamsSystem.ChatHistory)
{
    Debug.Log($"{message.User.Username}: {message.Content}");
}
```

### Create a team

Create a team for the user.

```csharp
var name = "MyTeam";
var description = "My team description";
var open = true;
var icon = "<iconUrl>";
var setupMetadata = "{}"; // Must be a valid JSON string.
var team = await teamsSystem.CreateTeamAsync(name, description, open, icon, setupMetadata);
```

### List available teams

List available teams by location.

```csharp
var location = "uk";
var limit = 10;
var cursor = "<optionalCursor>";

var teamList = await teamsSystem.ListTeamsAsync(location, limit, cursor);

foreach (var team in teamList.Teams)
{
    Debug.Log($"Team: {team.Name}. Total members: {team.EdgeCount}");
}
```

### Search for teams

Search for teams based on their name or shortcode.

```csharp
var name = "<name>";
var shortcode = "<optionalShortcode>";
var limit = 10;

var teamList = await teamsSystem.SearchTeamsAsync(name, shortcode, limit);

foreach (var team in teamList.Teams)
{
    Debug.Log($"Team: {team.Name}. Total members: {team.EdgeCount}");
}
```

### Join a team

Join a team for the user either by passing the team id or by passing a team object.

```csharp
await teamsSystem.JoinTeamAsync("<teamId>");
await teamsSystem.JoinTeamAsync(team);
```

### List join requests

List join requests for the user's current team

```csharp
foreach (var joinRequest in teamsSystem.JoinRequests)
{
    Debug.Log($"{joinRequest.User.Username} requested to join.");
}
```

### Approve join requests

Approve a single join request, either by passing the user id of the user requesting to join, or by directly passing the group user object.

```csharp
await teamsSystem.ApproveJoinRequestAsync("<teamId>", "<userId>");
await teamsSystem.ApproveJoinRequestAsync("<teamId>", groupUser);
```

Also approve multiple join requests at once by passing a collection of user IDs.

```csharp
// Approve one or more users
var userIds = new[] { "<userId1>", "<userId2>", "<userId3>" };
await teamsSystem.ApproveJoinRequestsAsync("<teamId>", userIds);
```

### Reject join requests

Reject a single join request, either by passing the user id of the user requesting to join, or by directly passing the group user object.

```csharp
await teamsSystem.RejectJoinRequestAsync("<teamId>", "<userId>");
await teamsSystem.RejectJoinRequestAsync("<teamId>", groupUser);
```

Also reject multiple join requests at once by passing a collection of user IDs.

```csharp
// Reject one or more users
var userIds = new[] { "<userId1>", "<userId2>", "<userId3>" };
await teamsSystem.RejectJoinRequestsAsync("<teamId>", userIds);
```

### Promote team members

Promote one or more team members if the user has sufficient privileges.

```csharp
// Promote one or more users to admin
var userIds = new[] { "<userId1>", "<userId2>" };
await teamsSystem.PromoteUsersAsync("<teamId>", userIds);
```

### Demote team members

Demote one or more team members from admin if the user has sufficient privileges.

```csharp
// Demote one or more admins
var userIds = new[] { "<userId1>", "<userId2>" };
await teamsSystem.DemoteUsersAsync("<teamId>", userIds);
```

### Kick team members

Kick one or more users from a team if the user has sufficient privileges.

```csharp
// Kick one or more users
var userIds = new[] { "<userId1>", "<userId2>" };
await teamsSystem.KickUsersAsync("<teamId>", userIds);
```

### Leave team

Leave a team for the user either by passing the team id.

```csharp
await teamsSystem.LeaveTeamAsync("<teamId>");
```

Or by passing a team object.

```csharp
await teamsSystem.LeaveTeamAsync(team);
```

### Delete team

Delete a team, providing the user has sufficient priviledges.

```csharp
await teamsSystem.DeleteTeamAsync("<teamId>");
```

### Send chat message

Send a chat message to the user's team.

```csharp
var messageAck = await teamsSystem.SendChatMessageAsync("<teamId>", "<contentJson>");
```

{{< note "warning" "Preview" >}}
The features described below are in preview at the moment.
{{< / note >}}

## Team Achievements

The Team Achievements API enables teams to work together toward shared goals and unlock rewards through collective effort.

**Function Reference**

| Function                                             | Description                                                             |
| :--------------------------------------------------- | :---------------------------------------------------------------------- |
| [GetAchievementsAsync](#get-team-achievements)       | Gets all available achievements and their current progress for the team |
| [UpdateAchievementsAsync](#update-team-achievements) | Submits progress updates for multiple achievements                      |
| [ClaimAchievementsAsync](#claim-team-achievements)   | Claims completed achievements to receive rewards (admins only)          |

### The Achievement object

| Attribute              | Type                          | Definition                                                                           |
| ---------------------- | ----------------------------- | ------------------------------------------------------------------------------------ |
| `Id`                   | `string`                      | Unique identifier for this achievement                                               |
| `Name`                 | `string`                      | Display name of the achievement (may be an i18n code)                                |
| `Description`          | `string`                      | Descriptive text explaining the achievement goal (may be an i18n code)               |
| `Category`             | `string`                      | Category grouping for organizing achievements                                        |
| `Count`                | `long`                        | Current progress toward completion                                                   |
| `MaxCount`             | `long`                        | Target value required for completion                                                 |
| `ClaimTimeSec`         | `long`                        | Unix timestamp when achievement reward was claimed, 0 if not claimed                 |
| `TotalClaimTimeSec`    | `long`                        | Unix timestamp when total achievement reward was claimed, 0 if not claimed           |
| `CurrentTimeSec`       | `long`                        | Unix timestamp for the current server time                                           |
| `ExpireTimeSec`        | `long`                        | Unix timestamp when achievement expires, 0 if it does not expire                     |
| `ResetTimeSec`         | `long`                        | Unix timestamp when achievement will reset                                           |
| `StartTimeSec`         | `long`                        | Unix timestamp when achievement becomes available, 0 if immediately available        |
| `EndTimeSec`           | `long`                        | Unix timestamp when achievement stops accepting updates, 0 if it does not end        |
| `PreconditionIds`      | `string[]`                    | Array of achievement IDs that must be completed before this one becomes available    |
| `AvailableRewards`     | `AvailableRewards`            | Potential rewards and their probabilities for completion                             |
| `Reward`               | `Reward`                      | Reward received after claiming, null if not claimed                                  |
| `AvailableTotalReward` | `AvailableRewards`            | Potential rewards for total completion (including sub-achievements)                  |
| `TotalReward`          | `Reward`                      | Reward received for total completion, null if not claimed                            |
| `SubAchievements`      | `Map<string, SubAchievement>` | Collection of sub-achievements within this achievement                               |
| `AdditionalProperties` | `Map<string, string>`         | Custom metadata and configuration properties                                         |
| `AutoClaim`            | `bool`                        | Whether the achievement reward is automatically given upon completion                |
| `AutoClaimTotal`       | `bool`                        | Whether the total reward is automatically given upon completing all sub-achievements |
| `AutoReset`            | `bool`                        | Whether the achievement automatically resets after completion                        |

### Get team achievements

```cs
GetAchievementsAsync()
```

Retrieves all available achievements and their current progress for the team.

**Results**

- `IAchievementList`: Complete collection of team achievements with progress data

**Permissions**

- Requires active team membership (member or admin)

**Example**

```csharp
var achievementList = await teamsSystem.GetAchievementsAsync();

foreach (var achievement in achievementList.Achievements)
{
    Debug.Log($"Achievement: {achievement.Id}");
    Debug.Log($"Progress: {achievement.Progress}/{achievement.Count}");
    Debug.Log($"Completed: {achievement.ClaimTimestamp > 0}");
}
```

### Update team achievements

Submits progress updates for achievements. Two overloads are available: one for updating multiple achievements with the same progress amount, and another for specifying different progress amounts per achievement.

**Bulk Update (Same Progress)**

```cs
UpdateAchievementsAsync(IEnumerable<string> achievementIds, long amount)
```

Updates multiple achievements with the same progress increment.

**Parameters**

- `achievementIds` (`IEnumerable<string>`, required): Collection of achievement IDs to update. Each ID must match a configured team achievement
- `amount` (`long`, required): Amount to add to current progress for all specified achievements. Negative values reduce progress, cannot go below 0

**Individual Update (Different Progress)**

```cs
UpdateAchievementsAsync(IDictionary<string, long> achievements)
```

Updates multiple achievements with individual progress amounts.

**Parameters**

- `achievements` (`IDictionary<string, long>`, required): Dictionary mapping achievement IDs to their progress increments
  - _Key_: Achievement identifier
  - _Value_: Amount to add to that achievement's progress
  - _Behavior_: Negative values reduce progress, cannot go below 0

**Results**

- `IAchievementsUpdateAck`: Updated achievement data reflecting new progress values

**Permissions**

- Requires active team membership (member or admin)

**Implementation Notes**

- Progress updates are applied atomically across all specified achievements
- Achievements with auto-claim enabled are automatically claimed when completed
- Recurring achievements reset progress after completion
- Updates are ignored for already-completed achievements unless they support multiple completions

**Example (Bulk Update)**

```csharp
var achievementIds = new List<string> { "monthly_pvp_wins", "weekly_dungeons", "daily_logins" };
var progressAmount = 1;

var achievementsUpdate = await teamsSystem.UpdateAchievementsAsync(achievementIds, progressAmount);

foreach (var achievement in achievementsUpdate.Achievements)
{
    Debug.Log($"Updated {achievement.Id}: {achievement.Progress}/{achievement.Count}");

    if (achievement.Progress >= achievement.Count)
    {
        Debug.Log($"{achievement.Id} is ready to claim!");
    }
}
```

**Example (Individual Update)**

```csharp
var achievementUpdates = new Dictionary<string, long>
{
    { "monthly_pvp_wins", 5 },
    { "weekly_dungeons", 2 },
    { "daily_logins", 1 }
};

var achievementsUpdate = await teamsSystem.UpdateAchievementsAsync(achievementUpdates);

foreach (var achievement in achievementsUpdate.Achievements)
{
    Debug.Log($"Updated {achievement.Id}: {achievement.Progress}/{achievement.Count}");
}
```

### Claim team achievements

```cs
ClaimAchievementsAsync(IEnumerable<string> achievementIds, bool claimTotalReward = false)
```

Claims completed achievements to receive configured rewards.

**Parameters**

- `achievementIds` (`IEnumerable<string>`, required): Collection of achievement identifiers to claim. Each ID must match a completed team achievement
- `claimTotalReward` (`bool`, optional): Whether to also claim the total reward for achievements with sub-achievements
  - _Default_: `false` - only claims individual achievement rewards

**Results**

- `IAchievementsUpdateAck`: Updated achievement data with claim timestamps and reward information

**Permissions**

- Requires admin or superadmin role within the team
- Regular members cannot claim achievements

**Implementation Notes**

- Achievements must be completed (progress >= count) before claiming
- Each achievement can only be claimed once per completion cycle
- Recurring achievements become available for new progress after claiming
- Claiming an already-claimed achievement has no effect and returns the same result

**Example**

```csharp
var achievementIds = new List<string> { "monthly_challenge_complete", "pvp_master" };

var achievementsUpdate = await teamsSystem.ClaimAchievementsAsync(achievementIds);

foreach (var achievement in achievementsUpdate.Achievements)
{
    if (achievement.ClaimTimestamp > 0)
    {
        Debug.Log($"Successfully claimed {achievement.Id}!");
    }
}
```

**Example (With Total Reward)**

```csharp
var achievementIds = new List<string> { "season_master" };

// Claim both the achievement reward and the total reward for all sub-achievements
var achievementsUpdate = await teamsSystem.ClaimAchievementsAsync(achievementIds, claimTotalReward: true);

foreach (var achievement in achievementsUpdate.Achievements)
{
    if (achievement.TotalClaimTimeSec > 0)
    {
        Debug.Log($"Successfully claimed total reward for {achievement.Id}!");
    }
}
```

## Team Event Leaderboards

The Team Event Leaderboards API enables teams to compete in time-limited events with tier-based matchmaking and cohort competition.

**Function Reference**

| Function                                                                  | Description                                                             |
| :------------------------------------------------------------------------ | :---------------------------------------------------------------------- |
| [ListEventLeaderboardsAsync](#list-team-event-leaderboards)               | Lists all available event leaderboards for the team                     |
| [GetEventLeaderboardAsync](#get-team-event-leaderboard)                   | Gets detailed information about a specific event leaderboard            |
| [RollEventLeaderboardAsync](#roll-into-team-event-leaderboard)            | Enrolls the team into a cohort for an event with tier-based matchmaking |
| [UpdateEventLeaderboardAsync](#update-team-event-leaderboard-score)       | Submits individual member scores that contribute to the team's total    |
| [ClaimEventLeaderboardRewardAsync](#claim-team-event-leaderboard-rewards) | Claims rewards after an event ends based on final team ranking          |

### The TeamEventLeaderboard object

| Attribute              | Type                                    | Definition                                                            |
| ---------------------- | --------------------------------------- | --------------------------------------------------------------------- |
| `Id`                   | `string`                                | Event leaderboard identifier                                          |
| `Name`                 | `string`                                | Display name of the event leaderboard (may be an i18n code)           |
| `Description`          | `string`                                | Descriptive text explaining the event (may be an i18n code)           |
| `Category`             | `string`                                | Category grouping for filtering events                                |
| `Ascending`            | `bool`                                  | Score ordering direction (true = lowest first, false = highest first) |
| `Operator`             | `string`                                | Score submission operator (INCREMENT, DECREMENT, SET, BEST)           |
| `Tier`                 | `int`                                   | Current tier level for matchmaking (0 = lowest tier)                  |
| `StartTimeSec`         | `long`                                  | Unix timestamp when the current event iteration started               |
| `EndTimeSec`           | `long`                                  | Unix timestamp when the current event iteration ends                  |
| `ExpiryTimeSec`        | `long`                                  | Unix timestamp when rewards expire and new iteration begins           |
| `AvailableRewards`     | `AvailableRewards`                      | Potential rewards and their probabilities for current rank            |
| `RewardTiers`          | `Map<int, EventLeaderboardRewardTiers>` | Reward structure for each tier of this event leaderboard              |
| `ChangeZones`          | `Map<int, EventLeaderboardChangeZone>`  | Per-tier promotion/demotion zones, if configured                      |
| `ClaimTimeSec`         | `long`                                  | Unix timestamp when rewards were claimed, 0 if not claimed            |
| `Reward`               | `Reward`                                | Reward received after claiming, null if not claimed                   |
| `AdditionalProperties` | `Map<string, string>`                   | Custom metadata and configuration properties                          |
| `Count`                | `long`                                  | Current participant count in the cohort                               |
| `MaxCount`             | `long`                                  | Maximum participant count for the cohort                              |
| `MaxNumScore`          | `long`                                  | Maximum number of score submissions per participant                   |
| `Scores`               | `TeamEventLeaderboardScore[]`           | Array of all team scores in the cohort                                |
| `IsActive`             | `bool`                                  | Whether new scores can be submitted to this event                     |
| `CanClaim`             | `bool`                                  | Whether rewards are available to claim                                |
| `CanRoll`              | `bool`                                  | Whether the team can enroll in a new iteration                        |
| `MatchmakerProperties` | `Struct`                                | Extra matchmaker properties for this cohort                           |
| `CurrentTimeSec`       | `long`                                  | Unix timestamp for the current server time                            |
| `CohortId`             | `string`                                | ID of the cohort this team is competing in, empty if not enrolled     |
| `BackingId`            | `string`                                | Backing ID for underlying score tracking                              |
| `Contributions`        | `TeamEventLeaderboardContribution[]`    | Array of individual member contributions to team score                |

#### TeamEventLeaderboardScore

| Attribute       | Type     | Definition                                       |
| --------------- | -------- | ------------------------------------------------ |
| `Id`            | `string` | Team ID for this leaderboard participant         |
| `Name`          | `string` | Team name                                        |
| `AvatarUrl`     | `string` | Team avatar URL                                  |
| `CreateTimeSec` | `long`   | Unix timestamp when team first joined this event |
| `UpdateTimeSec` | `long`   | Unix timestamp when team last submitted a score  |
| `Rank`          | `long`   | Position in cohort rankings (1 = first place)    |
| `Score`         | `long`   | Team's total score                               |
| `Subscore`      | `long`   | Team's subscore for tiebreaking                  |
| `NumScores`     | `long`   | Number of score submissions made by this team    |
| `Metadata`      | `string` | JSON metadata from team's last score submission  |

#### TeamEventLeaderboardContribution

| Attribute       | Type     | Definition                                        |
| --------------- | -------- | ------------------------------------------------- |
| `Id`            | `string` | User ID of contributing team member               |
| `Username`      | `string` | Username of contributing member                   |
| `DisplayName`   | `string` | Display name of contributing member               |
| `AvatarUrl`     | `string` | Avatar URL of contributing member                 |
| `CreateTimeSec` | `long`   | Unix timestamp when member first contributed      |
| `UpdateTimeSec` | `long`   | Unix timestamp of member's last contribution      |
| `Score`         | `long`   | Member's aggregate contribution to team score     |
| `Subscore`      | `long`   | Member's aggregate contribution to team subscore  |
| `NumScores`     | `long`   | Number of score submissions made by this member   |
| `Metadata`      | `string` | JSON metadata from member's last score submission |

### List team event leaderboards

```cs
ListEventLeaderboardsAsync()
```

Retrieves all available event leaderboards for the team, including both active and enrollable events.

**Results**

- `ITeamEventLeaderboards`: Collection containing all event leaderboards

**Permissions**

- Requires active team membership (member or admin)

**Example**

```csharp
var eventLeaderboards = await teamsSystem.ListEventLeaderboardsAsync();

foreach (var leaderboard in eventLeaderboards.EventLeaderboards)
{
    Debug.Log($"Event: {leaderboard.Id} - Active: {leaderboard.IsActive}");
    Debug.Log($"Team Rank: {leaderboard.Rank} - Can Claim: {leaderboard.CanClaim}");
}
```

### Get team event leaderboard

```cs
GetEventLeaderboardAsync(string leaderboardId)
```

Retrieves detailed information about a specific event leaderboard including team standings, scores, and member contributions.

**Parameters**

- `leaderboardId` (`string`, required): Event leaderboard identifier
  - _Constraints_: Must match a configured event leaderboard ID

**Results**

- `ITeamEventLeaderboard`: Complete event leaderboard data with scores and contributions

**Permissions**

- Requires active team membership (member or admin)

**Implementation Notes**

- May fail if the event has reached maximum participants or has other enrollment constraints

**Example**

```csharp
var leaderboardId = "weekly_tournament";
var eventLeaderboard = await teamsSystem.GetEventLeaderboardAsync(leaderboardId);

Debug.Log($"Team Rank: {eventLeaderboard.Rank}");
Debug.Log($"Team Score: {eventLeaderboard.Score}");
Debug.Log($"Can Claim: {eventLeaderboard.CanClaim}");

// View individual member contributions
foreach (var contribution in eventLeaderboard.Contributions)
{
    Debug.Log($"{contribution.Username}: {contribution.Score} points");
}
```

### Roll into team event leaderboard

```cs
RollEventLeaderboardAsync(string leaderboardId)
```

Enrolls the team into a cohort for an event that has not yet started, with automatic tier-based matchmaking.

**Parameters**

- `leaderboardId` (`string`, required): Event leaderboard identifier
  - _Constraints_: Must match a configured event leaderboard ID

**Results**

- `ITeamEventLeaderboard`: Event leaderboard data after enrollment

**Permissions**

- Requires active team membership (member or admin)

**Implementation Notes**

- Teams are automatically matched with others in the same tier
- Cohort assignment is permanent for the duration of the event
- Cannot re-roll while an active event is in progress
- Tier progression is based on previous event performance

**Example**

```csharp
var leaderboardId = "weekly_tournament";
var eventLeaderboard = await teamsSystem.RollEventLeaderboardAsync(leaderboardId);

Debug.Log($"Enrolled in cohort: {eventLeaderboard.CohortId}");
Debug.Log($"Current tier: {eventLeaderboard.Tier}");
Debug.Log($"Competing against {eventLeaderboard.Scores.Count} teams");
```

### Update team event leaderboard score

```cs
UpdateEventLeaderboardAsync(string leaderboardId, long score, long subscore = 0, Dictionary<string, string> metadata = default)
```

Submits individual member scores that contribute to the team's total score based on the event's operator configuration. (See: [Score Operators](../../concepts/event-leaderboards/#score-operators))

**Parameters**

- `leaderboardId` (`string`, required): Event leaderboard identifier
  - _Constraints_: Must match a configured event leaderboard ID
- `score` (`long`, required): Primary score to submit. Accepts negative values, but beware of interactions with the event leaderboard's configured operator e.g., a negative score combined with the DECR operator will increase the score.
- `subscore` (`long`, optional): Secondary score for tiebreaking
  - _Default behavior_: Uses 0 if not provided
- `metadata` (`Dictionary<string, string>`, optional): Custom data associated with this submission
  - _Default behavior_: Empty metadata if not provided; `null` dictionary becomes empty JSON object
  - _Constraints_: Values must be JSON-serializable strings

**Results**

- `ITeamEventLeaderboard`: Updated event leaderboard data with new rankings

**Permissions**

- Requires active team membership (member or admin)

**Implementation Notes**

- Scores are aggregated according to the event's operator (SUM, MAX, MIN, etc.)
- Team must be enrolled in the event before submitting scores
- Submissions are only accepted during the active event window
- Individual contributions are tracked separately from team totals
- Teams may only submit up to `MaxNumScore` as defined in the config

**Example**

```csharp
var leaderboardId = "weekly_tournament";
var score = 1500;
var subscore = 50;
var metadata = new Dictionary<string, string>
{
    {"mission_type", "daily_challenge"},
    {"difficulty", "hard"},
    {"completion_time", "120"}
};

var eventLeaderboard = await teamsSystem.UpdateEventLeaderboardAsync(
    leaderboardId, score, subscore, metadata);

Debug.Log($"Updated score. New team total: {eventLeaderboard.Score}");
Debug.Log($"Team rank: {eventLeaderboard.Rank}");
```

### Claim team event leaderboard rewards

```cs
ClaimEventLeaderboardRewardAsync(string leaderboardId)
```

Claims rewards after an event ends based on the team's final ranking and tier placement.

**Parameters**

- `leaderboardId` (`string`, required): Event leaderboard identifier
  - _Constraints_: Must match a configured event leaderboard ID

**Results**

- `ITeamEventLeaderboard`: Event leaderboard data with claimed reward information

**Permissions**

- Requires active team membership (member or admin)

**Implementation Notes**

- Rewards can only be claimed after the event has ended but before the reward expires
- Rewards are distributed based on final rank within the cohort
- Each event can only be claimed once per team
- Operation is idempotent - multiple claims return the same result

**Example**

```csharp
var leaderboardId = "weekly_tournament";
var eventLeaderboard = await teamsSystem.ClaimEventLeaderboardRewardAsync(leaderboardId);

Debug.Log($"Rewards claimed for rank {eventLeaderboard.Rank}");
Debug.Log($"Claim time: {eventLeaderboard.ClaimTimeSec}");

// Access reward details
if (eventLeaderboard.Reward != null)
{
    foreach (var currency in eventLeaderboard.Reward.Currencies)
    {
        Debug.Log($"Earned {currency.Value} {currency.Key}");
    }
}
```

## Team Inventory

The Team Inventory API enables teams to manage shared items, view item properties, consume items for rewards, and grant items to the group inventory.

**Function Reference**

| Function                                         | Description                                                   |
| :----------------------------------------------- | :------------------------------------------------------------ |
| [GetItemCodexAsync](#get-team-item-codex)        | Gets all available item definitions and configurations        |
| [GetItemsAsync](#get-team-items)                 | Gets the team's current inventory with item instances         |
| [ConsumeItemsAsync](#consume-team-items)         | Consumes items from inventory and receives configured rewards |
| [GrantItemsAsync](#grant-team-items)             | Adds items to the team's inventory                            |
| [UpdateItemsAsync](#update-team-item-properties) | Updates custom properties of existing inventory items         |

### The Inventory object

| Attribute | Type                         | Definition                                         |
| --------- | ---------------------------- | -------------------------------------------------- |
| `Items`   | `Map<string, InventoryItem>` | Collection of inventory items keyed by instance ID |

#### InventoryItem

| Attribute                 | Type                  | Definition                                                     |
| ------------------------- | --------------------- | -------------------------------------------------------------- |
| `Id`                      | `string`              | Unique identifier for this item type                           |
| `InstanceId`              | `string`              | Unique identifier for this specific item instance              |
| `Name`                    | `string`              | Display name of the item (may be an i18n code)                 |
| `Description`             | `string`              | Descriptive text explaining the item (may be an i18n code)     |
| `Category`                | `string`              | Category grouping for organizing items                         |
| `ItemSets`                | `string[]`            | Array of item set identifiers this item belongs to             |
| `Count`                   | `long`                | Current quantity of this item in the inventory                 |
| `MaxCount`                | `long`                | Maximum quantity that can be owned for this item type          |
| `Stackable`               | `bool`                | Whether multiple instances can be combined into a single stack |
| `Consumable`              | `bool`                | Whether this item can be consumed for rewards                  |
| `ConsumeAvailableRewards` | `AvailableRewards`    | Potential rewards and probabilities when consuming this item   |
| `StringProperties`        | `Map<string, string>` | Custom text-based properties for this item instance            |
| `NumericProperties`       | `Map<string, double>` | Custom numeric properties for this item instance               |
| `OwnedTimeSec`            | `long`                | Unix timestamp when the team acquired this item                |
| `UpdateTimeSec`           | `long`                | Unix timestamp when this item was last modified                |

### Get team item codex

```cs
GetItemCodexAsync(string category = "")
```

Retrieves the item catalog/reference for a team i.e., a list of all possible items that could exist in the team's inventory system, not the items the team actually owns.

**Parameters**

- `category` (`string`, optional): Filter items by category. Returns all items if not specified or empty

**Results**

- `IInventoryList`: Complete catalog of item definitions and metadata

**Permissions**

- Requires active team membership (member or admin)

**Example**

```csharp
// Get all items in the codex
var itemCodex = await teamsSystem.GetItemCodexAsync();

foreach (var item in itemCodex.Items)
{
    Debug.Log($"Item: {item.Value.Name} - Category: {item.Value.Category}");
    Debug.Log($"Stackable: {item.Value.Stackable} - Consumable: {item.Value.Consumable}");
    Debug.Log($"Max Count: {item.Value.MaxCount}");
}

// Get items filtered by category
var weaponCodex = await teamsSystem.GetItemCodexAsync("weapons");
```

### Get team items

```cs
GetItemsAsync(string category = "")
```

Retrieves the team's current inventory with all owned item instances.

**Parameters**

- `category` (`string`, optional): Filter items by category. Returns all items if not specified or empty

**Results**

- `IInventoryList`: Team's inventory containing owned items with quantities and properties

**Permissions**

- Requires active team membership (member or admin)

**Example**

```csharp
// Get all items in the team's inventory
var teamInventory = await teamsSystem.GetItemsAsync();

foreach (var item in teamInventory.Items)
{
    Debug.Log($"Owned: {item.Value.Name} x{item.Value.Count}");
    Debug.Log($"Instance ID: {item.Value.InstanceId}");
    Debug.Log($"Acquired: {item.Value.OwnedTimeSec}");

    // Check custom properties
    foreach (var prop in item.Value.StringProperties)
    {
        Debug.Log($"Property {prop.Key}: {prop.Value}");
    }
}

// Get items filtered by category
var teamWeapons = await teamsSystem.GetItemsAsync("weapons");
```

### Consume team items

```cs
ConsumeItemsAsync(Dictionary<string, long> itemsToConsume, Dictionary<string, long> instancesToConsume, bool overConsume)
```

Consumes items from the team inventory and receives configured rewards.

**Parameters**

- `itemsToConsume` (`Dictionary<string, long>`, required): Map of item IDs to quantities to consume
  - _Constraints_: Item IDs must exist in team inventory with sufficient quantities
- `instancesToConsume` (`Dictionary<string, long>`, required): Map of instance IDs to quantities to consume
  - _Constraints_: Instance IDs must exist in team inventory with sufficient quantities
- `overConsume` (`bool`, required): Whether to allow consuming more items than available
  - _Behavior_: If true, consumes all available items even if less than requested

**Results**

- `IInventoryConsumeRewards`: Rewards received from consumption and updated inventory state

**Permissions**

- Requires admin role within the team
- Regular members cannot consume team inventory items

**Implementation Notes**

- Items must be marked as consumable in their configuration
- Consumption is atomic: either all specified items are consumed or none are
- Rewards are determined by item configuration and applied immediately
- Non-consumable items cannot be consumed regardless of parameters

**Example**

```csharp
var itemsToConsume = new Dictionary<string, long>
{
    {"health_potion", 2},
    {"mana_crystal", 1}
};

var instancesToConsume = new Dictionary<string, long>
{
    {"unique_sword_instance_123", 1}
};

var rewards = await teamsSystem.ConsumeItemsAsync(itemsToConsume, instancesToConsume, false);

Debug.Log($"Consumed items successfully");
foreach (var currency in rewards.Reward.Currencies)
{
    Debug.Log($"Received {currency.Value} {currency.Key}");
}
```

### Grant team items

```cs
GrantItemsAsync(Dictionary<string, long> itemsToGrant)
```

Adds items to the team's shared inventory.

**Parameters**

- `itemsToGrant` (`Dictionary<string, long>`, required): Map of item IDs to quantities to grant
  - _Constraints_: Item IDs must be configured in the team inventory system
  - _Behavior_: Respects maximum count limits and stacking rules per item

**Results**

- `IInventoryUpdateAck`: Confirmation of granted items and updated inventory state

**Permissions**

- Requires admin role within the team
- Regular members cannot grant items to team inventory

**Implementation Notes**

- Items are subject to maximum count limits defined in configuration
- Stackable items will be combined with existing instances when possible
- Non-stackable items create individual instances for each quantity
- Granting beyond maximum limits will result in partial grants up to the limit

**Example**

```csharp
var itemsToGrant = new Dictionary<string, long>
{
    {"team_banner", 1},
    {"upgrade_material", 50},
    {"legendary_weapon", 1}
};

var updateAck = await teamsSystem.GrantItemsAsync(itemsToGrant);

Debug.Log($"Items granted successfully");
foreach (var item in updateAck.Items)
{
    Debug.Log($"Granted: {item.Value.Name} x{item.Value.Count}");
}
```

### Update team item properties

```cs
UpdateItemsAsync(Dictionary<string, UpdateInventoryItemProperties> itemsToUpdate)
```

Updates custom properties of existing inventory items.

**Parameters**

- `itemsToUpdate` (`Dictionary<string, UpdateInventoryItemProperties>`, required): Map of instance IDs to property updates
  - _Constraints_: Instance IDs must exist in team inventory
  - _Behavior_: Only updates specified properties, leaves others unchanged

**UpdateInventoryItemProperties Properties**

- `StringProperties` (`Dictionary<string, string>`, optional): Text-based properties to update
- `NumericProperties` (`Dictionary<string, double>`, optional): Numeric properties to update

**Results**

- `IInventoryUpdateAck`: Confirmation of updates and current item state

**Permissions**

- Requires admin role within the team
- Regular members cannot update team inventory item properties

**Implementation Notes**

- Only custom properties can be updated, not core item attributes like count or category
- Property updates are merged with existing properties
- Setting a property value to null or empty removes that property
- Property names and value types must match configuration constraints

**Example**

```csharp
var itemUpdates = new Dictionary<string, UpdateInventoryItemProperties>
{
    ["sword_instance_456"] = new UpdateInventoryItemProperties
    {
        StringProperties = new Dictionary<string, string>
        {
            {"enchantment", "fire_damage"},
            {"crafted_by", "master_smith"}
        },
        NumericProperties = new Dictionary<string, double>
        {
            {"damage_bonus", 15.5},
            {"durability", 98.2}
        }
    }
};

var updateAck = await teamsSystem.UpdateItemsAsync(itemUpdates);

Debug.Log($"Item properties updated successfully");
```

## Team Mailbox

The Team Mailbox API manages deferred reward delivery for teams, providing secure storage and controlled claiming of rewards sent to the team's shared mailbox.

**Function Reference**

| Function                                             | Description                                                            |
| :--------------------------------------------------- | :--------------------------------------------------------------------- |
| [ListMailboxAsync](#list-team-mailbox-entries)       | Retrieves paginated list of mailbox entries for the team               |
| [ClaimMailboxRewardAsync](#claim-team-mailbox-entry) | Claims rewards from a specific mailbox entry and optionally deletes it |
| [DeleteMailboxAsync](#delete-team-mailbox-entries)   | Removes one or more mailbox entries without claiming rewards           |

### The MailboxList object

| Attribute | Type             | Definition                                                |
| --------- | ---------------- | --------------------------------------------------------- |
| `Entries` | `MailboxEntry[]` | Array of mailbox entries for this team                    |
| `Cursor`  | `string`         | Pagination cursor to fetch more results, empty if no more |

#### MailboxEntry

| Attribute       | Type     | Definition                                                   |
| --------------- | -------- | ------------------------------------------------------------ |
| `Id`            | `string` | Unique identifier for this mailbox entry                     |
| `Reward`        | `Reward` | Reward object containing currencies, items, and other assets |
| `CreateTimeSec` | `long`   | Unix timestamp when this entry was created                   |
| `UpdateTimeSec` | `long`   | Unix timestamp when this entry was last modified             |
| `ExpiryTimeSec` | `long`   | Unix timestamp when this entry expires and becomes invalid   |
| `ClaimTimeSec`  | `long`   | Unix timestamp when this entry was claimed, 0 if unclaimed   |
| `CanClaim`      | `bool`   | Whether this entry's reward can currently be claimed         |

### List team mailbox entries

```cs
ListMailboxAsync(int limit = 100)
```

Retrieves a paginated list of mailbox entries for the team, including both claimed and unclaimed rewards.

**Parameters**

- `limit` (`int`, optional): Maximum number of entries to return per page
  - _Default behavior_: Returns up to 100 entries if not specified
  - _Constraints_: Must be between 1 and 100; values outside this range are clamped

**Results**

- `IMailboxList`: Paginated collection of mailbox entries with cursor for additional pages

**Permissions**

- Requires active team membership (member or admin)
- Cannot access mailbox from teams where user is banned or has pending membership

**Implementation Notes**

- Results are ordered by creation time (newest first)
- Expired entries are automatically filtered from results
- Pagination cursor enables efficient traversal of large mailbox collections

**Example**

```csharp
var mailboxList = await teamsSystem.ListMailboxAsync(50);

foreach (var entry in mailboxList.Entries)
{
    Debug.Log($"Entry {entry.Id}: Can Claim = {entry.CanClaim}");

    if (entry.Reward?.Currencies != null)
    {
        foreach (var currency in entry.Reward.Currencies)
        {
            Debug.Log($"  Reward: {currency.Value} {currency.Key}");
        }
    }
}

// Handle pagination if more entries exist
if (!string.IsNullOrEmpty(mailboxList.Cursor))
{
    Debug.Log("More entries available - use cursor for next page");
}
```

### Claim team mailbox entry

```cs
ClaimMailboxRewardAsync(string mailboxId, bool deleteAfterClaim = true)
```

Claims the reward from a specific mailbox entry and optionally removes the entry from the mailbox after successful claiming.

**Parameters**

- `mailboxId` (`string`, required): Unique identifier of the mailbox entry to claim
  - _Constraints_: Must be a valid entry ID that exists in the team's mailbox
- `deleteAfterClaim` (`bool`, optional): Whether to remove the entry after claiming
  - _Default behavior_: Entry is deleted after claiming if not specified
  - _Use case_: Set to false to keep entry for record-keeping or reference

**Results**

- `IMailboxEntry`: Updated mailbox entry with claim timestamp and reward details

**Permissions**

- Requires active team membership (member or admin)
- All team members can claim rewards from the shared mailbox

**Implementation Notes**

- Entry must be claimable (`CanClaim` = true) and not expired
- Rewards are added directly to team resources (wallet, inventory, etc.)
- Operation is idempotent - claiming the same entry multiple times returns the same result
- Claimed entries cannot be unclaimed once processed

**Example**

```csharp
var entryId = "mailbox_entry_123";
var claimedEntry = await teamsSystem.ClaimMailboxRewardAsync(entryId, deleteAfterClaim: true);

Debug.Log($"Claimed reward at: {claimedEntry.ClaimTimeSec}");

// Check what rewards were received
if (claimedEntry.Reward?.Currencies != null)
{
    foreach (var currency in claimedEntry.Reward.Currencies)
    {
        Debug.Log($"Received {currency.Value} {currency.Key}");
    }
}

// Entry is now deleted from mailbox (if deleteAfterClaim was true)
```

### Delete team mailbox entries

```cs
DeleteMailboxAsync(IEnumerable<string> entryIds)
```

Removes one or more mailbox entries without claiming their rewards, useful for clearing expired or unwanted entries.

**Parameters**

- `entryIds` (`IEnumerable<string>`, required): Collection of mailbox entry identifiers to delete
  - _Constraints_: All entry IDs must exist in the team's mailbox
  - _Behavior_: Non-existent entries are silently ignored without causing errors

**Results**

- `Task`: Completion task with no return value

**Permissions**

- Requires admin or superadmin role within the team
- Regular members cannot delete mailbox entries

**Implementation Notes**

- Deleted entries and their rewards are permanently lost and cannot be recovered
- Operation is atomic: either all specified entries are deleted or none are
- Useful for mailbox maintenance and removing expired promotional rewards

**Example**

```csharp
var entriesToDelete = new string[]
{
    "expired_entry_456",
    "promotional_reward_789",
    "outdated_bonus_012"
};

await teamsSystem.DeleteMailboxAsync(entriesToDelete);

Debug.Log($"Deleted {entriesToDelete.Length} mailbox entries");

// Verify deletion by listing mailbox again
var updatedMailbox = await teamsSystem.ListMailboxAsync();
Debug.Log($"Remaining entries: {updatedMailbox.Entries.Count}");
```

## Team Stats

The Team Stats API provides tracking and managing public and private stats for teams.

**Function Reference**

| Function                               | Description                                                      |
| :------------------------------------- | :--------------------------------------------------------------- |
| [GetStatsAsync](#get-team-stats)       | Gets the current team's public and private statistics            |
| [UpdateStatsAsync](#update-team-stats) | Updates public and/or private statistics with various operations |

### The TeamStat object

| Attribute              | Type     | Definition                                                                    |
| ---------------------- | -------- | ----------------------------------------------------------------------------- |
| `Name`                 | `string` | The unique identifier for this stat                                           |
| `Public`               | `bool`   | Indicates whether this stat is publicly visible (`true`) or private (`false`) |
| `UpdateTimeSec`        | `long`   | Unix timestamp when this stat was last modified                               |
| `Value`                | `long`   | Current value of the stat                                                     |
| `Count`                | `long`   | Number of values that have been submitted for this stat                       |
| `Total`                | `long`   | Total of all submitted values for this stat                                   |
| `Min`                  | `long`   | Smallest value that has ever been submitted for this stat                     |
| `Max`                  | `long`   | Largest value that has ever been submitted for this stat                      |
| `First`                | `long`   | First value that was submitted for this stat                                  |
| `Last`                 | `long`   | Latest value that was submitted for this stat                                 |
| `AdditionalProperties` | `Struct` | Custom metadata and configuration properties associated with this stat        |

### Get team stats

```cs
GetStatsAsync()
```

Retrieves the current team's complete stats data, including both public and private stats.

**Result**

- Returns `IStatList`: Complete stats object containing public and private stat collections
- Updates local `PrivateStats` and `PublicStats` properties
- Notifies observers

**Permissions**

- Requires active team membership (member or admin)
- Cannot retrieve stats from teams where user is banned or has pending membership

**Example**

```csharp
var statList = await teamsSystem.GetStatsAsync();

// Access via properties after refresh
foreach (var stat in teamsSystem.PrivateStats)
{
    Debug.Log($"Private Stat - {stat.Key}: {stat.Value.CurrentValue}");
}

foreach (var stat in teamsSystem.PublicStats)
{
    Debug.Log($"Public Stat - {stat.Key}: {stat.Value.CurrentValue}");
}

// Direct access to returned object
Debug.Log($"Team Level: {statList.Public["team_level"].CurrentValue}");
```

### Update team stats

```cs
UpdateStatsAsync(List<UpdateStat>? privateStats, List<UpdateStat>? publicStats)
```

Modifies team stats using various mathematical operations, supporting both individual and batch updates.

**Parameters**

- `privateStats` (`List<UpdateStat>`, optional): Collection of private stat modifications
  - _Default behavior_: No private stats updated if null or empty
- `publicStats` (`List<UpdateStat>`, optional): Collection of public stat modifications
  - _Default behavior_: No public stats updated if null or empty

**UpdateStat Properties**

- `Name` (`string`, required): Stat identifier matching configuration
  - _Constraints_: Must exist in team stats configuration; alphanumeric and underscore only. Case-sensitive
- `Value` (`long`, required): Numeric value for the operation
- `Operator` (`StatOperator`, required): Mathematical operation to perform

**Results**

- Updates local `PrivateStats` and `PublicStats` properties
- Notifies observers
- Generates publisher events for cross-system integrations

**Permissions**

- Requires admin or superadmin role within the team
- Regular members cannot directly update team statistics

**Implementation Notes**

- All stat names must be pre-configured in the team configuration
- Operations are atomic: either all updates succeed or all fail
- Concurrent updates by different admins may require retry logic
- Stat values are clamped to int64 range to prevent overflow

**Example**

```csharp
var privateStatsToUpdate = new List<UpdateStat>
{
    new UpdateStat { Name = "experience_points", Value = 100, Operator = StatOperator.DELTA },
    new UpdateStat { Name = "team_level", Value = 5, Operator = StatOperator.MAX }
};

var publicStatsToUpdate = new List<UpdateStat>
{
    new UpdateStat { Name = "wins", Value = 1, Operator = StatOperator.DELTA },
    new UpdateStat { Name = "total_score", Value = 1500, Operator = StatOperator.MAX }
};

await teamsSystem.UpdateStatsAsync(privateStatsToUpdate, publicStatsToUpdate);
```

**Stat Update Operators**

| Operation | Behavior                      | Considerations                                                           |
| --------- | ----------------------------- | ------------------------------------------------------------------------ |
| SET       | Overwrites current value      | Values exceeding int64 range are clamped, null treated as 0              |
| DELTA     | Adds/subtracts from current   | Negative results allowed; overflow protection applied, null treated as 0 |
| MIN       | Updates only if new < current | Works with negative values                                               |
| MAX       | Updates only if new > current | Works with negative values                                               |

## Team Store

The Team Store API enables teams to browse and purchase items using shared team currencies and view active promotional modifiers.

**Function Reference**

| Function                         | Description                                                              |
| :------------------------------- | :----------------------------------------------------------------------- |
| [GetStoreAsync](#get-team-store) | Gets available store items and active promotional modifiers for the team |

### The TeamStore object

| Attribute               | Type                     | Definition                                                        |
| ----------------------- | ------------------------ | ----------------------------------------------------------------- |
| `StoreItems`            | `TeamStoreItem[]`        | Array of items available for purchase in the team store           |
| `ActiveRewardModifiers` | `ActiveRewardModifier[]` | Array of currently active promotional modifiers affecting rewards |
| `CurrentTimeSec`        | `long`                   | Unix timestamp for the current server time                        |

#### TeamStoreItem

| Attribute              | Type                  | Definition                                                          |
| ---------------------- | --------------------- | ------------------------------------------------------------------- |
| `Id`                   | `string`              | Unique identifier for this store item                               |
| `Name`                 | `string`              | Display name of the store item (may be an i18n code)                |
| `Description`          | `string`              | Descriptive text explaining the item (may be an i18n code)          |
| `Category`             | `string`              | Category grouping for organizing store items                        |
| `Cost`                 | `TeamStoreItemCost`   | Currency requirements and pricing information for this item         |
| `AvailableRewards`     | `AvailableRewards`    | Potential rewards and their probabilities when purchasing this item |
| `AdditionalProperties` | `Map<string, string>` | Custom metadata and configuration properties                        |
| `Unavailable`          | `bool`                | Whether the item is visible but cannot be purchased                 |

### Get team store

```cs
GetStoreAsync()
```

Retrieves available store items and active promotional modifiers for the team.

**Results**

- `ITeamStore`: Complete store data including items and active promotions

**Permissions**

- Requires active team membership (member or admin)

**Implementation Notes**

- Store items may have availability restrictions based on team level or achievements
- Active reward modifiers automatically apply to relevant purchases
- Item prices and availability can change based on server-side configuration updates

**Example**

```csharp
var teamStore = await teamsSystem.GetStoreAsync();

// Browse available items
foreach (var item in teamStore.StoreItems)
{
    Debug.Log($"Item: {item.Id} - Available: {!item.Unavailable}");
    Debug.Log($"Name: {item.Name} - Category: {item.Category}");

    if (item.Cost != null)
    {
        Debug.Log($"Cost: {item.Cost} team currency");
    }

    if (item.Unavailable)
    {
        Debug.Log($"Item {item.Id} is currently unavailable for purchase");
    }
}

// Check active promotions
foreach (var modifier in teamStore.ActiveRewardModifiers)
{
    Debug.Log($"Active Promotion: {modifier.Id}");
    Debug.Log($"Expires: {modifier.EndTimeSec}");
}

Debug.Log($"Server time: {teamStore.CurrentTimeSec}");
```

## Team Wallet

The Team Wallet API manages shared currency resources for teams, enabling collective resource accumulation and administrative control over team finances.

**Function Reference**

| Function                             | Description                                                 |
| :----------------------------------- | :---------------------------------------------------------- |
| [GetWalletAsync](#get-team-wallet)   | Retrieves current team wallet balances for all currencies   |
| [GrantAsync](#grant-team-currencies) | Adds specified currency amounts to the team's shared wallet |

### The TeamWallet object

| Attribute       | Type                | Definition                                            |
| --------------- | ------------------- | ----------------------------------------------------- |
| `Id`            | `string`            | Team identifier this wallet belongs to                |
| `Currencies`    | `Map<string, long>` | Current currency balances in the team's shared wallet |
| `UpdateTimeSec` | `long`              | Unix timestamp when the wallet was last modified      |

### Get team wallet

```cs
GetWalletAsync()
```

Retrieves the current balances for all currencies in the team's shared wallet.

**Results**

- `Dictionary<string, long>`: Currency balances keyed by currency identifier
- Notifies observers

**Permissions**

- Requires active team membership (member or admin)

**Example**

```csharp
var walletBalances = await teamsSystem.GetWalletAsync();

// Access via returned dictionary
foreach (var currency in walletBalances)
{
    Debug.Log($"Currency {currency.Key}: {currency.Value}");
}

// Access via property after refresh
Debug.Log($"Team Coins: {teamsSystem.Wallet["coins"]}");
Debug.Log($"Team Gems: {teamsSystem.Wallet["gems"]}");
```

### Grant team currencies

```cs
GrantAsync(Dictionary<string, long> currencies)
```

Adds the specified currency amounts to the team's shared wallet, supporting multiple currencies in a single operation.

**Parameters**

- `currencies` (`Dictionary<string, long>`, required): Currency amounts to add to the team wallet
  - _Constraints_: Currency identifiers must match team wallet configuration; values must be positive
  - _Key format_: Currency identifier strings (e.g., "coins", "gems", "tokens")
  - _Value format_: Positive integer amounts to add

**Results**

- Updates local `Wallet` property with new balances
- Notifies observers
- Generates publisher events for currency tracking

**Permissions**

- Requires admin or superadmin role within the team
- Regular members cannot directly modify team wallet

**Implementation Notes**

- All currency identifiers must be pre-configured in the team wallet setup
- Operations are atomic: either all currency grants succeed or all fail
- Currency values are added to existing balances, not replaced
- Negative values are not supported - use other systems for currency deduction

**Example**

```csharp
var currenciesToGrant = new Dictionary<string, long>
{
    { "coins", 1000 },
    { "gems", 50 },
    { "event_tokens", 25 }
};

await teamsSystem.GrantAsync(currenciesToGrant);

// Check updated balances
Debug.Log($"New coin balance: {teamsSystem.Wallet["coins"]}");
Debug.Log($"New gem balance: {teamsSystem.Wallet["gems"]}");
```

## Additional information

- [Hiro Teams sample project](../../../../sample-projects/unity/hiro-teams)
