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

# Skills

> List, inspect, promote, approve, deny, activate, and delete the skills that power Playbook workflows

A **skill** is a reusable, versioned rule set that the Filed analyst applies
during a run. Skills are scoped to a workspace (`WORKSPACE` skills, shared firm
protocols) or to a single user (`USER` skills, personal protocols). The
Playbook screen in the Filed web app is the human UI over this API.

Skill operations are reached through the [`me`](/apis/me) query resolved as a
`WorkspaceUser`, so every read and mutation on this page requires a
**`workspaceToken`** (see [Authentication](/guides/authentication)). All
requests go to:

```
https://router.apps.filed.com/graphql
```

<Note>
  There is no top-level `skills` or `skill` query. Both are fields on `Workspace`,
  reached through `me { ... on WorkspaceUser { workspace { skills(...) } } }`.
  The `workspaceToken` already identifies which workspace, so you never pass a
  workspace ID.
</Note>

## The `Skill` type

```graphql theme={null}
type Skill {
  kind: SkillKind!
  taskType: String!
  name: String!
  description: String!
  body: String
  updatedAt: String!
  createdAt: String!
  returnType: ReturnType
  applicableWhen: String
  rules: [SkillRule!]
  strategies: [SkillStrategy!]
  status: SkillStatus
  owner: UserShortDetails
  activity: [SkillActivityEvent!]
}

enum SkillKind {
  WORKSPACE
  USER
}

enum SkillStatus {
  NONE
  PENDING
  APPROVED
  DENIED
  DISABLED
}

enum ReturnType {
  F1040
  F1041
  F1065
  F1120
  F1120S
  F990
}
```

<ResponseField name="kind" type="SkillKind!">
  The skill's scope: `WORKSPACE` (shared firm protocol) or `USER` (personal
  protocol). Determines who can edit, promote, and delete it.
</ResponseField>

<ResponseField name="taskType" type="String!">
  The task family this skill applies to, for example `tax-prep` or
  `tax-advisor`. Skills are grouped and listed by `taskType`.
</ResponseField>

<ResponseField name="name" type="String!">
  The skill's unique name within its `taskType` and `kind`. Together
  `kind` + `taskType` + `name` (+ optional `returnType`) identifies a single
  skill.
</ResponseField>

<ResponseField name="description" type="String!">
  A short human-readable summary of what the skill does.
</ResponseField>

<ResponseField name="body" type="String">
  The full rule body (the prompt / instruction text the analyst applies). May be
  empty for curated skills.
</ResponseField>

<ResponseField name="updatedAt" type="String!">
  ISO 8601 timestamp of the last edit.
</ResponseField>

<ResponseField name="createdAt" type="String!">
  ISO 8601 timestamp of creation.
</ResponseField>

<ResponseField name="returnType" type="ReturnType">
  When set, the skill only applies to clients of this return type
  (`F1040`, `F1041`, `F1065`, `F1120`, `F1120S`, `F990`). When `null`, the skill
  applies to all return types.
</ResponseField>

<ResponseField name="applicableWhen" type="String">
  A free-text condition describing when the skill should fire. Display only.
</ResponseField>

