> ## 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.

# Automate a workflow with Skills (Playbook)

> List a workspace's skills, promote a personal skill to firm-wide, toggle it on or off, and scope which skills apply to a single run

A **skill** is a reusable, versioned rule set the Filed analyst applies
automatically during a tax prep or tax advisor 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. This recipe is the minimal call sequence to list a
workspace's skills for a task family, share a personal skill with the firm,
approve or deny that promotion, toggle a skill on or off, and scope which skills
apply to a single run.

Every call uses a **`workspaceToken`** (see [Authentication](/guides/authentication))
and goes to the single GraphQL endpoint:

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

```mermaid theme={null}
flowchart LR
  A["USER skill<br/>status: NONE"] -->|"requestSkillPromotion"| B["status: PENDING"]
  B -->|"approveSkillPromotion"| C["WORKSPACE skill<br/>status: APPROVED"]
  B -->|"denySkillPromotion"| D["status: DENIED"]
  C -->|"setSkillActive active: false"| E["status: DISABLED"]
  E -->|"setSkillActive active: true"| C
  C -.->|"applied automatically<br/>by triggerTaxPrep / triggerTaxAdvisor"| F["A run with<br/>the skill active"]
```

This recipe does not re-document the types it touches. For the full `Skill`,
`SkillRule`, `SkillStrategy`, `SkillActivityEvent`, and `UserShortDetails` field
lists, the `SkillKind` and `SkillStatus` enums, and the `deleteSkill` mutation,
see [Skills](/apis/skills).

## 1. List a workspace's skills for a task type

There is no top-level `skills` query. Read `workspace.skills` through
`me { ... on WorkspaceUser { workspace { skills(...) } } }` (see
[Skills](/apis/skills#list-skills)). Pass `taskType` to scope the list to one
task family, and `showCuratedSkills: true` to include Filed's curated built-in
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
          }
        }
      }
    }
  }
}
```

```json theme={null}
{
  "taskType": "tax-prep"
}
```

<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": "NONE",
              "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>

Pick the `USER` skill you want to share with the firm. Save its `taskType` and
`name`; the promotion mutations in steps 3 and 4 identify a skill by those two
values (no `kind` argument).

## 2. Fetch one skill's detail and activity

Before promoting a skill, read its full detail and activity timeline with
[`workspace.skill`](/apis/skills#view-a-single-skill). Identify the skill with
`kind` + `taskType` + `name` (and optional `returnType` when the skill is scoped
to a return type).

```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
            }
          }
        }
      }
    }
  }
}
```

```json theme={null}
{
  "kind": "USER",
  "taskType": "tax-prep",
  "name": "my-firm-reconciliation"
}
```

<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": "USER",
        "taskType": "tax-prep",
        "name": "my-firm-reconciliation"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "id": "018f9c20-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
          "skill": {
            "kind": "USER",
            "taskType": "tax-prep",
            "name": "my-firm-reconciliation",
            "description": "Personal reconciliation protocol.",
            "body": "Flag any 1099-B where proceeds differ from the broker statement by more than $1, and surface the discrepancy as a review item.",
            "status": "NONE",
            "returnType": null,
            "updatedAt": "2026-07-04T18:22:01.000Z",
            "owner": {
              "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
              "name": "Jane Preparer",
              "email": "jane@example-firm.com"
            },
            "activity": [
              {
                "action": "created",
                "timestamp": "2026-07-04T18:22:01.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 before
  you try to promote. See [Skills, view a single skill](/apis/skills#view-a-single-skill).
</Note>

## 3. Request promoting a personal skill to the workspace

[`requestSkillPromotion`](/apis/skills#request-a-promotion) submits a `USER`
skill for firm-wide review. The skill's `status` moves from `NONE` to `PENDING`.
A workspace admin then approves or denies it in step 4. The mutation identifies
the skill by `taskType` + `name` only; there is no `kind` argument because only
`USER` skills can be promoted.

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

```json theme={null}
{
  "taskType": "tax-prep",
  "name": "my-firm-reconciliation"
}
```

<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>

## 4. Approve or deny the promotion

A workspace admin resolves the `PENDING` promotion with one of two mutations.
Both identify the skill by `taskType` + `name` and return the updated `Skill`.

To approve, call [`approveSkillPromotion`](/apis/skills#approve-a-promotion).
The skill's `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
  }
}
```

```json theme={null}
{
  "taskType": "tax-prep",
  "name": "my-firm-reconciliation"
}
```

<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>

To deny, call [`denySkillPromotion`](/apis/skills#deny-a-promotion). The skill's
`status` becomes `DENIED` and the optional `reason` is recorded on the activity
timeline so the owner can see why the promotion was rejected.

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

```json theme={null}
{
  "taskType": "tax-prep",
  "name": "my-firm-reconciliation",
  "reason": "Overlaps with existing firm protocol check-w2-totals."
}
```

<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>

<Tip>
  After any of the promotion mutations, 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. See [Skills, toggle a skill active or inactive](/apis/skills#toggle-a-skill-active-or-inactive).
</Tip>

## 5. Toggle a skill active or inactive

[`setSkillActive`](/apis/skills#toggle-a-skill-active-or-inactive) enables or
disables a skill without deleting it. Disabling sets `status: DISABLED` so the
skill stops applying during runs 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
  }
}
```

