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

# Generate a workpaper bundle

> Queue a server-side workpaper render, poll it to completion, download the bundle, and optionally save an edited workbook or list templates

Once a client's binder has been [browsed](/guides/recipes/browse-the-binder) and
the team is ready to package the work, the next step is to assemble a
**workpaper bundle**: a single downloadable archive that combines the binder's
PDF, leadsheets, forms, checklist, and source documents in the order the firm
wants. The bundle is rendered server-side from the binder, so you queue a render
job and poll it until the download URL is ready. This recipe walks through the
real call sequence an integration uses to generate, poll, and download a
workpaper bundle, and the two optional operations around it.

Every operation here is documented on the [Workpapers](/apis/workpapers)
reference page; this recipe sequences them rather than re-documenting the types.
Everything uses a **`workspaceToken`** (see [Authentication](/guides/authentication))
and goes to the single GraphQL endpoint:

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

<Note>
  The bundle render does **not** use the [Tasks](/apis/tasks) polling pattern. It
  has its own `workflowExecutionId` and its own `WorkpaperRenderStatus` enum
  (`RUNNING`, `COMPLETED`, `FAILED`), reached through
  `me { ... on WorkspaceUser { workspace { checkWorkpaperGenerationStatus(...) } } }`.
  Do not conflate it with `TaskStatus`. The optional
  [`triggerWorkpaperTranslate`](#optional-trigger-a-translation-task) flow at the
  end of this recipe is the one that does use the `Task` pattern.
</Note>

```mermaid theme={null}
flowchart LR
  A["Binder<br/>(subdocument IDs)"] --> B["1. Start a render<br/>(generateWorkpaperBundle)"]
  B --> C["2. Poll until done<br/>(checkWorkpaperGenerationStatus)"]
  C -->|"COMPLETED"| D["3. Download<br/>downloadUrl.url"]
  C -->|"FAILED"| E["Read errorMessage"]
  D --> F["Optional:<br/>saveTaxWorkpaperXlsx /<br/>workpaperTemplates"]
```

## 1. Start a render

Call [`generateWorkpaperBundle`](/apis/workpapers#generate-a-workpaper-bundle)
with the client's `binderId`, the `orderedGroups` outline that drives the
bundle's section order, and the flags for which sections to include. The
mutation returns a `WorkpaperRenderJob` carrying the `workflowExecutionId` you
poll in step 2.

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

```json theme={null}
{
  "input": {
    "binderId": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
    "orderedGroups": [
      {
        "bucketLabel": "Income",
        "categories": [
          {
            "category": "1099s",
            "subdocumentIds": [
              "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
              "018f9c2a-7b3d-7c3d-9a4e-2f6b1c8d2f7b"
            ]
          }
        ]
      },
      {
        "bucketLabel": "Deductions",
        "categories": [
          {
            "category": "Receipts",
            "subdocumentIds": [
              "018f9c2a-7c4d-7c3d-9a4e-2f6b1c8d3f8c"
            ]
          }
        ]
      }
    ],
    "clientName": "Jane Taxpayer",
    "includePdf": true,
    "includeLeadsheets": true,
    "leadsheetsFormat": "EXCEL",
    "includeForms": true,
    "includeChecklist": true,
    "includeSourceDocs": false
  }
}
```

<Tip>
  The `subdocumentIds` in `orderedGroups` are the `id` values from
  [`binder.subdocuments`](/apis/binder#list-the-files-in-a-binder). Run the
  [browse the binder](/guides/recipes/browse-the-binder#1-list-the-files-in-the-binder)
  recipe first to collect them, then group them into the buckets and categories
  that match the firm's preferred workpaper outline.
</Tip>

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

## 2. Poll until the render completes

Poll
[`checkWorkpaperGenerationStatus`](/apis/workpapers#check-a-bundle-render-status)
with the `workflowExecutionId` from step 1 until `status` is `COMPLETED` (then
read `downloadUrl.url`) or `FAILED` (then read `errorMessage`). The web app
polls every 1500ms with a hard timeout of 5 minutes; mirror that pattern and
treat a missing `downloadUrl` on `COMPLETED` as a failure.

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

```json theme={null}
{
  "workflowExecutionId": "018f9c2c-3d4e-7f50-9b66-7f7082901234"
}
```

<Warning>
  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. A
  workpaper render is not a `Task` and cannot be read through
  `tasks(type:, status:)`.
</Warning>

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

A `RUNNING` response (poll again) looks like:

```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": "RUNNING",
          "downloadUrl": null,
          "generatedAt": null,
          "errorMessage": null
        }
      }
    }
  }
}
```

And a `FAILED` response (stop polling and surface the error) looks like:

```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": "FAILED",
          "downloadUrl": null,
          "generatedAt": null,
          "errorMessage": "Subdocument 018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a is no longer in the binder."
        }
      }
    }
  }
}
```

## 3. Download the bundle

Once `status` is `COMPLETED`, fetch `downloadUrl.url` (or hand it to the
browser to download). The `url` is a signed link with a limited lifetime, so
download it promptly. `downloadUrl.filePath` shows where the bundle is stored
server-side and is useful for support tickets but is not a fetch target itself.

```bash theme={null}
curl -L -o jane-taxpayer-workpapers.zip \
  "https://signed.example.com/workpaper-bundles/018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c/2026-07-05.zip?token=..."
```

The web app opens `downloadUrl.url` in a new tab to trigger the browser's
download prompt; either approach works.

## Optional: Save an edited workpaper xlsx

If your integration edits the client's `tax_workpaper.xlsx` (for example an
in-browser workbook editor), commit each edit back with
[`saveTaxWorkpaperXlsx`](/apis/workpapers#save-a-tax-workpaper-xlsx). It takes
the full xlsx workbook base64-encoded plus a short `summary` (typically the
cell coordinate that changed) and returns a `ClientCommit` describing the new
git commit in the client's file store.

```graphql theme={null}
mutation SaveTaxWorkpaperXlsx($input: SaveTaxWorkpaperXlsxInput!) {
  saveTaxWorkpaperXlsx(input: $input) {
    sha
    shortSha
    message
    committedAt
  }
}
```

```json theme={null}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
    "xlsxBase64": "UEsDBBQACAgIAAAAAAAAAAAAAAAAAAAAAAA=",
    "summary": "Trial Balance!B12"
  }
}
```

<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 } }",
      "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"
      }
    }
  }
  ```
</ResponseExample>

## Optional: Trigger a translation task

When you want a fresh workpaper produced for a return type from a prior run's
output, call
[`triggerWorkpaperTranslate`](/apis/workpapers#trigger-a-workpaper-translation).
Unlike the bundle render, this is a real background [Task](/apis/tasks): it
returns a `TriggerTaskResult { taskId }` and you poll
`me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: ..., limit: 1) { id status } } } } }`
until `status` is `COMPLETED` or `FAILED`.

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

```json theme={null}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
    "returnType": "F1120",
    "templateName": "default"
  }
}
```

<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 the available template names for a return type with
[`workpaperTemplates`](/apis/workpapers#list-workpaper-templates):

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

```json theme={null}
{ "returnType": "F1120" }
```

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

<Note>
  As of this writing, `triggerWorkpaperTranslate` and `workpaperTemplates` are not
  yet wired into the web app's surface code. They exist in the live schema and
  are documented here for completeness, but verify the behavior end to end
  against your own workspace before relying on a specific shape.
</Note>

## Next steps

From here you can go deeper on the related surfaces:

* **Browse the binder first**: the `subdocumentIds` that drive
  `orderedGroups` come from `binder.subdocuments`. See
  [Browse a client's binder](/guides/recipes/browse-the-binder#1-list-the-files-in-the-binder).
* **Leadsheets export format**: when `includeLeadsheets` is `true`, set
  `leadsheetsFormat` to `EXCEL` or `CSV` (see
  [Exporting leadsheets](/apis/leadsheets#exporting-leadsheets)).
* **Task polling (for the translate flow)**: the optional
  `triggerWorkpaperTranslate` flow returns a `taskId` you poll through the
  standard `clients.tasks` pattern. See [Tasks](/apis/tasks).
* **Full reference**: every workpaper mutation, type, and `Workspace` field is
  documented on [Workpapers](/apis/workpapers).