<ResponseField name="rules" type="[SkillRule!]">
  The structured rules attached to the skill. See the [`SkillRule`](#the-skillrule-type) type.
</ResponseField>

<ResponseField name="strategies" type="[SkillStrategy!]">
  The strategies attached to the skill. See the [`SkillStrategy`](#the-skillstrategy-type) type.
</ResponseField>

<ResponseField name="status" type="SkillStatus">
  The skill's lifecycle state: `NONE`, `PENDING`, `APPROVED`, `DENIED`, or
  `DISABLED`. `PENDING` means a `USER` skill has been shared with the firm and is
  awaiting approval; `APPROVED`/`DENIED` are the resolved promotion states;
  `DISABLED` means an admin has turned it off without deleting it.
</ResponseField>

<ResponseField name="owner" type="UserShortDetails">
  The user who owns the skill. See [`UserShortDetails`](#the-usershortdetails-type).
</ResponseField>

<ResponseField name="activity" type="[SkillActivityEvent!]">
  The skill's activity timeline (promotions, approvals, activations, edits). See
  [`SkillActivityEvent`](#the-skillactivityevent-type).
</ResponseField>

## The `SkillRule` type

```graphql theme={null}
type SkillRule {
  id: String!
  severity: String!
  category: String!
  domain: String!
  tolerance: Int
  titleTemplate: String
}
```

<ResponseField name="id" type="String!">
  The rule's unique identifier within the skill.
</ResponseField>

<ResponseField name="severity" type="String!">
  The severity the rule raises when it fires (for example `critical`, `high`,
  `medium`, `low`).
</ResponseField>

<ResponseField name="category" type="String!">
  The review category the rule maps to (for example `data-entry`,
  `reconciliation`).
</ResponseField>

<ResponseField name="domain" type="String!">
  The knowledge domain the rule belongs to.
</ResponseField>

<ResponseField name="tolerance" type="Int">
  An optional numeric tolerance the rule allows before flagging.
</ResponseField>

<ResponseField name="titleTemplate" type="String">
  A template string used to render the rule's title in review output.
</ResponseField>

## The `SkillStrategy` type

```graphql theme={null}
type SkillStrategy {
  id: String!
  titleTemplate: String
}
```

<ResponseField name="id" type="String!">
  The strategy's unique identifier within the skill.
</ResponseField>

<ResponseField name="titleTemplate" type="String">
  A template string used to render the strategy's title in planning output.
</ResponseField>

## The `SkillActivityEvent` type

```graphql theme={null}
type SkillActivityEvent {
  action: String!
  timestamp: String!
  triggeredBy: UserShortDetails
  note: String
}
```

<ResponseField name="action" type="String!">
  What happened, for example `created`, `updated`, `shared`, `approved`, `denied`,
  `enabled`, `disabled`. The web app humanizes this by replacing hyphens and
  underscores with spaces and title-casing the result.
</ResponseField>

<ResponseField name="timestamp" type="String!">
  ISO 8601 timestamp of the event.
</ResponseField>

<ResponseField name="triggeredBy" type="UserShortDetails">
  The user who triggered the event. See
  [`UserShortDetails`](#the-usershortdetails-type).
</ResponseField>

<ResponseField name="note" type="String">
  An optional human-readable note attached to the event (for example the denial
  reason from `denySkillPromotion`).
</ResponseField>

## The `UserShortDetails` type

`UserShortDetails` is a federated entity. The `ai` subgraph declares it with
only `id`; the `platform` subgraph resolves the human-readable fields.

```graphql theme={null}
type UserShortDetails {
  id: ID!
  name: String!
  email: String!
  emailHash: String
}
```

<ResponseField name="id" type="ID!">
  The user's account ID.
</ResponseField>

<ResponseField name="name" type="String!">
  The user's display name.
</ResponseField>

<ResponseField name="email" type="String!">
  The user's email.
</ResponseField>

<ResponseField name="emailHash" type="String">
  An optional hash of the email (used for avatars).
</ResponseField>

## List skills

Read `workspace.skills` to list skills for a task family. The web app's
Playbook screen calls this with `showCuratedSkills: true` to include Filed's
built-in curated skills alongside the workspace's own.

```graphql theme={null}
query GetTaskSkills($taskType: String!) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        id
        skills(taskType: $taskType, showCuratedSkills: true) {
          kind
          taskType
          name
          description
          status
          returnType
          updatedAt
          owner {
            id
            name
            email
          }
        }
      }
    }
  }
}
```

### Arguments

```graphql theme={null}
skills(
  showCuratedSkills: Boolean = false
  taskType: String
  returnType: ReturnType
): [Skill!]!
```

<ParamField path="showCuratedSkills" type="Boolean">
  When `true`, include Filed's curated (built-in) skills in the result alongside
  the workspace's own. Defaults to `false`.
</ParamField>

<ParamField path="taskType" type="String">
  Filter to one task family, for example `tax-prep` or `tax-advisor`. Omit to
  list skills across all task families.
</ParamField>

<ParamField path="returnType" type="ReturnType">
  Filter to skills that apply to a specific return type. Omit to list skills
  that apply to all return types.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "query GetTaskSkills($taskType: String!) { me { ... on WorkspaceUser { id workspace { id skills(taskType: $taskType, showCuratedSkills: true) { kind taskType name description status returnType updatedAt owner { id name email } } } } } }",
      "variables": { "taskType": "tax-prep" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "id": "018f9c20-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
          "skills": [
            {
              "kind": "WORKSPACE",
              "taskType": "tax-prep",
              "name": "check-w2-totals",
              "description": "Verify W-2 wage totals against binder extractions.",
              "status": "APPROVED",
              "returnType": "F1040",
              "updatedAt": "2026-06-22T10:14:00.000Z",
              "owner": {
                "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
                "name": "Jane Preparer",
                "email": "jane@example-firm.com"
              }
            },
            {
              "kind": "USER",
              "taskType": "tax-prep",
              "name": "my-firm-reconciliation",
              "description": "Personal reconciliation protocol.",
              "status": "PENDING",
              "returnType": null,
              "updatedAt": "2026-07-04T18:22:01.000Z",
              "owner": {
                "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
                "name": "Jane Preparer",
                "email": "jane@example-firm.com"
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

## View a single skill

Read `workspace.skill` to fetch one skill's full detail, including its `body`,
`rules`, `strategies`, and `activity` timeline. Identify the skill with
`kind` + `taskType` + `name` (and optional `returnType`).

```graphql theme={null}
query GetSkill(
  $kind: SkillKind!
  $taskType: String!
  $name: String!
  $returnType: ReturnType
) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        id
        skill(
          kind: $kind
          taskType: $taskType
          name: $name
          returnType: $returnType
        ) {
          kind
          taskType
          name
          description
          body
          status
          returnType
          updatedAt
          owner {
            id
            name
            email
          }
          activity {
            action
            timestamp
            note
            triggeredBy {
              id
              name
              email
            }
          }
        }
      }
    }
  }
}
```

### Arguments

```graphql theme={null}
skill(
  kind: SkillKind!
  taskType: String!
  name: String!
  returnType: ReturnType
): Skill
```

<ParamField path="kind" type="SkillKind!" required>
  `WORKSPACE` or `USER`.
</ParamField>

<ParamField path="taskType" type="String!" required>
  The task family, for example `tax-prep`.
</ParamField>

<ParamField path="name" type="String!" required>
  The skill's name within its `taskType` and `kind`.
</ParamField>

<ParamField path="returnType" type="ReturnType">
  When the skill is scoped to a return type, pass it to disambiguate. Omit for
  skills that apply to all return types.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "query GetSkill($kind: SkillKind!, $taskType: String!, $name: String!, $returnType: ReturnType) { me { ... on WorkspaceUser { id workspace { id skill(kind: $kind, taskType: $taskType, name: $name, returnType: $returnType) { kind taskType name description body status returnType updatedAt owner { id name email } activity { action timestamp note triggeredBy { id name email } } } } } } }",
      "variables": {
        "kind": "WORKSPACE",
        "taskType": "tax-prep",
        "name": "check-w2-totals",
        "returnType": "F1040"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "id": "018f9c20-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
          "skill": {
            "kind": "WORKSPACE",
            "taskType": "tax-prep",
            "name": "check-w2-totals",
            "description": "Verify W-2 wage totals against binder extractions.",
            "body": "Flag any W-2 where the extracted wage total differs from the source document by more than $1.",
            "status": "APPROVED",
            "returnType": "F1040",
            "updatedAt": "2026-06-22T10:14:00.000Z",
            "owner": {
              "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
              "name": "Jane Preparer",
              "email": "jane@example-firm.com"
            },
            "activity": [
              {
                "action": "created",
                "timestamp": "2026-06-10T09:00:00.000Z",
                "note": null,
                "triggeredBy": {
                  "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
                  "name": "Jane Preparer",
                  "email": "jane@example-firm.com"
                }
              },
              {
                "action": "approved",
                "timestamp": "2026-06-22T10:14:00.000Z",
                "note": null,
                "triggeredBy": {
                  "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
                  "name": "Jane Preparer",
                  "email": "jane@example-firm.com"
                }
              }
            ]
          }
        }
      }
    }
  }
  ```
</ResponseExample>

<Note>
  `workspace.skill` returns `null` when no skill matches the supplied
  `kind` + `taskType` + `name` (+ `returnType`). Handle `null` as a not-found
  result.
</Note>

## The promotion workflow

A `USER` skill starts as a personal protocol visible only to its owner. To share
it with the whole firm, the owner requests a promotion; a workspace admin then
approves or denies it. On approval the skill becomes a `WORKSPACE` skill (or
its `WORKSPACE` counterpart is activated) and applies for every user in the
workspace. The three mutations below drive that flow. All require a
**`workspaceToken`**.

```mermaid theme={null}
flowchart LR
  A[USER skill<br/>status: NONE] -->|requestSkillPromotion| B[status: PENDING]
  B -->|approveSkillPromotion| C[status: APPROVED]
  B -->|denySkillPromotion| D[status: DENIED]
  C -->|setSkillActive active: false| E[status: DISABLED]
  E -->|setSkillActive active: true| C
```

### Request a promotion

`requestSkillPromotion` submits a `USER` skill for firm-wide review. Its
`status` becomes `PENDING`.

```graphql theme={null}
mutation RequestSkillPromotion($taskType: String!, $name: String!) {
  requestSkillPromotion(taskType: $taskType, name: $name) {
    kind
    taskType
    name
    status
  }
}
```

<ParamField path="taskType" type="String!" required>
  The skill's task family.
</ParamField>

<ParamField path="name" type="String!" required>
  The skill's name.
</ParamField>

### Returns: `Skill!`

The promoted [`Skill`](#the-skill-type) with its updated `status`.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation RequestSkillPromotion($taskType: String!, $name: String!) { requestSkillPromotion(taskType: $taskType, name: $name) { kind taskType name status } }",
      "variables": { "taskType": "tax-prep", "name": "my-firm-reconciliation" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "requestSkillPromotion": {
        "kind": "USER",
        "taskType": "tax-prep",
        "name": "my-firm-reconciliation",
        "status": "PENDING"
      }
    }
  }
  ```
</ResponseExample>

### Approve a promotion

`approveSkillPromotion` approves a `PENDING` skill. Its `status` becomes
`APPROVED` and it applies firm-wide.

```graphql theme={null}
mutation ApproveSkillPromotion($taskType: String!, $name: String!) {
  approveSkillPromotion(taskType: $taskType, name: $name) {
    kind
    taskType
    name
    status
  }
}
```

<ParamField path="taskType" type="String!" required>
  The skill's task family.
</ParamField>

<ParamField path="name" type="String!" required>
  The skill's name.
</ParamField>

### Returns: `Skill!`

The approved [`Skill`](#the-skill-type) with `status: APPROVED`.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation ApproveSkillPromotion($taskType: String!, $name: String!) { approveSkillPromotion(taskType: $taskType, name: $name) { kind taskType name status } }",
      "variables": { "taskType": "tax-prep", "name": "my-firm-reconciliation" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "approveSkillPromotion": {
        "kind": "USER",
        "taskType": "tax-prep",
        "name": "my-firm-reconciliation",
        "status": "APPROVED"
      }
    }
  }
  ```
</ResponseExample>

### Deny a promotion

`denySkillPromotion` denies a `PENDING` skill. Its `status` becomes `DENIED`
and the optional `reason` is recorded on the activity timeline.

```graphql theme={null}
mutation DenySkillPromotion(
  $taskType: String!
  $name: String!
  $reason: String
) {
  denySkillPromotion(taskType: $taskType, name: $name, reason: $reason) {
    kind
    taskType
    name
    status
  }
}
```

<ParamField path="taskType" type="String!" required>
  The skill's task family.
</ParamField>

<ParamField path="name" type="String!" required>
  The skill's name.
</ParamField>

<ParamField path="reason" type="String">
  An optional denial note. Stored on the `SkillActivityEvent` so the owner can see
  why the promotion was rejected.
</ParamField>

### Returns: `Skill!`

The denied [`Skill`](#the-skill-type) with `status: DENIED`.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation DenySkillPromotion($taskType: String!, $name: String!, $reason: String) { denySkillPromotion(taskType: $taskType, name: $name, reason: $reason) { kind taskType name status } }",
      "variables": {
        "taskType": "tax-prep",
        "name": "my-firm-reconciliation",
        "reason": "Overlaps with existing firm protocol check-w2-totals."
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "denySkillPromotion": {
        "kind": "USER",
        "taskType": "tax-prep",
        "name": "my-firm-reconciliation",
        "status": "DENIED"
      }
    }
  }
  ```
</ResponseExample>

## Toggle a skill active or inactive

`setSkillActive` enables or disables a skill without deleting it. Disabling sets
`status: DISABLED` so the skill stops applying but is still listed and can be
re-enabled. Pass `returnType` when the skill is scoped to a return type.

```graphql theme={null}
mutation SetSkillActive(
  $taskType: String!
  $name: String!
  $active: Boolean!
  $returnType: ReturnType
) {
  setSkillActive(
    taskType: $taskType
    name: $name
    active: $active
    returnType: $returnType
  ) {
    kind
    taskType
    name
    status
  }
}
```

<ParamField path="taskType" type="String!" required>
  The skill's task family.
</ParamField>

<ParamField path="name" type="String!" required>
  The skill's name.
</ParamField>

<ParamField path="active" type="Boolean!" required>
  `true` to enable, `false` to disable.
</ParamField>

<ParamField path="returnType" type="ReturnType">
  When the skill is scoped to a return type, pass it to disambiguate. Omit for
  skills that apply to all return types.
</ParamField>

### Returns: `Skill!`

The updated [`Skill`](#the-skill-type). Its `status` reflects the new active
state (`APPROVED` when enabled, `DISABLED` when disabled).

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation SetSkillActive($taskType: String!, $name: String!, $active: Boolean!, $returnType: ReturnType) { setSkillActive(taskType: $taskType, name: $name, active: $active, returnType: $returnType) { kind taskType name status } }",
      "variables": {
        "taskType": "tax-prep",
        "name": "check-w2-totals",
        "active": false,
        "returnType": "F1040"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "setSkillActive": {
        "kind": "WORKSPACE",
        "taskType": "tax-prep",
        "name": "check-w2-totals",
        "status": "DISABLED"
      }
    }
  }
  ```
</ResponseExample>

## Delete a skill

`deleteSkill` permanently removes a skill. For `WORKSPACE` skills this deletes
the skill for the whole firm (curated skills revert to their default). For
`USER` skills this deletes the personal protocol. The mutation returns `true`
on success.

<Warning>
  `deleteSkill` is irreversible. For a `WORKSPACE` skill it removes the protocol
  for every user in the firm. Prefer [`setSkillActive`](#toggle-a-skill-active-or-inactive)
  with `active: false` when you only need to turn a skill off.
</Warning>

```graphql theme={null}
mutation DeleteSkill(
  $kind: SkillKind!
  $taskType: String!
  $name: String!
  $returnType: ReturnType
) {
  deleteSkill(
    kind: $kind
    taskType: $taskType
    name: $name
    returnType: $returnType
  )
}
```

<ParamField path="kind" type="SkillKind!" required>
  `WORKSPACE` or `USER`.
</ParamField>

<ParamField path="taskType" type="String!" required>
  The skill's task family.
</ParamField>

<ParamField path="name" type="String!" required>
  The skill's name.
</ParamField>

<ParamField path="returnType" type="ReturnType">
  When the skill is scoped to a return type, pass it to disambiguate. Omit for
  skills that apply to all return types.
</ParamField>

### Returns: `Boolean!`

`true` when the skill was deleted.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://router.apps.filed.com/graphql \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
    -d '{
      "query": "mutation DeleteSkill($kind: SkillKind!, $taskType: String!, $name: String!, $returnType: ReturnType) { deleteSkill(kind: $kind, taskType: $taskType, name: $name, returnType: $returnType) }",
      "variables": {
        "kind": "USER",
        "taskType": "tax-prep",
        "name": "my-firm-reconciliation"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "deleteSkill": true
    }
  }
  ```
</ResponseExample>

<Tip>
  After any skill mutation, refetch `GetTaskSkills` and `GetSkill` so the
  Playbook UI reflects the new `status`. The web app calls
  `client.refetchQueries({ include: ["GetTaskSkills", "GetSkill"] })` after every
  bulk action.
</Tip>
