> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a skill

> Upload a Skill as a zip archive.

`POST /api/v1/cloud/skills`

Uploads and creates a new Skill resource. Skill content must be uploaded as a `.zip` archive using `multipart/form-data` encoding.

## Headers

| Header          | Required | Description                                                                      |
| --------------- | -------- | -------------------------------------------------------------------------------- |
| `Authorization` | Yes      | `Bearer $QODER_PAT`                                                              |
| `Content-Type`  | No       | Set automatically by `curl -F` to `multipart/form-data`; do not specify manually |

## Request body (multipart/form-data)

| Field         | Type        | Required | Description                                                                                |
| ------------- | ----------- | -------- | ------------------------------------------------------------------------------------------ |
| `file`        | file        | Yes      | `.zip` archive of skill content, maximum 5 MB                                              |
| `name`        | string      | No       | Accepted for compatibility, but the API uses the `name` from `SKILL.md` frontmatter        |
| `type`        | string      | No       | Skill type. One of: `custom`, `prebuilt` (default `custom`)                                |
| `description` | string      | No       | Accepted for compatibility, but the API uses the `description` from `SKILL.md` frontmatter |
| `metadata`    | JSON string | No       | Custom metadata as a JSON object                                                           |

## Zip file structure

The archive must contain `SKILL.md` at the archive root or one directory below the root. `SKILL.md` must start with YAML frontmatter in this format:

```text theme={null}
---
name: my-skill
description: Skill description
version: 1.0.0
---

# Skill title

## Steps
1. Step one
2. Step two
```

Zip entry paths may use either `/` or Windows-style `\` separators; the service normalizes them when locating `SKILL.md`.

## Example request

```bash theme={null}
# Prepare skill content
mkdir my-skill && cat > my-skill/SKILL.md << 'EOF'
---
name: my-custom-skill
description: Custom skill example
version: 1.0.0
---

# My Custom Skill

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

## Verification
Confirm the operation completed.
EOF

# Pack as zip
cd my-skill && zip ../my-skill.zip SKILL.md && cd ..

# Upload to create
curl -X POST https://api.qoder.com/api/v1/cloud/skills \
  -H "Authorization: Bearer $QODER_PAT" \
  -F "name=my-custom-skill" \
  -F "type=custom" \
  -F 'metadata={"team":"docs"}' \
  -F "description=Custom skill example" \
  -F "file=@my-skill.zip"
```

## Example response

**HTTP 201 Created**

```json theme={null}
{
  "id": "skill_019e3bba474b73cfaf19eae9b5f5e66d",
  "type": "skill",
  "display_title": "my-custom-skill",
  "description": "Custom skill example",
  "source": "custom",
  "latest_version": "1",
  "metadata": {
    "team": "docs"
  },
  "created_at": "2026-05-18T15:35:24.248164Z",
  "updated_at": "2026-05-18T15:35:24.248164Z"
}
```

## Response fields

| Field            | Type   | Description                                                    |
| ---------------- | ------ | -------------------------------------------------------------- |
| `id`             | string | Skill unique identifier with the `skill_` prefix               |
| `type`           | string | Always `"skill"`                                               |
| `display_title`  | string | Display title derived from the stored skill name               |
| `description`    | string | Skill description                                              |
| `source`         | string | Skill source: `custom` or `qoder`                              |
| `latest_version` | string | Latest version number as a string                              |
| `metadata`       | object | Custom metadata object stored with the Skill; defaults to `{}` |
| `created_at`     | string | Creation time (ISO 8601)                                       |
| `updated_at`     | string | Last update time (ISO 8601)                                    |

## Errors

| HTTP | Type                    | Trigger                                                               |
| ---- | ----------------------- | --------------------------------------------------------------------- |
| 400  | `invalid_request_error` | Non-multipart request: "Invalid multipart form or request too large." |
| 400  | `invalid_request_error` | Missing `file` field: "Field 'file' is required."                     |
| 400  | `invalid_request_error` | Not a zip file: "Only .zip files are accepted."                       |
| 401  | `TOKEN_INVALID`         | Missing or invalid authentication token                               |

## Notes

* Duplicate skill names are allowed (uniqueness is not enforced).
* `name` and `description` are always read from the `SKILL.md` frontmatter. Form values do not override the archive content.
* Skill names must be at most 64 characters and contain only lowercase letters, digits, hyphens (`-`), or underscores (`_`), starting with a letter or digit.
* Zip archives created on Windows are supported; backslash path separators are normalized.
* Initial version number is 1.
* JSON Content-Type is not supported; `multipart/form-data` is required.

See [Errors](/cloud-agents/api/conventions/errors) for the full error envelope.

## Related

<CardGroup cols={2}>
  <Card title="Agent Skills" icon="sparkles" href="/cloud-agents/skills">
    Attach domain expertise to your agent.
  </Card>
</CardGroup>
