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

# Binder

> Read a client's binder: list uploaded files, missing items, message counts, and search across the binder

A client's **binder** is the container for the documents Filed has ingested for
that client. It holds the uploaded files (subdocuments), the missing-item
checklist the run produces, message counts for quick badges, and a search
surface across bookmarks, annotations, and document contents. Read it with the
`binder` field on a client.

The binder is reached through the [`me`](/apis/me) query resolved as a
`WorkspaceUser`, so every binder operation requires a **`workspaceToken`** (see
[Authentication](/guides/authentication)). All requests go to:

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

<Note>
  There is no top-level `binder` query. The binder belongs to a client, so you
  read it through
  `me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { binder { ... } } } } }`.
  The `workspaceToken` already identifies the workspace, so you never pass a
  workspace ID to read the binder.
</Note>

## Query a client's binder

Use this query when an app, agent, or MCP tool needs the binder overview for a
client: filed documents, open missing items, quick counts, and signed URLs for
the original uploads and page renders.

```graphql theme={null}
query QueryClientBinder($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          id
          name
          binder {
            id
            clientId
            messageCounts {
              openMissing
              notes
            }
            subdocuments {
              id
              parentDocumentId
              fileName
              type
              issuer
              taxYear
              status
              category
              bucket
              pageRange
              canonicalPath
              parentDocument {
                id
                fileName
                contentType
                url {
                  filePath
                  url
                }
                subdocPages {
                  pageNumber
                  imageUrl {
                    filePath
                    url
                  }
                  markdownUrl {
                    filePath
                    url
                  }
                }
              }
            }
            missingItemsAssessment(filter: { status: OPEN }) {
              status
              reason
              count
              items {
                id
                item
                formType
                issuer
                taxYear
                severity
                reason
                status
                category
              }
            }
          }
        }
      }
    }
  }
}
```

### Arguments

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

### Returns

<ResponseField name="clients[].binder" type="Binder">
  The binder for the requested client. It contains filed documents, missing
  items, message counts, and search.
</ResponseField>

