# Skills

Aerion's **Skill Management** captures what each person on your team can do — their competencies, languages, and certificates — and puts it to work in planning and analytics. This page is for developers working against the [API](/api) or [MCP](/developer/mcp); if you manage skills through the web app, see the [Skills overview](/guides/skills-overview) guide instead.

This page covers the model and the behaviors you can't see from the [API reference](/api) alone; follow the links into the reference for full request and response schemas.

## In Aerion, Skills are an extension of Tags

A skill is a [Tag](/api/tag) record; there is no separate skill resource. Skills, languages, and certificates all live in the Tag model, distinguished by the `type` field:

| `type` | What the record is | How it's assigned |
| ------ | ------------------ | ----------------- |
| `null` | A **plain tag** — a free-form label | via [TagAssignment](/api/tagassignment) |
| `1`    | A **skill**       | to users, via [SkillAssignment](/api/skillassignment) |
| `2`    | A **language**    | to users, via [SkillAssignment](/api/skillassignment) |
| `3`    | A **certificate** | to users, via [SkillAssignment](/api/skillassignment) |

A plain tag is a free-form label you can attach to clients, projects, tasks, users, and expense categories. A skill, language, or certificate belongs to Skill Management and is assigned only to users.

Throughout this page, "skill" means any Tag with a non-null `type` — a skill, language, or certificate — unless a distinction matters.

Rules that follow from the model:

