Skip to main content
Skills

Create a Skill

Forward API reference.

Description

Uploads a Skill package and creates a new Skill record. Submit the request as multipart/form-data. A successful request also creates the initial version and points latest_version to it.

Path

POST /api/v1/forward/skills

Request headers

HeaderRequiredDescription
AuthorizationYesBearer <PAT or SAT>
Content-TypeYesmultipart/form-data
Idempotency-KeyNoRecommended. Requests with the same key and the same normalized files fingerprint can be retried safely.

Form fields

FieldTypeRequiredDescription
filesfileYes*Recommended upload field; it can appear multiple times. Supports two forms:
① A single .zip archive;
② A bare file tree, with one file per part and a relative path in filename (for example, code-review/SKILL.md or code-review/scripts/run.sh).
Both the compressed archive and its extracted contents must be no larger than 50 MB.
metadataJSON stringNoCaller metadata object with up to 15 keys. created_by is reserved and must not be supplied (supplying it returns 400).
icon_idstringNoPublic Forward Resource icon ID.
filefileYes*⚠️ Deprecated: A single .zip archive using the relaxed package rules. Matching requests return Deprecation: true in the response headers. Migrate to files.
namestringNo⚠️ Deprecated: The final name is always parsed from name in the uploaded package's SKILL.md frontmatter. This field is retained only for compatibility and is ignored when supplied.
descriptionstringNo⚠️ Deprecated: The final description is always parsed from SKILL.md.
typestringNo⚠️ Deprecated: Skill creation type, either custom or prebuilt; defaults to custom. With prebuilt, the response source is qoder; otherwise it is custom.
*Provide either files or file. If both are supplied, files takes precedence.
See Skill package for package structure rules. A package must contain SKILL.md, have exactly one top-level directory, and use the name from SKILL.md as the directory name.

Example request

Single zip archive:
curl -X POST "https://api.qoder.com/api/v1/forward/skills" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Idempotency-Key: create-skill-001" \
  -F "files=@skill.zip;type=application/zip" \
  -F "type=custom" \
  -F 'metadata={"source":"console"}' \
  -F "icon_id=pic_skill_default"
Bare file tree (repeat the files field):
curl -X POST "https://api.qoder.com/api/v1/forward/skills" \
  -H "Authorization: Bearer $QODER_ACCESS_TOKEN" \
  -H "Idempotency-Key: create-skill-002" \
  -F "files=@code-review/SKILL.md;filename=code-review/SKILL.md" \
  -F "files=@code-review/scripts/run.sh;filename=code-review/scripts/run.sh"

Example response

HTTP 201 Created
{
  "id": "skill_xxx",
  "type": "skill",
  "display_title": "code-review",
  "description": "Code review skill",
  "source": "custom",
  "latest_version": "1759178010641129",
  "metadata": {
    "source": "console"
  },
  "created_at": "2026-07-23T10:00:00Z",
  "updated_at": "2026-07-23T10:00:00Z",
  "identity_id": null,
  "icon_url": null,
  "binding_info": {
    "agent_template_count": 0
  }
}

Response fields

The response is a Skill object. The request also creates an initial Skill version object, available through List Skill Versions. latest_version points to the version's 16-digit Unix microsecond timestamp.

Errors

HTTPTypeTrigger
400invalid_request_errorMultipart parsing failed; the package structure is invalid (for example, missing SKILL.md, multiple top-level directories, or a directory name that differs from name); type is invalid; or metadata is invalid or contains a reserved key.
400invalid_request_errorIf the reserved key created_by is supplied, message is metadata key "created_by" is reserved, identifying the invalid field.
400skill_content_too_largeThe compressed archive or its extracted contents exceed 50 MB.
401authentication_errorThe authentication token is missing or invalid.
403permission_errorThe caller cannot access this resource.
409conflict_errorThe Idempotency-Key maps to a different request fingerprint.
413invalid_request_errorThe request body exceeds the service limit.
429rate_limit_errorThe caller exceeded the API rate limit.
500/502/503api_errorForward or a dependent service failed.