# Skills

> Manage and access skills via REST.

**URL:** https://inference.sh/docs/api/rest/skills
**Last updated:** 2026-10-30

---

Manage and access skills via REST.

Skills are indexed in the `skills` search collection. Discover public skills via `GET /suggest` or filter with `GET /search?collection=skills`. Browse approved registry listings with `GET /store/skills` — see [Store API](/docs/api/rest/store#list-approved-skills).

List responses use cursor pagination. See [REST overview — response format](/docs/api/rest/overview#response-format).

Write endpoints (create, update, delete, supersede, transfer, visibility, sharing, GitHub import) require the `knowledge:write` scope, as do general knowledge entries (observations, references, preferences). See [Knowledge API](/docs/api/rest/knowledge).

→ [Store API](/docs/api/rest/store) · [Search concept](/docs/concepts/search) · [Search API](/docs/api/rest/search) · [Skill registry](/docs/skills/registry)

---

## List Skills

`GET /skills` or `POST /skills/list`

Returns skills with cursor-based pagination.

### Query Parameters

| Field | Type | Description |
|-------|------|-------------|
| `cursor` | string | Pagination cursor |
| `limit` | integer | Max results (default 50, max 100) |

### Response

| Field | Type | Description |
|-------|------|-------------|
| `items` | array | Skill objects |
| `next_cursor` | string | Cursor for the next page |
| `prev_cursor` | string | Cursor for the previous page |
| `has_next` | boolean | More results available |
| `has_previous` | boolean | Previous page available |
| `items_per_page` | number | Items returned in this page |

Responses may include RFC 8288 `Link` headers (`rel="next"`, `rel="prev"`) with the same cursors.

**Example:**

```json
{
  "items": [
    {
      "id": "skill_abc123",
      "namespace": "myteam",
      "name": "code-review",
      "description": "Guidelines for reviewing pull requests",
      "version_id": "ver_xyz789",
      "version": {
        "id": "ver_xyz789",
        "instructions": { "path": "SKILL.md", "uri": "https://..." },
        "files": [{ "path": "references/checklist.md", "uri": "https://..." }],
        "content_hash": "a3f5c9e2b1d4...",
        "tags": ["development"],
        "license": "MIT"
      },
      "visibility": "public",
      "created_at": "2026-01-15T10:30:00Z"
    }
  ],
  "next_cursor": "abc123",
  "has_next": true,
  "has_previous": false,
  "items_per_page": 50
}
```

The paginated object is returned under `data`. See [REST overview — response format](/docs/api/rest/overview#response-format).

---

## Get Skill

`GET /skills/{id}`

Returns a single skill by ID.

### Version `content_hash`

Each `version` includes `content_hash` — a hash over the SKILL.md instructions and supporting files. Use it to detect when a skill changed without comparing full content.

`GET /knowledge/{namespace}/{name}` and related knowledge ref routes set `ETag` from this hash and support `If-None-Match` (**304 Not Modified**). See [Knowledge API — Content hash and conditional GET](/docs/api/rest/knowledge#content-hash-and-conditional-get).

---

## Get References

`GET /skills/{id}/references`

Returns the same `ReferencesResponse` as [Get References](/docs/api/rest/knowledge#get-references) on knowledge entries — outgoing and incoming content reference edges for the skill. Requires read access to the skill.

```bash
curl https://api.inference.sh/skills/skill_abc123/references \
  -H "Authorization: Bearer inf_your_key"
```

Content reference edges use graph edge type `references` (namespace/name mentions in markdown). See [Get References](/docs/api/rest/knowledge#get-references) for the response shape.

---

## Get Lineage

`GET /skills/{id}/lineage`

Returns the same `SkillLineageResponse` as [Get Lineage](/docs/api/rest/knowledge#get-lineage) on knowledge entries — parents, siblings, forks, duplicates, and `fork_depth`.

```bash
curl https://api.inference.sh/skills/skill_abc123/lineage \
  -H "Authorization: Bearer inf_your_key"
```

---

## Get Skill by Ref

`GET /skills/{namespace}/{name}`

Returns a skill by namespace and name.

```bash
curl https://api.inference.sh/skills/myteam/code-review \
  -H "Authorization: Bearer inf_your_key"
```

---

## Get Skill Content

`GET /skills/{namespace}/{name}/content`

Returns the raw SKILL.md content as `text/markdown`.

```bash
curl https://api.inference.sh/skills/myteam/code-review/content
```

No authentication required for public skills.

---

## Get Skill File

`GET /skills/{namespace}/{name}/files/{path}`

Returns a supporting file (redirects to CDN).

```bash
curl -L https://api.inference.sh/skills/myteam/code-review/files/references/checklist.md
```

---

## Resolve Skill

`GET /skills/resolve?ref={ref}`

Resolves a skill from the registry or GitHub. The API looks up `ref` in your team's store first; if nothing matches, it fetches from GitHub.

| Parameter | Description |
|-----------|-------------|
| `ref` | Skill reference (e.g. `myteam/code-review`, `github.com/org/repo`, or `org/repo` with GitHub fallback) |
| `skill` | Optional skill name within a repo |

### GitHub fallback and team copies

When the skill is fetched from GitHub:

| Caller | Behavior |
|--------|----------|
| Signed in with `knowledge:write` | A copy is stored in **your team's namespace** as `<github-owner>-<skill>` (for example `anthropics` + `frontend-design` → `anthropics-frontend-design`). The prefix is skipped when the skill name already starts with the owner (for example `vercel` + `vercel` → `vercel`). The response points at that team copy (`source: "store"`). Resolving the same GitHub skill again adds a **new version** when upstream content changed; identical content is skipped. |
| Anonymous, guest, or read-only key (`knowledge:read` only) | Returns the fetched content only — **nothing is written** to the store (`source: "github"`). |

Use the team copy's ref (`myteam/anthropics-frontend-design`) for later `GET /skills/{namespace}/{name}/content` calls and agent URLs.

### Response

```json
{
  "source": "store",
  "namespace": "myteam",
  "name": "anthropics-frontend-design",
  "content": "# Frontend Design\n...",
  "files": [{ "path": "references/checklist.md", "uri": "https://..." }]
}
```

| Field | Description |
|-------|-------------|
| `source` | `"store"` when served from the registry (including a team copy just imported from GitHub), `"github"` when content was fetched live and not stored |

### GitHub auto-refresh (legacy registry copies)

When a **legacy system-owned** registry skill is already in the store and linked to GitHub (`repo_url` or version `source_url` contains `github.com`), resolve checks whether the current version is older than **24 hours**. If it is stale:

1. The API returns the **cached** store content immediately (this request is not blocked).
2. A **background re-import** from GitHub runs in the background (concurrent requests for the same skill are deduped).
3. If upstream content changed, a new version is published; identical content is skipped (no duplicate version).
4. The **next** resolve returns the updated content.

**Team-owned copies** (skills you imported or resolved into your workspace) do **not** refresh in the background. Pull upstream changes by calling resolve again (with `knowledge:write`) or re-running [Import from GitHub](#import-from-github).

**CLI:** `belt skill use <ref>` calls this endpoint (including GitHub refs like `github.com/org/repo` or `org/repo:skill-name`). When logged in with a key that can write knowledge, GitHub resolves create or update the team copy described above.

**Note:** `GET /skills/{namespace}/{name}/content` always serves the latest **stored** version but does **not** fetch from GitHub. Use resolve (or `belt skill use`) when you want upstream changes pulled into your team's copy.

---

## Create Skill

`POST /skills`

Requires `knowledge:write` scope. Creates a new skill or new version of an existing skill.

Each version stores **only** the files in the request — supplementary files from an earlier version are not carried forward unless you include them again. The REST API accepts whatever you send; `belt skill upload` and skill uploads via `belt know upload` refuse partial uploads that would drop existing supplementary files unless you pass `--prune`.

Accepts **`application/json`** (web UI) or **`multipart/form-data`** (`belt skill upload`). Multipart fields mirror the JSON body; upload files as `files` with relative paths as filenames (for example `SKILL.md`, `references/checklist.md`).

### Request

```json
{
  "namespace": "belt-sh",
  "name": "code-review",
  "description": "Guidelines for reviewing pull requests",
  "instructions": "# Code Review Guidelines\n\nWhen reviewing...",
  "files": [
    { "path": "references/checklist.md", "content": "## Checklist\n..." }
  ],
  "license": "MIT",
  "allowed_tools": "Read Grep",
  "compatibility": "Claude Code",
  "repo_url": "https://github.com/myteam/skills"
}
```

| Field | Required | Description |
|-------|----------|-------------|
| `namespace` | no | Target namespace (admin accounts only; regular users publish under their workspace username) |
| `name` | yes | Kebab-case, immutable after creation |
| `description` | yes | What the skill does (max [1024 characters](/docs/api/rest/overview#description-length-limits); SKILL.md frontmatter) |
| `instructions` | yes | Markdown content (SKILL.md body) |
| `files` | no | Supporting files with path and content |
| `license` | no | License identifier (MIT, Apache-2.0) |
| `allowed_tools` | no | Space-separated pre-approved tools |
| `compatibility` | no | Environment requirements |
| `repo_url` | no | Source repository URL |

**Multipart form fields:** `namespace`, `name`, `description`, `license`, `allowed_tools`, `compatibility`, `disable_model_invocation`, `user_invocable`, `context`, plus `files` uploads. The CLI reads `namespace` from `SKILL.md` frontmatter when present.

### Response

Returns the created `SkillDTO`.

---

## Update Skill

`POST /skills/{id}`

Creates a new version of an existing skill. Same request body as create.

---

## Delete Skill

`DELETE /skills/{id}`

Soft-deletes a skill. The entry is removed from the skills search collection, so it no longer appears in `/suggest` or other discovery results.

Requires `knowledge:write` scope.

**CLI:** `belt skill delete <id or namespace/name>` — resolves refs, then deletes via the knowledge delete API (same behavior as removing a skill-type entry with `belt know delete`). Destructive: prompts unless `--yes` / `-y`. To point users at a replacement instead of removing the old skill, use [Supersede Skill](#supersede-skill) (`belt skill supersede`).

---

## Supersede Skill

`POST /skills/{id}/supersede`

Mark an older skill as replaced by the skill at `{id}`. Creates a graph edge of type `supersedes` from the replacement skill to the outdated one. The superseded skill is excluded from suggest and other discovery results; the replacement skill is unchanged.

Requires `knowledge:write` scope.

```json
{
  "superseded_id": "skill_old123"
}
```

```bash
curl -X POST https://api.inference.sh/skills/skill_new456/supersede \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"superseded_id": "skill_old123"}'
```

Response:

```json
{"status": "ok"}
```

Use this when you publish a replacement under a new name instead of versioning in place. The CLI equivalent is `belt skill supersede <old> <new>` — see [Skills (CLI)](/docs/extend/cli-setup#skills-cli). Python SDK: `GraphEdgeType.SUPERSEDES` in `inferencesh.types` matches the edge type string.

---

## Import from GitHub

GitHub import endpoints require `knowledge:write` and a signed-in workspace. Each imported skill is stored in **your team's namespace** with name `<github-owner>-<skill>` (same rules as [resolve](#github-fallback-and-team-copies)). Re-importing the same skill adds a version when content changed.

### Query parameter: `keep_namespace`

Append `?keep_namespace=true` to discover, batch import, or streaming import to store skills under the **GitHub owner's namespace** with the upstream skill name (legacy curated layout). **Platform admins** only. Everyone else, including workspace API keys, gets **403** `forbidden` (`keep_namespace is for admins`). GitHub [resolve](#resolve-skill) does not accept this parameter; it always imports into your team namespace unless you use the import endpoints below.

### Discover Skills

`POST /skills/import/github/discover`

Scan a GitHub repository for SKILL.md files.

```json
{
  "url": "https://github.com/anthropics/skills"
}
```

### Response

`namespace` is your team username. Each `skills[].name` is the target name after applying the `<owner>-<skill>` prefix.

```json
{
  "namespace": "myteam",
  "repo_url": "https://github.com/anthropics/skills",
  "skills": [
    {
      "path": "frontend-design",
      "name": "anthropics-frontend-design",
      "description": "Guidelines for frontend design",
      "file_count": 3,
      "exists": false
    }
  ]
}
```

### Import Skills

`POST /skills/import/github`

Import discovered skills from a GitHub repository. Requires `knowledge:write` scope.

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `url` | string | yes | GitHub repository URL |
| `paths` | string[] | no | Skill directory paths to import (from discover). Omit to import all discovered skills |

```json
{
  "url": "https://github.com/anthropics/skills",
  "paths": ["frontend-design", "api-docs"]
}
```

**Response:**

```json
{
  "results": [
    {
      "path": "frontend-design",
      "name": "anthropics-frontend-design",
      "namespace": "myteam",
      "skill_id": "skill_abc123",
      "version_id": "ver_xyz789",
      "created": true,
      "index": 1,
      "total": 2
    },
    {
      "path": "api-docs",
      "name": "anthropics-api-docs",
      "namespace": "myteam",
      "error": "SKILL.md not found",
      "index": 2,
      "total": 2
    }
  ]
}
```

| Field | Description |
|-------|-------------|
| `created` | `true` when a new skill entry was created; `false` when the skill already existed (a new version is still added when content changed) |
| `error` | Per-skill failure message (other skills in the batch may still succeed) |
| `index` / `total` | Progress position within the batch |

**Errors:** `403` (`keep_namespace` without admin, or missing team/write scope), `404` (repository or path not found), `429` (GitHub API rate limit), `400` (invalid request).

### Import Skills (streaming)

`POST /skills/import/github/stream`

Same request body and scope as the batch import. Instead of waiting for the full batch, the API streams **one JSON object per line** (NDJSON) as each skill finishes importing.

**Response:** `Content-Type: application/x-ndjson`

Each line is a `SkillGitHubImportEntry` with the same fields as a single item in the batch `results` array above.

```
{"path":"code-review","name":"code-review","namespace":"myteam","skill_id":"skill_abc123","version_id":"ver_xyz789","created":true,"index":1,"total":2}
{"path":"api-docs","name":"api-docs","namespace":"myteam","skill_id":"skill_def456","version_id":"ver_ghi789","created":true,"index":2,"total":2}
```

Use the streaming endpoint when importing many skills from CI pipelines or when building a UI with per-skill progress bars. For small batches, the non-streaming import is simpler.

**Example (process each line as it arrives):**

```bash
curl -N -X POST https://api.inference.sh/skills/import/github/stream \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com/myteam/skills-repo"}' \
  | while IFS= read -r line; do echo "$line" | jq .; done
```

Imported skills keep their GitHub `repo_url`. To pull upstream changes into **your team's copy**, call [resolve](#resolve-skill) again or re-run import — team copies are not updated by the legacy 24-hour background refresh. Only legacy system-owned registry skills use that background re-import.

---

## Visibility

`POST /skills/{id}/visibility`

```json
{
  "visibility": "public"
}
```

| Value | Description |
|-------|-------------|
| `private` | Only the creator |
| `team` | Every member of your workspace |
| `org` | Every workspace in your organization |
| `unlisted` | Anyone with the link; not listed outside your workspace |
| `public` | Anyone; listed in the skill registry |

### Sharing

A skill is a knowledge entry, so it is shared through the knowledge routes with the skill's id: `GET /knowledge/{id}/shares`, `POST /knowledge/{id}/share`, `DELETE /knowledge/{id}/share/{userId}`, `DELETE /knowledge/{id}/team-share`, `DELETE /knowledge/{id}/org-share`. A `read` share shows the skill and opens its content and files, including loading it into an agent. `use` is the same as `read`; `write` also allows editing it and managing its shares. → [Visibility and sharing](/docs/api/rest/overview#visibility-and-sharing)

---

## JavaScript SDK

```typescript
import { inference } from '@inferencesh/sdk';

const client = inference({ apiKey: 'inf_your_key' });

const { items } = await client.skills.list({ limit: 50 });
const skill = await client.skills.getByName('myteam', 'code-review');
const content = await client.skills.getContent('myteam', 'code-review');

await client.skills.create({
  name: 'my-skill',
  description: 'My custom skill',
  instructions: '# Instructions\n\nDo the thing.',
});

const { items: storeSkills } = await client.skills.listStore({ limit: 20 });
```

→ [JavaScript SDK](/docs/api/sdk-javascript) · [SDK overview](/docs/api/sdk/overview)

---

## cURL Examples

```bash
# List public skills
curl https://api.inference.sh/skills

# Get skill content
curl https://api.inference.sh/skills/myteam/code-review/content

# Create a skill
curl -X POST https://api.inference.sh/skills \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-skill",
    "description": "My custom skill",
    "instructions": "# Instructions\n\nDo the thing."
  }'

# Import from GitHub (batch)
curl -X POST https://api.inference.sh/skills/import/github \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com/myteam/skills-repo"}'

# Import from GitHub (streaming NDJSON)
curl -N -X POST https://api.inference.sh/skills/import/github/stream \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com/myteam/skills-repo", "paths": ["code-review"]}'
```
