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

# Workpapers

> Save a tax workpaper workbook, generate a bundled workpaper download, and trigger workpaper translation

A **workpaper** is the workbook or bundled packet that captures a client's tax
work: the editable `tax_workpaper.xlsx` for a business return, or a generated
bundle (PDF, leadsheets, forms, checklist, source documents) assembled from the
binder. The workpaper API covers three operations:

1. **Save** an edited xlsx workbook back to the client's git-backed file store
   (`saveTaxWorkpaperXlsx`).
2. **Generate** a bundled workpaper download rendered server-side from the
   binder (`generateWorkpaperBundle` plus `checkWorkpaperGenerationStatus`).
3. **Translate** a return into a workpaper via a background task
   (`triggerWorkpaperTranslate`), and list the templates available for a given
   return type (`workpaperTemplates`).

All workpaper operations are reached through the [`me`](/apis/me) query resolved
as a `WorkspaceUser` (for the `Workspace.workpaperTemplates` and
`Workspace.checkWorkpaperGenerationStatus` reads) or are top-level mutations.
They require a **`workspaceToken`** (see [Authentication](/guides/authentication)).
All requests go to:

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

<Note>
  There is no top-level `workpapers` query. The two read fields
  (`workpaperTemplates` and `checkWorkpaperGenerationStatus`) live on the
  `Workspace` type, so you read them through
  `me { ... on WorkspaceUser { workspace { ... } } }`. The `workspaceToken`
  already identifies the workspace.
</Note>

## Save a tax workpaper xlsx

`saveTaxWorkpaperXlsx` commits an edited xlsx workbook back to the client's
git-backed file store as `tax_workpaper.xlsx`. The workbook is sent as a
base64-encoded `.xlsx` payload. The mutation returns a `ClientCommit` describing
the new git commit.

<Tip>
  The web app uses this as an autosave for the in-browser workpaper editor (an
  `exceljs` workbook). The `summary` argument names the cell that was just edited
  (for example `Trial Balance!B12`) and becomes part of the git commit message.
</Tip>

```graphql theme={null}
mutation SaveTaxWorkpaperXlsx($input: SaveTaxWorkpaperXlsxInput!) {
  saveTaxWorkpaperXlsx(input: $input) {
    sha
    shortSha
    message
    committedAt
    author {
      kind
      name
      email
      userId
      agentName
    }
    parents
    files {
      path
      status
      additions
      deletions
      renamedFrom
    }
    taskId
    runId
  }
}
```

### Input: `SaveTaxWorkpaperXlsxInput`

```graphql theme={null}
input SaveTaxWorkpaperXlsxInput {
  clientId: ID!
  xlsxBase64: String!
  summary: String
}
```

<ParamField path="clientId" type="ID!" required>
  The client whose `tax_workpaper.xlsx` you are saving.
</ParamField>

<ParamField path="xlsxBase64" type="String!" required>
  The full xlsx workbook, base64-encoded. The server writes this verbatim to
  `tax_workpaper.xlsx` in the client's file store.
</ParamField>

<ParamField path="summary" type="String">
  Optional. A short summary of the edit (for example the cell coordinate that
  changed). Becomes part of the git commit message.
</ParamField>

### Returns: `ClientCommit!`

A `ClientCommit` is the git commit the AI service recorded for the write. The
same type is returned by other git-backed writes (`updateSubDocumentField`,
`revertClientCommit`).

```graphql theme={null}
type ClientCommit {
  sha: String!
  shortSha: String!
  author: GitAuthor!
  committedAt: String!
  message: String!
  parents: [String!]!
  files: [GitFileChange!]!
  taskId: String
  runId: String
}

type GitAuthor {
  kind: GitAuthorKind!
  name: String!
  email: String!
  userId: ID
  agentName: String
}

type GitFileChange {
  path: String!
  status: GitChangeStatus!
  additions: Int!
  deletions: Int!
  renamedFrom: String
}

enum GitAuthorKind {
  USER
  AGENT
  SYSTEM
}

enum GitChangeStatus {
  ADDED
  MODIFIED
  DELETED
  RENAMED
  COPIED
}
```

