Files
coder/docs/reference/api-key-scopes.md
T
Michael Suchacz b67587c875 feat: add the organization skills data model (#30609)
## Stack

Part of
[CODAGT-1341](https://linear.app/codercom/issue/CODAGT-1341/support-organization-level-skills-in-coder-agents).
This stack adds organization skills to Coder Agents: admins publish a
skill once per organization, share it with groups or members, and chats
in that organization load it next to personal and workspace skills. It
replaces the 21-PR draft stack #30584 to #30604 with five PRs, bottom to
top:

1. **#30609** data model: one migration, queries, RBAC and dbauthz (this
PR)
2. #30610 organization skill API routes and the scope-neutral SDK
3. #30611 sharing ACL routes
4. #30612 settings UI: shared skills table, admin page, member section
5. #30613 chats: chatd loading, `/` menu, docs

## What changes

This PR adds the data model and authorization for organization skills.
Personal skills keep working unchanged: `git diff origin/main --
codersdk site` shows only generated RBAC and API key scope additions
(`apikey_scopes_gen.go`, `rbacresources_gen.go`,
`rbacresourcesGenerated.ts`, the RBAC and scope unions in
`typesGenerated.ts`) and `codersdk.ResourceTypeOrganizationSkill` with
its friendly string. No personal route, SDK type or site component
changes, and `coderd/userskills_test.go` changes only by mechanical
constant renames.

**One migration, `000621_skills`** (please review it closely):

- renames `user_skills` to `skills` with its constraints, index and
triggers, and recreates the cap function and
`delete_deleted_user_resources`, whose PL/pgSQL bodies name the table;
- adds `organization_id` and `project_id` (`ON DELETE CASCADE`),
`enabled` (default true), and `group_acl` / `user_acl` (`jsonb`, default
`{}`), with CHECKs for exactly one owner, ACLs that are JSON objects,
and ACLs that stay empty unless an organization owns the row, plus
partial unique name indexes per owner kind;
- replaces the cap trigger with `enforce_skills_per_owner_limit()`: 100
rows per owner, disabled rows included, locking the owner row `FOR NO
KEY UPDATE` so unrelated foreign key checks are not blocked;
- adds a `BEFORE DELETE` trigger on `organization_members` that removes
a departing member from `user_acl` on that organization's skills, so
rejoining doesn't restore a direct grant;
- adds the `organization_skill` `resource_type` and `api_key_scope` enum
values last; nothing in the migration uses them.

The down migration undoes the three old migrations in reverse order: it
drops organization and project rows and restores `user_skills`, its
names, the cap function and `user_id NOT NULL`.

Authorization: a new RBAC resource `organization_skill` (create, read,
update, delete, share). Owner and org admin get it; site and org
auditors get read; members read only through the skill's ACL, which
applies only to members of its organization. `Skill.RBACObject()`
returns the organization object with both ACLs; project rows stay denied
to everyone. New sqlc queries cover organization insert, get, list
(SQL-filtered through `GetAuthorizedOrganizationSkillMetadata` and a
`regosql` converter), update and delete, with dbauthz wrappers and the
audit table entries. No routes yet.

## Commits

The first commit reproduces the reviewed code from the old stack; each
later commit is a labeled trim or fix.

- `baa3150e3b7` feat: add the organization skills data model.
Reproduction: every non-site file this commit changes equals the
reviewed old `authz` head `1e16b3004c4`, except that the old stack's
migrations 000615, 000616 and 000617 are merged into one migration (then
`000615_skills`, now `000621_skills`) with one fixture
(`testdata/fixtures/000621_skills.up.sql`) and one migration test (now
`testMigration000621Skills`). At that commit, `dump.sql` was
byte-identical to the old head's.
- `1b84e83463a` test(coderd/database): keep only the SQL paths of the
org skill matrix. `TestOrganizationSkillAuthorization` becomes
`TestOrganizationSkillQueries` instead of being deleted, because
coverage showed it is this PR's only run of the
`GetAuthorizedOrganizationSkillMetadata` row scan (80% to 65% without
it) and of the organization update and delete queries (100% to 0%).
Swapping `group_acl` and `user_acl` in the converter makes
`GrantedMember` fail. The removed cases are decided by
`TestRolePermissions`, `TestSkillRBACObjectOrganizationACL` and the
dbauthz method suite.
- `e2931b412e9` test(coderd/database): fold the skill cap cases into
existing coverage. The user case is covered by `TestUserSkillLimit` and
`TestUserSkillLimitConcurrentCreates`; the organization case folds into
`ConcurrentOrganizationInsertsHonorCap`, which now also checks that
disabled rows count and another organization is unaffected (mutation
checked); the project case stays.
- `be62aea9021` test(coderd/rbac): drop
`TestOrganizationSkillReadScope`, covered by `TestExternalScopeNames`
and the generated scope file.
- `0c275ba37de` merges main at `454a2fcc697`, which took 000615 to
000618: it renumbers the migration to 000619 and resolves `dump.sql`,
`check_constraint.go` and the generated API docs. `8f2b4b87ee2` merges
main at `ad85646164e`, which took 000619: it renumbers the migration to
000620.
- `5248d56b64f` merges main at `e7b76872098`, which took 000620
(`000620_audit_chat_project_instructions_resource_type`): it renumbers
the migration, fixture and migration test to `000621_skills` (step
`Migration000621Skills`, version 621), and resolves
`enterprise/audit/table.go` (keeping main's
`AuditableChatProjectInstructions` entries and the `Skill` entry),
`dump.sql` and `models.go` (the `resource_type` enum gets
`chat_project_instructions`, then `organization_skill`) and the
regenerated `schemas.md`. The up and down SQL are unchanged by every
renumbering.

## Size

Production +549/-67, tests +728/-33, generated +881/-314; total
+2158/-414. This is above the 500-line reviewer-friendly limit on
purpose: Mike asked for 4 to 5 PRs instead of the 21 small ones this
stack replaces.

## Replaces

#30584 (rename `user_skills` to `skills`), #30585 (skill owners), #30586
(organization skill authorization).

## Validation

- Migration test: up keeps the existing personal row and its ID; down
restores the `user_skills` names, cap function and `NOT NULL`, and drops
organization rows; soft-deleting a user removes their skills after up,
after down, and after re-up.
- `go test` passed on `be62aea9021`, before the main merges, for
`coderd` (skill tests), `coderd/database`, `dbauthz`, `migrations`,
`gentest`, `coderd/rbac/...`, `coderd/x/chatd`, `coderd/x/skills`,
`enterprise/audit` and `enterprise/coderd`; `make lint/check-scopes`
passed; the four personal skills Vitest files pass unchanged.
- On `5248d56b64f`: build, vet, `TestMigrate`, `TestMigrationChain`, the
`migrations`, `database`, `dbauthz`, `gentest`, `rbac` and
`enterprise/audit` packages pass; `make gen` run twice is clean, and
`make pre-commit` passes.
- Remote UAT round 7 through Coder Agents passed on the whole stack; see
#30613's Validation for scope and findings.

> Xum opened this PR on behalf of @ibetitsmike.
2026-10-13 15:51:31 +00:00

12 KiB
Generated

title, description, icon_path
title description icon_path
API key scopes Reference for the scopes an API key can request, including the built-in, composite, and low-level scopes. ./images/icons/api.svg

A scope limits what an API key can do. A scoped key never exceeds the permissions of the user who created it: Coder checks the scope and the user's roles on every request, so a scope narrows access and never widens it.

Pass --scope once per scope when you create a token:

coder tokens create --scope coder:workspaces.access --scope template:read

A token created without an explicit scope uses coder:all, which grants the full permissions of its owner. To create and revoke tokens, refer to Sessions & API Tokens.

This page lists every canonical scope a token can request and the deprecated names Coder accepts for backward compatibility. Coder rejects any scope name not listed on this page with a 400 response.

Built-in scopes

Built-in scopes cover the two broadest cases: the full permissions of the owner, and connections to workspace applications.

Scope Grants
coder:all All operations.
coder:application_connect Ability to connect to applications.

Composite scopes

A composite scope groups the permissions that one task needs, so you can scope a token to that task without listing each permission. Each scope below grants the permissions in its table.

coder:apikeys.manage_self

Resource Actions
api_key create, delete, read, update

coder:templates.author

Resource Actions
file create, read
template create, delete, read, update, view_insights

coder:templates.build

Resource Actions
file create, read
provisioner_jobs read
template read

coder:workspaces.access

Resource Actions
organization_member read
template read
workspace application_connect, read, ssh

coder:workspaces.create

Resource Actions
organization_member read
template read, use
workspace create, read, start, stop, update

coder:workspaces.delete

Resource Actions
organization_member read
template read, use
workspace delete, read

coder:workspaces.operate

Resource Actions
organization_member read
template read
workspace read, start, stop, update

Low-level scopes

A low-level scope grants one action on one resource, written as resource:action. Combine low-level scopes when no composite scope matches the task.

The resource:* form grants every action on that resource, including actions not listed on this page.

api_key

Scope Description
api_key:* Every action on api_key, including actions not listed on this page.
api_key:create Create an API key.
api_key:delete Delete an API key.
api_key:read Read API key details (secrets are not stored).
api_key:update Update an API key, for example its expiry.

chat_model_config

Scope Description
chat_model_config:read Read chat model configs.
chat_model_config:share Share a chat model config with other users or groups.

file

Scope Description
file:* Every action on file, including actions not listed on this page.
file:create Create a file.
file:read Read files.

inbox_notification

Scope Description
inbox_notification:read Read inbox notifications.
inbox_notification:update Update inbox notifications.

organization

Scope Description
organization:* Every action on organization, including actions not listed on this page.
organization:delete Delete an organization.
organization:read Read organizations.
organization:update Update an organization.

organization_skill

Scope Description
organization_skill:* Every action on organization_skill, including actions not listed on this page.
organization_skill:create Create an organization skill.
organization_skill:delete Delete an organization skill.
organization_skill:read Read organization skill metadata and content.
organization_skill:share Share an organization skill with other users or groups.
organization_skill:update Update an organization skill.

template

Scope Description
template:* Every action on template, including actions not listed on this page.
template:create Create a template.
template:delete Delete a template.
template:read Read template.
template:update Update a template.
template:use Use the template to initially create a workspace, then workspace lifecycle permissions take over.

user

Scope Description
user:* Every action on user, including actions not listed on this page.
user:read Read user data.
user:read_personal Read personal user data like user settings and auth links.
user:update_personal Update personal data.

user_secret

Scope Description
user_secret:* Every action on user_secret, including actions not listed on this page.
user_secret:create Create a user secret.
user_secret:delete Delete a user secret.
user_secret:read Read user secret metadata and value.
user_secret:update Update user secret metadata and value.

user_skill

Scope Description
user_skill:* Every action on user_skill, including actions not listed on this page.
user_skill:create Create a user skill.
user_skill:delete Delete a user skill.
user_skill:read Read user skill metadata and content.
user_skill:update Update user skill metadata and content.

workspace

Scope Description
workspace:* Every action on workspace, including actions not listed on this page.
workspace:application_connect Connect to workspace apps via browser.
workspace:create Create a new workspace.
workspace:delete Delete workspace.
workspace:read Read workspace data to view on the UI.
workspace:ssh SSH into a given workspace.
workspace:start Start a workspace.
workspace:stop Stop a workspace.
workspace:update Edit workspace settings (scheduling, permissions, parameters).

Deprecated scope names

Coder still accepts the following names and stores each one as its canonical equivalent. Use the canonical name.

Deprecated name Canonical name
all coder:all
application_connect coder:application_connect