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

## 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!
  missingItems(filter: BinderMissingItemsFilter): [BinderMissingItem!]!
  openMissingItemsCount: Int!
  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="missingItems" type="[BinderMissingItem!]!">
  The missing-item checklist for this binder. Pass a `BinderMissingItemsFilter`
  to narrow by status. See [List missing items](#list-missing-items).
</ResponseField>

<ResponseField name="openMissingItemsCount" type="Int!">
  The number of missing items in the `OPEN` status. Use this as a quick badge
  count without fetching the full `missingItems` list.
</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 `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 these for quick badge rendering without
fetching the full `missingItems` list.

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

<ResponseField name="openMissing" type="Int!">
  The number of missing items in the `OPEN` status (same value as
  `Binder.openMissingItemsCount`).
</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.missingItems` to list the missing-document checklist the run
produced. Filter by status to read only the open, ignored, or resolved items.

```graphql theme={null}
query GetClientBinderMissingItems($clientId: ID!, $filter: BinderMissingItemsFilter) {
  me {
    ... on WorkspaceUser {
      id
      workspace {
        clients(filters: { ids: [$clientId] }) {
          id
          binder {
            id
            openMissingItemsCount
            missingItems(filter: $filter) {
              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 list every missing item regardless of status.
</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 GetClientBinderMissingItems($clientId: ID!, $filter: BinderMissingItemsFilter) { me { ... on WorkspaceUser { id workspace { clients(filters: { ids: [$clientId] }) { id binder { id openMissingItemsCount missingItems(filter: $filter) { 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",
                "openMissingItemsCount": 1,
                "missingItems": [
                  {
                    "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 the open missing-item count and the open notes
count. This is the cheap query for badge rendering: it does not return the full
missing-items or notes lists.

```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 removes the row from the `OPEN` list and
  decrements `openMissingItemsCount` in its cache, then refetches to confirm.
</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 increments `openMissingItemsCount` in its
  cache, then refetches to confirm.
</Tip>
