mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add GET /groups/{group}/members/ai/spend (#27130)
## Description
Adds `GET /api/v2/groups/{group}/members/ai/spend?user_ids=...` (also available org-scoped at `/api/v2/organizations/{org}/groups/{groupName}/members/ai/spend`) to return per-member AI spend attributed to a group, along with each member's effective budget group and the applied spend limit when the queried group is their effective budget source.
In the UI, this endpoint is used alongside the existing `/api/v2/groups/{group}/members` endpoint. AI spend data is kept separate from that endpoint so that:
- Different concepts stay on different endpoints: identity (group members) vs. cost control (spend). Cost control is an additional feature layered on top of groups/orgs.
- Callers that don't need spend information don't pay for its computation.
UI flow:
1. Request `/api/v2/groups/{group}/members` → returns the group's members.
2. Request `/api/v2/groups/{group}/members/ai/spend?user_ids=...` with the IDs from step 1.
**Note:** Only current members of the queried group are returned. `spend_limit_micros` and `limit_source` are populated only when the queried group is the member's effective budget source (its own limit or a user override). `effective_group_id` is null when the member's budget resolves to a group in another organization, since an organization is treated as a tenant boundary.
<img width="2880" height="1904" alt="image" src="https://github.com/user-attachments/assets/33ed395d-d1a3-4b46-bb04-c8d3f41c8886" />
## Changes
- Add `codersdk.GroupMembersAISpend` and `GroupMemberAISpend` types, reusing the shared `AISpendPeriodWindow`.
- Add `GetGroupMembersAISpend` SQL query with a dbauthz per-row filter that mirrors `GET /api/v2/groups/{group}/members`.
- Add handler and routes under `/groups/{group}/members/ai/spend` (and the org-scoped alias) with a required `user_ids` query param (cap 100). Callers with more than 100 members are expected to batch across multiple requests.
- Add codersdk client method.
- Tests: dbauthz, raw SQL, endpoint, and role-access.
Closes https://linear.app/codercom/issue/AIGOV-471/backend-group-members-endpoint-with-members-spend
> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
This commit is contained in:
Generated
+109
@@ -1092,6 +1092,60 @@ curl -X GET http://coder-server:8080/api/v2/groups/{group}/members \
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get group members AI spend
|
||||
|
||||
### Code samples
|
||||
|
||||
```sh
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/groups/{group}/members/ai/spend?user_ids=string \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /api/v2/groups/{group}/members/ai/spend`
|
||||
|
||||
Returns aggregate AI spend attributed to the group per requested user.
|
||||
A maximum of 100 user IDs may be requested per call, and requests with more are rejected, so callers are expected to batch across multiple requests.
|
||||
User IDs that are not members of the group, or that the caller has no read access to, are silently omitted.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|------------|-------|--------------|----------|------------------------------------------------|
|
||||
| `group` | path | string(uuid) | true | Group ID |
|
||||
| `user_ids` | query | string | true | Comma-separated list of user IDs (maximum 100) |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"members": [
|
||||
{
|
||||
"effective_group_id": "85e2b926-ddfb-4c66-b68e-b66e5acec6c0",
|
||||
"group_budget": {
|
||||
"limit_source": "user_override",
|
||||
"spend_limit_micros": 0
|
||||
},
|
||||
"group_spend_micros": 0,
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
],
|
||||
"period_end": "2019-08-24T14:15:22Z",
|
||||
"period_start": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|------------------------------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.GroupMembersAISpend](schemas.md#codersdkgroupmembersaispend) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get licenses
|
||||
|
||||
### Code samples
|
||||
@@ -2010,6 +2064,61 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get group members AI spend by organization
|
||||
|
||||
### Code samples
|
||||
|
||||
```sh
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/{groupName}/members/ai/spend?user_ids=string \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /api/v2/organizations/{organization}/groups/{groupName}/members/ai/spend`
|
||||
|
||||
Returns aggregate AI spend attributed to the group per requested user.
|
||||
A maximum of 100 user IDs may be requested per call, and requests with more are rejected, so callers are expected to batch across multiple requests.
|
||||
User IDs that are not members of the group, or that the caller has no read access to, are silently omitted.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|----------------|-------|--------------|----------|------------------------------------------------|
|
||||
| `organization` | path | string(uuid) | true | Organization ID |
|
||||
| `groupName` | path | string | true | Group name |
|
||||
| `user_ids` | query | string | true | Comma-separated list of user IDs (maximum 100) |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"members": [
|
||||
{
|
||||
"effective_group_id": "85e2b926-ddfb-4c66-b68e-b66e5acec6c0",
|
||||
"group_budget": {
|
||||
"limit_source": "user_override",
|
||||
"spend_limit_micros": 0
|
||||
},
|
||||
"group_spend_micros": 0,
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
],
|
||||
"period_end": "2019-08-24T14:15:22Z",
|
||||
"period_start": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|------------------------------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.GroupMembersAISpend](schemas.md#codersdkgroupmembersaispend) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get workspace quota by user
|
||||
|
||||
### Code samples
|
||||
|
||||
Generated
+67
@@ -1030,6 +1030,22 @@
|
||||
| `last_heartbeat_at` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
|
||||
## codersdk.AIGroupBudget
|
||||
|
||||
```json
|
||||
{
|
||||
"limit_source": "user_override",
|
||||
"spend_limit_micros": 0
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------|--------------------------------------------------------------|----------|--------------|-------------|
|
||||
| `limit_source` | [codersdk.AIBudgetLimitSource](#codersdkaibudgetlimitsource) | false | | |
|
||||
| `spend_limit_micros` | integer | false | | |
|
||||
|
||||
## codersdk.AIProvider
|
||||
|
||||
```json
|
||||
@@ -7685,6 +7701,57 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
| `spend_limit_micros` | integer | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
|
||||
## codersdk.GroupMemberAISpend
|
||||
|
||||
```json
|
||||
{
|
||||
"effective_group_id": "85e2b926-ddfb-4c66-b68e-b66e5acec6c0",
|
||||
"group_budget": {
|
||||
"limit_source": "user_override",
|
||||
"spend_limit_micros": 0
|
||||
},
|
||||
"group_spend_micros": 0,
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------|--------------------------------------------------|----------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `effective_group_id` | string | false | | Effective group ID is the user's effective budget group within the queried group's organization. Null when no effective budget group is visible in this organization, including when the user's budget resolves to a group in another organization. |
|
||||
| `group_budget` | [codersdk.AIGroupBudget](#codersdkaigroupbudget) | false | | Group budget is the budget when the queried group is this user's effective budget source. Null when the user's budget resolves to another group or no budget applies to the user. |
|
||||
| `group_spend_micros` | integer | false | | Group spend micros is the user's spend attributed to the queried group over the current budget period. |
|
||||
| `user_id` | string | false | | |
|
||||
|
||||
## codersdk.GroupMembersAISpend
|
||||
|
||||
```json
|
||||
{
|
||||
"members": [
|
||||
{
|
||||
"effective_group_id": "85e2b926-ddfb-4c66-b68e-b66e5acec6c0",
|
||||
"group_budget": {
|
||||
"limit_source": "user_override",
|
||||
"spend_limit_micros": 0
|
||||
},
|
||||
"group_spend_micros": 0,
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
],
|
||||
"period_end": "2019-08-24T14:15:22Z",
|
||||
"period_start": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------|---------------------------------------------------------------------|----------|--------------|-------------------------------------------------------------------------|
|
||||
| `members` | array of [codersdk.GroupMemberAISpend](#codersdkgroupmemberaispend) | false | | |
|
||||
| `period_end` | string | false | | Period end is the exclusive upper bound of the current budget period. |
|
||||
| `period_start` | string | false | | Period start is the inclusive lower bound of the current budget period. |
|
||||
|
||||
## codersdk.GroupMembersResponse
|
||||
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user