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

# The Label object

> One company-wide pool of tags, arranged in a tree, attachable to skills, certifications, learning items and careers

<Info>
  **Quick Summary:** A label is a company-wide tag with a name, colour, icon and description. Labels form a tree of any depth, and the same label can be attached to skills, certifications, learning items and careers — one pool, four attachment points. Attaching is idempotent and all-or-nothing; deleting is guarded.
</Info>

## Attributes

| Field                     | Type           | Description                                                                               |
| ------------------------- | -------------- | ----------------------------------------------------------------------------------------- |
| `id`                      | string         | Opaque label id                                                                           |
| `name`                    | string         | Name (up to 50 characters)                                                                |
| `description`             | string \| null |                                                                                           |
| `icon`                    | string \| null | Icon identifier                                                                           |
| `color`                   | string         | Display colour                                                                            |
| `position`                | integer        | 1-based position **among its siblings** — ordering is scoped per parent, not company-wide |
| `parentId`                | string \| null | The parent label; `null` for a top-level label                                            |
| `createdAt` / `updatedAt` | string \| null |                                                                                           |

The tree read returns the same object with a `children` array, in position order.

```json theme={null}
{
  "id": "12",
  "name": "Safety",
  "description": "Statutory safety training and licences.",
  "icon": "shield",
  "color": "#D94F4F",
  "position": 1,
  "parentId": null,
  "createdAt": "2026-01-12T08:30:00.000Z",
  "updatedAt": null
}
```

## Operations

### Library

| Operation      | Method and path               | Requires scope |
| -------------- | ----------------------------- | -------------- |
| List labels    | `GET /api/v1/labels`          | `labels:read`  |
| Get a label    | `GET /api/v1/labels/{id}`     | `labels:read`  |
| Create labels  | `POST /api/v1/labels`         | `labels:write` |
| Update a label | `PATCH /api/v1/labels/{id}`   | `labels:write` |
| Reorder labels | `POST /api/v1/labels/reorder` | `labels:write` |
| Delete a label | `DELETE /api/v1/labels/{id}`  | `labels:write` |

The flat list is filterable by `name`, `description` and `parentId`, and sortable by `name` and `position`.

**Create** is plural by nature: `{ "labels": [{ name, color, description?, icon?, parentId? }] }` in, the created labels out. `color` is required. Each new label is placed at the end of its sibling group.

**Update** takes `name`, `description`, `icon` and `color`; an explicit `null` clears the description or icon. Moving a label is its own operation (below).

**Reorder** sets positions within **one** sibling group: `{ "parentId": "4" | null, "ids": [...] }` must list every label of that group exactly once, in the desired order. A partial or foreign list is [422 unprocessable](/api-docs/errors/unprocessable).

**Delete** is guarded. If any object carries the label, or it has children, the call answers [409 conflict](/api-docs/errors/conflict):

```json theme={null}
{
  "type": "https://help.skillsdb.com/api-docs/errors/conflict",
  "title": "Conflict",
  "status": 409,
  "detail": "The label is attached to objects or has child labels. Retry with ?confirm=true to delete it anyway: it is removed from every object and its children become top-level labels.",
  "confirmRequired": true,
  "dependencies": { "skills": 14, "certifications": 2, "learningItems": 0, "careers": 3, "children": 2 },
  "childrenPromotedToTopLevel": 2
}
```

Retry with `?confirm=true`: the label is stripped from every object and each child is promoted to top level, renumbered after the existing roots — what the app does silently, now stated.

### Hierarchy

| Operation               | Method and path                    | Requires scope |
| ----------------------- | ---------------------------------- | -------------- |
| List labels as a tree   | `GET /api/v1/labels/tree`          | `labels:read`  |
| List top-level labels   | `GET /api/v1/labels/top-level`     | `labels:read`  |
| List a label's children | `GET /api/v1/labels/{id}/children` | `labels:read`  |
| Reparent a label        | `PATCH /api/v1/labels/{id}/parent` | `labels:write` |

**Reparent** takes `{ "parentId": "4" }` to move the label, or `{ "parentId": null }` to promote it to top level. It refuses with a [409](/api-docs/errors/conflict) when the proposed parent is the label itself or one of its own descendants — the server checks the whole tree, not just the browser.

### Attachment

The same three operations against each kind of object:

| Operation                     | Method and path                                                                | Requires scope |
| ----------------------------- | ------------------------------------------------------------------------------ | -------------- |
| List an object's labels       | `GET /api/v1/{skills\|certifications\|learning-items\|careers}/{id}/labels`    | `labels:read`  |
| Attach labels                 | `POST /api/v1/{skills\|certifications\|learning-items\|careers}/{id}/labels`   | `labels:write` |
| Detach labels                 | `DELETE /api/v1/{skills\|certifications\|learning-items\|careers}/{id}/labels` | `labels:write` |
| List labels in use for a kind | `GET /api/v1/labels/in-use?kind=skill\|certification\|learning_item\|career`   | `labels:read`  |

Attach and detach take `{ "labelIds": ["12", "15"] }` and answer the object's current labels. **Attaching a label the object already carries succeeds without change**, and the call applies **wholly or not at all** — a retry after a network failure is safe. Detaching a label the object does not carry is likewise a no-op. An unknown label is a 404; an unknown or foreign object is the same 404.

The in-use read returns only labels attached to at least one live object of the kind — the read that drives filter options — and works the same way for all four kinds.

## Permissions

Any person who can read labels in the app can read them here. **Creating, updating, reordering, reparenting, deleting, attaching and detaching are restricted to global administrators**; a key acting as anyone else receives [insufficient-permissions](/api-docs/errors/insufficient-permissions) even when it holds `labels:write`.

## Excluded

People, skill, section and skill-group **flags** are a separate feature (see [Flag](/api-docs/resources/flag)). The labelled objects themselves belong to their own resources. CSV label import is an in-app path.

## Related errors

* [not-found](/api-docs/errors/not-found) — the label, or the object being labelled, is not in your company
* [validation-error](/api-docs/errors/validation-error) — a missing name or colour, an unknown parent on create or reparent, a bad `kind`
* [unprocessable](/api-docs/errors/unprocessable) — an incomplete reorder list
* [conflict](/api-docs/errors/conflict) — a reparent that would create a cycle; the guarded delete
