> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Skills

> Versioned, org-scoped skills attached to agents.

A **skill** is a reusable, versioned instruction module — a `SKILL.md` document — that agents load on demand. Skills are org-scoped and immutable per version: each push creates a new version identified by its content hash.

## The skill object

<ResponseField name="id" type="string">Unique id (e.g. `skl_01H...`).</ResponseField>
<ResponseField name="name" type="string">Unique, human-readable name within the org (e.g. `refund-policy`).</ResponseField>
<ResponseField name="description" type="string | null">Short description of what the skill does.</ResponseField>
<ResponseField name="latest_version" type="integer">The highest version number.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

## The skill version object

<ResponseField name="id" type="string">The skill this version belongs to (`skl_…`).</ResponseField>
<ResponseField name="slug" type="string">The skill's slug.</ResponseField>
<ResponseField name="version" type="integer">Monotonic version number.</ResponseField>
<ResponseField name="sha256" type="string">SHA-256 of the `SKILL.md` content. Verifies integrity.</ResponseField>
<ResponseField name="size_bytes" type="integer">Content size.</ResponseField>
<ResponseField name="created_at" type="string">When this version was pushed.</ResponseField>

## Create a skill

`POST /v1/skills` → `201 Created` for a new slug, `200 OK` when the slug already exists (the push is applied as a new version of it). A skill is never created empty: the create *is* the first push. Both responses are the [skill version object](#the-skill-version-object).

<ParamField body="content" type="string" required>The `SKILL.md` text. Version 1 of a new slug.</ParamField>
<ParamField body="slug" type="string">Unique slug within the org — lowercase letters, digits and dashes. Required unless you send `name`.</ParamField>
<ParamField body="name" type="string">The same thing spelled `name`. Send one of `slug` or `name`.</ParamField>
<ParamField body="description" type="string">Short description. Defaults to `null`.</ParamField>
<ParamField body="sha256" type="string">SHA-256 hex digest of `content`. Optional: the server hashes the bytes itself and refuses a digest that does not match.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/skills \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -H "idempotency-key: $(uuidgen)" \
    -d '{ "slug": "refund-policy", "description": "How to process refunds.", "content": "# Refund policy\n...", "sha256": "9f86d0..." }'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "id": "skl_01H8XZ...",
    "slug": "refund-policy",
    "version": 1,
    "sha256": "9f86d0...",
    "size_bytes": 1024,
    "created_at": "2026-08-20T17:00:00Z"
  }
  ```
</ResponseExample>

## Retrieve & list

```bash theme={"system"}
GET /v1/skills/{id}    # retrieve one
GET /v1/skills         # list (cursor-paginated)
```

See [Pagination](/docs/api/pagination).

## Push a new version

`POST /v1/skills/{id}/versions` uploads a new `SKILL.md`. The body is the document text, optionally with its `sha256`; the server rejects the request with `validation_failed` if the hash does not match the content. Pushing bytes identical to the current version mints nothing and answers `200`.

<ParamField body="content" type="string" required>The full `SKILL.md` text.</ParamField>
<ParamField body="sha256" type="string">SHA-256 hex digest of `content`. Optional: the server hashes the bytes itself and refuses a digest that does not match.</ParamField>
<ParamField body="description" type="string">Replace the skill's description as part of this push.</ParamField>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/skills/skl_01H8XZ.../versions \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "content": "# Refund policy\nConfirm the order first.\n...", "sha256": "b1946ac9..." }'
```

```json Response theme={"system"}
{
  "id": "skl_01H8XZ...",
  "slug": "refund-policy",
  "version": 2,
  "sha256": "b1946ac9...",
  "size_bytes": 2048,
  "created_at": "2026-08-20T18:00:00Z"
}
```

## List versions

`GET /v1/skills/{id}/versions` returns version history, newest first. Cursor-paginated.

```json Response theme={"system"}
{
  "data": [
    { "version": 2, "sha256": "b1946ac9...", "size_bytes": 2048, "created_at": "2026-08-20T18:00:00Z" },
    { "version": 1, "sha256": "9f86d0...", "size_bytes": 1024, "created_at": "2026-08-20T17:00:00Z" }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Retrieve one version

`GET /v1/skills/{id}/versions/{n}` — scope `agents:read`. Returns that immutable version's metadata **with its body**: the [skill version object](#the-skill-version-object) plus a `content` string carrying the raw `SKILL.md`.

This is the second half of progressive disclosure. The list above carries only `version`, `sha256`, `size_bytes`, and `created_at`; the text itself costs this extra call, so a runtime pays for it once when the skill is actually used rather than on every turn.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/skills/refund-policy/versions/2 \
  -H "authorization: Bearer sk_live_..."
```

`{id}` accepts a slug or an id, and `{n}` is the integer version. `not_found` (404) if either does not resolve.

## Delete a skill

