Once a client’s binder has been browsed 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
reference page; this recipe sequences them rather than re-documenting the types.
Everything uses a workspaceToken (see Authentication)
and goes to the single GraphQL endpoint:
https://router.apps.filed.com/graphql
The bundle render does not use the 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 flow at the
end of this recipe is the one that does use the Task pattern.
1. Start a render
Call generateWorkpaperBundle
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.
mutation GenerateWorkpaperBundle($input: GenerateWorkpaperBundleInput!) {
generateWorkpaperBundle(input: $input) {
workflowExecutionId
status
}
}
{
"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
}
}
The subdocumentIds in orderedGroups are the id values from
binder.subdocuments. Run the
browse the binder
recipe first to collect them, then group them into the buckets and categories
that match the firm’s preferred workpaper outline.
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
}
}
}'
{
"data": {
"generateWorkpaperBundle": {
"workflowExecutionId": "018f9c2c-3d4e-7f50-9b66-7f7082901234",
"status": "RUNNING"
}
}
}
2. Poll until the render completes
Poll
checkWorkpaperGenerationStatus
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.
query CheckWorkpaperGenerationStatus($workflowExecutionId: ID!) {
me {
... on WorkspaceUser {
id
workspace {
id
checkWorkpaperGenerationStatus(workflowExecutionId: $workflowExecutionId) {
workflowExecutionId
status
downloadUrl {
filePath
url
}
generatedAt
errorMessage
}
}
}
}
}
{
"workflowExecutionId": "018f9c2c-3d4e-7f50-9b66-7f7082901234"
}
This poll pattern is distinct from the Task 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:).
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"
}
}'
{
"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
}
}
}
}
}
A RUNNING response (poll again) looks like:
{
"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:
{
"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.
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. 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.
mutation SaveTaxWorkpaperXlsx($input: SaveTaxWorkpaperXlsxInput!) {
saveTaxWorkpaperXlsx(input: $input) {
sha
shortSha
message
committedAt
}
}
{
"input": {
"clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
"xlsxBase64": "UEsDBBQACAgIAAAAAAAAAAAAAAAAAAAAAAA=",
"summary": "Trial Balance!B12"
}
}
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"
}
}
}'
{
"data": {
"saveTaxWorkpaperXlsx": {
"sha": "7c4d5e6f7081901a2b3c4d5e6f7081901a2b3c4d",
"shortSha": "7c4d5e6",
"message": "Edit Trial Balance!B12",
"committedAt": "2026-07-05T14:08:22.000Z"
}
}
}
Optional: Trigger a translation task
When you want a fresh workpaper produced for a return type from a prior run’s
output, call
triggerWorkpaperTranslate.
Unlike the bundle render, this is a real background Task: 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.
mutation TriggerWorkpaperTranslate($input: TriggerWorkpaperTranslateInput!) {
triggerWorkpaperTranslate(input: $input) {
taskId
}
}
{
"input": {
"clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
"returnType": "F1120",
"templateName": "default"
}
}
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"
}
}
}'
{
"data": {
"triggerWorkpaperTranslate": {
"taskId": "018f9c2c-4e5f-7f60-9c77-8082901234ab"
}
}
}
List the available template names for a return type with
workpaperTemplates:
query WorkpaperTemplates($returnType: ReturnType!) {
me {
... on WorkspaceUser {
id
workspace {
id
workpaperTemplates(returnType: $returnType)
}
}
}
}
{ "returnType": "F1120" }
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" }
}'
{
"data": {
"me": {
"id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
"workspace": {
"id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
"workpaperTemplates": ["default", "detailed"]
}
}
}
}
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.
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.
- Leadsheets export format: when
includeLeadsheets is true, set
leadsheetsFormat to EXCEL or CSV (see
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.
- Full reference: every workpaper mutation, type, and
Workspace field is
documented on Workpapers.