## Summary When a workspace build fails because the user is over their group quota, the chat tools currently surface the failure as a bare `"workspace build failed: insufficient quota"` string with no machine-readable error code and no visibility into the user's current usage. Agents and the UI cannot distinguish quota failures from any other Terraform error, so users see an opaque message and have no clear path to recovery. This PR tags quota failures with a typed error code at the source and propagates it through the chat tool layer so callers can react to it explicitly. Relates to CODAGT-20 ## Changes **Provisioner runner** - Add `InsufficientQuotaErrorCode = "INSUFFICIENT_QUOTA"` and set it explicitly at the `commitQuota` failure site via a new `failedWorkspaceBuildfCode` helper, so `provisioner_jobs.error_code` is populated only on the genuine quota path. The substring matcher used for externally produced sentinels (e.g. `"missing parameter"`, `"required template variables"`) is intentionally not extended; provider errors that happen to mention "insufficient quota" stay classified as generic build failures. **SDK and API contract** - Add `JobErrorCodeInsufficientQuota` and a `JobIsInsufficientQuotaErrorCode` helper to `codersdk`. - Extend the swagger `enums` tag on `ProvisionerJob.ErrorCode` to include `INSUFFICIENT_QUOTA`. - Regenerate `coderd/apidoc`, `docs/reference/api/*`, and `site/src/api/typesGenerated.ts`. **chattool create_workspace / start_workspace** - `waitForBuild` now returns a typed `*workspaceBuildError` carrying both the message and the `JobErrorCode`, instead of a bare error string. - New `quotaerror.go` introduces a structured `quotaErrorResult` (with `error_code`, `title`, `message`, `build_id`, and optional `quota`) and a best-effort `workspaceQuotaDetails` lookup that wraps owner authorization internally and fetches `credits_consumed` and `budget` from the database. Quota lookup failures (including authorization failures) never block the failure payload. - On quota-coded build failures, both `create_workspace` and `start_workspace` now return the structured response (with the recovery guidance inlined into `message`) instead of the bare `"insufficient quota"` string. This applies to all three failure paths: post-creation, an in-progress existing build, and a freshly triggered start build. Non-quota build failures continue to use the existing `buildToolResponse` / `newBuildError` path. - Owner authorization is wrapped only on the call sites that need it (the `CreateFn` and `StartFn` invocations and the quota-detail lookup), so idempotent fast paths (already running, already in progress, existing-workspace early returns) do not pay for an extra RBAC round-trip or fail when role lookup is transient. ## Out of scope - No changes to quota math, allowances, or bypass behavior. - No automatic retries. - No new quota-inspection tools and no changes to MCP `coder_create_workspace` (which returns immediately and never observed the build outcome here). - No frontend UI changes; those will land in a follow-up PR that consumes the new `INSUFFICIENT_QUOTA` code.
20 KiB
Generated
Organizations
Get organizations
Code samples
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
GET /api/v2/organizations
Example responses
200 Response
[
{
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
"updated_at": "2019-08-24T14:15:22Z"
}
]
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | OK | array of codersdk.Organization |
Response Schema
Status Code 200
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
[array item] |
array | false | ||
» created_at |
string(date-time) | true | ||
» description |
string | false | ||
» display_name |
string | false | ||
» icon |
string | false | ||
» id |
string(uuid) | true | ||
» is_default |
boolean | true | ||
» name |
string | false | ||
» updated_at |
string(date-time) | true |
To perform this operation, you must be authenticated. Learn more.
Create organization
Code samples
# Example request using curl
curl -X POST http://coder-server:8080/api/v2/organizations \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
POST /api/v2/organizations
Body parameter
{
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string"
}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
body |
body | codersdk.CreateOrganizationRequest | true | Create organization request |
Example responses
201 Response
{
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
"updated_at": "2019-08-24T14:15:22Z"
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 201 | Created | Created | codersdk.Organization |
To perform this operation, you must be authenticated. Learn more.
Get organization by ID
Code samples
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
GET /api/v2/organizations/{organization}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization |
path | string(uuid) | true | Organization ID |
Example responses
200 Response
{
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
"updated_at": "2019-08-24T14:15:22Z"
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | OK | codersdk.Organization |
To perform this operation, you must be authenticated. Learn more.
Delete organization
Code samples
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/organizations/{organization} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
DELETE /api/v2/organizations/{organization}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization |
path | string | true | Organization ID or name |
Example responses
200 Response
{
"detail": "string",
"message": "string",
"validations": [
{
"detail": "string",
"field": "string"
}
]
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | OK | codersdk.Response |
To perform this operation, you must be authenticated. Learn more.
Update organization
Code samples
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
PATCH /api/v2/organizations/{organization}
Body parameter
{
"description": "string",
"display_name": "string",
"icon": "string",
"name": "string"
}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization |
path | string | true | Organization ID or name |
body |
body | codersdk.UpdateOrganizationRequest | true | Patch organization request |
Example responses
200 Response
{
"created_at": "2019-08-24T14:15:22Z",
"description": "string",
"display_name": "string",
"icon": "string",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"is_default": true,
"name": "string",
"updated_at": "2019-08-24T14:15:22Z"
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | OK | codersdk.Organization |
To perform this operation, you must be authenticated. Learn more.
Get provisioner jobs
Code samples
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisionerjobs \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
GET /api/v2/organizations/{organization}/provisionerjobs
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization |
path | string(uuid) | true | Organization ID |
limit |
query | integer | false | Page limit |
ids |
query | array(uuid) | false | Filter results by job IDs |
status |
query | string | false | Filter results by status |
tags |
query | object | false | Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'}) |
initiator |
query | string(uuid) | false | Filter results by initiator |
Enumerated Values
| Parameter | Value(s) |
|---|---|
status |
canceled, canceling, failed, pending, running, succeeded, unknown |
Example responses
200 Response
[
{
"available_workers": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
],
"canceled_at": "2019-08-24T14:15:22Z",
"completed_at": "2019-08-24T14:15:22Z",
"created_at": "2019-08-24T14:15:22Z",
"error": "string",
"error_code": "REQUIRED_TEMPLATE_VARIABLES",
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
"input": {
"error": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"workspace_build_id": "badaf2eb-96c5-4050-9f1d-db2d39ca5478"
},
"logs_overflowed": true,
"metadata": {
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"template_version_name": "string",
"workspace_build_transition": "start",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
"workspace_name": "string"
},
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"queue_position": 0,
"queue_size": 0,
"started_at": "2019-08-24T14:15:22Z",
"status": "pending",
"tags": {
"property1": "string",
"property2": "string"
},
"type": "template_version_import",
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b",
"worker_name": "string"
}
]
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | OK | array of codersdk.ProvisionerJob |
Response Schema
Status Code 200
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
[array item] |
array | false | ||
» available_workers |
array | false | ||
» canceled_at |
string(date-time) | false | ||
» completed_at |
string(date-time) | false | ||
» created_at |
string(date-time) | false | ||
» error |
string | false | ||
» error_code |
codersdk.JobErrorCode | false | ||
» file_id |
string(uuid) | false | ||
» id |
string(uuid) | false | ||
» initiator_id |
string(uuid) | false | ||
» input |
codersdk.ProvisionerJobInput | false | ||
»» error |
string | false | ||
»» template_version_id |
string(uuid) | false | ||
»» workspace_build_id |
string(uuid) | false | ||
» logs_overflowed |
boolean | false | ||
» metadata |
codersdk.ProvisionerJobMetadata | false | ||
»» template_display_name |
string | false | ||
»» template_icon |
string | false | ||
»» template_id |
string(uuid) | false | ||
»» template_name |
string | false | ||
»» template_version_name |
string | false | ||
»» workspace_build_transition |
codersdk.WorkspaceTransition | false | ||
»» workspace_id |
string(uuid) | false | ||
»» workspace_name |
string | false | ||
» organization_id |
string(uuid) | false | ||
» queue_position |
integer | false | ||
» queue_size |
integer | false | ||
» started_at |
string(date-time) | false | ||
» status |
codersdk.ProvisionerJobStatus | false | ||
» tags |
object | false | ||
»» [any property] |
string | false | ||
» type |
codersdk.ProvisionerJobType | false | ||
» worker_id |
string(uuid) | false | ||
» worker_name |
string | false |
Enumerated Values
| Property | Value(s) |
|---|---|
error_code |
INSUFFICIENT_QUOTA, REQUIRED_TEMPLATE_VARIABLES |
workspace_build_transition |
delete, start, stop |
status |
canceled, canceling, failed, pending, running, succeeded |
type |
template_version_dry_run, template_version_import, workspace_build |
To perform this operation, you must be authenticated. Learn more.
Get provisioner job
Code samples
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisionerjobs/{job} \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
GET /api/v2/organizations/{organization}/provisionerjobs/{job}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
organization |
path | string(uuid) | true | Organization ID |
job |
path | string(uuid) | true | Job ID |
Example responses
200 Response
{
"available_workers": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
],
"canceled_at": "2019-08-24T14:15:22Z",
"completed_at": "2019-08-24T14:15:22Z",
"created_at": "2019-08-24T14:15:22Z",
"error": "string",
"error_code": "REQUIRED_TEMPLATE_VARIABLES",
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
"input": {
"error": "string",
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
"workspace_build_id": "badaf2eb-96c5-4050-9f1d-db2d39ca5478"
},
"logs_overflowed": true,
"metadata": {
"template_display_name": "string",
"template_icon": "string",
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
"template_name": "string",
"template_version_name": "string",
"workspace_build_transition": "start",
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
"workspace_name": "string"
},
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"queue_position": 0,
"queue_size": 0,
"started_at": "2019-08-24T14:15:22Z",
"status": "pending",
"tags": {
"property1": "string",
"property2": "string"
},
"type": "template_version_import",
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b",
"worker_name": "string"
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | OK | codersdk.ProvisionerJob |
To perform this operation, you must be authenticated. Learn more.