---
title: "Create a skill."
method: POST
path: "/v1/skills"
tags: ["skills"]
---

# Create a skill.

`POST /v1/skills`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/skills`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

Creates an org-scoped skill (ADR-0061) — a versioned markdown authoring skill served to dashboard-building agents over MCP at `dvt://skill/org/{slug}`. `slug` is derived from `name` when omitted and is unique within the org. Creating a `public` (org-wide) skill requires `skill:manage`; a `private`/`shared` skill requires `skill:write`.

## Query parameters

- `reason` string

## Request body

- CreateSkillRequest
  - `name` string, required
  - `slug` string: Optional org-unique slug; derived from `name` when omitted. Lowercase alphanumerics + hyphen only (no `/`, `.`, `%`) — see Skill.slug.
  - `sectionId` string, uuid, nullable: Optional admin-curated grouping (DVT-943 M3) — an id from GET /v1/skill-sections. Must resolve in your organization.
  - `description` string
  - `whenToUse` string
  - `whoFor` string
  - `howToApply` string
  - `body` string
  - `visibility` 'private' | 'shared' | 'public': Creating a `public` (org-wide) skill requires `skill:manage`; `private`/`shared` requires `skill:write` (ADR-0061 §4 — enforced per-value in the handler, not just at the route floor; security review, BFLA).

## Response `201`

Skill created.

- SkillEnvelope
  - `data` Skill, required: An org-scoped authoring skill (ADR-0061): a versioned markdown document — house conventions, metric definitions, warehouse semantics — served to dashboard-building agents over MCP at `dvt://skill/org/{slug}`. Org-scoped and tenant-isolated (ADR-0025). Content is author-written text, never customer warehouse data (ADR-0057).
    - `id` string, uuid, required
    - `folderId` string, uuid, nullable, required: The containing library folder, or null when unfoldered (DVT-1563). Set via PUT /v1/skills/{id}/folder. Only the skill's owner (or an org admin/owner) may move it between folders. Independent of sectionId — folders are a library-navigation grouping, sections are the curated-doc taxonomy (skill_sections); moving between one never touches the other.
    - `ownerId` string, uuid, required: The user who created the skill.
    - `name` string, required
    - `slug` string, required: Org-unique URL segment that drives the MCP resource URI `dvt://skill/org/{slug}`. Derived from `name` at create time when omitted; stable across renames. Constrained to lowercase alphanumerics + hyphen (no `/`, `.`, `%`) so it cannot traverse out of the `org/` MCP namespace and shadow the canonical `dvt://skill/spec-authoring` resource (ADR-0061 §3; security review). Enforced on both caller-supplied and name-derived slugs.
    - `sectionId` string, uuid, nullable: Optional admin-curated grouping (DVT-943 M3) — an id from GET /v1/skill-sections. Omitted/null when the skill is unsectioned. Cleared automatically when its section is deleted.
    - `description` string, required: What this skill is (metadata field 1 of 4).
    - `whenToUse` string, required: When an agent should apply it (metadata field 2 of 4).
    - `whoFor` string, required: Who it's for (metadata field 3 of 4).
    - `howToApply` string, required: How an agent should apply it (metadata field 4 of 4).
    - `body` string, required: The skill body — markdown, served verbatim over MCP. Length-capped to bound the served prompt-injection + unbounded-markdown DoS surface (ADR-0061 §5).
    - `visibility` 'private' | 'shared' | 'public', required: 'public' = org-wide skill (served to every member); 'private'/'shared' = personal (owner + ACL grantees).
    - `version` integer, required: Optimistic-concurrency version. Pass this back on PATCH.
    - `schemaVersion` integer, required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `409`: A skill with this slug already exists in the organization.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