<ResponseField name="sha" type="String!">
  The full commit SHA.
</ResponseField>

<ResponseField name="shortSha" type="String!">
  The abbreviated commit SHA.
</ResponseField>

<ResponseField name="author" type="GitAuthor!">
  Who made the commit. `kind` is `USER` for a workspace user, `AGENT` for an
  automated agent, or `SYSTEM` for a system-level write.
</ResponseField>

<ResponseField name="committedAt" type="String!">
  ISO timestamp when the commit was recorded.
</ResponseField>

<ResponseField name="message" type="String!">
  The commit message (includes the `summary` you passed in, when supplied).
</ResponseField>

<ResponseField name="parents" type="[String!]!">
  Parent commit SHAs. Empty for the initial commit.
</ResponseField>

<ResponseField name="files" type="[GitFileChange!]!">
  Files changed by this commit. For `saveTaxWorkpaperXlsx` this is the
  `tax_workpaper.xlsx` row.
</ResponseField>

<ResponseField name="taskId" type="String">
  The task ID associated with the write, when the commit was produced by a
  background task. `null` for direct user edits.
</ResponseField>

<ResponseField name="runId" type="String">
  The run ID associated with the write, when relevant. `null` for direct user
  edits.
</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 SaveTaxWorkpaperXlsx($input: SaveTaxWorkpaperXlsxInput!) { saveTaxWorkpaperXlsx(input: $input) { sha shortSha message committedAt author { kind name email userId agentName } parents files { path status additions deletions renamedFrom } taskId runId } }",
      "variables": {
        "input": {
          "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
          "xlsxBase64": "UEsDBBQACAgIAAAAAAAAAAAAAAAAAAAAAAA=",
          "summary": "Trial Balance!B12"
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "saveTaxWorkpaperXlsx": {
        "sha": "7c4d5e6f7081901a2b3c4d5e6f7081901a2b3c4d",
        "shortSha": "7c4d5e6",
        "message": "Edit Trial Balance!B12",
        "committedAt": "2026-07-05T14:08:22.000Z",
        "author": {
          "kind": "USER",
          "name": "Jane Preparer",
          "email": "jane@example-firm.com",
          "userId": "019f0fb6-3001-7900-b7bc-0d11288504b1",
          "agentName": null
        },
        "parents": [
          "3d5e6f7081901a2b3c4d5e6f7081901a2b3c4d5e"
        ],
        "files": [
          {
            "path": "tax_workpaper.xlsx",
            "status": "MODIFIED",
            "additions": 0,
            "deletions": 0,
            "renamedFrom": null
          }
        ],
        "taskId": null,
        "runId": null
      }
    }
  }
  ```
</ResponseExample>

## Generate a workpaper bundle

`generateWorkpaperBundle` queues a server-side render that assembles a bundled
workpaper download (PDF, leadsheets, forms, checklist, source documents) from
the binder. It returns a `WorkpaperRenderJob` carrying the
`workflowExecutionId` you poll with
[`checkWorkpaperGenerationStatus`](#check-a-bundle-render-status).

The bundle's table of contents and section order is driven by `orderedGroups`:
a list of buckets, each containing categories, each containing the subdocument
IDs to include. This mirrors the binder sidebar outline.

```graphql theme={null}
mutation GenerateWorkpaperBundle($input: GenerateWorkpaperBundleInput!) {
  generateWorkpaperBundle(input: $input) {
    workflowExecutionId
    status
  }
}
```

### Input: `GenerateWorkpaperBundleInput`

```graphql theme={null}
input GenerateWorkpaperBundleInput {
  binderId: ID!
  orderedGroups: [WorkpaperBucketGroupInput!]!
  clientName: String
  includePdf: Boolean
  includeLeadsheets: Boolean
  leadsheetsFormat: LeadsheetsExportFormat
  includeForms: Boolean
  includeChecklist: Boolean
  includeSourceDocs: Boolean
}