- **A `type` can't cross the tag/skill boundary.** You cannot convert a plain tag into a skill or vice versa after creation. You *can* change a skill's `type` **between** `1`, `2`, and `3` (skill ↔ language ↔ certificate).
- **`label` is unique per account within a `type`.** You can have a plain tag and a skill that share a label, but not two skills with the same label.
- **A skill requires a `category`; a plain tag does not use one.** See [Skill categories](#skill-categories) below.

### Creating a skill

Create it against the Tag endpoint, with a `type` and a `category`:

```http
POST /v1/tags
Content-Type: application/json
```

```json
{
  "tag": {
    "label": "Kubernetes",
    "type": 1,
    "category": 12,
    "description": "Container orchestration"
  }
}
```

`category` is the ID of a [TagCategory](/api/tagcategory) record and is **required for skills**. Full field list: [Create Tag](/api/tag#create-tag). Omitting `type` and `category` creates a plain tag instead.

### Listing skills vs. plain tags

Skills and plain tags share [`GET /v1/tags`](/api/tag#list-tag-find-where). By default it returns **only plain tags**; skills are excluded unless explicitly requested:

- **`?includeSkills=true`** returns skills *alongside* plain tags.
- **`?type=1`** (or `2`, `3`) returns only that one kind of skill.
- **`?status=1`** filters by active (`1`) / inactive (`0`); combine with the above.

```http
GET /v1/tags?type=1&status=1        # active skills only
GET /v1/tags?includeSkills=true     # plain tags + every kind of skill
GET /v1/tags                        # plain tags only — no skills
```

## Skill categories

Every skill belongs to a [TagCategory](/api/tagcategory), whose ID is held in `Tag.category`. Categories are semantic groupings for your skill catalog (e.g. *Cloud & Infrastructure*, *Programming Languages*), each with a `label` and a display `color`.

Despite the name, **TagCategory currently categorizes only skills.** Categorizing plain tags is planned for a future release; for now, treat categories as a skills-only concept.

Two behaviors to note:

- **`isDefault` marks the account's fallback category.** When a category is deleted, its skills fall back to the default category rather than being orphaned. The default category itself cannot be deleted.
- **Category is not the same as type.** A skill has exactly one `type` (skill/language/certificate) *and* one `category`, and the two are independent — the category organizes your catalog; the type controls how the skill behaves (notably, whether it's leveled).

Manage categories via [`POST /v1/tag-categories`](/api/tagcategory#create-tagcategory) and the rest of the [TagCategory](/api/tagcategory) CRUD.

## Assigning skills to users

A skill is attached to a user with a [SkillAssignment](/api/skillassignment).

> **Use SkillAssignment, not TagAssignment, for skills.** Both can point a `user` at a Tag, but only SkillAssignment is a skill relationship: it carries the proficiency [`level`](#levels), holds each skill at most once per user, and [preserves history](#history-removals-are-archived-not-deleted) when a skill is removed. TagAssignment is a flat label with none of that. The skill features — staffing, planning, and analytics — read SkillAssignment, so a skill attached through TagAssignment carries no level and never appears among the user's skills.

```http
POST /v1/skill-assignments
Content-Type: application/json
```

```json
{
  "skillAssignment": {
    "user": 6789,
    "skill": 42,
    "level": 4
  }
}
```

- **`skill`** is the ID of the Tag (the skill, language, or certificate).
- **`user`** is the ID of the User who holds it.
- **`level`** is the proficiency — see below.
- **A user holds each skill at most once.**

Full field list: [Create SkillAssignment](/api/skillassignment#create-skillassignment).

### Levels

`level` records proficiency on a scale **1 to 5**:

- **Certificates (`type: 3`) are not leveled** — send `level: null`. A user either holds a certificate or doesn't.
- Skills and languages (`type: 1`/`2`) take a level of `1`–`5`.

> The 1–5 scale is fixed today but is slated to become admin-customizable, at which point `level` will become a foreign key to a level record rather than a bare integer. Store it as an opaque value you read back from the API rather than hard-coding assumptions about the range.

Change a level later with [`PUT /v1/skill-assignments/{id}`](/api/skillassignment#update-skillassignment).

### History: removals are archived, not deleted

When a skill is removed from a user, the SkillAssignment is **archived** — its `status` is set to `0` — rather than deleted. The record persists, so the history of who held what is preserved.

- **`status: 1`** is an active assignment; **`status: 0`** is an archived (former) one.
- **List queries filter to active assignments by default.** To see historical/archived assignments, query with the archived status explicitly:

```http
GET /v1/skill-assignments?where={"user":6789}                 # a user's current skills
GET /v1/skill-assignments?where={"user":6789,"status":0}      # skills they used to hold
```

The `where` value is JSON and must be URL-encoded. Full spec: [List SkillAssignment](/api/skillassignment#list-skillassignment-find-where).

> Today this is the extent of skill history — active and archived assignments. Fuller **skill progression** tracking, such as a record of how a user's level changes over time, is planned for a future release.

## Skill sets

A [SkillSet](/api/skillset) is a **named, reusable bundle of skills** — typically a company role like *Cloud Security Engineer* — that lets you apply a whole role's worth of skills to a person in a single operation.

A skill set is built from two relationships:

- **Skills are added to a skill set via [TagAssignment](/api/tagassignment), with `model: "skill-set"`.** The skill-set membership reuses the same tag-assignment mechanism (`skill-set` is one of the allowed `model` values), where `record` is the skill set's ID and `tag` is the skill's ID.
- **Each skill set belongs to a [SkillSetCategory](/api/skillsetcategory).** These are a separate category family from TagCategory — they group skill *sets*, not skills. `category` on SkillSet is required and points at a SkillSetCategory. Like TagCategory, SkillSetCategory has an `isDefault` fallback that can't be deleted.

### Skill sets are a bulk-apply convenience

> **A skill set is not permanently linked to any user.** Applying a skill set to a user creates the individual [SkillAssignment](/api/skillassignment) records for its skills — a bulk shortcut for the per-skill assignment above. Once created, those assignments are independent of the set.

Consequences:

- **Editing a skill set does not touch anyone's skills.** Adding or removing a skill from a set has no effect on users who were previously given that set — their existing SkillAssignments are unchanged.
- **There is no "skill set assignment" record to query.** To find a user's skills you query SkillAssignments (above); there is no persistent link from a user back to the skill set their skills came from.

Attach a skill to a set:

```http
POST /v1/tag-assignments
Content-Type: application/json
```

```json
{
  "tagAssignment": {
    "model": "skill-set",
    "record": 500,
    "tag": 42
  }
}
```

Full spec: [Create TagAssignment](/api/tagassignment#create-tagassignment).

## End-to-end example

Creating a "Kubernetes" skill, filing it under a category, and giving it to a user at level 4:

```bash
# 1. Create (or reuse) a category for the skill
curl -X POST https://your-account.aerion.app/v1/tag-categories \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "tagCategory": { "label": "Cloud & Infrastructure" } }'
# → { "tagCategory": { "id": 12, ... } }

# 2. Create the skill (a Tag with type=1) in that category
curl -X POST https://your-account.aerion.app/v1/tags \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "tag": { "label": "Kubernetes", "type": 1, "category": 12 } }'
# → { "tag": { "id": 42, "type": 1, "category": 12, ... } }

# 3. Assign it to user 6789 at proficiency level 4
curl -X POST https://your-account.aerion.app/v1/skill-assignments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "skillAssignment": { "user": 6789, "skill": 42, "level": 4 } }'
# → { "skillAssignment": { "id": 3456, "status": 1, ... } }

# 4. Later: list that user's current skills
curl "https://your-account.aerion.app/v1/skill-assignments?where=%7B%22user%22%3A6789%7D" \
  -H "Authorization: Bearer $TOKEN"
```

A certificate is the same flow with `type: 3` on the Tag and `level: null` on the SkillAssignment. A language uses `type: 2`.
