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

# Tasks

> List background tasks and check the status of a single task

A **task** is a background job Filed runs for a workspace: binder ingestion, tax
prep, tax review, tax advisor, or chat. Mutations like
[`createClient`](/apis/clients#create-a-client) and
[`addClientDocuments`](/apis/clients#add-documents-to-a-client) return a
`taskId`; you use the tasks API to follow that work to completion.

Tasks are reached through the [`me`](/apis/me) query as a `WorkspaceUser`, so
authenticate with a **`workspaceToken`** (see
[Authentication](/guides/authentication)). All requests go to:

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

## The `Task` type

```graphql theme={null}
type Task {
  id: ID!
  type: TaskType!
  status: TaskStatus!
  startedAt: String!
  completedAt: String
  errorMessage: String
  attributes: [TaskAttribute!]!
  subTasks: [SubTask!]!
  result: TaskResult!
  client: ClientShortDetails
  triggeredBy: UserShortDetails
}

enum TaskType {
  BINDER
  TAX_PREP
  TAX_REVIEW
  TAX_ADVISOR
  CHAT
}

enum TaskStatus {
  RUNNING
  COMPLETED
  FAILED
}
```

<ResponseField name="id" type="ID!">
  The task's unique identifier. This is the value returned as `taskId` by the
  mutations that start work.
</ResponseField>

<ResponseField name="type" type="TaskType!">
  What kind of work this is: `BINDER`, `TAX_PREP`, `TAX_REVIEW`, `TAX_ADVISOR`, or
  `CHAT`.
</ResponseField>

<ResponseField name="status" type="TaskStatus!">
  `RUNNING`, `COMPLETED`, or `FAILED`. Poll this to know when work finishes.
</ResponseField>

<ResponseField name="startedAt" type="String!">
  ISO 8601 timestamp of when the task started.
</ResponseField>

<ResponseField name="completedAt" type="String">
  ISO 8601 timestamp of when the task finished. `null` while `RUNNING`.
</ResponseField>

<ResponseField name="errorMessage" type="String">
  A human-readable error message when `status` is `FAILED`. `null` otherwise.
</ResponseField>

<ResponseField name="attributes" type="[TaskAttribute!]!">
  Arbitrary `name`/`value` metadata pairs describing the task.
</ResponseField>

<ResponseField name="subTasks" type="[SubTask!]!">
  The individual stages of the task, each with its own status. Use these for
  granular progress while a task is `RUNNING`.
</ResponseField>

<ResponseField name="result" type="TaskResult!">
  The typed result of the task, resolved by `type`. See [Task
  result](#task-result).
</ResponseField>

<ResponseField name="client" type="ClientShortDetails">
  The client this task belongs to (`id`, `name`), when applicable.
</ResponseField>

<ResponseField name="triggeredBy" type="UserShortDetails">
  The user who started the task (`id`, `name`, `email`).
</ResponseField>

### Supporting types

```graphql theme={null}
type TaskAttribute {
  name: String!
  value: String!
}

type SubTask {
  id: ID!
  type: SubTaskType!
  status: TaskStatus!
  startedAt: String!
  completedAt: String
  errorMessage: String
  errorCode: DataEntryErrorCode
}

type ClientShortDetails {
  id: ID!
  name: String!
}

type UserShortDetails {
  id: ID!
  name: String!
  email: String!
  emailHash: String
}
```

<ResponseField name="SubTask.type" type="SubTaskType!">
  The stage, for example `CONVERT_DOCUMENTS`, `CLASSIFY_SUBDOCS`,
  `EXTRACT_SUBDOCS`, or `EXPORT_AND_INDEX`. The full set of stages depends on the
  parent task's `type`.
</ResponseField>

<ResponseField name="SubTask.errorCode" type="DataEntryErrorCode">
  A machine-readable code when a data-entry stage fails, for example
  `INVALID_CREDENTIALS`, `CLIENT_NOT_FOUND`, or `TIMEOUT`. `null` otherwise.
</ResponseField>

## List tasks

Read `workspace.tasks` to list tasks across the whole workspace. Filter, page,
and sort with the arguments below.

```graphql theme={null}
query ListTasks($filters: TaskFilters, $sortBy: SortBy, $limit: Int, $offset: Int) {
  me {
    ... on WorkspaceUser {
      workspace {
        tasks(filters: $filters, sortBy: $sortBy, limit: $limit, offset: $offset) {
          id
          type
          status
          startedAt
          completedAt
          client {
            id
            name
          }
          triggeredBy {
            id
            name
          }
        }
      }
    }
  }
}
```

### Arguments

```graphql theme={null}
input TaskFilters {
  type: TaskType
  status: TaskStatus
  triggeredBy: ID
  search: String
}

input SortBy {
  field: String!
  order: SortByOrder!   # ASC | DESC
}
```

<ParamField path="filters.type" type="TaskType">
  Return only tasks of this type.
</ParamField>

<ParamField path="filters.status" type="TaskStatus">
  Return only tasks in this status (`RUNNING`, `COMPLETED`, `FAILED`).
</ParamField>

<ParamField path="filters.triggeredBy" type="ID">
  Return only tasks started by this user.
</ParamField>

<ParamField path="filters.search" type="String">
  Free-text search over task metadata.
</ParamField>

<ParamField path="sortBy" type="SortBy">
  Sort order, for example `{ "field": "startedAt", "order": "DESC" }`.
</ParamField>

<ParamField path="limit" type="Int">
  Maximum number of tasks to return.
</ParamField>

<ParamField path="offset" type="Int">
  Number of tasks to skip, for pagination.
</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 ListTasks($filters: TaskFilters, $sortBy: SortBy, $limit: Int) { me { ... on WorkspaceUser { workspace { tasks(filters: $filters, sortBy: $sortBy, limit: $limit) { id type status startedAt completedAt client { id name } triggeredBy { id name } } } } } }",
      "variables": {
        "filters": { "type": "BINDER", "status": "RUNNING" },
        "sortBy": { "field": "startedAt", "order": "DESC" },
        "limit": 20
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "tasks": [
            {
              "id": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
              "type": "BINDER",
              "status": "RUNNING",
              "startedAt": "2026-07-04T09:15:00.000Z",
              "completedAt": null,
              "client": {
                "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
                "name": "Jane Taxpayer"
              },
              "triggeredBy": {
                "id": "019f0fb6-26e9-74b7-a842-cb43a2a41682",
                "name": "Jane Preparer"
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

## Check a single task's status

There is no `task(id:)` query. To follow one task (for example the `taskId`
returned by `createClient` or `addClientDocuments`), list the tasks for its
client with `client.tasks` and read the entry whose `id` matches. Because a
client's task list is small and typed, this is the reliable way to poll a
specific task.

```graphql theme={null}
query ClientTaskStatus($clientId: ID!, $type: TaskType) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: $type) {
            id
            status
            startedAt
            completedAt
            errorMessage
            subTasks {
              type
              status
            }
          }
        }
      }
    }
  }
}
```

`Client.tasks` accepts these arguments:

```graphql theme={null}
tasks(type: TaskType, status: TaskStatus, triggeredBy: ID, limit: Int): [Task!]!
```

<ParamField path="type" type="TaskType">
  Narrow to one task type, for example `BINDER` to watch document ingestion.
</ParamField>

<ParamField path="limit" type="Int">
  Cap the number of tasks returned (for example `1` for the most recent).
</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 ClientTaskStatus($clientId: ID!, $type: TaskType) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: $type) { id status startedAt completedAt errorMessage subTasks { type status } } } } } } }",
      "variables": { "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c", "type": "BINDER" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "clients": [
            {
              "tasks": [
                {
                  "id": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                  "status": "COMPLETED",
                  "startedAt": "2026-07-04T09:15:00.000Z",
                  "completedAt": "2026-07-04T09:17:42.000Z",
                  "errorMessage": null,
                  "subTasks": [
                    { "type": "CONVERT_DOCUMENTS", "status": "COMPLETED" },
                    { "type": "CLASSIFY_SUBDOCS", "status": "COMPLETED" },
                    { "type": "EXTRACT_SUBDOCS", "status": "COMPLETED" },
                    { "type": "EXPORT_AND_INDEX", "status": "COMPLETED" }
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  Poll on an interval (for example every few seconds) until `status` is no longer
  `RUNNING`. `COMPLETED` means the work succeeded; `FAILED` means it did not, and
  `errorMessage` explains why. `subTasks` show which stage is currently running.
</Tip>

## Task result

Every task carries a typed `result`. `TaskResult` is a union whose concrete type
is determined by the task's `type`. Select fields with an inline fragment on the
member you expect, and read `__typename` to know which one you got.

```graphql theme={null}
union TaskResult =
    TaskUnknownResult
  | TaskTaxPrepResult
  | TaskTaxReviewResult
  | TaskTaxAdvisorResult
```

<ResponseField name="TaskUnknownResult" type="object">
  The fallback result, including for `BINDER` and `CHAT` tasks.

  ```graphql theme={null}
  type TaskUnknownResult {
    message: String!
  }
  ```

  <Expandable title="fields">
    <ResponseField name="message" type="String!">
      A human-readable description of the outcome.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="TaskTaxPrepResult" type="object">
  Returned for `TAX_PREP` tasks: a summary plus document/form counts and the
  individual review items.

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

  <Expandable title="fields">
    <ResponseField name="taxYear" type="Int!">The tax year.</ResponseField>
    <ResponseField name="returnType" type="ReturnType!">The return form (see [ReturnType](/apis/clients#the-client-type)).</ResponseField>
    <ResponseField name="summary" type="String!">A summary of the tax prep result.</ResponseField>
    <ResponseField name="documentCount" type="Int!">Number of documents processed.</ResponseField>
    <ResponseField name="extractedFormCount" type="Int!">Number of forms extracted.</ResponseField>
    <ResponseField name="reviewItemCount" type="Int!">Number of review items produced.</ResponseField>

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

<ResponseField name="TaskTaxReviewResult" type="object">
  Returned for `TAX_REVIEW` tasks: a summary plus issue breakdowns by severity and
  by form.

  ```graphql theme={null}
  type TaskTaxReviewResult {
    taxYear: Int!
    returnType: ReturnType!
    summary: String!
    issueCountBySeverity: IssueCountBySeverity!
    issueCountByForm: [FormIssueCount!]!
  }

  type IssueCountBySeverity {
    critical: Int!
    high: Int!
    medium: Int!
    low: Int!
  }

  type FormIssueCount {
    form: String!
    count: Int!
  }
  ```

  <Expandable title="fields">
    <ResponseField name="taxYear" type="Int!">The tax year.</ResponseField>
    <ResponseField name="returnType" type="ReturnType!">The return form.</ResponseField>
    <ResponseField name="summary" type="String!">A summary of the review result.</ResponseField>

    <ResponseField name="issueCountBySeverity" type="IssueCountBySeverity!">
      Issue counts by severity: `critical`, `high`, `medium`, `low` (all `Int!`).
    </ResponseField>

    <ResponseField name="issueCountByForm" type="[FormIssueCount!]!">
      Issue counts per form, each `{ form: String!, count: Int! }`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="TaskTaxAdvisorResult" type="object">
  Returned for `TAX_ADVISOR` tasks: a summary plus strategy counts. The `by*`
  fields are `JSON` maps.

  ```graphql theme={null}
  type TaskTaxAdvisorResult {
    taxYear: Int!
    returnType: ReturnType!
    summary: String!
    strategyTotal: Int!
    byDomain: JSON!
    bySavingsHorizon: JSON!
    estimatedSavingsCentsByHorizon: JSON!
  }
  ```

  <Expandable title="fields">
    <ResponseField name="taxYear" type="Int!">The tax year.</ResponseField>
    <ResponseField name="returnType" type="ReturnType!">The return form.</ResponseField>
    <ResponseField name="summary" type="String!">A summary of the advisor result.</ResponseField>
    <ResponseField name="strategyTotal" type="Int!">Total number of strategies.</ResponseField>
    <ResponseField name="byDomain" type="JSON!">Strategy counts keyed by domain.</ResponseField>
    <ResponseField name="bySavingsHorizon" type="JSON!">Strategy counts keyed by savings horizon.</ResponseField>
    <ResponseField name="estimatedSavingsCentsByHorizon" type="JSON!">Estimated savings (in cents) keyed by horizon.</ResponseField>
  </Expandable>
</ResponseField>

Because the members share `taxYear`, `returnType`, and `summary`, you can select
those on each fragment and branch on `__typename`:

```graphql theme={null}
query TaskResult($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: TAX_PREP, limit: 1) {
            id
            status
            result {
              __typename
              ... on TaskTaxPrepResult {
                summary
                documentCount
                extractedFormCount
                reviewItems {
                  severity
                  category
                  description
                }
              }
              ... on TaskTaxReviewResult {
                summary
                issueCountBySeverity {
                  critical
                  high
                  medium
                  low
                }
                issueCountByForm {
                  form
                  count
                }
              }
              ... on TaskUnknownResult {
                message
              }
            }
          }
        }
      }
    }
  }
}
```