input WorkpaperBucketGroupInput {
  bucketLabel: String
  categories: [WorkpaperCategoryGroupInput!]!
}

input WorkpaperCategoryGroupInput {
  category: String!
  subdocumentIds: [ID!]!
}
```

<ParamField path="binderId" type="ID!" required>
  The client's binder ID (read it from
  `me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { binder { id } } } } }`,
  see [Binder](/apis/binder)).
</ParamField>

<ParamField path="orderedGroups" type="[WorkpaperBucketGroupInput!]!" required>
  The bucket-to-category-to-subdocument outline that drives the bundle's section
  order and PDF TOC. Each entry has a `bucketLabel` and a list of `categories`;
  each category has a `category` label and the `subdocumentIds` to include.
</ParamField>

<ParamField path="clientName" type="String">
  Optional. The client's display name, used to set the download filename (for
  example `Filed workpaper - Jane Taxpayer.pdf`).
</ParamField>

<ParamField path="includePdf" type="Boolean">
  When `true`, render a combined PDF in the bundle.
</ParamField>

<ParamField path="includeLeadsheets" type="Boolean">
  When `true`, include the client's leadsheets in the bundle, exported in the
  format set by `leadsheetsFormat`.
</ParamField>

<ParamField path="leadsheetsFormat" type="LeadsheetsExportFormat">
  The leadsheets export format: `EXCEL` or `CSV` (see
  [Leadsheets and review](/apis/leadsheets#exporting-leadsheets)). Only meaningful
  when `includeLeadsheets` is `true`.
</ParamField>

<ParamField path="includeForms" type="Boolean">
  When `true`, include the client's forms in the bundle.
</ParamField>

<ParamField path="includeChecklist" type="Boolean">
  When `true`, include the binder checklist in the bundle.
</ParamField>

<ParamField path="includeSourceDocs" type="Boolean">
  When `true`, include the binder's source documents in the bundle.
</ParamField>

### Returns: `WorkpaperRenderJob!`

```graphql theme={null}
type WorkpaperRenderJob {
  workflowExecutionId: ID!
  status: WorkpaperRenderStatus!
}

enum WorkpaperRenderStatus {
  RUNNING
  COMPLETED
  FAILED
}
```

<ResponseField name="workflowExecutionId" type="ID!">
  The render job's workflow execution ID. Pass it to
  [`checkWorkpaperGenerationStatus`](#check-a-bundle-render-status) to poll for
  completion and the download URL.
</ResponseField>

<ResponseField name="status" type="WorkpaperRenderStatus!">
  The render status: `RUNNING` while the bundle is being rendered, `COMPLETED`
  when the download is ready, or `FAILED` if the render errored. Immediately after
  `generateWorkpaperBundle` returns, this is `RUNNING`.
</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 GenerateWorkpaperBundle($input: GenerateWorkpaperBundleInput!) { generateWorkpaperBundle(input: $input) { workflowExecutionId status } }",
      "variables": {
        "input": {
          "binderId": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
          "orderedGroups": [
            {
              "bucketLabel": "Income",
              "categories": [
                {
                  "category": "1099s",
                  "subdocumentIds": [
                    "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
                    "018f9c2a-7c3d-7c3d-9a4e-2f6b1c8d2f7b"
                  ]
                }
              ]
            }
          ],
          "clientName": "Jane Taxpayer",
          "includePdf": true,
          "includeLeadsheets": true,
          "leadsheetsFormat": "EXCEL",
          "includeForms": true,
          "includeChecklist": true,
          "includeSourceDocs": false
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "generateWorkpaperBundle": {
        "workflowExecutionId": "018f9c2c-3d4e-7f50-9b66-7f7082901234",
        "status": "RUNNING"
      }
    }
  }
  ```
