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

# Leadsheets and review

> Read a client's leadsheets, drill into field-level trace and sourcing, and sign off on review items

**Leadsheets** are the per-form workpapers a tax prep or review run produces:
each leadsheet maps a tax form (for example Schedule B) to the binder sources
that feed every line, flags the issues the run found on that form, and carries
the sign-offs a reviewer records against it. Read them with the `leadsheets`
field on the client's [binder](/apis/binder), and record sign-offs with the
`createDocumentMessage` mutation (a sign-off is an `activity` document message
with `markType: "signoff"`, see [Document messages](/apis/document-messages)).

Leadsheets are reached through the [`me`](/apis/me) query resolved as a
`WorkspaceUser`, so reading them and recording sign-offs both require a
**`workspaceToken`** (see [Authentication](/guides/authentication)). All
requests go to:

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

<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.
</Note>

<Note>
  A leadsheets tree is the output of a `TAX_PREP` (or `TAX_REVIEW`) background
  task. Pass that task's `taskId` to `leadsheets(taskId:)` to read the exact tree
  that run produced. See [Tasks](/apis/tasks) for how to start and poll a run, and
  [Tax prep](/apis/tax-prep) for the `TaskTaxPrepResult` shape (whose `reviewItems`
  are the same review items surfaced here as `LeadsheetSheetIssue`).
</Note>

## The `Leadsheets` type

The top-level leadsheets container for one client and one task run.

```graphql theme={null}
type Leadsheets {
  id: ID!
  task: Task!
  documentPath: String!
  sheets(id: ID): [Leadsheet!]!
  issueCount: Int!
  returnType: String!
  taxYear: Int
}
```

<ResponseField name="id" type="ID!">
  The leadsheets container ID.
</ResponseField>

<ResponseField name="task" type="Task!">
  The background [task](/apis/tasks) that produced this leadsheets tree. Its
  `type` is `TAX_PREP` or `TAX_REVIEW`; its `status` tells you whether the tree is
  still being built (`RUNNING`) or ready to read (`COMPLETED`).
</ResponseField>

<ResponseField name="documentPath" type="String!">
  The binder path the leadsheets container lives at.
</ResponseField>

