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

# Tax prep

> Start a tax prep run, poll it to completion, and read the review items it produces

**Tax prep** is the pipeline that extracts forms from a client's binder,
reconciles them, optionally enters them into tax software, and produces a set of
review items. It is a background task: you start it with the `triggerTaxPrep`
mutation and follow the resulting task to completion with the
[tasks API](/apis/tasks).

Tax prep operations are reached through the [`me`](/apis/me) query resolved as
a `WorkspaceUser`, so the trigger mutation and the task poll both require a
**`workspaceToken`** (see [Authentication](/guides/authentication)). The two
backoffice operations on this page (`retriggerTaxPrepStep` and
`setTaxPrepTaskStatus`) require a **user** token instead, called out below. All
requests go to:

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

<Note>
  Tax prep runs as a polled background task. This page documents how to start a
  run and read its result. For the polling pattern itself (listing tasks, reading
  `status`, `subTasks`, and the `TaskResult` union), see
  [Tasks](/apis/tasks); this page does not re-explain it.
</Note>

## The `TaskTaxPrepResult` type

When a `TAX_PREP` task reaches `status: COMPLETED`, its `result` field resolves
to `TaskTaxPrepResult`. It carries a summary plus document and form counts, and
the individual review items the run produced.

```graphql theme={null}
type TaskTaxPrepResult {
  taxYear: Int!
  returnType: ReturnType!
  summary: String!
  documentCount: Int!
  extractedFormCount: Int!
  reviewItemCount: Int!
  reviewItems: [TaxPrepReviewItem!]!
}

type TaxPrepReviewItem {
  severity: String!
  category: String!
  description: String!
}

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

<ResponseField name="taxYear" type="Int!">
  The tax year the run was prepared for, for example `2025`.
</ResponseField>

<ResponseField name="returnType" type="ReturnType!">
  The return form: `F1040`, `F1041`, `F1065`, `F1120`, `F1120S`, or `F990` (see
  [ReturnType](/apis/clients#the-client-type)).
</ResponseField>

<ResponseField name="summary" type="String!">
  A human-readable summary of the prepared return.
</ResponseField>

<ResponseField name="documentCount" type="Int!">
  Number of documents processed from the client's binder.
</ResponseField>

<ResponseField name="extractedFormCount" type="Int!">
  Number of forms extracted from those documents.
</ResponseField>

<ResponseField name="reviewItemCount" type="Int!">
  Number of review items produced. Use this as a quick "needs attention" count
  before paging through `reviewItems`.
</ResponseField>

<ResponseField name="reviewItems" type="[TaxPrepReviewItem!]!">
  The review items. Each has `severity`, `category`, and `description` (all
  `String!`).
</ResponseField>

<Note>
  `TaskTaxPrepResult` is one member of the `TaskResult` union. The other tax
  members are `TaskTaxReviewResult` (returned for `TAX_REVIEW` tasks, with issue
  counts by severity and form) and `TaskTaxAdvisorResult` (returned for
  `TAX_ADVISOR` tasks). See [Tasks, task result](/apis/tasks#task-result) for the
  full union and the inline-fragment pattern used to select it.
</Note>

## Start a tax prep run

`triggerTaxPrep` starts a tax prep run for a client and returns the
`taskId` you poll. It requires a **`workspaceToken`**.

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

### Input: `TriggerTaxPrepInput`

```graphql theme={null}
input TriggerTaxPrepInput {
  taskId: ID
  clientId: ID!
  returnType: ReturnType!
  software: String
  softwareClientId: String
  softwareClientVersion: String
  runDataEntry: Boolean
  forceReconcile: Boolean
  force: Boolean
  skills: RunSkillSelectionInput
}