</ResponseExample>

## Check a bundle render status

`checkWorkpaperGenerationStatus` is a `Workspace` field that returns the current
state of a workpaper render job. Poll it with the `workflowExecutionId` returned
by [`generateWorkpaperBundle`](#generate-a-workpaper-bundle) until `status` is
`COMPLETED` (then read `downloadUrl.url`) or `FAILED` (then read `errorMessage`).

```graphql theme={null}
query CheckWorkpaperGenerationStatus($workflowExecutionId: ID!) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        id
        checkWorkpaperGenerationStatus(workflowExecutionId: $workflowExecutionId) {
          workflowExecutionId
          status
          downloadUrl {
            filePath
            url
          }
          generatedAt
          errorMessage
        }
      }
    }
  }
}
```

### Arguments

<ParamField path="workflowExecutionId" type="ID!" required>
  The render job's workflow execution ID (from
  `generateWorkpaperBundle.workflowExecutionId`).
</ParamField>

### Returns: `WorkpaperRender!`

```graphql theme={null}
type WorkpaperRender {
  workflowExecutionId: ID!
  status: WorkpaperRenderStatus!
  downloadUrl: SignedPath
  generatedAt: String
  errorMessage: String
}

type SignedPath {
  filePath: String!
  url: String!
}
```

<ResponseField name="workflowExecutionId" type="ID!">
  The render job's workflow execution ID.
</ResponseField>

