feat: add CSV export for AI spend data (#27491)

## Description

Adds `GET /api/v2/organizations/{organization}/ai/spend/export`,
returning `text/csv` with per-user, per-group, per-model, per-provider
aggregated AI spend. The data is built from the raw AI Gateway token
usage tables rather than the `ai_user_daily_spend` rollup, but stays
consistent with it: spend is attributed through the token usage's
effective group and bucketed by the token usage `created_at`, the same
values the daily rollup derives from.

The period defaults to the current UTC month, narrowed to the configured
AI Gateway retention window when the month begins before retained data
does. Explicit `period_start`/`period_end` params must be provided
together, are interpreted as UTC, and may span at most 31 days. Unlike
the default period, an explicit period that begins before the retention
window is rejected rather than narrowed. Every row echoes the applied
bounds, so a narrowed window is visible in the export.

The endpoint requires organization-level admin permissions.

## Changes

- Add the `ExportOrganizationAISpend` query aggregating
`aibridge_token_usages` joined to `aibridge_interceptions`, scoped to
the organization via the effective group, resolving the username, group
name, and organization name alongside their IDs.
- Add the `exportOrganizationAISpend` handler and route, gated by the
`aigateway-cost-control` experiment and the `AIBridge` feature,
returning the CSV in a single response.
- Add the `ExportOrganizationAISpend` codersdk client method.
- Require organization-wide `ResourceGroupMember` read, since the export
aggregates every user in the organization. The per-row filter stays in
`dbauthz` as defence in depth.
- Escape leading formula characters in the free-text columns, so a model
or provider name recorded from an intercepted request cannot be
evaluated when the CSV is opened in a spreadsheet.
- Add an index on `aibridge_token_usages (effective_group_id,
created_at)`, which the period and group predicates otherwise cannot
use.

Closes
https://linear.app/codercom/issue/AIGOV-293/add-csv-export-for-ai-spend-data

> [!NOTE]
> Generated by Coder Agents on behalf of @ssncferreira
This commit is contained in:
Susana Ferreira
2026-07-28 10:58:38 +01:00
committed by GitHub
parent 16cadcf2c8
commit c3895ff9c0
19 changed files with 1558 additions and 2 deletions
+33
View File
@@ -1715,6 +1715,39 @@ curl -X DELETE http://coder-server:8080/api/v2/oauth2-provider/apps/{app}/secret
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Export organization AI spend as CSV
### Code samples
```sh
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/ai/spend/export \
-H 'Coder-Session-Token: API_KEY'
```
`GET /api/v2/organizations/{organization}/ai/spend/export`
Returns per-user, per-group, per-model, per-provider aggregated AI spend for the organization as CSV, built from raw AI Gateway token usage.
The optional period_start and period_end query parameters bound the period and are interpreted as UTC. They must be provided together and span at most 31 days. When both are omitted, the current UTC monthly period is used.
An explicit period_start must fall within the configured AI Gateway data retention window, since older token usage is purged. The default period is narrowed to that window instead, and every row echoes the applied bounds.
Requires organization-level administrator permissions.
### Parameters
| Name | In | Type | Required | Description |
|----------------|-------|-------------------|----------|---------------------------------|
| `organization` | path | string(uuid) | true | Organization ID |
| `period_start` | query | string(date-time) | false | Inclusive lower bound (RFC3339) |
| `period_end` | query | string(date-time) | false | Exclusive upper bound (RFC3339) |
### Responses
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|--------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get groups by organization
### Code samples