Skills

A skill is a versioned playbook an agent loads on demand. Skills live as cold_memory graph nodes (type='skill', meta.loadable=true) — built-in ones are ingested from the runtime's SKILL.md files, and custom skills are DB-backed and creatable via the control plane (POST /v1/skills, PATCH /v1/skills/{slug}, DELETE /v1/skills/{slug}). This SDK only wraps the read side.

For the runtime skill contract, see API → Skills.

Auth. Every route in this namespace requires the control plane's x-catentio-session header, which this SDK never sends. Neither apiKey mode nor Bearer-session mode authenticates here. See SDKs → Auth: what actually works.

Namespace

catentio.skills — two methods:

catentio.skills.list()
catentio.skills.get(slug)

create/update/delete exist on the control plane but aren't wrapped by this SDK — call the REST API directly for custom skills.

Methods

skills.list

Signature. catentio.skills.list(token?): Promise<{ data: SkillListRow[]; meta: { total: number; builtin: number; custom: number } }>

import { CatentioSaasClient } from '@forjio/catentio-saas-node';

const catentio = new CatentioSaasClient({ session });

const { data: skills } = await catentio.skills.list();
for (const s of skills) {
  console.log(s.slug, s.title, s.category, s.is_builtin, s.used_by);
}

Real list-row shape — note there is no body here, only its length:

interface SkillListRow {
  slug: string;
  title: string;
  description: string;
  category: string;
  path: null;               // dead field, always null (kept for shape stability)
  body_length: number;
  is_builtin: boolean;
  used_by: number;          // how many other skills declare a `[[slug]]` depends_on link
}

There is no displayName, version, triggers, tools, body, createdAt, or updatedAt field on this row — none of those exist.

skills.get

Signature. catentio.skills.get(slug, token?): Promise<SkillDetail>

const skill = await catentio.skills.get('fewer-permission-prompts');
console.log(skill.body);   // full markdown body (parsed, frontmatter stripped)

Real detail shape:

interface SkillDetail {
  id: number;                  // the underlying cold_memory node id
  slug: string;
  title: string;
  description: string;
  category: string;
  body: string;
  body_length: number;
  is_builtin: boolean;
  always_load: boolean;
  status: string;
  agent: string | null;
  agents: string[];
  scope: string | null;
}

There is no version, triggers object, or tools array — skills don't carry a semver, a structured trigger rule, or a declared tool list on this endpoint.

Common patterns

Render the skill catalogue in your own UI

const { data: skills } = await catentio.skills.list();
const cards = skills.map(s => ({
  title: s.title,
  blurb: s.description,
  badge: s.is_builtin ? 'built-in' : 'custom',
}));

Pre-flight: confirm the runtime has the skill you expect

async function requireSkill(slug: string) {
  try {
    await catentio.skills.get(slug);
  } catch (err) {
    if (err instanceof CatentioError && err.status === 404) {
      throw new Error(`Skill ${slug} is not deployed on this runtime.`);
    }
    throw err;
  }
}

await requireSkill('schedule');

Find unused custom skills

const { data: skills } = await catentio.skills.list();
const unused = skills.filter(s => !s.is_builtin && s.used_by === 0);

Errors

The real error body is FastAPI's {"detail": ...}. Because the wire response never has an error key, the SDK doesn't parse out a structured code/message — you get CatentioError with a generic HTTP_ERROR code and the HTTP reason phrase as the message.

Status Cause
401 Missing/invalid session — this SDK never sends x-catentio-session.
404 get() on a slug with no loadable skill node.

Next

  • Tools — the capability registry skills reference.
  • Agents — the principal that loads skills.
  • API → Skills — HTTP-level reference, including custom-skill create/update/delete.