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

# Review a return and sign off

> Read a client's leadsheets and review items, then record reviewer sign-offs with the real document-message write surface

After a tax prep run completes, the work moves to review: read the leadsheets
the run produced, walk the issues it flagged, and record sign-offs on the sheets
and rows you have reviewed. This recipe is the minimal call sequence for that
flow against one client and one completed tax prep task.

Every call uses a **`workspaceToken`** (see [Authentication](/guides/authentication))
and goes to the single GraphQL endpoint:

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

```mermaid theme={null}
flowchart LR
  A["Completed<br/>TAX_PREP task"] -->|"leadsheets(taskId)"| B["Leadsheets tree"]
  B --> C["Sheets + issues<br/>+ rows + traces"]
  C --> D{"Reviewer<br/>signs off?"}
  D -->|yes| E["createDocumentMessage<br/>type=activity, markType=signoff"]
  D -->|undo| F["hideDocumentMessage<br/>(id of sign-off)"]
  E --> G["Refetch leadsheets<br/>resolved/issueCount recompute"]
  F --> G
  G --> H["Done"]
```

This recipe does not re-document the types it touches. For the full
`Leadsheets` / `Leadsheet` / `LeadsheetSheetIssue` / `LeadsheetFieldRow` /
`LeadsheetTrace` field list, and for the `DocumentMessage` annotation and
thread APIs, see [Leadsheets and review](/apis/leadsheets). For how to start
and poll the `TAX_PREP` task whose `taskId` you feed into this flow, see
[Run tax prep end to end](/guides/recipes/run-tax-prep) and [Tasks](/apis/tasks).

## 1. Read the leadsheets for a completed run

A leadsheets tree is the output of a `TAX_PREP` (or `TAX_REVIEW`) background
task. Pass that task's `taskId` to `binder.leadsheets(taskId:)` to read the
exact tree that run produced. The query below mirrors the web app's
`GetBinderLeadsheets` document: it walks sheets, their issues and per-severity
counts, and the field rows with their traces and sources.

