feat: add GET /organizations/{org}/groups/ai/spend (#27123)

## Description

Adds `GET /api/v2/organizations/{org}/groups/ai/spend?group_ids=...` to return per-group AI spend and configured limits for a set of groups in an organization.

In the UI, this endpoint is used alongside the existing `/api/v2/organizations/{org}/groups` endpoint. AI spend data is kept separate from that endpoint so that:

- Different concepts stay on different endpoints: identity (groups) 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/organizations/{org}/groups` → returns the organization's groups.
2. Request `/api/v2/organizations/{org}/groups/ai/spend?group_ids=...` with the IDs from step 1.

The groups endpoint from 1) is currently not paginated, but if pagination is added later, this design keeps the two responses in sync. This spend endpoint intentionally takes `group_ids` rather than paginating on its own, since it depends on the group set from step 1. Pagination could be added in the future, especially for Cost Control-focused pages.

<img width="2880" height="1460" alt="image" src="https://github.com/user-attachments/assets/ea83b74d-6a4f-45a6-af2f-1024e019da07" />

## Changes

- Add `codersdk.OrganizationGroupsAISpend` and `OrganizationGroupAISpend` types, plus a shared `AISpendPeriodWindow` embedded in the spend response.
- Add `GetOrganizationGroupsAISpend` SQL query with a dbauthz per-row filter that mirrors `GET /organizations/{org}/groups`.
- Add handler and route under `/organizations/{organization}/groups/ai/spend` with a required `group_ids` query param (cap 100). Callers with more than 100 groups 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-466/backend-organization-groups-endpoint-with-groups-spend

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
This commit is contained in:
Susana Ferreira
2026-07-20 12:54:52 +01:00
committed by GitHub
parent b511a68ab0
commit 2adc8f5272
19 changed files with 1216 additions and 23 deletions
+50
View File
@@ -1839,6 +1839,56 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/groups
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get organization groups AI spend
### Code samples
```sh
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/groups/ai/spend?group_ids=string \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /api/v2/organizations/{organization}/groups/ai/spend`
Returns AI spend limits and aggregate spend for the requested groups.
A maximum of 100 group IDs may be requested per call, and requests with more are rejected, so callers are expected to batch across multiple requests.
Unknown or unreadable group IDs are silently omitted.
### Parameters
| Name | In | Type | Required | Description |
|----------------|-------|--------------|----------|-------------------------------------------------|
| `organization` | path | string(uuid) | true | Organization ID |
| `group_ids` | query | string | true | Comma-separated list of group IDs (maximum 100) |
### Example responses
> 200 Response
```json
{
"groups": [
{
"current_spend_micros": 0,
"group_id": "306db4e0-7449-4501-b76f-075576fe2d8f",
"spend_limit_micros": 0
}
],
"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.OrganizationGroupsAISpend](schemas.md#codersdkorganizationgroupsaispend) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get group by organization and group name
### Code samples
+42
View File
@@ -9142,6 +9142,48 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
| `name` | string | false | | |
| `updated_at` | string | true | | |
## codersdk.OrganizationGroupAISpend
```json
{
"current_spend_micros": 0,
"group_id": "306db4e0-7449-4501-b76f-075576fe2d8f",
"spend_limit_micros": 0
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|------------------------|---------|----------|--------------|------------------------------------------------------------------------------------------------------------|
| `current_spend_micros` | integer | false | | Current spend micros is the group's spend over the current budget period. |
| `group_id` | string | false | | |
| `spend_limit_micros` | integer | false | | Spend limit micros is the group's configured AI spend limit. Null when the group has no configured budget. |
## codersdk.OrganizationGroupsAISpend
```json
{
"groups": [
{
"current_spend_micros": 0,
"group_id": "306db4e0-7449-4501-b76f-075576fe2d8f",
"spend_limit_micros": 0
}
],
"period_end": "2019-08-24T14:15:22Z",
"period_start": "2019-08-24T14:15:22Z"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|----------------|---------------------------------------------------------------------------------|----------|--------------|-------------------------------------------------------------------------|
| `groups` | array of [codersdk.OrganizationGroupAISpend](#codersdkorganizationgroupaispend) | 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.OrganizationMember
```json