```json theme={null}
{
  "taskType": "tax-prep",
  "name": "check-w2-totals",
  "active": false,
  "returnType": "F1040"
}
```

<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>

Re-enable the same skill by calling `setSkillActive` again with `active: true`;
its `status` returns to `APPROVED`.

<Warning>
  There is no `runSkill` mutation. Skills are never invoked directly. The analyst
  applies every `APPROVED` (active) skill automatically during a `triggerTaxPrep`
  or `triggerTaxAdvisor` run. The next section shows how to scope which active
  skills apply to one specific run.
</Warning>

## 6. Scope which skills apply to a single run

Because skills apply automatically based on their `status`, the way to control
which skills a particular run uses is the optional `skills` argument
(`RunSkillSelectionInput`) on the trigger mutations:

* [`triggerTaxPrep(input: { ..., skills: { workspace: [...], user: [...] } })`](/apis/tax-prep#start-a-tax-prep-run)
  for a tax prep run.
* [`triggerTaxAdvisor(input: { ..., skills: { workspace: [...], user: [...] } })`](/apis/planning#trigger-via-triggertaxadvisor)
  or [`initiateTaxAdvisor(input: { ..., skills: { workspace: [...], user: [...] } })`](/apis/planning#trigger-via-initiatetaxadvisor)
  for a tax advisor run.

`RunSkillSelectionInput` takes two optional lists of skill names:

```graphql theme={null}
input RunSkillSelectionInput {
  workspace: [String!]
  user: [String!]
}
```

Omit `skills` entirely to apply all active skills in both scopes. Pass an empty
list in one scope to censor every skill in that scope. Pass a non-empty list to
apply only those named skills.

For example, to start a tax prep run that applies only the workspace skill
`check-w2-totals` and the user skill `my-firm-reconciliation`, and no others:

```graphql theme={null}
mutation TriggerTaxPrep($input: TriggerTaxPrepInput!) {
  triggerTaxPrep(input: $input) {
    taskId
  }
}
```

```json theme={null}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
    "returnType": "F1040",
    "skills": {
      "workspace": ["check-w2-totals"],
      "user": ["my-firm-reconciliation"]
    }
  }
}
```

<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 TriggerTaxPrep($input: TriggerTaxPrepInput!) { triggerTaxPrep(input: $input) { taskId } }",
      "variables": {
        "input": {
          "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
          "returnType": "F1040",
          "skills": {
            "workspace": ["check-w2-totals"],
            "user": ["my-firm-reconciliation"]
          }
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "triggerTaxPrep": {
        "taskId": "018f9c2c-4a1b-7e20-8b33-7c4d5e6f7080"
      }
    }
  }
  ```
</ResponseExample>

Poll the returned `taskId` per [Run tax prep end to end](/guides/recipes/run-tax-prep)
and [Tasks](/apis/tasks#check-a-single-tasks-status). The selected skills shape
the review items the analyst raises during the run.

## Next steps

* For the full `Skill`, `SkillRule`, `SkillStrategy`, `SkillActivityEvent`, and
  `UserShortDetails` field lists, the `SkillKind` and `SkillStatus` enums, and
  the `deleteSkill` mutation, see [Skills](/apis/skills).
* To run tax prep with the active skills and read the resulting review items,
  see [Run tax prep end to end](/guides/recipes/run-tax-prep) and
  [Tax prep](/apis/tax-prep).
* To run tax planning with the active skills and read the resulting strategies,
  see [Run tax planning / advisory](/guides/recipes/run-tax-planning) and
  [Tax planning](/apis/planning).
