Skip to content

Create a skill

POST /v1/skills

Creates a skill from uploaded files. The skill belongs to the owner of the API key.

Send multipart/form-data with the skill’s files in files[]. To upload a zip archive instead, send it as the only files[] part. For the rules an upload must follow, see Skills. Only the key’s owner can see the skill until they share it in the Context app.

If the request fails partway, the server deletes the skill, so no partial skill is left. If your organization uses the newer permissions model, a skill uploaded with files besides SKILL.md is saved as two versions: SKILL.md alone, then the whole upload. latest_version_id always points to the version that holds the whole upload.

The key must be a personal key with the skills:write capability. For what each capability allows, see Keys.

Body (multipart/form-data)

NameTypeRequiredDefaultDescription
filesarray<binary>YesThe skill’s files. On the wire each file is one files[] part, with the file’s path in the skill as the filename. The SDKs send the files parameter this way. You can instead send one zip archive that contains all the files.
display_namestringNoThe skill’s display name. Must equal the name in SKILL.md.

Response

Returns 200 with the skill object. For every field, see Skills.

{
"id": "8b3f6c1e-2a4d-4f7b-9c1e-5d6a7b8c9d0e",
"type": "skill",
"created_at": "2026-10-27T15:02:11.204Z",
"display_name": "pr-summary",
"latest_version_id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"source": {
"type": "custom"
},
"updated_at": "2026-10-27T15:02:11.204Z"
}

Errors

This endpoint can return these errors as well as the common errors.

Statuserror.typeCause and fix
400invalid_request_errorThe upload breaks a rule: SKILL.md is missing or not at the root, name or description is missing or invalid, a path is unsafe, or the files total over 30 MiB (31,457,280 bytes) uncompressed. The message names the rule. Fix the files and upload again.
400invalid_request_errordisplay_name is not the name in SKILL.md. Leave it out, or send that name.
400invalid_request_errorThe form has a field other than files[] and display_name, or the request has a query parameter. Remove it.
403permission_errorThe key lacks skills:write, is not a personal key, or its owner doesn’t have permission to create skills in this organization. Use a personal key with skills:write.
409invalid_request_errorThe key’s owner already has a skill with this name. The message gives its ID. Upload a new version of that skill, or change name in SKILL.md.
413request_too_largeThe request is over 32 MiB (33,554,432 bytes). Remove files until the request is under 32 MiB.
429rate_limit_errorThis key already has 2 skill uploads in progress, or the server is processing too many uploads. Wait for the time in Retry-After, then retry. The SDKs retry it for you.

Example

cURL
curl -sS -X POST "$CONTEXT_BASE_URL/v1/skills" -H "x-api-key: $CONTEXT_API_KEY" \
-F "files[]=@pr-summary/SKILL.md;filename=pr-summary/SKILL.md" \
-F "files[]=@pr-summary/summarize.py;filename=pr-summary/summarize.py"
Python
skill = client.beta.skills.create(
files=[
("pr-summary/SKILL.md", open("pr-summary/SKILL.md", "rb")),
("pr-summary/summarize.py", open("pr-summary/summarize.py", "rb")),
],
)
print(skill.id, skill.latest_version_id)
TypeScript
import { readFile } from "node:fs/promises"
import { toFile } from "@anthropic-ai/sdk"
const skill = await client.beta.skills.create({
files: [
await toFile(await readFile("pr-summary/SKILL.md"), "pr-summary/SKILL.md"),
await toFile(await readFile("pr-summary/summarize.py"), "pr-summary/summarize.py"),
],
})