```graphql theme={null}
query GetClientLeadsheets($clientId: ID!, $taskId: ID) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        id
        clients(filters: { ids: [$clientId] }) {
          id
          binder {
            id
            leadsheets(taskId: $taskId) {
              id
              documentPath
              issueCount
              returnType
              taxYear
              sheets {
                id
                formName
                category
                issueCount {
                  critical
                  high
                  medium
                  low
                }
                issues {
                  id
                  markType
                  anchorPoint
                  body
                  createdBy
                  createdAt
                  hiddenAt
                  resolved
                }
                fields {
                  id
                  rows {
                    id
                    fieldPath
                    value
                    trace {
                      reasoning
                      sources {
                        subdocId
                        label
                        amount
                        page
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

```json theme={null}
{
  "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
  "taskId": "018f9c2b-7c4d-7e10-9a22-6b3c4d5e6f70"
}
```

<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 GetClientLeadsheets($clientId: ID!, $taskId: ID) { me { ... on WorkspaceUser { id workspace { id clients(filters: { ids: [$clientId] }) { id binder { id leadsheets(taskId: $taskId) { id documentPath issueCount returnType taxYear sheets { id formName category issueCount { critical high medium low } issues { id markType anchorPoint body createdBy createdAt hiddenAt resolved } fields { id rows { id fieldPath value trace { reasoning sources { subdocId label amount page } } } } } } } } } } } } }",
      "variables": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "taskId": "018f9c2b-7c4d-7e10-9a22-6b3c4d5e6f70"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "id": "019f0fb6-3001-7900-b7bc-0d11288504b1",
          "clients": [
            {
              "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
              "binder": {
                "id": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
                "leadsheets": {
                  "id": "018f9c2b-8e10-7f20-9a33-7c4d5e6f7081",
                  "documentPath": "leadsheets",
                  "issueCount": 3,
                  "returnType": "F1040",
                  "taxYear": 2025,
                  "sheets": [
                    {
                      "id": "leadsheets/schedule_b/0",
                      "formName": "Schedule B",
                      "category": "income",
                      "issueCount": {
                        "critical": 0,
                        "high": 1,
                        "medium": 1,
                        "low": 1
                      },
                      "issues": [
                        {
                          "id": "018f9c2c-1a2b-7f30-9b44-7d5e6f708190",
                          "markType": "flag",
                          "anchorPoint": { "page": 1, "coordinates": { "x": 0, "y": 0 } },
                          "body": "Schedule B interest total differs from 1099-INT sum by $42.",
                          "createdBy": "019f0fb6-3001-7900-b7bc-0d11288504b1",
                          "createdAt": "2026-07-04T10:12:00.000Z",
                          "hiddenAt": null,
                          "resolved": false
                        }
                      ],
                      "fields": [
                        {
                          "id": "leadsheets/schedule_b/0/interest_income",
                          "rows": [
                            {
                              "id": "leadsheets/schedule_b/0/interest_income/0",
                              "fieldPath": "interest_income.total",
                              "value": "428.00",
                              "trace": {
                                "reasoning": "Total interest is the sum of the three 1099-INT sources in the binder.",
                                "sources": [
                                  {
                                    "subdocId": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
                                    "label": "1099-INT from Acme Broker",
                                    "amount": "210.00",
                                    "page": 1
                                  },
                                  {
                                    "subdocId": "018f9c2a-7c3d-7c3d-9a4e-2f6b1c8d2f7b",
                                    "label": "1099-INT from Globex",
                                    "amount": "176.00",
                                    "page": 1
                                  },
                                  {
                                    "subdocId": "018f9c2a-8e2f-7c3d-9a4e-2f6b1c8d3f8c",
                                    "label": "1099-INT from Initech",
                                    "amount": "42.00",
                                    "page": 1
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

The shape above matches what the web app's binder and review screens read. Use
`Leadsheets.issueCount` for a quick "needs attention" number, drill into
`Leadsheet.issueCount` for per-severity counts, then page through
`LeadsheetSheetIssue` for per-issue detail. `resolved` is server-computed from
the document messages on the sheet, so refresh the query after every sign-off or
undo (see step 4).

<Note>
  There is no top-level `leadsheets` query. Leadsheets belong to a client's
  binder, so you read them through
  `me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { binder { leadsheets(taskId: $taskId) { ... } } } } } }`.
  The `workspaceToken` already identifies the workspace. See
  [Leadsheets and review](/apis/leadsheets#read-a-clients-leadsheets) for the
  full field list.
</Note>

## 2. Record a sign-off

<Warning>
  There is **no** `signOffSubDocuments` mutation. The schema defines an input
  type called `SignOffSubDocumentsInput`, but no field on `Mutation` is wired to
  it. The real sign-off write surface is
  [`createDocumentMessage`](/apis/leadsheets#sign-off-on-a-sheet-or-row) with
  `type: "activity"` and `markType: "signoff"`, one call per subdocument you are
  signing off on. Do not look for a `signOffSubDocuments` mutation, it does not
  exist.
</Warning>

A sign-off is a `createDocumentMessage` call anchored to the subdocument path
you are signing off on. The `anchorPoint` carries the reviewer's `level` and
`user_role` so the UI renders the sign-off with the right label.

```graphql theme={null}
mutation CreateDocumentMessage($input: CreateDocumentMessageInput!) {
  createDocumentMessage(input: $input) {
    id
    documentPath
    type
    markType
    anchorPoint
    body
    createdBy
    createdAt
    hiddenAt
  }
}
```

```json theme={null}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
    "documentPath": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
    "type": "activity",
    "markType": "signoff",
    "anchorPoint": {
      "page": 1,
      "coordinates": { "x": 0, "y": 0 },
      "level": 2,
      "user_role": "l2"
    }
  }
}
```

<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 CreateDocumentMessage($input: CreateDocumentMessageInput!) { createDocumentMessage(input: $input) { id documentPath type markType anchorPoint body createdBy createdAt hiddenAt } }",
      "variables": {
        "input": {
          "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
          "documentPath": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
          "type": "activity",
          "markType": "signoff",
          "anchorPoint": {
            "page": 1,
            "coordinates": { "x": 0, "y": 0 },
            "level": 2,
            "user_role": "l2"
          }
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "createDocumentMessage": {
        "id": "018f9c2c-2b3c-7f40-9b55-7e6f70829001",
        "documentPath": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
        "type": "activity",
        "markType": "signoff",
        "anchorPoint": {
          "page": 1,
          "coordinates": { "x": 0, "y": 0 },
          "level": 2,
          "user_role": "l2"
        },
        "body": null,
        "createdBy": "019f0fb6-3001-7900-b7bc-0d11288504b1",
        "createdAt": "2026-07-04T18:22:01.000Z",
        "hiddenAt": null
      }
    }
  }
  ```
