Skills
A skill is a reusable bundle of instructions in the open Agent Skills format: a SKILL.md file with YAML frontmatter (name, description and optional fields) followed by Markdown instructions, plus optional supporting files under scripts/, references/ and assets/. An agent sees the name and description of every skill bound to it and loads a skill's instructions and files when a task matches.
System skills (scope: "system") ship with the deployment and are read-only. User skills (scope: "user") are the ones you create; only the owner can change them, and the owner can share them through Share a resource. You see the system skills, your own skills and skills shared with you or with one of your groups. Skills are addressed by name.
Every save records a snapshot under the version in the frontmatter's metadata.version (1.0 when absent). Saving again under the same version replaces that snapshot, so raise the version when agents should be able to keep using the earlier content.
Administrator access. Some endpoints on this page require an administrator account; each one says so under Permission.
- GET /api/skills List skills
- POST /api/skills Create a skill
- POST /api/skills/validate Validate a skill
- GET /api/skills/{name} Retrieve a skill
- PUT /api/skills/{name} Update a skill
- DEL /api/skills/{name} Delete a skill
- GET /api/skills/{name}/files/{file_path} Retrieve a skill file
- GET /api/skills/{name}/export Export a skill
- GET /api/skills/{name}/agents List agents that use a skill
- GET /api/skills/{name}/versions List skill versions
- GET /api/skills/{name}/versions/{version} Retrieve a skill version
- POST /api/skills/export-plugin Export skills as a plugin
- POST /api/skills/import-plugin Import skills from a plugin
- POST /api/skills/sync Synchronise system skills
Paths are relative to /agent-hub-api
The skill object
A skill as the caller sees it. Lists return the summary fields; single-skill responses add body, frontmatter and files.
Attributes
-
namestringUnique name of the skill for the caller: lowercase letters, digits and single hyphens, at most 64 characters.
-
descriptionstringWhat the skill does and when to use it. Agents decide whether to load a skill from this text.
-
versionstringCurrent version, taken from the frontmatter's
metadata.version, or1.0. -
scopestringsystemfor skills that ship with the deployment,userfor skills created by users.Possible values-
system -
user
-
-
sourcestringdiskfor system skills read from the deployment's skill directory,dbfor stored skills.Possible values-
disk -
db
-
-
owner_user_idnullable integerId of the user who owns the skill, or
nullfor system skills. -
editablebooleanWhether the caller can update or delete the skill, which is true only for their own user skills.
-
allowed_toolsarray of stringsTools the skill expects to use, from the frontmatter's
allowed-tools. -
unresolved_toolsarray of stringsEntries of
allowed_toolsthat did not match an active catalogue tool when the skill was saved. -
allow_implicit_invocationbooleanWhether an agent may load the skill on its own. When
false, only a user can start it explicitly. -
argument_hintnullable stringHint shown to users for the arguments the skill takes, from the frontmatter's
argument-hint. -
licensenullable stringLicence from the frontmatter.
-
compatibilitynullable stringCompatibility note from the frontmatter.
-
metadataobjectThe frontmatter's
metadatamap, with every value as a string. -
file_countintegerNumber of bundled files besides
SKILL.md. -
body_charsintegerLength of the instructions after the frontmatter, in characters.
-
idnullable stringId of the stored skill (a UUID), or
nullfor system skills read from the deployment's skill directory. -
interfacenullable objectPresentation settings from an optional
agents/openai.yamlfile in the bundle. -
dependenciesnullable objectTool dependencies declared in an optional
agents/openai.yamlfile in the bundle. -
updated_atnullable stringWhen the skill was last saved (UTC, ISO 8601), or
nullfor system skills read from the deployment's skill directory. -
is_activebooleanAlways
truefor skills you can see. -
boundbooleanOnly in List skills when
agent_idis given: whether that agent binds the skill. -
bodystringThe Markdown instructions after the frontmatter. Not included in lists.
-
frontmatterobjectThe complete frontmatter as written, including keys this deployment does not use. Not included in lists.
-
filesarray of objectsEvery file in the bundle,
SKILL.mdincluded. Not included in lists.Show child attributes Hide child attributes
-
pathstringPath relative to the skill root, for example
references/style-guide.md. -
sizeintegerSize in bytes.
-
is_binarybooleanWhether the file is binary rather than UTF-8 text.
-
kindstringskill_mdforSKILL.md, the name of the conventional top-level folder the file is in, orother.Possible values-
skill_md -
scripts -
references -
assets -
agents -
evals -
other
-
-
-
warningsarray of objectsOnly after a create or update, and only when there are any: problems that did not block saving, each with a
code,pathandmessage. -
agentsarray of objectsOnly in Retrieve a skill: your agents and the system agents that bind the skill, each with
id,nameandis_user_created.
{
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.1",
"scope": "user",
"source": "db",
"owner_user_id": 42,
"allowed_tools": [
"mcp__ticketing__search_tickets"
],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": null,
"compatibility": null,
"metadata": {
"version": "1.1"
},
"file_count": 1,
"body_chars": 234,
"id": "b7e3d2a1-5c4f-4e8b-a9d6-1f2e3c4b5a69",
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": "2026-09-03T08:12:44.118262",
"is_active": true,
"editable": true,
"body": "# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n",
"frontmatter": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"metadata": {
"version": "1.1"
},
"allowed-tools": "mcp__ticketing__search_tickets"
},
"files": [
{
"path": "SKILL.md",
"size": 458,
"is_binary": false,
"kind": "skill_md"
},
{
"path": "references/style-guide.md",
"size": 92,
"is_binary": false,
"kind": "references"
}
]
} List skills
GET /agent-hub-api/api/skills
Returns the skills visible to the caller, sorted by name.
Returns the system skills, your own skills and skills shared with you or with one of your groups. List entries carry the summary fields of the skill object, without body, frontmatter or files.
- Authentication
- Bearer token How it works
Query parameters
-
scopestringsystemfor system skills only,userfor user skills only (your own and those shared with you). Any other value returns both. -
qstringCase-insensitive text to look for in each skill's name or description.
-
agent_idstringId of an agent you can see. Each returned skill then carries
bound, which says whether that agent binds it.
Returns
Returns skills, a list of skill objects with summary fields only, and total, the number of skills returned.
curl "$VDF_BASE_URL/agent-hub-api/api/skills?q=notes" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills?q=notes`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
params={
"q": "notes",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"skills": [
{
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.1",
"scope": "user",
"source": "db",
"owner_user_id": 42,
"allowed_tools": [
"mcp__ticketing__search_tickets"
],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": null,
"compatibility": null,
"metadata": {
"version": "1.1"
},
"file_count": 1,
"body_chars": 234,
"id": "b7e3d2a1-5c4f-4e8b-a9d6-1f2e3c4b5a69",
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": "2026-09-03T08:12:44.118262",
"is_active": true,
"editable": true
},
{
"name": "plain-language-writing",
"description": "Rewrite specialist text so a non-expert can act on it, without dropping what is legally required. Use when the user asks for plain language, a user-facing notice, or says something is too technical or too legal.",
"version": "1.0",
"scope": "system",
"source": "disk",
"owner_user_id": null,
"allowed_tools": [
"document_generate"
],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": "Proprietary",
"compatibility": null,
"metadata": {
"author": "vdf-ai",
"version": "1.0"
},
"file_count": 2,
"body_chars": 3521,
"id": null,
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": null,
"is_active": true,
"editable": false
}
],
"total": 2
} Create a skill
POST /agent-hub-api/api/skills
Validates a skill bundle and saves it as one of your skills.
Send the bundle as JSON, either the whole SKILL.md in skill_md or every file in files, or upload a zip archive as the multipart field file. A zip must hold SKILL.md at its root or inside a single top-level folder named after the skill; hidden files are ignored and symbolic links are rejected.
The frontmatter needs name (lowercase letters, digits and single hyphens, at most 64 characters) and description (at most 1,024 characters). A bundle may hold up to 200 files of up to 512 KiB each and 4 MiB in total, and the instructions after the frontmatter may be up to 24,000 characters. File paths must be relative, use forward slashes and contain no . or .. segments. Problems that do not block saving come back in warnings.
- Authentication
- Bearer token How it works
- Permission
- Your administrator can limit this to roles that may create skills.
Body parameters application/json
-
skill_mdstringThe complete
SKILL.md, frontmatter included, for a skill that has no other files. Ignored whenfilesis present. -
filesarray of objectsEvery file in the bundle,
SKILL.mdincluded. Send eitherfilesorskill_md.Show child parameters Hide child parameters
-
pathstring RequiredPath relative to the skill root, for example
SKILL.mdorreferences/style-guide.md. -
contentstringText content of the file.
-
content_base64stringBase64-encoded content, for binary files. Takes precedence over
content.
-
Returns
Returns skill, the created skill object with body, frontmatter, files and any warnings.
Errors
- 400 The bundle is invalid.
validation_errorslists each problem with acode,pathandmessage, andwarningslists the non-blocking ones. - 403 Your role does not allow creating skills.
- 409 You already have a skill with this name, or the name belongs to a system skill.
curl -X POST "$VDF_BASE_URL/agent-hub-api/api/skills" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"path": "SKILL.md",
"content": "---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: \"1.1\"\nallowed-tools: mcp__ticketing__search_tickets\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n"
},
{
"path": "references/style-guide.md",
"content": "# Style guide\n\n- Write for customers, not engineers.\n- Keep each line under 120 characters.\n"
}
]
}' const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
files: [
{
path: 'SKILL.md',
content: '---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: "1.1"\nallowed-tools: mcp__ticketing__search_tickets\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n',
},
{
path: 'references/style-guide.md',
content: '# Style guide\n\n- Write for customers, not engineers.\n- Keep each line under 120 characters.\n',
},
],
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"files": [
{
"path": "SKILL.md",
"content": "---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: \"1.1\"\nallowed-tools: mcp__ticketing__search_tickets\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n",
},
{
"path": "references/style-guide.md",
"content": "# Style guide\n\n- Write for customers, not engineers.\n- Keep each line under 120 characters.\n",
},
],
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"skill": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.1",
"scope": "user",
"source": "db",
"owner_user_id": 42,
"allowed_tools": [
"mcp__ticketing__search_tickets"
],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": null,
"compatibility": null,
"metadata": {
"version": "1.1"
},
"file_count": 1,
"body_chars": 234,
"id": "b7e3d2a1-5c4f-4e8b-a9d6-1f2e3c4b5a69",
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": "2026-09-03T08:12:44.118262",
"is_active": true,
"editable": true,
"body": "# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n",
"frontmatter": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"metadata": {
"version": "1.1"
},
"allowed-tools": "mcp__ticketing__search_tickets"
},
"files": [
{
"path": "SKILL.md",
"size": 458,
"is_binary": false,
"kind": "skill_md"
},
{
"path": "references/style-guide.md",
"size": 92,
"is_binary": false,
"kind": "references"
}
]
}
} Validate a skill
POST /agent-hub-api/api/skills/validate
Checks a skill bundle against the format rules without saving it.
Accepts the same bundle formats as Create a skill and applies the same format rules, but does not check whether the name is already taken. The status is 200 whether or not the bundle is valid; read valid, errors and warnings, where each problem has a code, path and message.
- Authentication
- Bearer token How it works
Body parameters application/json
-
skill_mdstringThe complete
SKILL.md, frontmatter included. Ignored whenfilesis present. -
filesarray of objectsEvery file in the bundle,
SKILL.mdincluded, aspathpluscontentorcontent_base64. Send eitherfilesorskill_md.
Returns
Returns valid, errors and warnings. For a valid bundle, skill summarises it: name, description, version, allowed_tools, allow_implicit_invocation, body_lines and files.
curl -X POST "$VDF_BASE_URL/agent-hub-api/api/skills/validate" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"skill_md": "---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: \"1.1\"\nallowed-tools: mcp__ticketing__search_tickets\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/validate`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
skill_md: '---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: "1.1"\nallowed-tools: mcp__ticketing__search_tickets\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/validate",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"skill_md": "---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: \"1.1\"\nallowed-tools: mcp__ticketing__search_tickets\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"valid": true,
"errors": [],
"warnings": [],
"skill": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.1",
"allowed_tools": [
"mcp__ticketing__search_tickets"
],
"allow_implicit_invocation": true,
"body_lines": 6,
"files": [
{
"path": "SKILL.md",
"size": 458,
"is_binary": false,
"kind": "skill_md"
}
]
}
} Retrieve a skill
GET /agent-hub-api/api/skills/{name}
Returns one skill visible to the caller, with its instructions and file list.
Also lists the agents that bind the skill among your own agents and the system agents.
- Authentication
- Bearer token How it works
Path parameters
-
namestring RequiredName of the skill.
Returns
Returns skill, the skill object with body, frontmatter, files and agents.
Errors
- 404 No skill with this name is visible to you.
curl "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"skill": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.1",
"scope": "user",
"source": "db",
"owner_user_id": 42,
"allowed_tools": [
"mcp__ticketing__search_tickets"
],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": null,
"compatibility": null,
"metadata": {
"version": "1.1"
},
"file_count": 1,
"body_chars": 234,
"id": "b7e3d2a1-5c4f-4e8b-a9d6-1f2e3c4b5a69",
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": "2026-09-03T08:12:44.118262",
"is_active": true,
"editable": true,
"body": "# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first and link each to its ticket.\n4. Follow the tone in references/style-guide.md.\n",
"frontmatter": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"metadata": {
"version": "1.1"
},
"allowed-tools": "mcp__ticketing__search_tickets"
},
"files": [
{
"path": "SKILL.md",
"size": 458,
"is_binary": false,
"kind": "skill_md"
},
{
"path": "references/style-guide.md",
"size": 92,
"is_binary": false,
"kind": "references"
}
],
"agents": [
{
"id": "9d2f4b61-3a7c-4e15-8b0d-2c6e9f1a7b34",
"name": "Release Manager",
"is_user_created": true
}
]
}
} Update a skill
PUT /agent-hub-api/api/skills/{name}
Replaces every file of one of your skills with a new bundle.
Send the complete new bundle in any format accepted by Create a skill; it replaces all existing files. The frontmatter name must equal name in the path, so a skill cannot be renamed. The save records a snapshot under the frontmatter's version; reusing the current version replaces that version's snapshot.
- Authentication
- Bearer token How it works
- Permission
- Your administrator can limit this to roles that may edit skills.
Path parameters
-
namestring RequiredName of one of your skills.
Body parameters application/json
-
skill_mdstringThe complete new
SKILL.md, for a skill that has no other files. Ignored whenfilesis present. -
filesarray of objectsEvery file of the new bundle,
SKILL.mdincluded, aspathpluscontentorcontent_base64. Send eitherfilesorskill_md.
Returns
Returns skill, the updated skill object with body, frontmatter, files and any warnings.
Errors
- 400 The bundle is invalid, for example because its
namediffers from the one in the path.validation_errorslists each problem. - 403 The skill is a system skill or belongs to someone else, or your role does not allow editing skills.
- 404 No skill with this name is visible to you.
curl -X PUT "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"skill_md": "---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: \"1.2\"\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first.\n"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
skill_md: '---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: "1.2"\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first.\n',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.put(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"skill_md": "---\nname: release-notes\ndescription: Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.\nmetadata:\n version: \"1.2\"\n---\n\n# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first.\n",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"skill": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.2",
"scope": "user",
"source": "db",
"owner_user_id": 42,
"allowed_tools": [],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": null,
"compatibility": null,
"metadata": {
"version": "1.2"
},
"file_count": 0,
"body_chars": 157,
"id": "b7e3d2a1-5c4f-4e8b-a9d6-1f2e3c4b5a69",
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": "2026-09-04T10:22:09.731550",
"is_active": true,
"editable": true,
"body": "# Release notes\n\n1. Group the changes into Features, Fixes and Maintenance.\n2. Write one line per change, in the past tense.\n3. List breaking changes first.\n",
"frontmatter": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"metadata": {
"version": "1.2"
}
},
"files": [
{
"path": "SKILL.md",
"size": 335,
"is_binary": false,
"kind": "skill_md"
}
]
}
} Delete a skill
DEL /agent-hub-api/api/skills/{name}
Deletes one of your skills.
The skill stops appearing in lists and is no longer loaded for agents that bind it.
- Authentication
- Bearer token How it works
- Permission
- Your administrator can limit this to roles that may delete skills.
Path parameters
-
namestring RequiredName of one of your skills.
Returns
Returns success: true once the skill is deleted.
Errors
- 403 The skill is a system skill or belongs to someone else, or your role does not allow deleting skills.
- 404 No skill with this name is visible to you.
curl -X DELETE "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.delete(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true
} Retrieve a skill file
GET /agent-hub-api/api/skills/{name}/files/{file_path}
Returns one file bundled with a skill visible to the caller.
Returns a text file as text/plain and a binary file as a download. With format=json the file comes back as JSON instead: path, is_binary, size in bytes, and content, which is the text or null for a binary file.
- Authentication
- Bearer token How it works
Path parameters
-
namestring RequiredName of the skill.
-
file_pathstring RequiredPath of the file relative to the skill root, as listed in the skill's
files, for examplereferences/style-guide.md.
Query parameters
-
formatstringSet to
jsonto receive the file wrapped in a JSON object.
Returns
Returns the file's raw contents.
Errors
- 404 No skill with this name is visible to you, or the skill has no file at this path.
curl "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes/files/references/style-guide.md" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-o files.bin const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes/files/references/style-guide.md`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const file = await response.blob(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes/files/references/style-guide.md",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
stream=True,
timeout=30,
)
response.raise_for_status()
with open("files.bin", "wb") as output:
for chunk in response.iter_content(chunk_size=65536):
output.write(chunk) Export a skill
GET /agent-hub-api/api/skills/{name}/export
Downloads a skill visible to the caller as a zip archive.
The archive <name>.zip holds the skill's files inside a top-level folder named after the skill, the layout that Create a skill and other Agent Skills tools accept.
- Authentication
- Bearer token How it works
Path parameters
-
namestring RequiredName of the skill.
Returns
Returns the zip archive as a download.
Errors
- 404 No skill with this name is visible to you.
curl "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes/export" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-o export.bin const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes/export`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const file = await response.blob(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes/export",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
stream=True,
timeout=30,
)
response.raise_for_status()
with open("export.bin", "wb") as output:
for chunk in response.iter_content(chunk_size=65536):
output.write(chunk) List agents that use a skill
GET /agent-hub-api/api/skills/{name}/agents
Returns your agents and the system agents that bind a skill.
- Authentication
- Bearer token How it works
Path parameters
-
namestring RequiredName of the skill.
Returns
Returns agents, a list of objects with the agent's id, name and is_user_created.
Errors
- 404 No skill with this name is visible to you.
curl "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes/agents" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes/agents`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes/agents",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"agents": [
{
"id": "9d2f4b61-3a7c-4e15-8b0d-2c6e9f1a7b34",
"name": "Release Manager",
"is_user_created": true
}
]
} List skill versions
GET /agent-hub-api/api/skills/{name}/versions
Returns the saved versions of a skill, newest first.
System skills read from the deployment's skill directory have no saved versions and return an empty list.
- Authentication
- Bearer token How it works
Path parameters
-
namestring RequiredName of the skill.
Returns
Returns current, the skill's current version, and versions, each with id, version, content_hash (a SHA-256 over the bundle's files) and created_at.
Errors
- 404 No skill with this name is visible to you.
curl "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes/versions" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes/versions`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes/versions",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"current": "1.1",
"versions": [
{
"id": "0c6f2e9a-7b1d-4f3e-8a25-9d4c1b7e6f03",
"version": "1.1",
"content_hash": "2129e6301f0450bb127a1652d77f03648890f27c7d7dec77ce70dce40c85088f",
"created_at": "2026-09-03T08:12:44.120953"
},
{
"id": "a2d9e4f1-3c8b-4b6a-9e17-5f0d2c8a1b64",
"version": "1.0",
"content_hash": "8f41c0d7b2e95a36c1e04d7f9b2a68e3d5c0f174a9b8e2d61c3f5a07e9d4b182",
"created_at": "2026-09-01T09:41:07.556102"
}
]
} Retrieve a skill version
GET /agent-hub-api/api/skills/{name}/versions/{version}
Returns a skill as it was saved under one version.
Returns the current skill when version is the current version, and otherwise the snapshot saved under it. editable is always false, and updated_at is when the snapshot was saved.
- Authentication
- Bearer token How it works
Path parameters
-
namestring RequiredName of the skill.
-
versionstring RequiredVersion label, for example
1.0.
Returns
Returns skill, the skill object as saved under that version, with body, frontmatter and files.
Errors
- 404 No skill with this name is visible to you, or it has no snapshot for this version.
curl "$VDF_BASE_URL/agent-hub-api/api/skills/release-notes/versions/1.0" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/release-notes/versions/1.0`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/release-notes/versions/1.0",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"skill": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog.",
"version": "1.0",
"scope": "user",
"source": "db",
"owner_user_id": 42,
"allowed_tools": [],
"allow_implicit_invocation": true,
"argument_hint": null,
"license": null,
"compatibility": null,
"metadata": {},
"file_count": 0,
"body_chars": 112,
"id": "b7e3d2a1-5c4f-4e8b-a9d6-1f2e3c4b5a69",
"unresolved_tools": [],
"interface": null,
"dependencies": null,
"updated_at": "2026-09-01T09:41:07.556102",
"is_active": true,
"editable": false,
"body": "# Release notes\n\n1. Group the changes into Features and Fixes.\n2. Write one line per change, in the past tense.\n",
"frontmatter": {
"name": "release-notes",
"description": "Drafts release notes from a list of merged changes. Use when the user asks for release notes or a changelog."
},
"files": [
{
"path": "SKILL.md",
"size": 263,
"is_binary": false,
"kind": "skill_md"
}
]
}
} Export skills as a plugin
POST /agent-hub-api/api/skills/export-plugin
Downloads a set of skills packaged as an Agent Plugins archive.
Packages the named skills as a zip archive in the Agent Plugins 1.0.0 layout: a plugin.json manifest, then each skill under skills/<name>/, all inside a top-level folder named after the plugin. You can export any skill you can see.
With include_mcp, the archive also carries an mcp.json that points MCP clients at mcp_url, with a placeholder for the user's access token in the Authorization header.
- Authentication
- Bearer token How it works
Body parameters application/json
-
namesarray of strings RequiredNames of the skills to include.
-
pluginobject RequiredFields for the plugin manifest.
Show child parameters Hide child parameters
-
namestring RequiredPlugin name: 1 to 64 lowercase letters, digits, hyphens or periods, starting and ending with a letter or digit. Also names the archive and its top-level folder.
-
versionstringPlugin version.
-
descriptionstringPlugin description.
-
homepagestringHomepage URL.
-
repositorystringSource repository URL.
-
licensestringLicence identifier.
-
authorobjectAuthor with optional
name,emailandurl. -
keywordsarray of stringsKeywords for the manifest.
-
-
include_mcpbooleanWhether to add an
mcp.jsonto the archive. -
mcp_urlstringURL of your deployment's MCP endpoint to write into
mcp.json. Required wheninclude_mcpistrue.
Returns
Returns the zip archive <plugin name>.zip as a download.
Errors
- 400
namesis missing or empty, the plugin name is missing or invalid, orinclude_mcpis set withoutmcp_url. - 404 One or more names are not skills visible to you;
errorlists them.
curl -X POST "$VDF_BASE_URL/agent-hub-api/api/skills/export-plugin" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"names": [
"release-notes",
"plain-language-writing"
],
"plugin": {
"name": "acme-writing",
"version": "1.0.0",
"description": "Writing skills used by the Acme product team.",
"author": {
"name": "Ada Lovelace",
"email": "ada@example.com"
}
}
}' \
-o export-plugin.bin const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/export-plugin`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
names: ['release-notes', 'plain-language-writing'],
plugin: {
name: 'acme-writing',
version: '1.0.0',
description: 'Writing skills used by the Acme product team.',
author: {
name: 'Ada Lovelace',
email: 'ada@example.com',
},
},
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const file = await response.blob(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/export-plugin",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"names": ["release-notes", "plain-language-writing"],
"plugin": {
"name": "acme-writing",
"version": "1.0.0",
"description": "Writing skills used by the Acme product team.",
"author": {
"name": "Ada Lovelace",
"email": "ada@example.com",
},
},
},
stream=True,
timeout=30,
)
response.raise_for_status()
with open("export-plugin.bin", "wb") as output:
for chunk in response.iter_content(chunk_size=65536):
output.write(chunk) Import skills from a plugin
POST /agent-hub-api/api/skills/import-plugin
Creates your own skills from the skills in an Agent Plugins archive.
Reads plugin.json (at the archive root or inside a single top-level folder) and creates one user skill for every folder under skills/ that contains a SKILL.md; other plugin components are ignored. Each skill's frontmatter name must match its folder name. Skills are created independently, and each one is reported as created, invalid (with errors) or skipped (with error, for example when you already have a skill with that name).
Each file may be up to 512 KiB, all files together up to 16 MiB, and a plugin may carry up to 200 skills; each skill must also meet the rules in Create a skill. Hidden files are ignored and symbolic links are rejected.
- Authentication
- Bearer token How it works
- Permission
- Your administrator can limit this to roles that may create skills.
Form fields multipart/form-data
-
filefile RequiredThe plugin as a zip archive.
Returns
Returns plugin, with the name and version from plugin.json, and skills, one result per skill folder.
Errors
- 400 No archive was uploaded, or the archive is not a usable plugin: not a zip,
plugin.jsonmissing or invalid, a file over the size limits, a symbolic link, an unsafe path, or too many skills. - 403 Your role does not allow creating skills.
curl -X POST "$VDF_BASE_URL/agent-hub-api/api/skills/import-plugin" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-F "file=@acme-writing.zip" import { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('acme-writing.zip'), 'acme-writing.zip');
const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/import-plugin`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
body: form,
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
with open("acme-writing.zip", "rb") as file:
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/import-plugin",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
files={"file": file},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"plugin": {
"name": "acme-writing",
"version": "1.0.0"
},
"skills": [
{
"name": "meeting-summary",
"status": "created",
"version": "1.0"
},
{
"name": "release-notes",
"status": "skipped",
"error": "skill 'release-notes' already exists"
}
]
} Synchronise system skills
POST /agent-hub-api/api/skills/sync
Rereads the deployment's system skill bundles from disk and upserts them into the catalogue, recording a version for each. System skills that are no longer present on disk are deactivated. Skills that users created are never touched. Use it after an administrator has added or edited system skills on the server.
- Authentication
- Bearer token How it works
- Permission
- Requires an administrator account.
Parameters
No parameters.
Returns
Returns the number of system skills synchronised.
Errors
- 403 The caller is not an administrator.
curl -X POST "$VDF_BASE_URL/agent-hub-api/api/skills/sync" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/agent-hub-api/api/skills/sync`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/agent-hub-api/api/skills/sync",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"synced": 12
}