Skills

A skill is a named playbook an agent loads on demand. Where a tool is a single capability (one HTTP call, one DB query), a skill is a recipe — a body of instructions that teaches the agent how to do a multi-step task using whatever tools it has.

Skills are loadable graph-memory nodescold_memory rows with type = "skill" and meta.loadable = true (the same table Memory documents), not a separate skills table. Both built-in and custom skills are nodes; meta.is_builtin is what tells them apart. The CP /v1/skills API is a read/write proxy over that graph that the Dashboard → Skills page consumes.

There is no skill versioning, PR-flow, or CI validation suite. Older references to semver skill versions, a PR-managed skill repo, or a CI schema/lint/trigger-overlap check are stale — skills today are plain CRUD against graph nodes. There is also no trigger, load_tools, bound_agents, or is_dangerous field on a skill; see the object below for what actually exists.

The skill object

List row:

{
  "slug": "fewer-permission-prompts",
  "title": "Fewer permission prompts",
  "description": "Scan transcripts and add an allowlist to settings.json.",
  "category": "productivity",
  "path": null,
  "body_length": 4213,
  "is_builtin": true,
  "used_by": 2
}

Detail (GET /v1/skills/{slug}) adds the full body and the assignment fields:

{
  "id": 9931,
  "slug": "fewer-permission-prompts",
  "title": "Fewer permission prompts",
  "description": "Scan transcripts and add an allowlist to settings.json.",
  "category": "productivity",
  "body": "# fewer-permission-prompts\n\n1. Read the project's .claude/settings.json...",
  "body_length": 4213,
  "is_builtin": true,
  "always_load": false,
  "status": "active",
  "agent": null,
  "agents": [],
  "scope": "shared"
}
Field Type Notes
id int Detail-only. The underlying graph-node id — pass it to GET /v1/memory/entries/{id}/links for the relationships panel.
slug string URL-safe handle.
title string Display title, with any Skill: <slug> — ingestion prefix stripped.
description string One-liner shown in the picker.
category string Derived from the slug when not explicitly set.
path null Always null — skills stopped being files on disk (Skillunify S5); the field is kept for shape compatibility.
body string Detail-only. The full playbook text, unredacted for admin callers and blanked (with redacted: true) for public/portal callers — see Agents for the redaction model.
body_length int Length of body in characters. On the list endpoint this is the only signal of body size (the body itself isn't returned there).
is_builtin bool true for graph-ingested skills (read-only via this API); false for portal-created custom skills.
used_by int List-only. How many other skills reference this one via a [[slug]] depends_on wikilink.
always_load bool Detail-only. Whether the skill renders into its scoped agents' boot context on every run.
status string Detail-only. Graph-node lifecycle status (e.g. active).
agent string | null Detail-only. Legacy single-agent column; superseded by agents.
agents string[] Detail-only. The full set of agents this skill is scoped to.
scope string Detail-only. shared or agentagent scope requires at least one entry in agents; shared scope forces agents to be empty.

Endpoints

List skills

GET /v1/skills

Returns every loadable skill node, redacted to the caller's permissions.

const { data } = await catentio.skills.list();
skills = catentio.skills.list()
curl 'https://catent.io/api/v1/cp/v1/skills' \
  -H "Cookie: catentio_session=<session cookie set at login>"

Meta: {"total", "builtin", "custom"} — same convention as Agents and Tools.

Retrieve a skill

GET /v1/skills/{slug}

Returns the full detail row. body is unredacted for admin callers only.

Create a skill

POST /v1/skills

Creates a custom (non-builtin) loadable node. Body:

{
  "slug": "my-pipeline-skill",
  "name": "My pipeline skill",
  "description": "Drive the X pipeline end-to-end.",
  "category": "pipelines",
  "prompt_addendum": "1. ...",
  "agents": ["fumi"],
  "scope": "agent"
}

slug (1–128 chars) and name are required. prompt_addendum becomes the skill's body. category is derived from the slug when omitted. A tier cap on custom_skills applies. 409 if the slug is already taken by any loadable node, builtin or custom.

Update a skill

PATCH /v1/skills/{slug}

Partial update on a custom node only — builtin skills (meta.is_builtin = true) return 400, since they're ingested from the graph and not portal-editable. Patchable: name (re-rendered into title), description, category, prompt_addendum (becomes the new body), and the assignment fields scope/agent(s)/always_load.

Delete a skill

DELETE /v1/skills/{slug}

Removes a custom loadable node. Builtin skills return 400. Agents lose the load_skill <slug> capability immediately; a run that already loaded the skill's body carries on with what it has.

Errors

Status When
400 Attempted edit/delete of a builtin skill; missing/empty name on create or update; a field that must be a list (tools, dependencies) wasn't one.
404 Slug doesn't exist.
409 Slug collision on create.
502 CP couldn't reach the runtime.

There is no 422 validation_error on this resource.

Events

Skill CRUD isn't currently emitted as outbox events — reserved for a later release.

Next

  • Tools — the building blocks skills compose.
  • Memory — skills are nodes in the same graph, and what skills frequently pull context from.
  • Agents — how to bind a skill to a persona.