Skip to main content
Skills

Create a skill

Upload and create a new Skill resource together with its first version.

POST /api/v1/cloud/skills Uploads and creates a new Skill resource together with its first immutable version snapshot. Use multipart/form-data encoding; content is uploaded through the files field as a .zip archive or a bare file tree.

Headers

HeaderRequiredDescription
AuthorizationYesBearer <PAT or SAT>
Content-TypeNoSet automatically by curl -F to multipart/form-data; do not specify manually

Request body (multipart/form-data)

FieldTypeRequiredDescription
filesfile (repeatable)YesSkill content. Two forms: a single .zip archive, or a bare file tree (multiple parts whose filenames carry relative paths). Both forms must share a single top-level directory whose name equals the name in SKILL.md
display_titlestringNoDisplay title; uniqueness is not enforced. Defaults to the zip filename or the SKILL.md name. Immutable after creation
metadataJSON stringNoCustom metadata as a JSON object
filefileNo⚠️ Deprecated: legacy single-zip field (use files instead)
namestringNo⚠️ Deprecated: now mapped to display_title (use display_title instead; the model-facing skill name always comes from SKILL.md frontmatter name)
descriptionstringNo⚠️ Deprecated: has no effect; the description is always read from SKILL.md frontmatter (no replacement)
typestringNo⚠️ Deprecated: custom/prebuilt, default custom; this field will be removed and all created skills will be custom

Package rules

The content package must have a single top-level directory (named after the SKILL.md name) containing a SKILL.md that starts with YAML frontmatter. See Skill package for the full rules (frontmatter constraints, size limits, parsing behavior, etc.).

Example request

# Prepare skill content
mkdir my-custom-skill && cat > my-custom-skill/SKILL.md << 'EOF'
---
name: my-custom-skill
description: Custom skill example
---

# My Custom Skill

## Steps
1. Perform action A
2. Perform action B

## Verification
Confirm the operation completed.
EOF

# Pack as zip (keep the top-level directory)
zip -r my-custom-skill.zip my-custom-skill/

# Upload to create
curl -X POST "https://api.qoder.com/api/v1/cloud/skills" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -F "display_title=My Custom Skill" \
  -F 'metadata={"team":"docs"}' \
  -F "files=@my-custom-skill.zip"
Bare file tree upload (no zip; filenames carry relative paths):
curl -X POST "https://api.qoder.com/api/v1/cloud/skills" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -F "files=@my-custom-skill/SKILL.md;filename=my-custom-skill/SKILL.md" \
  -F "files=@my-custom-skill/templates/report.md;filename=my-custom-skill/templates/report.md"

Example response

HTTP 201 Created
{
  "id": "skill_019e3bba474b73cfaf19eae9b5f5e66d",
  "type": "skill",
  "display_title": "My Custom Skill",
  "source": "custom",
  "latest_version": "1759178010641129",
  "metadata": {
    "team": "docs"
  },
  "created_at": "2026-05-18T15:35:24.248164Z",
  "updated_at": "2026-05-18T15:35:24.248164Z"
}

Response fields

The response is a Skill object.
FieldTypeDescription
idstringSkill unique identifier with the skill_ prefix
typestringAlways "skill"
display_titlestringDisplay title
sourcestringSkill source: custom or qoder
latest_versionstringLatest version identifier
metadataobjectCustom metadata object stored with the Skill; defaults to {}
created_atstringCreation time
updated_atstringLast update time

Errors

HTTPTypeTrigger
400invalid_request_errorNon-multipart request, missing files field, or content package violating the Skill package rules
401TOKEN_INVALIDMissing or invalid authentication token
413request_too_large_errorArchive larger than 50 MB
429rate_limit_errorRate limit or quota exceeded

Notes

  • The model-facing skill name always comes from the SKILL.md frontmatter name, decoupled from display_title.
  • The name must stay consistent across all subsequent versions of the skill (see Create a skill version).
See Errors for the full error envelope.