"""
Per-run selection of tenant (firm + user) skills. Omitted = all active skills
apply; an empty list censors every skill in that scope.
"""
input RunSkillSelectionInput {
  workspace: [String!]
  user: [String!]
}
```

<ParamField path="clientId" type="ID!" required>
  The client to prepare the return for.
</ParamField>

<ParamField path="returnType" type="ReturnType!" required>
  The return form to prepare. Must match the client's `returnType` (see
  [clients](/apis/clients#the-client-type)).
</ParamField>

<ParamField path="taskId" type="ID">
  Optional. Pass an existing tax prep task ID to target an in-flight or prior run
  rather than starting a brand-new one. Omit for a fresh run.
</ParamField>

<ParamField path="software" type="String">
  The tax software provider key, as exposed by the workspace's connected tax
  software integrations. Required only when `runDataEntry` is `true`.
</ParamField>

<ParamField path="softwareClientId" type="String">
  The client identifier inside the tax software. Required only when
  `runDataEntry` is `true`.
</ParamField>

<ParamField path="softwareClientVersion" type="String">
  The tax software client version string, when relevant to the integration.
</ParamField>

<ParamField path="runDataEntry" type="Boolean">
  When `true`, the pipeline continues past extraction and reconciliation into
  data entry, writing the prepared return back to the tax software. Requires
  `software` and `softwareClientId`.
</ParamField>

<ParamField path="forceReconcile" type="Boolean">
  When `true`, re-run reconciliation even if a prior reconcile already
  succeeded.
</ParamField>

<ParamField path="force" type="Boolean">
  When `true`, start a fresh run even if another tax prep task for this client
  is already `RUNNING`.
</ParamField>

<ParamField path="skills" type="RunSkillSelectionInput">
  Optional. Override which workspace and user skills apply to this run. Omit to
  apply all active skills; pass an empty list for a scope to censor every skill
  in that scope.
</ParamField>

### Returns: `TriggerTaskResult`

```graphql theme={null}
type TriggerTaskResult {
  taskId: ID!
}
```

<ResponseField name="taskId" type="ID!">
  The ID of the started `TAX_PREP` [task](/apis/tasks). Poll it until `status` is
  no longer `RUNNING`, then read `result` as `TaskTaxPrepResult`.
</ResponseField>

<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",
          "runDataEntry": false
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "triggerTaxPrep": {
        "taskId": "018f9c2b-7c4d-7e10-9a22-6b3c4d5e6f70"
      }
    }
  }
  ```
</ResponseExample>

## Poll the task to completion

There is no `task(id:)` query. Poll the task you just started by listing the
client's `TAX_PREP` tasks and reading the entry whose `id` matches the
`taskId` returned above. The polling mechanics are documented on
[Tasks](/apis/tasks#check-a-single-tasks-status); the short version:

```graphql theme={null}
query PollTaxPrep($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: TAX_PREP, limit: 1) {
            id
            status
            startedAt
            completedAt
            errorMessage
            subTasks {
              type
              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": "query PollTaxPrep($clientId: ID!) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: TAX_PREP, limit: 1) { id status startedAt completedAt errorMessage subTasks { type status } } } } } } }",
      "variables": { "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "clients": [
            {
              "tasks": [
                {
                  "id": "018f9c2b-7c4d-7e10-9a22-6b3c4d5e6f70",
                  "status": "RUNNING",
                  "startedAt": "2026-07-04T10:02:00.000Z",
                  "completedAt": null,
                  "errorMessage": null,
                  "subTasks": [
                    { "type": "EXTRACT", "status": "COMPLETED" },
                    { "type": "RECONCILE", "status": "RUNNING" }
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  Poll on an interval (for example every few seconds) until `status` is no longer
  `RUNNING`. `COMPLETED` means the run succeeded and `result` is now
  selectable as `TaskTaxPrepResult`; `FAILED` means it did not, and
  `errorMessage` (plus `subTasks[].errorMessage`) explains which stage failed.
</Tip>

## Read the completed result

When the task is `COMPLETED`, select `result` with an inline fragment on
`TaskTaxPrepResult` to read the summary, counts, and review items.

```graphql theme={null}
query TaxPrepResult($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: TAX_PREP, limit: 1) {
            id
            status
            completedAt
            result {
              __typename
              ... on TaskTaxPrepResult {
                taxYear
                returnType
                summary
                documentCount
                extractedFormCount
                reviewItemCount
                reviewItems {
                  severity
                  category
                  description
                }
              }
              ... on TaskUnknownResult {
                message
              }
            }
          }
        }
      }
    }
  }
}
```

<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 TaxPrepResult($clientId: ID!) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: TAX_PREP, limit: 1) { id status completedAt result { __typename ... on TaskTaxPrepResult { taxYear returnType summary documentCount extractedFormCount reviewItemCount reviewItems { severity category description } } ... on TaskUnknownResult { message } } } } } } } } }",
      "variables": { "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "clients": [
            {
              "tasks": [
                {
                  "id": "018f9c2b-7c4d-7e10-9a22-6b3c4d5e6f70",
                  "status": "COMPLETED",
                  "completedAt": "2026-07-04T10:11:48.000Z",
                  "result": {
                    "__typename": "TaskTaxPrepResult",
                    "taxYear": 2025,
                    "returnType": "F1040",
                    "summary": "Return prepared from 14 documents with 9 extracted forms. 3 items need review before sign-off.",
                    "documentCount": 14,
                    "extractedFormCount": 9,
                    "reviewItemCount": 3,
                    "reviewItems": [
                      {
                        "severity": "high",
                        "category": "missing_form",
                        "description": "W-2 from Acme Corp referenced in prior year but not present in this year's binder."
                      },
                      {
                        "severity": "medium",
                        "category": "value_mismatch",
                        "description": "Schedule B interest total differs from 1099-INT sum by $42."
                      },
                      {
                        "severity": "low",
                        "category": "data_entry",
                        "description": "Filing status set to Married Filing Jointly; confirm against intake form."
                      }
                    ]
                  }
                }
              ]
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

<Note>
  Always read `__typename` on `result` and include a `... on TaskUnknownResult`
  fallback. A `TAX_PREP` task that fails after the run starts can resolve to
  `TaskUnknownResult` instead of `TaskTaxPrepResult`; branching on `__typename`
  keeps your client from throwing on the unexpected member.
</Note>

## Re-trigger a tax prep step

`retriggerTaxPrepStep` re-runs a single stage of an existing tax prep task. It
is a backoffice operation and requires a **user** token (not a
`workspaceToken`); the schema marks it `@requiresScopes(scopes: [["user"]])`.

```graphql theme={null}
mutation RetriggerTaxPrepStep($input: RetriggerTaxPrepStepInput!) {
  retriggerTaxPrepStep(input: $input) {
    taskId
  }
}
```

### Input: `RetriggerTaxPrepStepInput`

```graphql theme={null}
input RetriggerTaxPrepStepInput {
  workspaceId: ID!
  clientId: ID!
  step: TaxPrepStep!
  software: String
  softwareClientId: String
}