`DELETE /v1/skills/{id}` — scope `agents:write`. Removes the skill and its whole version history.

<Warning>
  Agents referencing the slug — pinned (`refund-policy@3`) or floating (`refund-policy`) — fail to resolve it at session start once it is gone. Check what depends on a skill before deleting it.
</Warning>

```json Response theme={"system"}
{ "id": "skl_01H9BC...", "object": "skill", "deleted": true }
```

## Pin a version

An [agent](/docs/api/agents)'s `skills[]` accepts two reference forms:

| Reference                   | Resolution                                                                                                    |
| --------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `refund-policy`             | Floats to the skill's `latest_version`, resolved **at session start**.                                        |
| `refund-policy@3`           | Pinned to the immutable version `3`. Never moves, even after new pushes.                                      |
| `naive/landing-page-copy`   | The [platform catalogue](#the-platform-catalogue), floating to its newest published version at session start. |
| `naive/landing-page-copy@1` | The platform catalogue, pinned.                                                                               |

The `naive/` prefix is the whole difference: a bare slug is always one of **your** skills, a prefixed one is always the platform's, and your `landing-page-copy` and the platform's never collide. Either kind that cannot be resolved fails the agent write with `404 not_found`, `param: "skills"`.

Skill versions are content-addressed and immutable, so pushing a new `SKILL.md` never silently changes an already-pinned agent. Use `GET /v1/skills/{id}/versions` to find the numbers you can pin.

<Note>
  Attach a skill to an agent by slug or id. An unpinned reference (`refund-policy`) resolves to the latest version at session start; a pinned one (`refund-policy@3`) is frozen.
</Note>

## The platform catalogue

The platform publishes a small catalogue of skills — landing-page copy, client onboarding, SEO briefs, clip selection and the like — that any agent can reference as `naive/<slug>`. It is read-only from the API: versions are published with a platform release, and like your own skills they are content-addressed, so a published version never changes.

```http theme={"system"}
GET /v1/skills/catalog                          # list, newest version per slug
GET /v1/skills/catalog/{slug}                   # one slug, its newest version
GET /v1/skills/catalog/{slug}/versions/{n}      # one version, with its content
```

All three accept any authenticated caller — an API key with any scope, or a signed-in session that does not yet belong to an organization — because a catalogue is something to browse before there is anything to attach it to.

### The skill catalogue entry object

<ResponseField name="object" type="string" required>`skill_catalog_entry`</ResponseField>
<ResponseField name="slug" type="string" required>The handle you reference as `naive/<slug>`.</ResponseField>
<ResponseField name="version" type="integer" required>The version described; the number to pin.</ResponseField>
<ResponseField name="name" type="string" required>Display name.</ResponseField>
<ResponseField name="description" type="string" required>One line on what the skill is for.</ResponseField>
<ResponseField name="tags" type="string[]" required>Families the skill belongs to, e.g. `agency`, `seo`, `media`.</ResponseField>
<ResponseField name="content_hash" type="string" required>SHA-256 of `content`, lowercase hex.</ResponseField>
<ResponseField name="published_at" type="string" required>When this version was published.</ResponseField>
<ResponseField name="content" type="string">The raw `SKILL.md`. **Only** on the version read; the list never carries it.</ResponseField>

### List the catalogue

`GET /v1/skills/catalog` — the newest version of every slug, alphabetical, cursor-paginated (the cursor is a slug). `?tag=` narrows to one family and `?search=` is free text over `slug`, `name` and `description`; the two compose.

```bash theme={"system"}
curl -fsSL "https://api.vetta.sh/v1/skills/catalog?tag=seo" \
  -H "Authorization: Bearer $VETTA_API_KEY"
```

```json Response theme={"system"}
{
  "data": [
    {
      "object": "skill_catalog_entry",
      "slug": "seo-content-brief",
      "version": 1,
      "name": "SEO content brief",
      "description": "Intent, target query, outline, entities to cover, internal links — before a word of the draft.",
      "tags": ["media", "seo", "content"],
      "content_hash": "3f1a…",
      "published_at": "2026-09-08T00:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

### Resolve one `naive/<slug>`

`GET /v1/skills/catalog/{slug}` — the **newest** version of that slug, without `content`. This is the route that answers "does `naive/short-video-hooks` exist, and at what version?" — `GET /v1/skills/{ref}` is *your* skills and answers `404` for a `naive/` name, which is not the same claim. A slug nothing published answers `404 not_found`, `param: "slug"`.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/skills/catalog/seo-content-brief \
  -H "Authorization: Bearer $VETTA_API_KEY"
```

### Read a catalogue version

`GET /v1/skills/catalog/{slug}/versions/{n}` — the entry **with** `content`. An unknown slug or version answers `404 not_found`, `param: "slug"`.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/skills/catalog/seo-content-brief/versions/1 \
  -H "Authorization: Bearer $VETTA_API_KEY"
```