<ResponseField name="status" type="WorkpaperRenderStatus!">
  `RUNNING`, `COMPLETED`, or `FAILED` (see
  [`WorkpaperRenderStatus`](#returns-workpaperrenderjob)).
</ResponseField>

<ResponseField name="downloadUrl" type="SignedPath">
  The signed download URL for the rendered bundle. Present only when `status` is
  `COMPLETED`. `SignedPath` is `{ filePath, url }`; the `url` is the one you fetch
  (or hand to the browser) to download the bundle. `null` while the render is
  running or if it failed.
</ResponseField>

<ResponseField name="generatedAt" type="String">
  ISO timestamp when the render completed. `null` while `RUNNING` or `FAILED`.
</ResponseField>

<ResponseField name="errorMessage" type="String">
  The error message when `status` is `FAILED`. `null` otherwise.
</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": "query CheckWorkpaperGenerationStatus($workflowExecutionId: ID!) { me { ... on WorkspaceUser { id workspace { id checkWorkpaperGenerationStatus(workflowExecutionId: $workflowExecutionId) { workflowExecutionId status downloadUrl { filePath url } generatedAt errorMessage } } } } }",
      "variables": {
        "workflowExecutionId": "018f9c2c-3d4e-7f50-9b66-7f7082901234"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
          "checkWorkpaperGenerationStatus": {
            "workflowExecutionId": "018f9c2c-3d4e-7f50-9b66-7f7082901234",
            "status": "COMPLETED",
            "downloadUrl": {
              "filePath": "workpaper-bundles/018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c/2026-07-05.zip",
              "url": "https://signed.example.com/workpaper-bundles/018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c/2026-07-05.zip?token=..."
            },
            "generatedAt": "2026-07-05T14:10:11.000Z",
            "errorMessage": null
          }
        }
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  The web app polls `checkWorkpaperGenerationStatus` every 1500ms with a hard
  timeout of 5 minutes, and on `COMPLETED` opens `downloadUrl.url` in a new tab.
  Mirror that pattern: poll on a short interval, stop on `COMPLETED` or `FAILED`,
  and treat a missing `downloadUrl` on `COMPLETED` as a failure.
</Tip>

<Note>
  This poll pattern is **distinct from the [Task](/apis/tasks) polling pattern**.
  Workpaper renders use a `workflowExecutionId` and the `WorkpaperRenderStatus`
  enum (`RUNNING`, `COMPLETED`, `FAILED`) on a `WorkpaperRender` shape returned by
  `Workspace.checkWorkpaperGenerationStatus`. They do **not** use the `Task` /
  `TaskStatus` type, the `clients.tasks` list, or `TaskResult` unions. Do not
  conflate the two: a workpaper render is not a `Task` and cannot be read through
  `tasks(type:, status:)`.
</Note>

## Trigger a workpaper translation

`triggerWorkpaperTranslate` kicks off a background translation task that
produces a workpaper for a client. It returns a `TriggerTaskResult` carrying the
`taskId` you poll through the standard [Task](/apis/tasks) polling pattern
(unlike the [bundle render](#check-a-bundle-render-status) above, the translate
flow is a real `Task`).

<Note>
  As of this writing, `triggerWorkpaperTranslate` and
  [`workpaperTemplates`](#list-workpaper-templates) are not yet wired into the web
  app's surface code (they exist only in the generated GraphQL types). They are
  documented here from the live schema for completeness. If you build against
  them, verify the behavior end to end against your own workspace before relying
  on a specific shape.
</Note>

```graphql theme={null}
mutation TriggerWorkpaperTranslate($input: TriggerWorkpaperTranslateInput!) {
  triggerWorkpaperTranslate(input: $input) {
    taskId
  }
}
```

### Input: `TriggerWorkpaperTranslateInput`

```graphql theme={null}
input TriggerWorkpaperTranslateInput {
  clientId: ID!
  returnType: ReturnType!
  sourceTaskType: String
  sourceRunId: ID
  templateName: String
}

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

<ParamField path="clientId" type="ID!" required>
  The client to translate a workpaper for.
</ParamField>

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

<ParamField path="sourceTaskType" type="String">
  Optional. The task type that produced the source data, when you are translating
  from the output of a prior run.
</ParamField>

<ParamField path="sourceRunId" type="ID">
  Optional. The run ID of the source run, when relevant.
</ParamField>

<ParamField path="templateName" type="String">
  Optional. A specific template to apply. List the available templates for a
  return type with [`workpaperTemplates`](#list-workpaper-templates).
</ParamField>

### Returns: `TriggerTaskResult!`

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

<ResponseField name="taskId" type="ID!">
  The background [task](/apis/tasks) ID. Poll
  `me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: ..., limit: 1) { id status } } } } }`
  until `status` is `COMPLETED` or `FAILED`. See [Tasks](/apis/tasks) for the
  polling pattern and the `TaskResult` union.
</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 TriggerWorkpaperTranslate($input: TriggerWorkpaperTranslateInput!) { triggerWorkpaperTranslate(input: $input) { taskId } }",
      "variables": {
        "input": {
          "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
          "returnType": "F1120",
          "templateName": "default"
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "triggerWorkpaperTranslate": {
        "taskId": "018f9c2c-4e5f-7f60-9c77-8082901234ab"
      }
    }
  }
  ```
</ResponseExample>

## List workpaper templates

`workpaperTemplates` is a `Workspace` field that returns the list of workpaper
template names available for a given return type. Pass a template name as
`templateName` to [`triggerWorkpaperTranslate`](#trigger-a-workpaper-translation).

```graphql theme={null}
query WorkpaperTemplates($returnType: ReturnType!) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        id
        workpaperTemplates(returnType: $returnType)
      }
    }
  }
}
```

### Arguments

<ParamField path="returnType" type="ReturnType!" required>
  The return form to list templates for: `F1040`, `F1041`, `F1065`, `F1120`,
  `F1120S`, or `F990`.
</ParamField>

### Returns: `[String!]!`

A list of template name strings. Pass any one of them as `templateName` to
`triggerWorkpaperTranslate`.

<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 WorkpaperTemplates($returnType: ReturnType!) { me { ... on WorkspaceUser { id workspace { id workpaperTemplates(returnType: $returnType) } } } }",
      "variables": { "returnType": "F1120" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
          "workpaperTemplates": ["default", "detailed"]
        }
      }
    }
  }
  ```
</ResponseExample>
