feat: display the AI add-on column in the UI on the Users and Organization Members tables (#23291)

## Summary

Adds an entitlement-gated **AI add-on** column to both the **Users**
table and the **Organization Members** table. When
`ai_governance_user_limit` is entitled, each row shows whether the user
is consuming an AI seat.

## Background

The AI governance add-on tracks which users are consuming AI seats.
Admins need visibility into per-user seat consumption directly from the
user management tables. This change surfaces that information through
both the site-wide Users table and the per-organization Members table,
gated behind the `ai_governance_user_limit` entitlement so the column
only appears when the feature is licensed.

## Implementation

### Backend
- **New SQL query** `GetUserAISeatStates`
(`coderd/database/queries/aiseatstate.sql`) — returns user IDs consuming
an AI seat, derived from:
  - Users with entries in `aibridge_interceptions` (AI Bridge usage)
- Users who own workspaces with `has_ai_task = true` builds (AI Tasks
usage)
- **SDK types** — added `has_ai_seat: boolean` to `codersdk.User` and
`codersdk.OrganizationMemberWithUserData`
- **Handler wiring** — both the Users list endpoint (`coderd/users.go`)
and all Members endpoints (`coderd/members.go`) query AI seat state per
page of user IDs and populate the response field
- **dbauthz** — per-user `ActionRead` checks on `ResourceUserObject`

### Frontend
- **Shared `AISeatCell` component**
(`site/src/modules/users/AISeatCell.tsx`) — green `CircleCheck` for
consuming, gray `X` for non-consuming
- **`TableColumnHelpTooltip`** — extended with `ai_addon` variant with
tooltip: *"Users with access to AI features like AI Bridge, Boundary, or
Tasks who are actively consuming a seat."*
- **Column visibility** gated behind
`useFeatureVisibility().ai_governance_user_limit`

## Validation

- Backend: dbauthz full method suite (`TestMethodTestSuite`) passes
including new `GetUserAISeatStates` test
- Backend: `TestGetUsers`, `TestUsersFilter`, CLI golden file tests pass
- Frontend: 7/7 tests pass across `UsersPage.test.tsx` and
`OrganizationMembersPage.test.tsx` (column visibility gating both
directions)
- `go build ./coderd/...` compiles clean
- `pnpm --dir site run lint:types` passes
- `make gen` clean

## Risks

- **Pagination performance**: The AI seat query is scoped to the current
page's user IDs (not a full table scan), keeping it efficient for
paginated views.
- **Semantic scope**: The workspace-side AI seat derivation uses "any
build with `has_ai_task = true`" rather than "latest build only". If the
product intent is latest-build-only, this can be tightened in a
follow-up.

---

_Generated with `mux` • Model: `anthropic:claude-opus-4-6` • Thinking:
`xhigh` • Cost: `$27.25`_

<!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=xhigh
costs=27.25 -->
This commit is contained in:
Jaayden Halko
2026-03-26 10:36:40 +00:00
committed by GitHub
parent 09d2588e2a
commit 3fb7c6264f
38 changed files with 707 additions and 161 deletions
+21
View File
@@ -150,3 +150,24 @@ entitlement limits.
<small>Agent Workspace Build usage showing current consumption against
entitlement limits in the Licenses page.</small>
## Identifying AI seat consumers
When the AI Governance add-on is licensed, the **Users** table and
**Organization Members** table display an **AI add-on** column that shows
whether each user is consuming an AI seat:
- A green check icon indicates the user is actively consuming an AI seat.
- A gray X icon indicates the user is not consuming an AI seat.
A user consumes an AI seat when they use AI features such as AI Bridge or
Tasks. The column helps administrators identify which users contribute to
the organization's AI seat count, making it easier to manage seat
allocations and stay within license limits.
The **AI add-on** column only appears when the deployment has an active
`ai_governance_user_limit` entitlement. If the entitlement is not present
or the license has expired, the column is hidden.
> **Tip:** Hover over the **AI add-on** column header for a tooltip
> describing what the column represents.
+1
View File
@@ -66,6 +66,7 @@ curl -X GET http://coder-server:8080/api/v2/audit?limit=0 \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
+4
View File
@@ -262,6 +262,7 @@ curl -X GET http://coder-server:8080/api/v2/connectionlog?limit=0 \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -3355,6 +3356,7 @@ curl -X PUT http://coder-server:8080/api/v2/scim/v2/Users/{id} \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -3446,6 +3448,7 @@ curl -X PATCH http://coder-server:8080/api/v2/scim/v2/Users/{id} \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -3842,6 +3845,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/acl \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
+51 -46
View File
@@ -36,6 +36,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
"organization_id": "string"
}
],
"has_ai_seat": true,
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
"login_type": "",
@@ -68,28 +69,29 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|------------------------|------------------------------------------------------|----------|--------------|-------------|
| `[array item]` | array | false | | |
| `» avatar_url` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» email` | string | false | | |
| `» global_roles` | array | false | | |
| `»» display_name` | string | false | | |
| `»» name` | string | false | | |
| `»» organization_id` | string | false | | |
| `» is_service_account` | boolean | false | | |
| `» last_seen_at` | string(date-time) | false | | |
| `» login_type` | [codersdk.LoginType](schemas.md#codersdklogintype) | false | | |
| `» name` | string | false | | |
| `» organization_id` | string(uuid) | false | | |
| `» roles` | array | false | | |
| `» status` | [codersdk.UserStatus](schemas.md#codersdkuserstatus) | false | | |
| `» updated_at` | string(date-time) | false | | |
| `» user_created_at` | string(date-time) | false | | |
| `» user_id` | string(uuid) | false | | |
| `» user_updated_at` | string(date-time) | false | | |
| `» username` | string | false | | |
| Name | Type | Required | Restrictions | Description |
|------------------------|------------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------------|
| `[array item]` | array | false | | |
| `» avatar_url` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» email` | string | false | | |
| `» global_roles` | array | false | | |
| `»» display_name` | string | false | | |
| `»» name` | string | false | | |
| `»» organization_id` | string | false | | |
| `» has_ai_seat` | boolean | false | | Has ai seat intentionally omits omitempty so the API always includes the field, even when false. |
| `» is_service_account` | boolean | false | | |
| `» last_seen_at` | string(date-time) | false | | |
| `» login_type` | [codersdk.LoginType](schemas.md#codersdklogintype) | false | | |
| `» name` | string | false | | |
| `» organization_id` | string(uuid) | false | | |
| `» roles` | array | false | | |
| `» status` | [codersdk.UserStatus](schemas.md#codersdkuserstatus) | false | | |
| `» updated_at` | string(date-time) | false | | |
| `» user_created_at` | string(date-time) | false | | |
| `» user_id` | string(uuid) | false | | |
| `» user_updated_at` | string(date-time) | false | | |
| `» username` | string | false | | |
#### Enumerated Values
@@ -595,6 +597,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
"organization_id": "string"
}
],
"has_ai_seat": true,
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
"login_type": "",
@@ -802,6 +805,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/paginat
"organization_id": "string"
}
],
"has_ai_seat": true,
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
"login_type": "",
@@ -836,30 +840,31 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/paginat
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|-------------------------|------------------------------------------------------|----------|--------------|-------------|
| `[array item]` | array | false | | |
| `» count` | integer | false | | |
| `» members` | array | false | | |
| `»» avatar_url` | string | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» email` | string | false | | |
| `»» global_roles` | array | false | | |
| `»»» display_name` | string | false | | |
| `»»» name` | string | false | | |
| `»»» organization_id` | string | false | | |
| `»» is_service_account` | boolean | false | | |
| `»» last_seen_at` | string(date-time) | false | | |
| `»» login_type` | [codersdk.LoginType](schemas.md#codersdklogintype) | false | | |
| `»» name` | string | false | | |
| `»» organization_id` | string(uuid) | false | | |
| `»» roles` | array | false | | |
| `»» status` | [codersdk.UserStatus](schemas.md#codersdkuserstatus) | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» user_created_at` | string(date-time) | false | | |
| `»» user_id` | string(uuid) | false | | |
| `»» user_updated_at` | string(date-time) | false | | |
| `»» username` | string | false | | |
| Name | Type | Required | Restrictions | Description |
|-------------------------|------------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------------|
| `[array item]` | array | false | | |
| `» count` | integer | false | | |
| `» members` | array | false | | |
| `»» avatar_url` | string | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» email` | string | false | | |
| `»» global_roles` | array | false | | |
| `»»» display_name` | string | false | | |
| `»»» name` | string | false | | |
| `»»» organization_id` | string | false | | |
| `»» has_ai_seat` | boolean | false | | Has ai seat intentionally omits omitempty so the API always includes the field, even when false. |
| `»» is_service_account` | boolean | false | | |
| `»» last_seen_at` | string(date-time) | false | | |
| `»» login_type` | [codersdk.LoginType](schemas.md#codersdklogintype) | false | | |
| `»» name` | string | false | | |
| `»» organization_id` | string(uuid) | false | | |
| `»» roles` | array | false | | |
| `»» status` | [codersdk.UserStatus](schemas.md#codersdkuserstatus) | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» user_created_at` | string(date-time) | false | | |
| `»» user_id` | string(uuid) | false | | |
| `»» user_updated_at` | string(date-time) | false | | |
| `»» username` | string | false | | |
#### Enumerated Values
+65 -51
View File
@@ -1296,6 +1296,7 @@
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1387,6 +1388,7 @@
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1737,6 +1739,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1813,6 +1816,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1882,6 +1886,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -4524,6 +4529,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -6127,6 +6133,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
"organization_id": "string"
}
],
"has_ai_seat": true,
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
"login_type": "",
@@ -6150,24 +6157,25 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
### Properties
| Name | Type | Required | Restrictions | Description |
|----------------------|-------------------------------------------------|----------|--------------|-------------|
| `avatar_url` | string | false | | |
| `created_at` | string | false | | |
| `email` | string | false | | |
| `global_roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `is_service_account` | boolean | false | | |
| `last_seen_at` | string | false | | |
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | |
| `name` | string | false | | |
| `organization_id` | string | false | | |
| `roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | |
| `updated_at` | string | false | | |
| `user_created_at` | string | false | | |
| `user_id` | string | false | | |
| `user_updated_at` | string | false | | |
| `username` | string | false | | |
| Name | Type | Required | Restrictions | Description |
|----------------------|-------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------------|
| `avatar_url` | string | false | | |
| `created_at` | string | false | | |
| `email` | string | false | | |
| `global_roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `has_ai_seat` | boolean | false | | Has ai seat intentionally omits omitempty so the API always includes the field, even when false. |
| `is_service_account` | boolean | false | | |
| `last_seen_at` | string | false | | |
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | |
| `name` | string | false | | |
| `organization_id` | string | false | | |
| `roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | |
| `updated_at` | string | false | | |
| `user_created_at` | string | false | | |
| `user_id` | string | false | | |
| `user_updated_at` | string | false | | |
| `username` | string | false | | |
#### Enumerated Values
@@ -6431,6 +6439,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
"organization_id": "string"
}
],
"has_ai_seat": true,
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
"login_type": "",
@@ -9070,6 +9079,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -9519,6 +9529,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -9544,23 +9555,24 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
### Properties
| Name | Type | Required | Restrictions | Description |
|----------------------|-------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------|
| `avatar_url` | string | false | | |
| `created_at` | string | true | | |
| `email` | string | true | | |
| `id` | string | true | | |
| `is_service_account` | boolean | false | | |
| `last_seen_at` | string | false | | |
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | |
| `name` | string | false | | |
| `organization_ids` | array of string | false | | |
| `role` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | |
| `roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | |
| `theme_preference` | string | false | | Deprecated: this value should be retrieved from `codersdk.UserPreferenceSettings` instead. |
| `updated_at` | string | false | | |
| `username` | string | true | | |
| Name | Type | Required | Restrictions | Description |
|----------------------|-------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------------|
| `avatar_url` | string | false | | |
| `created_at` | string | true | | |
| `email` | string | true | | |
| `has_ai_seat` | boolean | false | | Has ai seat intentionally omits omitempty so the API always includes the field, even when false. |
| `id` | string | true | | |
| `is_service_account` | boolean | false | | |
| `last_seen_at` | string | false | | |
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | |
| `name` | string | false | | |
| `organization_ids` | array of string | false | | |
| `role` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | |
| `roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | |
| `theme_preference` | string | false | | Deprecated: this value should be retrieved from `codersdk.UserPreferenceSettings` instead. |
| `updated_at` | string | false | | |
| `username` | string | true | | |
#### Enumerated Values
@@ -10415,6 +10427,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -10439,22 +10452,23 @@ If the schedule is empty, the user will be updated to use the default schedule.|
### Properties
| Name | Type | Required | Restrictions | Description |
|----------------------|-------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------|
| `avatar_url` | string | false | | |
| `created_at` | string | true | | |
| `email` | string | true | | |
| `id` | string | true | | |
| `is_service_account` | boolean | false | | |
| `last_seen_at` | string | false | | |
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | |
| `name` | string | false | | |
| `organization_ids` | array of string | false | | |
| `roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | |
| `theme_preference` | string | false | | Deprecated: this value should be retrieved from `codersdk.UserPreferenceSettings` instead. |
| `updated_at` | string | false | | |
| `username` | string | true | | |
| Name | Type | Required | Restrictions | Description |
|----------------------|-------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------------|
| `avatar_url` | string | false | | |
| `created_at` | string | true | | |
| `email` | string | true | | |
| `has_ai_seat` | boolean | false | | Has ai seat intentionally omits omitempty so the API always includes the field, even when false. |
| `id` | string | true | | |
| `is_service_account` | boolean | false | | |
| `last_seen_at` | string | false | | |
| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | |
| `name` | string | false | | |
| `organization_ids` | array of string | false | | |
| `roles` | array of [codersdk.SlimRole](#codersdkslimrole) | false | | |
| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | |
| `theme_preference` | string | false | | Deprecated: this value should be retrieved from `codersdk.UserPreferenceSettings` instead. |
| `updated_at` | string | false | | |
| `username` | string | true | | |
#### Enumerated Values
+8
View File
@@ -34,6 +34,7 @@ curl -X GET http://coder-server:8080/api/v2/users \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -112,6 +113,7 @@ curl -X POST http://coder-server:8080/api/v2/users \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -455,6 +457,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1389,6 +1392,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1447,6 +1451,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/roles \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1517,6 +1522,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1575,6 +1581,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/activate \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",
@@ -1633,6 +1640,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/suspend \
"avatar_url": "http://example.com",
"created_at": "2019-08-24T14:15:22Z",
"email": "user@example.com",
"has_ai_seat": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_service_account": true,
"last_seen_at": "2019-08-24T14:15:22Z",