<ResponseField name="binder.subdocuments" type="[SubDocument!]!">
  The logical documents Filed extracted or filed into the binder. Save each
  `id`: it is the `documentPath` used by
  [document messages](/apis/document-messages) and file-level
  [leadsheet signoff](/apis/leadsheets#sign-off-on-a-sheet-or-row).
</ResponseField>

<ResponseField name="binder.subdocuments[].parentDocument.url" type="SignedPath!">
  Signed URL for the original uploaded file. Signed URLs are time-limited.
</ResponseField>

<ResponseField name="binder.subdocuments[].parentDocument.subdocPages" type="[SubdocPage!]!">
  Signed page image and markdown URLs for rendering or reading the parent
  document page by page.
</ResponseField>

<ResponseField name="binder.missingItemsAssessment" type="BinderMissingItemsAssessment!">
  The status, reason, count, and filtered items for the missing-document check.
  Treat an empty item list as an all-clear only when `status` is `AVAILABLE`.
</ResponseField>

<ResponseField name="binder.messageCounts" type="BinderMessageCounts!">
  Small counts for badges and summaries without fetching all messages.
</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 QueryClientBinder($clientId: ID!) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { id name binder { id clientId messageCounts { openMissing notes } subdocuments { id parentDocumentId fileName type issuer taxYear status category bucket pageRange canonicalPath parentDocument { id fileName contentType url { filePath url } subdocPages { pageNumber imageUrl { filePath url } markdownUrl { filePath url } } } } missingItemsAssessment(filter: { status: OPEN }) { status reason count items { id item formType issuer taxYear severity reason status category } } } } } } } }",
      "variables": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "clients": [
            {
              "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
              "name": "Jane Smith",
              "binder": {
                "id": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
                "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
                "messageCounts": {
                  "openMissing": 1,
                  "notes": 2
                },
                "subdocuments": [
                  {
                    "id": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
                    "parentDocumentId": "018f9c2a-6a5b-7c3d-9a4e-2f6b1c8d0e49",
                    "fileName": "1099-INT-Acme-Broker.pdf",
                    "type": "1099-INT",
                    "issuer": "Acme Broker",
                    "taxYear": 2025,
                    "status": "ingested",
                    "category": "income",
                    "bucket": "source_docs",
                    "pageRange": [1],
                    "canonicalPath": "clients/018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c/source_docs/018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a/canonical.json",
                    "parentDocument": {
                      "id": "018f9c2a-6a5b-7c3d-9a4e-2f6b1c8d0e49",
                      "fileName": "uploads-packet.pdf",
                      "contentType": "application/pdf",
                      "url": {
                        "filePath": "uploads/018f9c2a-6a5b-7c3d-9a4e-2f6b1c8d0e49.pdf",
                        "url": "https://signed.example.com/uploads-packet.pdf?sig=..."
                      },
                      "subdocPages": [
                        {
                          "pageNumber": 1,
                          "imageUrl": {
                            "filePath": "renders/page-1.png",
                            "url": "https://signed.example.com/page-1.png?sig=..."
                          },
                          "markdownUrl": {
                            "filePath": "renders/page-1.md",
                            "url": "https://signed.example.com/page-1.md?sig=..."
                          }
                        }
                      ]
                    }
                  }
                ],
                "missingItemsAssessment": {
                  "status": "AVAILABLE",
                  "reason": null,
                  "count": 1,
                  "items": [
                    {
                      "id": "018f9c2a-8c2f-7c3d-9a4e-2f6b1c8d0e5b",
                      "item": "W-2 from Example Employer",
                      "formType": "W-2",
                      "issuer": "Example Employer",
                      "taxYear": 2025,
                      "severity": "CRITICAL",
                      "reason": "Expected a W-2 from Example Employer but no matching document was found in the binder.",
                      "status": "OPEN",
                      "category": "income"
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

### MCP usage pattern

1. Start with `clients(filters: { ids: [$clientId] })` and read `binder`.
2. Use `binder.subdocuments` for the document list and preserve each
   subdocument `id` for later annotation, signoff, or leadsheet operations.
3. Use `parentDocument.url` when the user needs the original upload, and
   `parentDocument.subdocPages` when the user needs page images or page
   markdown.
4. Use `missingItemsAssessment.count` and `messageCounts` for compact summaries
   before displaying full lists.
5. Use [`binder.search`](#search-the-binder) when the user asks about specific
   content, issuers, categories, annotations, marks, or values inside the
   documents.

## The `Binder` type

The top-level binder container for one client.

```graphql theme={null}
type Binder {
  id: ID!
  clientId: ID!
  subdocuments(filter: SubDocumentsFilter): [SubDocument!]!
  createdAt: String!
  updatedAt: String!
  missingItemsAssessment(
    filter: BinderMissingItemsFilter
  ): BinderMissingItemsAssessment!
  missingItems(filter: BinderMissingItemsFilter): [BinderMissingItem!]!
    @deprecated(reason: "Use missingItemsAssessment instead.")
  openMissingItemsCount: Int!
    @deprecated(reason: "Use missingItemsAssessment.count instead.")
  messageCounts: BinderMessageCounts!
  search(query: String!, limit: Int = 20): BinderSearchResults!
  leadsheets(taskId: ID): Leadsheets
}
```

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

<ResponseField name="clientId" type="ID!">
  The client this binder belongs to.
</ResponseField>

<ResponseField name="subdocuments" type="[SubDocument!]!">
  The files filed in the binder. Pass a `SubDocumentsFilter` to narrow to
  unreviewed, flagged, or files under one parent document. See
  [List the files in a binder](#list-the-files-in-a-binder).
</ResponseField>

<ResponseField name="createdAt" type="String!">
  When the binder was created (ISO 8601 timestamp).
</ResponseField>

<ResponseField name="updatedAt" type="String!">
  When the binder was last updated (ISO 8601 timestamp).
</ResponseField>

<ResponseField name="missingItemsAssessment" type="BinderMissingItemsAssessment!">
  The state and filtered result of the missing-document check. Pass a
  `BinderMissingItemsFilter` to narrow by item status. See
  [List missing items](#list-missing-items).
</ResponseField>

<ResponseField name="missingItems" type="[BinderMissingItem!]!">
  Deprecated. Use `missingItemsAssessment.items` so you can distinguish an
  available empty result from a pending or unavailable assessment.
</ResponseField>

<ResponseField name="openMissingItemsCount" type="Int!">
  Deprecated. Use `missingItemsAssessment.count`.
</ResponseField>

<ResponseField name="messageCounts" type="BinderMessageCounts!">
  Open missing-item and notes counts for badge rendering. See
  [Read message counts](#read-message-counts).
</ResponseField>

<ResponseField name="search" type="BinderSearchResults!">
  Search across bookmarks, annotations, marks, and document contents. See
  [Search the binder](#search-the-binder).
</ResponseField>

<ResponseField name="leadsheets" type="Leadsheets">
  The leadsheets tree for a `TAX_PREP` or `TAX_REVIEW` run. Pass the run's
  `taskId` to read that run's tree; omit it to read the most recent tree.
  Leadsheets are documented separately on
  [Leadsheets and review](/apis/leadsheets#read-a-clients-leadsheets); this page
  does not re-document them.
</ResponseField>

## The `SubDocument` type

One file in a binder. A subdocument is one logical document extracted from an
uploaded parent (for example one 1099-INT inside a larger uploaded packet).

```graphql theme={null}
type SubDocument {
  id: ID!
  clientId: ID!
  parentDocumentId: String!
  fileName: String!
  pageRange: [Int!]!
  type: String!
  issuer: String!
  taxYear: Int!
  status: String!
  category: String
  order: Int!
  createdAt: String!
  updatedAt: String!
  parentDocument: ParentDocument
  canonicalPath: String
  bucket: String
}
```

<ResponseField name="id" type="ID!">
  The subdocument's unique identifier. This is the value you pass as
  `documentPath` when [creating a document message](/apis/document-messages) or
  [signing off](/apis/leadsheets#sign-off-on-a-sheet-or-row) on a file.
</ResponseField>

<ResponseField name="clientId" type="ID!">
  The client this subdocument belongs to.
</ResponseField>

<ResponseField name="parentDocumentId" type="String!">
  The ID of the parent document this subdocument was extracted from.
</ResponseField>

<ResponseField name="fileName" type="String!">
  The file name, for example `1099-INT-Acme-Broker.pdf`.
</ResponseField>

<ResponseField name="pageRange" type="[Int!]!">
  The pages inside the parent document this subdocument covers, 1-indexed.
</ResponseField>

<ResponseField name="type" type="String!">
  The document type the extractor classified this as, for example `1099-INT` or
  `W-2`.
</ResponseField>

<ResponseField name="issuer" type="String!">
  The issuer or payer named on the document, for example `Acme Broker`.
</ResponseField>

<ResponseField name="taxYear" type="Int!">
  The tax year this document covers, for example `2025`.
</ResponseField>

<ResponseField name="status" type="String!">
  The ingestion or review status of this subdocument, for example `ingested` or
  `reviewed`.
</ResponseField>

<ResponseField name="category" type="String">
  The binder grouping category, when one has been assigned. Nullable: some
  subdocuments are uncategorized until a reviewer files them.
</ResponseField>

<ResponseField name="order" type="Int!">
  The sort order of this subdocument within the binder.
</ResponseField>

<ResponseField name="createdAt" type="String!">
  When the subdocument was created (ISO 8601 timestamp).
</ResponseField>

<ResponseField name="updatedAt" type="String!">
  When the subdocument was last updated (ISO 8601 timestamp).
</ResponseField>

<ResponseField name="parentDocument" type="ParentDocument">
  The parent document this subdocument was extracted from. See
  [`ParentDocument`](#the-parentdocument-type).
</ResponseField>

<ResponseField name="canonicalPath" type="String">
  The path to the `canonical.json` file in the per-client git repo. Populated
  when a tax-prep run has picked up this subdocument; `null` until then.
</ResponseField>

<ResponseField name="bucket" type="String">
  The use-case bucket the subdocument lives in on disk, for example
  `source_docs`, `prior_year_docs`, or `current_year_drafts`. `null` when the
  subdocument has not yet been migrated into a bucket.
</ResponseField>

## The `ParentDocument` type

The original uploaded document a subdocument was extracted from. Carries the
file URL and per-page render URLs.

```graphql theme={null}
type ParentDocument {
  id: ID!
  fileName: String!
  contentType: String!
  url: SignedPath!
  subdocPages: [SubdocPage!]!
}

type SubdocPage {
  pageNumber: Int!
  imageUrl: SignedPath
  markdownUrl: SignedPath
}
```

<ResponseField name="id" type="ID!">
  The parent document's unique identifier.
</ResponseField>

<ResponseField name="fileName" type="String!">
  The original uploaded file name.
</ResponseField>

<ResponseField name="contentType" type="String!">
  The MIME type, for example `application/pdf`.
</ResponseField>

<ResponseField name="url" type="SignedPath!">
  A signed URL for downloading the original file. `SignedPath` is
  `{ filePath: String!, url: String! }`.
</ResponseField>

<ResponseField name="subdocPages" type="[SubdocPage!]!">
  Per-page render URLs for the parent document: one image URL and one markdown
  URL per page, both signed and time-limited.
</ResponseField>

## The `BinderMissingItemsAssessment` type

The result of the missing-document check for the requested item status.

```graphql theme={null}
type BinderMissingItemsAssessment {
  status: BinderMissingItemsAssessmentStatus!
  reason: String
  count: Int!
  items: [BinderMissingItem!]!
}

enum BinderMissingItemsAssessmentStatus {
  PENDING
  AVAILABLE
  UNAVAILABLE
}
```

<ResponseField name="status" type="BinderMissingItemsAssessmentStatus!">
  `PENDING` while the check has not completed, `AVAILABLE` when the result can be
  used, or `UNAVAILABLE` when the binder lacks enough evidence to determine
  missing documents.
</ResponseField>

<ResponseField name="reason" type="String">
  An explanation for a pending or unavailable assessment. It is `null` when the
  assessment is available.
</ResponseField>

<ResponseField name="count" type="Int!">
  The number of items returned for the requested filter.
</ResponseField>

<ResponseField name="items" type="[BinderMissingItem!]!">
  The missing items matching the requested filter. For an `OPEN` filter this is
  empty while the assessment is pending or unavailable.
</ResponseField>

## The `BinderMissingItem` type

One item on the missing-document checklist the binder produces: a form the run
expected to find but did not, with a severity, a reason, and a status you can
move between `OPEN`, `IGNORED`, and `RESOLVED`.

```graphql theme={null}
type BinderMissingItem {
  id: ID!
  binderId: ID!
  item: String!
  formType: String
  issuer: String
  taxYear: Int
  severity: ChecklistItemSeverity!
  reason: String!
  status: ChecklistItemStatus!
  category: String
  createdAt: String!
  updatedAt: String!
}

enum ChecklistItemSeverity {
  CRITICAL
  MEDIUM
  LOW
}

enum ChecklistItemStatus {
  OPEN
  IGNORED
  RESOLVED
}
```

<ResponseField name="id" type="ID!">
  The missing-item record ID. Pass this to
  [`ignoreBinderMissingItem`](#ignore-a-missing-item) or
  [`restoreBinderMissingItem`](#restore-a-missing-item).
</ResponseField>

<ResponseField name="binderId" type="ID!">
  The binder this missing item belongs to.
</ResponseField>

<ResponseField name="item" type="String!">
  The missing item, for example `1099-INT` or `W-2 from Acme`.
</ResponseField>

<ResponseField name="formType" type="String">
  The form type expected, when the checklist is form-specific.
</ResponseField>

<ResponseField name="issuer" type="String">
  The issuer the run expected to find, when relevant.
</ResponseField>

<ResponseField name="taxYear" type="Int">
  The tax year the missing item applies to.
</ResponseField>

<ResponseField name="severity" type="ChecklistItemSeverity!">
  How blocking the missing item is: `CRITICAL`, `MEDIUM`, or `LOW`.
</ResponseField>

<ResponseField name="reason" type="String!">
  Why the run flagged this as missing, for example
  `Expected a 1099-INT from Acme Broker but no matching document was found in the binder.`
</ResponseField>

<ResponseField name="status" type="ChecklistItemStatus!">
  The current status: `OPEN` (still needs the document), `IGNORED` (a reviewer
  dismissed it via [`ignoreBinderMissingItem`](#ignore-a-missing-item)), or
  `RESOLVED` (the document was later found and filed).
</ResponseField>

<ResponseField name="category" type="String">
  A grouping category, when one has been assigned.
</ResponseField>

<ResponseField name="createdAt" type="String!">
  When the missing-item record was created (ISO 8601 timestamp).
</ResponseField>

<ResponseField name="updatedAt" type="String!">
  When the missing-item record was last updated (ISO 8601 timestamp).
</ResponseField>

## The `BinderMessageCounts` type

Open missing-item and notes counts. Use `missingItemsAssessment` when you also
need to know whether the missing-document check is available.

```graphql theme={null}
type BinderMessageCounts {
  openMissing: Int!
  notes: Int!
}
```

<ResponseField name="openMissing" type="Int!">
  The number of stored missing items in the `OPEN` status. Do not use this field
  alone to infer that the missing-document check completed successfully.
</ResponseField>

<ResponseField name="notes" type="Int!">
  The number of open annotation notes on the binder. See
  [Document messages](/apis/document-messages) for the annotation API.
</ResponseField>

## The `BinderSearchResults` type

The result of `Binder.search`: four buckets of hits, one per search surface
(bookmarks, annotations, marks, and document contents).

```graphql theme={null}
type BinderSearchResults {
  bookmarks: [BookmarkSearchHit!]!
  annotations: [BinderMessageSearchHit!]!
  marks: [BinderMessageSearchHit!]!
  contents: [BinderContentSearchHit!]!
}
```

<ResponseField name="bookmarks" type="[BookmarkSearchHit!]!">
  Subdocuments whose file name, issuer, type, or category matched the query. See
  [`BookmarkSearchHit`](#the-bookmarksearchhit-type).
</ResponseField>

<ResponseField name="annotations" type="[BinderMessageSearchHit!]!">
  Binder messages (annotations and notes) whose body or expression matched the
  query. See [`BinderMessageSearchHit`](#the-bindermessagesearchhit-type).
</ResponseField>

<ResponseField name="marks" type="[BinderMessageSearchHit!]!">
  Binder messages that are marks, whose body or expression matched the query. See
  [`BinderMessageSearchHit`](#the-bindermessagesearchhit-type).
</ResponseField>

<ResponseField name="contents" type="[BinderContentSearchHit!]!">
  Hits inside document contents, with the matching field names, values, and
  bounding boxes. See [`BinderContentSearchHit`](#the-bindercontentsearchhit-type).
</ResponseField>

### The `BookmarkSearchHit` type

```graphql theme={null}
type BookmarkSearchHit {
  subdocument: SubDocument!
  matchedField: BookmarkMatchField!
  snippet: String!
}

enum BookmarkMatchField {
  FILE_NAME
  ISSUER
  TYPE
  CATEGORY
}
```

<ResponseField name="subdocument" type="SubDocument!">
  The subdocument whose bookmark matched. See
  [`SubDocument`](#the-subdocument-type).
</ResponseField>

<ResponseField name="matchedField" type="BookmarkMatchField!">
  Which subdocument field matched: `FILE_NAME`, `ISSUER`, `TYPE`, or `CATEGORY`.
</ResponseField>

<ResponseField name="snippet" type="String!">
  A snippet of the matched value, for display.
</ResponseField>

### The `BinderMessageSearchHit` type

A search hit inside a binder message (an annotation or a mark). The underlying
message is a `BinderMessage`, the binder's internal message type used by
search. Annotation and sign-off writes use the `DocumentMessage` type (see
[Document messages](/apis/document-messages)); `BinderMessage` is the read
shape the search surface returns.

```graphql theme={null}
type BinderMessageSearchHit {
  message: BinderMessage!
  matchedField: BinderMessageMatchField!
  snippet: String!
  isReply: Boolean!
}

enum BinderMessageMatchField {
  BODY
  EXPRESSION
}
```

<ResponseField name="message" type="BinderMessage!">
  The binder message that matched. See [`BinderMessage`](#the-bindermessage-type).
</ResponseField>

<ResponseField name="matchedField" type="BinderMessageMatchField!">
  Which message field matched: `BODY` (the message body) or `EXPRESSION` (an
  expression inside the message content).
</ResponseField>

<ResponseField name="snippet" type="String!">
  A snippet of the matched text, for display.
</ResponseField>

<ResponseField name="isReply" type="Boolean!">
  `true` when the hit is a reply inside a thread, `false` when it is a top-level
  message.
</ResponseField>

### The `BinderMessage` type

The binder's internal message type, returned by `Binder.search`. It carries the
message's anchor (subdocument path, page number, coordinates), content, threads,
and tagged users.

```graphql theme={null}
type BinderMessage {
  id: ID!
  binderId: ID!
  subDocumentPath: ID!
  pageNumber: Int!
  coordinates: JSON
  type: String!
  content: JSON!
  createdBy: ID!
  threads: [BinderMessageThread!]!
  taggedUsers: [BinderTaggedUser!]!
  isRead: Boolean!
  deletedAt: String
  deletedBy: ID
  createdAt: String!
  updatedAt: String!
}
```

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

<ResponseField name="binderId" type="ID!">
  The binder this message belongs to.
</ResponseField>

<ResponseField name="subDocumentPath" type="ID!">
  The subdocument path the message is anchored to.
</ResponseField>

<ResponseField name="pageNumber" type="Int!">
  The page number inside the subdocument the message is anchored to, 1-indexed.
</ResponseField>

<ResponseField name="coordinates" type="JSON">
  The on-page coordinates of the message anchor, when the message is pinned to a
  region. `null` for messages that are not pinned to a region.
</ResponseField>

<ResponseField name="type" type="String!">
  The message type label, for example `annotation` or `mark`.
</ResponseField>

<ResponseField name="content" type="JSON!">
  The message content as a JSON object.
</ResponseField>

<ResponseField name="createdBy" type="ID!">
  The user who created the message.
</ResponseField>

<ResponseField name="threads" type="[BinderMessageThread!]!">
  Reply threads on the message.
</ResponseField>

<ResponseField name="taggedUsers" type="[BinderTaggedUser!]!">
  Users tagged on the message.
</ResponseField>

<ResponseField name="isRead" type="Boolean!">
  Whether the message has been read by the current user.
</ResponseField>

<ResponseField name="deletedAt" type="String">
  When the message was deleted, if applicable. `null` while the message is live.
</ResponseField>

<ResponseField name="deletedBy" type="ID">
  The user who deleted the message, if applicable.
</ResponseField>

<ResponseField name="createdAt" type="String!">
  When the message was created (ISO 8601 timestamp).
</ResponseField>

<ResponseField name="updatedAt" type="String!">
  When the message was last updated (ISO 8601 timestamp).
</ResponseField>

### The `BinderContentSearchHit` type

A search hit inside a subdocument's contents: the matching field names, values,
and the page-level bounding boxes that pin them.

```graphql theme={null}
type BinderContentSearchHit {
  subdocument: SubDocument!
  snippet: String!
  pages: [BinderContentPageHit!]!
}

type BinderContentPageHit {
  pageNumber: Int!
  fieldMatches: [BinderContentFieldMatch!]!
}

type BinderContentFieldMatch {
  fieldName: String!
  value: String!
  bbox: BinderContentBbox
}

type BinderContentBbox {
  xMin: Int!
  yMin: Int!
  xMax: Int!
  yMax: Int!
  pageNumber: Int!
}
```

<ResponseField name="subdocument" type="SubDocument!">
  The subdocument whose contents matched. See
  [`SubDocument`](#the-subdocument-type).
</ResponseField>

<ResponseField name="snippet" type="String!">
  A snippet of the matched content, for display.
</ResponseField>

<ResponseField name="pages" type="[BinderContentPageHit!]!">
  The pages inside the subdocument that carried matches, with the field-level
  matches on each page.
</ResponseField>

<ResponseField name="pageNumber" type="Int!">
  The page number, 1-indexed.
</ResponseField>

<ResponseField name="fieldMatches" type="[BinderContentFieldMatch!]!">
  The field-level matches on this page.
</ResponseField>

<ResponseField name="fieldName" type="String!">
  The name of the field that matched.
</ResponseField>

<ResponseField name="value" type="String!">
  The value of the field that matched.
</ResponseField>

<ResponseField name="bbox" type="BinderContentBbox">
  The bounding box that pins this match on the page. See `BinderContentBbox`
  below. `null` when the match is not pinned to a region.
</ResponseField>

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

## List the files in a binder

Read `binder.subdocuments` to list the files filed in a client's binder. This
is the most common read against the binder: it backs the binder's Documents
screen.

```graphql theme={null}
query GetClientBinderSubdocuments($clientId: ID!, $filter: SubDocumentsFilter) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        id
        clients(filters: { ids: [$clientId] }) {
          id
          binder {
            id
            subdocuments(filter: $filter) {
              id
              parentDocumentId
              fileName
              pageRange
              type
              issuer
              taxYear
              status
              category
              order
              createdAt
              bucket
              canonicalPath
              parentDocument {
                id
                fileName
                contentType
                url {
                  url
                }
              }
            }
          }
        }
      }
    }
  }
}
```

### Arguments

```graphql theme={null}
input SubDocumentsFilter {
  unreviewed: Boolean
  flagged: Boolean
  parentDocumentId: ID
}
```

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

<ParamField path="filter.unreviewed" type="Boolean">
  When `true`, return only subdocuments that have not been reviewed yet.
</ParamField>

<ParamField path="filter.flagged" type="Boolean">
  When `true`, return only subdocuments that carry a flag.
</ParamField>

<ParamField path="filter.parentDocumentId" type="ID">
  Return only subdocuments extracted from this parent document.
</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 GetClientBinderSubdocuments($clientId: ID!, $filter: SubDocumentsFilter) { me { ... on WorkspaceUser { id workspace { id clients(filters: { ids: [$clientId] }) { id binder { id subdocuments(filter: $filter) { id parentDocumentId fileName pageRange type issuer taxYear status category order createdAt bucket canonicalPath parentDocument { id fileName contentType url { url } } } } } } } } }",
      "variables": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "filter": null
      }
    }'
  ```
</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",
                "subdocuments": [
                  {
                    "id": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
                    "parentDocumentId": "018f9c2a-6a5b-7c3d-9a4e-2f6b1c8d0e49",
                    "fileName": "1099-INT-Acme-Broker.pdf",
                    "pageRange": [1],
                    "type": "1099-INT",
                    "issuer": "Acme Broker",
                    "taxYear": 2025,
                    "status": "ingested",
                    "category": "income",
                    "order": 0,
                    "createdAt": "2026-07-01T15:10:00.000Z",
                    "bucket": "source_docs",
                    "canonicalPath": "clients/018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c/source_docs/018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a/canonical.json",
                    "parentDocument": {
                      "id": "018f9c2a-6a5b-7c3d-9a4e-2f6b1c8d0e49",
                      "fileName": "uploads-packet.pdf",
                      "contentType": "application/pdf",
                      "url": {
                        "url": "https://signed.example.com/uploads-packet.pdf?sig=..."
                      }
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  The subdocument `id` is the value you pass as `documentPath` when [creating a
  document message](/apis/document-messages#create-an-annotation) or
  [signing off](/apis/leadsheets#sign-off-on-a-sheet-or-row) on a file. Save it
  when you list the binder so you can reference it later.
</Tip>

## List missing items

Read `binder.missingItemsAssessment` to get the state and filtered result of
the missing-document check. Filter by item status to read only open, ignored,
or resolved history.

```graphql theme={null}
query GetClientBinderMissingItems($clientId: ID!, $filter: BinderMissingItemsFilter) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        clients(filters: { ids: [$clientId] }) {
          id
          binder {
            id
            missingItemsAssessment(filter: $filter) {
              status
              reason
              count
              items {
                id
                binderId
                item
                formType
                issuer
                taxYear
                severity
                reason
                status
                category
                createdAt
                updatedAt
              }
            }
          }
        }
      }
    }
  }
}
```

### Arguments

```graphql theme={null}
input BinderMissingItemsFilter {
  status: ChecklistItemStatus
}
```

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

<ParamField path="filter.status" type="ChecklistItemStatus">
  Return only missing items in this status: `OPEN`, `IGNORED`, or `RESOLVED`.
  Omit it to use the default `OPEN` status.
</ParamField>

### Returns

```graphql theme={null}
type BinderMissingItemsAssessment {
  status: BinderMissingItemsAssessmentStatus!
  reason: String
  count: Int!
  items: [BinderMissingItem!]!
}
```

<ResponseField name="status" type="BinderMissingItemsAssessmentStatus!">
  Whether the missing-document check is `PENDING`, `AVAILABLE`, or `UNAVAILABLE`.
</ResponseField>

<ResponseField name="reason" type="String">
  Why the assessment is pending or unavailable. It is `null` when available.
</ResponseField>

<ResponseField name="count" type="Int!">
  The number of items matching the requested filter.
</ResponseField>

<ResponseField name="items" type="[BinderMissingItem!]!">
  The filtered missing-item rows. Open items are empty for pending or unavailable
  assessments.
</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 GetClientBinderMissingItems($clientId: ID!, $filter: BinderMissingItemsFilter) { me { ... on WorkspaceUser { id workspace { clients(filters: { ids: [$clientId] }) { id binder { id missingItemsAssessment(filter: $filter) { status reason count items { id binderId item formType issuer taxYear severity reason status category createdAt updatedAt } } } } } } } }",
      "variables": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "filter": { "status": "OPEN" }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "clients": [
            {
              "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
              "binder": {
                "id": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
                "missingItemsAssessment": {
                  "status": "AVAILABLE",
                  "reason": null,
                  "count": 1,
                  "items": [
                    {
                      "id": "018f9c2c-5d6e-7f20-9a33-7c4d5e6f7090",
                      "binderId": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
                      "item": "W-2 from Initech",
                      "formType": "W-2",
                      "issuer": "Initech",
                      "taxYear": 2025,
                      "severity": "CRITICAL",
                      "reason": "Expected a W-2 from Initech but no matching document was found in the binder.",
                      "status": "OPEN",
                      "category": "income",
                      "createdAt": "2026-07-01T15:30:00.000Z",
                      "updatedAt": "2026-07-01T15:30:00.000Z"
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

## Read message counts

Read `binder.messageCounts` for stored missing-item and open note counts. Use
`missingItemsAssessment` for a missing-document badge so the UI can distinguish
an available empty result from a pending or unavailable check.

```graphql theme={null}
query GetBinderMessageCounts($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          id
          binder {
            id
            messageCounts {
              openMissing
              notes
            }
          }
        }
      }
    }
  }
}
```

### Arguments

<ParamField path="clientId" type="ID!" required>
  The client whose binder counts you want. Pass it via `filters.ids` on
  `clients`.
</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 GetBinderMessageCounts($clientId: ID!) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { id binder { id messageCounts { openMissing notes } } } } } } }",
      "variables": { "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "workspace": {
          "clients": [
            {
              "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
              "binder": {
                "id": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
                "messageCounts": {
                  "openMissing": 1,
                  "notes": 2
                }
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

## Search the binder

Read `binder.search` to search across bookmarks (subdocuments by file name,
issuer, type, or category), annotations, marks, and document contents in one
call. The query is debounced in the web app: pass at least two characters.

```graphql theme={null}
query BinderSearch($clientId: ID!, $query: String!, $limit: Int = 20) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        clients(filters: { ids: [$clientId] }) {
          id
          binder {
            id
            search(query: $query, limit: $limit) {
              bookmarks {
                matchedField
                snippet
                subdocument {
                  id
                  fileName
                  type
                  issuer
                  category
                  pageRange
                }
              }
              annotations {
                matchedField
                snippet
                isReply
                message {
                  id
                  type
                  subDocumentPath
                  pageNumber
                  content
                  createdBy
                  createdAt
                }
              }
              marks {
                matchedField
                snippet
                isReply
                message {
                  id
                  type
                  subDocumentPath
                  pageNumber
                  content
                  createdBy
                  createdAt
                }
              }
              contents {
                snippet
                subdocument {
                  id
                  fileName
                  type
                  issuer
                  category
                  pageRange
                  canonicalPath
                }
                pages {
                  pageNumber
                  fieldMatches {
                    fieldName
                    value
                    bbox {
                      xMin
                      yMin
                      xMax
                      yMax
                      pageNumber
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

### Arguments

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

<ParamField path="query" type="String!" required>
  The search query. The web app debounces the input and requires at least two
  characters before firing the query.
</ParamField>

<ParamField path="limit" type="Int">
  Maximum number of hits to return per bucket. Defaults to `20`.
</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 BinderSearch($clientId: ID!, $query: String!, $limit: Int = 20) { me { ... on WorkspaceUser { id workspace { clients(filters: { ids: [$clientId] }) { id binder { id search(query: $query, limit: $limit) { bookmarks { matchedField snippet subdocument { id fileName type issuer category pageRange } } annotations { matchedField snippet isReply message { id type subDocumentPath pageNumber content createdBy createdAt } } marks { matchedField snippet isReply message { id type subDocumentPath pageNumber content createdBy createdAt } } contents { snippet subdocument { id fileName type issuer category pageRange canonicalPath } pages { pageNumber fieldMatches { fieldName value bbox { xMin yMin xMax yMax pageNumber } } } } } } } } } } }",
      "variables": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "query": "Acme",
        "limit": 20
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "me": {
        "id": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
        "workspace": {
          "clients": [
            {
              "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
              "binder": {
                "id": "018f9c2a-4b6f-7a10-b2c4-9e8d7f6a5b4d",
                "search": {
                  "bookmarks": [
                    {
                      "matchedField": "ISSUER",
                      "snippet": "Acme Broker",
                      "subdocument": {
                        "id": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
                        "fileName": "1099-INT-Acme-Broker.pdf",
                        "type": "1099-INT",
                        "issuer": "Acme Broker",
                        "category": "income",
                        "pageRange": [1]
                      }
                    }
                  ],
                  "annotations": [],
                  "marks": [],
                  "contents": [
                    {
                      "snippet": "Acme Broker paid $210.00 in interest",
                      "subdocument": {
                        "id": "018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a",
                        "fileName": "1099-INT-Acme-Broker.pdf",
                        "type": "1099-INT",
                        "issuer": "Acme Broker",
                        "category": "income",
                        "pageRange": [1],
                        "canonicalPath": "clients/018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c/source_docs/018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a/canonical.json"
                      },
                      "pages": [
                        {
                          "pageNumber": 1,
                          "fieldMatches": [
                            {
                              "fieldName": "payer",
                              "value": "Acme Broker",
                              "bbox": {
                                "xMin": 88,
                                "yMin": 412,
                                "xMax": 220,
                                "yMax": 428,
                                "pageNumber": 1
                              }
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }
  ```
</ResponseExample>

## Ignore a missing item

`ignoreBinderMissingItem` moves a missing item from `OPEN` to `IGNORED`. Use it
when a reviewer dismisses a missing item the run flagged. The mutation takes
the missing-item `id` and the `workspaceId`, and returns the updated
`BinderMissingItem` with its new `status`.

```graphql theme={null}
mutation IgnoreBinderMissingItem($id: ID!, $workspaceId: String!) {
  ignoreBinderMissingItem(id: $id, workspaceId: $workspaceId) {
    id
    status
  }
}
```

### Input

<ParamField path="id" type="ID!" required>
  The ID of the missing item to ignore (the `id` from
  [List missing items](#list-missing-items)).
</ParamField>

<ParamField path="workspaceId" type="String!" required>
  The workspace ID. Unlike the read fields, this mutation takes the workspace ID
  explicitly. Use the same workspace ID the `workspaceToken` was issued for.
</ParamField>

### Returns: `BinderMissingItem!`

The ignored [`BinderMissingItem`](#the-bindermissingitem-type), with `status`
set to `IGNORED`.

<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 IgnoreBinderMissingItem($id: ID!, $workspaceId: String!) { ignoreBinderMissingItem(id: $id, workspaceId: $workspaceId) { id status } }",
      "variables": {
        "id": "018f9c2c-5d6e-7f20-9a33-7c4d5e6f7090",
        "workspaceId": "019f0fb6-3001-7900-b7bc-0d11288504b1"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "ignoreBinderMissingItem": {
        "id": "018f9c2c-5d6e-7f20-9a33-7c4d5e6f7090",
        "status": "IGNORED"
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  After ignoring a missing item, refetch the
  [List missing items](#list-missing-items) query so the open count and the list
  agree. The web app optimistically moves the row from the `OPEN` assessment to
  the `IGNORED` assessment and updates both counts.
</Tip>

## Restore a missing item

`restoreBinderMissingItem` moves a missing item from `IGNORED` back to `OPEN`.
Use it when a reviewer wants to re-surface a missing item they previously
ignored.

```graphql theme={null}
mutation RestoreBinderMissingItem($id: ID!, $workspaceId: String!) {
  restoreBinderMissingItem(id: $id, workspaceId: $workspaceId) {
    id
    status
  }
}
```

### Input

<ParamField path="id" type="ID!" required>
  The ID of the missing item to restore (the `id` from
  [List missing items](#list-missing-items)).
</ParamField>

<ParamField path="workspaceId" type="String!" required>
  The workspace ID. Use the same workspace ID the `workspaceToken` was issued
  for.
</ParamField>

### Returns: `BinderMissingItem!`

The restored [`BinderMissingItem`](#the-bindermissingitem-type), with `status`
set to `OPEN`.

<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 RestoreBinderMissingItem($id: ID!, $workspaceId: String!) { restoreBinderMissingItem(id: $id, workspaceId: $workspaceId) { id status } }",
      "variables": {
        "id": "018f9c2c-5d6e-7f20-9a33-7c4d5e6f7090",
        "workspaceId": "019f0fb6-3001-7900-b7bc-0d11288504b1"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "restoreBinderMissingItem": {
        "id": "018f9c2c-5d6e-7f20-9a33-7c4d5e6f7090",
        "status": "OPEN"
      }
    }
  }
  ```
</ResponseExample>

<Tip>
  After restoring a missing item, refetch the
  [List missing items](#list-missing-items) query so the open count and the list
  agree. The web app optimistically moves the row from the `IGNORED` assessment
  to the `OPEN` assessment when the check is available.
</Tip>