enum TaxPrepStep {
  IMPORT_PRIOR_YEAR
  EXTRACT
  RECONCILE
  PRE_ENTRY_EXPORT
  DATA_ENTRY
  POST_ENTRY_EXPORT
  VALIDATE
}
```

<ParamField path="workspaceId" type="ID!" required>
  The workspace the client belongs to.
</ParamField>

<ParamField path="clientId" type="ID!" required>
  The client whose tax prep run you want to re-run a step for.
</ParamField>

<ParamField path="step" type="TaxPrepStep!" required>
  The stage to re-run: `IMPORT_PRIOR_YEAR`, `EXTRACT`, `RECONCILE`,
  `PRE_ENTRY_EXPORT`, `DATA_ENTRY`, `POST_ENTRY_EXPORT`, or `VALIDATE`.
</ParamField>

<ParamField path="software" type="String">
  The tax software provider key. Pass it when the re-triggered step writes to or
  reads from the tax software (the data-entry and export stages).
</ParamField>

<ParamField path="softwareClientId" type="String">
  The client identifier inside the tax software. Pass it alongside `software`
  for the data-entry and export stages.
</ParamField>

### Returns: `BackofficeTriggerResult`

```graphql theme={null}
type BackofficeTriggerResult {
  taskId: ID!
}
```

<ResponseField name="taskId" type="ID!">
  The ID of the task the re-triggered step belongs to. Poll it with the
  [tasks API](/apis/tasks#check-a-single-tasks-status) for the new stage's
  outcome.
</ResponseField>

<Warning>
  `retriggerTaxPrepStep` requires a **user** token (a personal backoffice
  session), not the `workspaceToken` used by the rest of the tax prep flow. The
  `workspaceToken` issued from an API key is rejected by this operation.
</Warning>

## Set a tax prep task's status

`setTaxPrepTaskStatus` forces a tax prep task into a given `TaskStatus`. Like
`retriggerTaxPrepStep`, it is a backoffice operation and requires a **user**
token (`@requiresScopes(scopes: [["user"]])`).

```graphql theme={null}
mutation SetTaxPrepTaskStatus($input: SetTaxPrepTaskStatusInput!) {
  setTaxPrepTaskStatus(input: $input) {
    taskId
  }
}
```

### Input: `SetTaxPrepTaskStatusInput`

```graphql theme={null}
input SetTaxPrepTaskStatusInput {
  workspaceId: ID!
  taskId: ID!
  status: TaskStatus!
}

enum TaskStatus {
  RUNNING
  COMPLETED
  FAILED
}
```

<ParamField path="workspaceId" type="ID!" required>
  The workspace the task belongs to.
</ParamField>

<ParamField path="taskId" type="ID!" required>
  The tax prep task whose status you want to set.
</ParamField>

<ParamField path="status" type="TaskStatus!" required>
  The status to force the task into: `RUNNING`, `COMPLETED`, or `FAILED`.
</ParamField>

### Returns: `BackofficeTriggerResult`

The same `BackofficeTriggerResult { taskId: ID! }` shape as
`retriggerTaxPrepStep`. See [above](#returns-backofficetriggerresult) for the
field.

<Warning>
  This mutation overwrites the task's `status` directly, bypassing the normal
  pipeline. Use it for backoffice recovery (for example marking a task
  `COMPLETED` after a manual fix, or `FAILED` to release a stuck run). It
  requires a **user** token, not a `workspaceToken`.
</Warning>