</ResponseExample>

Save the returned `id`. You pass it to `hideDocumentMessage` in step 3 if you
ever need to undo the sign-off.

<Tip>
  `documentPath` for a subdocument sign-off is the subdocument's ID (the same
  value you read as `LeadsheetFieldRow.id` parent path, or the anchor a
  `LeadsheetSheetIssue` is tied to). The web app signs off one subdocument at a
  time, one `createDocumentMessage` call per subdocument.
</Tip>

## 3. Undo a sign-off

Undoing a sign-off is a soft-hide of the sign-off `DocumentMessage`. The
sign-off row stays in history (with `hiddenAt` set), and the leadsheets query's
`resolved` and `issueCount` recomputation backs it out.

```graphql theme={null}
mutation HideDocumentMessage($id: ID!) {
  hideDocumentMessage(id: $id) {
    id
    documentPath
    type
    markType
    hiddenAt
    hiddenBy
  }
}
```

```json theme={null}
{
  "id": "018f9c2c-2b3c-7f40-9b55-7e6f70829001"
}
```

<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 HideDocumentMessage($id: ID!) { hideDocumentMessage(id: $id) { id documentPath type markType hiddenAt hiddenBy } }",
      "variables": { "id": "018f9c2c-2b3c-7f40-9b55-7e6f70829001" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "hideDocumentMessage": {
        "id": "018f9c2c-2b3c-7f40-9b55-7e6f70829001",
        "documentPath": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
        "type": "activity",
        "markType": "signoff",
        "hiddenAt": "2026-07-04T18:30:00.000Z",
        "hiddenBy": "019f0fb6-3001-7900-b7bc-0d11288504b1"
      }
    }
  }
  ```
</ResponseExample>

## 4. Refetch the leadsheets query

`LeadsheetSheetIssue.resolved` and `Leadsheet.issueCount` (and the per-severity
`Leadsheet.issueCount { critical high medium low }` breakdown) are
server-computed from the document messages on the sheet. After any sign-off or
undo, refetch the `GetClientLeadsheets` query from step 1 so the server
recomputes them. The web app does exactly this: it refetches the leadsheets
query (and the missing-items query) whenever the review task ends or a sign-off
is toggled.

```graphql theme={null}
# Same query as step 1. Re-run it with the same { clientId, taskId }.
query GetClientLeadsheets($clientId: ID!, $taskId: ID) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          binder {
            leadsheets(taskId: $taskId) {
              id
              issueCount
              sheets {
                id
                formName
                issueCount { critical high medium low }
                issues { id resolved }
                signOffs { id markType hiddenAt }
              }
            }
          }
        }
      }
    }
  }
}
```

After the refetch, the issue you signed off on shows `resolved: true`, the
sheet's `issueCount` drops by one, and the `Leadsheets.issueCount` total
reflects the new state. If you undo a sign-off, the same refetch backs the
issue out again.

## See also

* [Leadsheets and review](/apis/leadsheets) for the full `Leadsheets`,
  `Leadsheet`, `LeadsheetSheetIssue`, `LeadsheetFieldRow`, and `LeadsheetTrace`
  type definitions, plus the `DocumentMessage` annotation and thread APIs.
* [Document messages](/apis/document-messages#the-documentmessage-type)
  for the broader `DocumentMessage` API (annotations, flags, threads,
  update/unhide) that sign-offs are one use of.
* [Tasks](/apis/tasks) for the polling mechanics behind the `TAX_PREP` task
  whose `taskId` you feed into `leadsheets(taskId:)`.
* [Run tax prep end to end](/guides/recipes/run-tax-prep) for the recipe that
  produces the review items this flow consumes.