<ResponseField name="sheets" type="[Leadsheet!]!">
  The per-form leadsheets. Pass `sheets(id: $id)` to fetch a single leadsheet by
  ID; omit the argument to list them all. See [`Leadsheet`](#the-leadsheet-type).
</ResponseField>

<ResponseField name="issueCount" type="Int!">
  Total number of unresolved issues across every sheet. Use this as a quick
  "needs attention" count before paging into `sheets`.
</ResponseField>

<ResponseField name="returnType" type="String!">
  The return form this run targeted, for example `"F1040"`. This is a `String`
  here, not the `ReturnType` enum (see [clients](/apis/clients#the-client-type)).
</ResponseField>

<ResponseField name="taxYear" type="Int">
  The tax year this run targeted, for example `2025`. Nullable: older runs may not
  record it.
</ResponseField>

## The `Leadsheet` type

One form's leadsheet: its fields, the issues the run flagged on it, and the
sign-offs reviewers have recorded against it.

```graphql theme={null}
type Leadsheet {
  id: ID!
  formName: String!
  category: String!
  issueCount: IssueCountBySeverity!
  issues: [LeadsheetSheetIssue!]!
  signOffs: [DocumentMessage!]!
  fields: [LeadsheetField!]!
}
```

<ResponseField name="id" type="ID!">
  The leadsheet ID. Pass it to `Leadsheets.sheets(id:)` to fetch this sheet alone.
  The ID encodes the form name and shard index (for example
  `leadsheets/schedule_b/0`).
</ResponseField>

<ResponseField name="formName" type="String!">
  The tax form this leadsheet covers, for example `"Schedule B"` or `"Form 1040"`.
</ResponseField>

<ResponseField name="category" type="String!">
  The grouping category the binder assigns this form to (for example `income` or
  `deductions`).
</ResponseField>

<ResponseField name="issueCount" type="IssueCountBySeverity!">
  Issue counts broken down by severity. See
  [`IssueCountBySeverity`](#the-issuecountbyseverity-type).
</ResponseField>

<ResponseField name="issues" type="[LeadsheetSheetIssue!]!">
  The issues the run flagged on this sheet. See
  [`LeadsheetSheetIssue`](#the-leadsheetsheetissue-type).
</ResponseField>

<ResponseField name="signOffs" type="[DocumentMessage!]!">
  Sign-off messages reviewers have recorded against this sheet. Each entry is a
  `DocumentMessage` with `markType: "signoff"` (see
  [Sign off on a sheet or row](#sign-off-on-a-sheet-or-row)). A sheet with no
  sign-offs returns an empty array.
</ResponseField>

<ResponseField name="fields" type="[LeadsheetField!]!">
  The form's fields, each with its rows of values, prior-year values, source
  anchors, and traces. See [`LeadsheetField`](#the-leadsheetfield-type).
</ResponseField>

## The `LeadsheetSheetIssue` type

One issue the run flagged on a sheet: a mismatch, a missing form, a value the
extractor was not confident about, and so on.

```graphql theme={null}
type LeadsheetSheetIssue {
  id: String!
  ruleId: String!
  severity: Severity!
  category: String!
  title: String!
  description: String
  evidence: String
  expectedValue: String
  actualValue: String
  fieldPath: String
  lineRef: String
  columnRef: String
  mappingNote: String
  binderMessageId: String
  resolved: Boolean!
}

enum Severity {
  CRITICAL
  HIGH
  MEDIUM
  LOW
}
```

<ResponseField name="id" type="String!">
  The issue's unique identifier.
</ResponseField>

<ResponseField name="ruleId" type="String!">
  The rule that fired this issue. Stable across runs of the same rule set, so you
  can use it to deduplicate or track an issue across re-runs.
</ResponseField>

<ResponseField name="severity" type="Severity!">
  How blocking the issue is: `CRITICAL`, `HIGH`, `MEDIUM`, or `LOW`.
</ResponseField>

<ResponseField name="category" type="String!">
  A machine-readable grouping, for example `value_mismatch` or `missing_form`.
</ResponseField>

<ResponseField name="title" type="String!">
  A short, human-readable summary of the issue.
</ResponseField>

<ResponseField name="description" type="String">
  A longer explanation. Nullable: some rules only emit a `title`.
</ResponseField>

<ResponseField name="evidence" type="String">
  The evidence the rule used, for example the binder text the value was extracted
  from.
</ResponseField>

<ResponseField name="expectedValue" type="String">
  What the rule expected to find, when relevant.
</ResponseField>

<ResponseField name="actualValue" type="String">
  What the rule actually found, when relevant.
</ResponseField>

<ResponseField name="fieldPath" type="String">
  The leadsheet field path the issue is anchored to, when the issue is tied to a
  specific field.
</ResponseField>

<ResponseField name="lineRef" type="String">
  A reference to the line on the form, when relevant.
</ResponseField>

<ResponseField name="columnRef" type="String">
  A reference to the column on the form, when relevant.
</ResponseField>

<ResponseField name="mappingNote" type="String">
  A note about how the issue's field was mapped to the form, when the mapping is
  ambiguous.
</ResponseField>

<ResponseField name="binderMessageId" type="String">
  The ID of the binder message (annotation, flag, or sign-off) linked to this
  issue, when one exists.
</ResponseField>

<ResponseField name="resolved" type="Boolean!">
  Whether the issue has been resolved. An issue is resolved when the underlying
  document message is hidden (for example a reviewer dismissed the flag, or a
  sign-off covered it). Refetch the leadsheets query after a
  [sign-off](#sign-off-on-a-sheet-or-row) to recompute this.
</ResponseField>

## The `LeadsheetField` type

A single field on a form, with one row per occurrence (for example one row per
1099-INT under "Interest Income").

```graphql theme={null}
type LeadsheetField {
  id: ID!
  rows: [LeadsheetFieldRow!]!
}
```

<ResponseField name="id" type="ID!">
  The field ID.
</ResponseField>

<ResponseField name="rows" type="[LeadsheetFieldRow!]!">
  The rows for this field. See [`LeadsheetFieldRow`](#the-leadsheetfieldrow-type).
</ResponseField>

## The `LeadsheetFieldRow` type

One row of a field: its value, the prior-year value, the source anchor in the
binder, the trace that explains where the value came from, and the issues and
sign-offs tied to this row.

```graphql theme={null}
type LeadsheetFieldRow {
  id: ID!
  fieldPath: String!
  value: String
  priorYearValue: String
  sourceAnchor: SubDocBBox
  trace: LeadsheetTrace
  issues: [DocumentMessage!]!
  signOffs: [DocumentMessage!]!
}
```

<ResponseField name="id" type="ID!">
  The row ID.
</ResponseField>

<ResponseField name="fieldPath" type="String!">
  The path of this field on the form, for example `interest_income.total`.
</ResponseField>

<ResponseField name="value" type="String">
  The value extracted for this row, for example `"428.00"`. Nullable when the row
  exists for layout but carries no value.
</ResponseField>

<ResponseField name="priorYearValue" type="String">
  The value the same field held in the prior year, when prior-year data is
  available.
</ResponseField>

<ResponseField name="sourceAnchor" type="SubDocBBox">
  The bounding box in the binder subdocument this value was extracted from. See
  [`SubDocBBox`](#the-subdocbbox-type).
</ResponseField>

<ResponseField name="trace" type="LeadsheetTrace">
  The trace explaining how this row's value was sourced and reconciled. See
  [`LeadsheetTrace`](#the-leadsheettrace-type).
</ResponseField>

<ResponseField name="issues" type="[DocumentMessage!]!">
  Document messages (flags, notes) anchored to this row. Each is a
  `DocumentMessage`; see [Document messages](#the-documentmessage-type).
</ResponseField>

<ResponseField name="signOffs" type="[DocumentMessage!]!">
  Sign-off messages reviewers have recorded against this row. Each is a
  `DocumentMessage` with `markType: "signoff"`. See
  [Sign off on a sheet or row](#sign-off-on-a-sheet-or-row).
</ResponseField>

## The `LeadsheetTrace` type

The reasoning and source citations behind a row's value: why the extractor chose
this value, and which binder subdocuments (and pages, and bounding boxes) it
came from.

```graphql theme={null}
type LeadsheetTrace {
  reasoning: String
  sources: [LeadsheetTraceSource!]!
}
```

<ResponseField name="reasoning" type="String">
  A human-readable explanation of how the value was sourced and reconciled.
</ResponseField>

<ResponseField name="sources" type="[LeadsheetTraceSource!]!">
  The binder sources this value was taken from. See
  [`LeadsheetTraceSource`](#the-leadsheettracesource-type).
</ResponseField>

## The `LeadsheetTraceSource` type

One source contributing to a trace: a subdocument, a label, an amount, and a
page-level bounding box.

```graphql theme={null}
type LeadsheetTraceSource {
  subdocId: ID!
  label: String!
  amount: String
  page: Int
  bbox: SubDocBBox
}
```

<ResponseField name="subdocId" type="ID!">
  The binder subdocument this source came from.
</ResponseField>

<ResponseField name="label" type="String!">
  A human-readable label for the source, for example `"1099-INT from Acme Broker"`.
</ResponseField>

<ResponseField name="amount" type="String">
  The amount this source contributed, as a string, for example `"42.00"`.
</ResponseField>

<ResponseField name="page" type="Int">
  The page number inside the subdocument, when relevant.
</ResponseField>

<ResponseField name="bbox" type="SubDocBBox">
  The bounding box on the page that pins this source. See
  [`SubDocBBox`](#the-subdocbbox-type).
</ResponseField>

## The `SubDocBBox` type

A bounding box that pins a value or source to a specific region on a specific
page of a binder subdocument.

```graphql theme={null}
type SubDocBBox {
  yMin: Int!
  xMin: Int!
  yMax: Int!
  xMax: Int!
  pageNumber: Int!
  subdocId: String!
}
```

<ResponseField name="yMin" type="Int!">Top edge of the box, in page pixels.</ResponseField>
<ResponseField name="xMin" type="Int!">Left edge of the box, in page pixels.</ResponseField>
<ResponseField name="yMax" type="Int!">Bottom edge of the box, in page pixels.</ResponseField>
<ResponseField name="xMax" type="Int!">Right edge of the box, in page pixels.</ResponseField>
<ResponseField name="pageNumber" type="Int!">The page this box is on, 1-indexed.</ResponseField>
<ResponseField name="subdocId" type="String!">The subdocument this box belongs to.</ResponseField>

## The `IssueCountBySeverity` type

Issue counts bucketed by severity, used by `Leadsheet.issueCount`.

```graphql theme={null}
type IssueCountBySeverity {
  critical: Int!
  high: Int!
  medium: Int!
  low: Int!
}
```

<ResponseField name="critical" type="Int!">Number of `CRITICAL` issues.</ResponseField>
<ResponseField name="high" type="Int!">Number of `HIGH` issues.</ResponseField>
<ResponseField name="medium" type="Int!">Number of `MEDIUM` issues.</ResponseField>
<ResponseField name="low" type="Int!">Number of `LOW` issues.</ResponseField>

## The `DocumentMessage` type

A document message is the underlying write surface for annotations, flags, and
sign-offs on binder documents and leadsheet rows. A sign-off is a `DocumentMessage`
with `type: "activity"` and `markType: "signoff"`.

```graphql theme={null}
type DocumentMessage {
  id: ID!
  workspaceId: ID!
  documentPath: String!
  type: DocumentMessageType!
  markType: String!
  anchorPoint: JSON!
  contentPath: String
  body: String
  hiddenAt: String
  hiddenBy: ID
  createdBy: ID!
  createdAt: String!
  updatedAt: String!
  threads: [DocumentMessageThread!]!
  taggedUsers: [DocumentMessageTaggedUser!]!
}

enum DocumentMessageType {
  annotation
  activity
  missing_document
}
```

<ResponseField name="id" type="ID!">The message ID.</ResponseField>
<ResponseField name="workspaceId" type="ID!">The workspace the message belongs to.</ResponseField>

<ResponseField name="documentPath" type="String!">
  The binder path the message is anchored to. For a sign-off on a subdocument or
  leadsheet row, this is the subdocument's path.
</ResponseField>

<ResponseField name="type" type="DocumentMessageType!">
  `annotation`, `activity`, or `missing_document`. Sign-offs use `activity`.
</ResponseField>

<ResponseField name="markType" type="String!">
  The kind of mark: `signoff`, `flag`, `note`, and so on. The schema types this as
  a free-form `String`; the web app treats `signoff` as the sign-off mark.
</ResponseField>

<ResponseField name="anchorPoint" type="JSON!">
  A JSON object pinning the message to a location. For a sign-off it carries
  `{ page, coordinates: { x, y }, level, user_role }`, where `level` is the
  reviewer's sign-off level and `user_role` is their workspace role.
</ResponseField>

<ResponseField name="contentPath" type="String">An optional content path.</ResponseField>
<ResponseField name="body" type="String">An optional body, for notes and replies.</ResponseField>

<ResponseField name="hiddenAt" type="String">
  When the message was soft-hidden (for example when a sign-off is undone). `null`
  while the message is visible.
</ResponseField>

<ResponseField name="hiddenBy" type="ID">The user who hid the message, when applicable.</ResponseField>
<ResponseField name="createdBy" type="ID!">The user who created the message.</ResponseField>
<ResponseField name="createdAt" type="String!">When the message was created.</ResponseField>
<ResponseField name="updatedAt" type="String!">When the message was last updated.</ResponseField>
<ResponseField name="threads" type="[DocumentMessageThread!]!">Reply threads on the message.</ResponseField>
<ResponseField name="taggedUsers" type="[DocumentMessageTaggedUser!]!">Users tagged on the message.</ResponseField>

<Note>
  A sign-off is a `DocumentMessage` with `type: "activity"` and
  `markType: "signoff"`. This page documents only the two operations the review
  sign-off flow uses: [`createDocumentMessage`](#sign-off-on-a-sheet-or-row) and
  [`hideDocumentMessage`](#undo-a-sign-off). The wider `DocumentMessage` API
  (annotations, flags, threads, hide/unhide on binder documents) is documented
  separately.
</Note>

## Read a client's leadsheets

Read `binder.leadsheets(taskId:)` to get the leadsheets tree a specific run
produced. Pass the `taskId` of the `TAX_PREP` or `TAX_REVIEW` task you want the
tree for; omit it to read the client's most recent tree.

```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
                }
                signOffs {
                  id
                  markType
                  anchorPoint
                  body
                  createdBy
                  createdAt
                  hiddenAt
                }
                fields {
                  id
                  rows {
                    id
                    fieldPath
                    value
                    priorYearValue
                    sourceAnchor {
                      yMin
                      xMin
                      yMax
                      xMax
                      pageNumber
                      subdocId
                    }
                    trace {
                      reasoning
                      sources {
                        subdocId
                        label
                        amount
                        page
                        bbox {
                          yMin
                          xMin
                          yMax
                          xMax
                          pageNumber
                          subdocId
                        }
                      }
                    }
                    issues {
                      id
                      markType
                      anchorPoint
                      body
                      createdBy
                      createdAt
                      hiddenAt
                    }
                    signOffs {
                      id
                      markType
                      anchorPoint
                      body
                      createdBy
                      createdAt
                      hiddenAt
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

### Arguments

<ParamField path="clientId" type="ID!" required>
  The client whose leadsheets you want to read. Pass it via `filters.ids` on
  `clients`.
</ParamField>

<ParamField path="taskId" type="ID">
  Optional. The `TAX_PREP` or `TAX_REVIEW` task whose tree you want. Omit it to
  read the client's most recent tree.
</ParamField>

<ParamField path="sheets.id" type="ID">
  Optional. Pass it on `Leadsheets.sheets(id:)` to fetch a single leadsheet by ID
  instead of listing them all.
</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 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 } signOffs { id markType anchorPoint body createdBy createdAt hiddenAt } fields { id rows { id fieldPath value priorYearValue sourceAnchor { yMin xMin yMax xMax pageNumber subdocId } trace { reasoning sources { subdocId label amount page bbox { yMin xMin yMax xMax pageNumber subdocId } } } issues { id markType anchorPoint body createdBy createdAt hiddenAt } signOffs { id markType anchorPoint body createdBy createdAt hiddenAt } } } } } } } } } } } }",
      "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
                      },
                      "signOffs": [],
                      "fields": [
                        {
                          "id": "leadsheets/schedule_b/0/interest_income",
                          "rows": [
                            {
                              "id": "leadsheets/schedule_b/0/interest_income/0",
                              "fieldPath": "interest_income.total",
                              "value": "428.00",
                              "priorYearValue": "386.00",
                              "sourceAnchor": {
                                "yMin": 412,
                                "xMin": 88,
                                "yMax": 428,
                                "xMax": 220,
                                "pageNumber": 1,
                                "subdocId": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"
                              },
                              "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,
                                    "bbox": {
                                      "yMin": 412,
                                      "xMin": 88,
                                      "yMax": 428,
                                      "xMax": 220,
                                      "pageNumber": 1,
                                      "subdocId": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"
                                    }
                                  },
                                  {
                                    "subdocId": "018f9c2a-7c3d-7c3d-9a4e-2f6b1c8d2f7b",
                                    "label": "1099-INT from Globex",
                                    "amount": "176.00",
                                    "page": 1,
                                    "bbox": {
                                      "yMin": 300,
                                      "xMin": 88,
                                      "yMax": 316,
                                      "xMax": 220,
                                      "pageNumber": 1,
                                      "subdocId": "018f9c2a-7c3d-7c3d-9a4e-2f6b1c8d2f7b"
                                    }
                                  },
                                  {
                                    "subdocId": "018f9c2a-8e2f-7c3d-9a4e-2f6b1c8d3f8c",
                                    "label": "1099-INT from Initech",
                                    "amount": "42.00",
                                    "page": 1,
                                    "bbox": {
                                      "yMin": 244,
                                      "xMin": 88,
                                      "yMax": 260,
                                      "xMax": 220,
                                      "pageNumber": 1,
                                      "subdocId": "018f9c2a-8e2f-7c3d-9a4e-2f6b1c8d3f8c"
                                    }
                                  }
                                ]
                              },
                              "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
                                }
                              ],
                              "signOffs": []
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  The leadsheets query is the same one the web app's binder and review screens
  run. After a sign-off, refetch it to recompute `LeadsheetSheetIssue.resolved`
  and `Leadsheet.issueCount` (the server recomputes them from the document
  messages you just wrote).
</Tip>

## Sign off on a sheet or row

A sign-off is a `createDocumentMessage` call with `type: "activity"` and
`markType: "signoff"`, anchored to the subdocument path you are signing off on.
The `anchorPoint` carries the reviewer's `level` and `user_role` so the UI can
render 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
  }
}
```

### Input: `CreateDocumentMessageInput`

```graphql theme={null}
input CreateDocumentMessageInput {
  clientId: ID!
  documentPath: String!
  type: DocumentMessageType!
  markType: String!
  anchorPoint: JSON!
  body: String
  taskId: ID
  taggedUserIds: [ID!]
}
```

<ParamField path="clientId" type="ID!" required>
  The client whose binder you are signing off in.
</ParamField>

<ParamField path="documentPath" type="String!" required>
  The binder path you are signing off on. For a subdocument or leadsheet row
  sign-off, this is the subdocument's path (the same value you read as
  `LeadsheetFieldRow.id` or `LeadsheetSheetIssue` is anchored to).
</ParamField>

<ParamField path="type" type="DocumentMessageType!" required>
  `activity` for a sign-off (the only value the sign-off flow uses).
</ParamField>

<ParamField path="markType" type="String!" required>
  `"signoff"` for a sign-off.
</ParamField>

<ParamField path="anchorPoint" type="JSON!" required>
  A JSON object pinning the sign-off. The web app uses
  `{ "page": 1, "coordinates": { "x": 0, "y": 0 }, "level": <number>, "user_role": "<role>" }`,
  where `level` is the reviewer's sign-off level (for example `2` for an `l2`
  reviewer) and `user_role` is their workspace role.
</ParamField>

<ParamField path="body" type="String">
  An optional note attached to the sign-off.
</ParamField>

<ParamField path="taskId" type="ID">
  The task the sign-off belongs to, when relevant.
</ParamField>

<ParamField path="taggedUserIds" type="[ID!]">
  Workspace users to tag on the sign-off.
</ParamField>

### Returns: `DocumentMessage!`

The created [`DocumentMessage`](#the-documentmessage-type). Its `id` is what you
pass to [`hideDocumentMessage`](#undo-a-sign-off) to undo the sign-off.

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

<Warning>
  The schema also defines an input type called `SignOffSubDocumentsInput`
  (`{ binderId, subDocumentPaths }`), but no mutation field is wired to it: there
  is no `signOffSubDocuments` (or similar) mutation on the live `Mutation` type.
  The real sign-off write surface is `createDocumentMessage` with
  `markType: "signoff"`, one call per subdocument. Do not look for a
  `signOffSubDocuments` mutation, it does not exist.
</Warning>

## 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` / `issueCount` recomputation backs it out.

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

### Input

<ParamField path="id" type="ID!" required>
  The ID of the sign-off `DocumentMessage` to undo (the `id` returned by
  [`createDocumentMessage`](#sign-off-on-a-sheet-or-row)).
</ParamField>

### Returns: `DocumentMessage!`

The hidden [`DocumentMessage`](#the-documentmessage-type), with `hiddenAt` and
`hiddenBy` now populated.

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

## Exporting leadsheets

The `LeadsheetsExportFormat` enum is used by the workpaper bundle generator
(`generateWorkpaperBundle`), not by the leadsheets query itself. When you
generate a workpaper bundle and want leadsheets included, pass
`includeLeadsheets: true` and pick a format.

```graphql theme={null}
enum LeadsheetsExportFormat {
  EXCEL
  CSV
}
```

The `generateWorkpaperBundle` mutation and its `GenerateWorkpaperBundleInput`
input are documented on the [Workpapers](/apis/workpapers) page. This page lists
`LeadsheetsExportFormat` only to anchor where the enum is actually consumed.
