Skip to main content
Document messages are the annotation, sign-off, and reply layer that lives on a client’s binder documents. Every document message is a DocumentMessage record anchored to a subdocument path, with a type that says what kind of mark it is and a markType label that further classifies it. The API is one and the same for two distinct use cases:
  1. Annotations, free-text notes and flags a reviewer leaves on a document (type: "annotation"), with threaded replies for back-and-forth discussion.
  2. Sign-offs, the reviewer sign-off marks the review flow records against a sheet or row (type: "activity", markType: "signoff"). See Review a return and sign off for the end-to-end recipe.
All document-message operations are reached with a workspaceToken (see Authentication) and go to the single GraphQL endpoint:
https://router.apps.filed.com/graphql

The DocumentMessage type

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
}
id
ID!
The message’s unique identifier. Save this to later update or hide it.
workspaceId
ID!
The workspace that owns the message.
documentPath
String!
The subdocument path the message is anchored to. For a sign-off this is the subdocument being signed off; for an annotation it is the document location the note is attached to.
type
DocumentMessageType!
annotation for free-text annotations, activity for activity events such as sign-offs, missing_document for missing-document flags.
markType
String!
A free-form label for the kind of mark. Sign-offs use "signoff". Annotations are surface-defined (for example "note", "flag").
anchorPoint
JSON!
A JSON object describing where the mark is anchored. For a sign-off the shape is { page, coordinates: { x, y }, level, user_role }. The exact fields depend on the surface that created the message.
contentPath
String
Optional path into the document content that the mark references.
body
String
The message body text. Annotations carry their note text here. null for activity messages such as sign-offs that have no prose.
hiddenAt
String
ISO 8601 timestamp when the message was soft-hidden via hideDocumentMessage. null while the message is visible.
hiddenBy
ID
The user who hid the message. null while the message is visible.
createdBy
ID!
The user who created the message.
createdAt
String!
ISO 8601 timestamp of creation.
updatedAt
String!
ISO 8601 timestamp of the last mutation (edit, hide, unhide).
threads
[DocumentMessageThread!]!
Replies on this message (see Threads (replies)).
taggedUsers
[DocumentMessageTaggedUser!]!
Users tagged on this message. See the DocumentMessageTaggedUser type below.

The DocumentMessageThread type

type DocumentMessageThread {
  id: ID!
  documentMessageId: ID!
  contentPath: String!
  body: String
  createdBy: ID!
  createdAt: String!
  updatedAt: String!
  taggedUsers: [DocumentMessageTaggedUser!]!
}
id
ID!
The thread reply’s unique identifier.
documentMessageId
ID!
The parent DocumentMessage.id.
contentPath
String!
Path into the document content the reply is anchored to.
body
String
The reply body. null when empty.
createdBy
ID!
The user who posted the reply.
createdAt
String!
ISO 8601 timestamp of creation.
updatedAt
String!
ISO 8601 timestamp of the last edit.
taggedUsers
[DocumentMessageTaggedUser!]!
Users tagged on this reply.

The DocumentMessageTaggedUser type

type DocumentMessageTaggedUser {
  id: ID!
  userId: ID!
  documentMessageId: ID
  documentMessageThreadId: ID
  createdAt: String!
  updatedAt: String!
}
id
ID!
The tag record’s unique identifier.
userId
ID!
The workspace user who was tagged.
documentMessageId
ID
Set when the tag is on a top-level DocumentMessage. null when the tag is on a thread reply.
documentMessageThreadId
ID
Set when the tag is on a DocumentMessageThread reply. null when the tag is on a top-level message.
createdAt
String!
ISO 8601 timestamp of the tag.
updatedAt
String!
ISO 8601 timestamp of the last update to the tag.

Read document messages

There is no top-level documentMessages query. Read them through the Client.documentMessages(filter) field, reached via me { ... on WorkspaceUser { workspace { clients(...) { documentMessages(...) } } } }.
query GetClientDocumentMessages($clientId: ID!, $filter: DocumentMessagesFilter) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          documentMessages(filter: $filter) {
            id
            documentPath
            type
            markType
            anchorPoint
            body
            hiddenAt
            hiddenBy
            createdBy
            createdAt
            updatedAt
            threads {
              id
              documentMessageId
              contentPath
              body
              createdBy
              createdAt
              updatedAt
            }
          }
        }
      }
    }
  }
}

Arguments

input DocumentMessagesFilter {
  taskId: ID
  documentPath: String
  markTypes: [String!]
  types: [DocumentMessageType!]
  includeHidden: Boolean
}
filter.taskId
ID
Return only messages created in the context of this task ID.
filter.documentPath
String
Return only messages anchored to this subdocument path.
filter.markTypes
[String!]
Return only messages whose markType is in this list (for example ["signoff"] for sign-offs, ["note"] for notes).
filter.types
[DocumentMessageType!]
Return only messages whose type is in this list.
filter.includeHidden
Boolean
When true, include soft-hidden messages in the results. Defaults to false, which excludes hidden messages.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "query GetClientDocumentMessages($clientId: ID!, $filter: DocumentMessagesFilter) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { documentMessages(filter: $filter) { id documentPath type markType anchorPoint body createdBy createdAt hiddenAt } } } } } }",
    "variables": {
      "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
      "filter": { "types": ["annotation"], "includeHidden": false }
    }
  }'
{
  "data": {
    "me": {
      "workspace": {
        "clients": [
          {
            "documentMessages": [
              {
                "id": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
                "documentPath": "binder/jane/2025/w2.pdf#page=1",
                "type": "annotation",
                "markType": "note",
                "anchorPoint": {
                  "page": 1,
                  "coordinates": { "x": 120, "y": 340 }
                },
                "body": "Box 1 total matches the 1099-INT sum, but box 2 looks high. Recheck.",
                "createdBy": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
                "createdAt": "2026-07-04T16:10:00.000Z",
                "hiddenAt": null
              }
            ]
          }
        ]
      }
    }
  }
}

Annotations

An annotation is a DocumentMessage with type: "annotation" that a reviewer leaves on a document. Use a markType label your surface understands (the web app uses "note" for free-text notes and "flag" for review flags). Annotations carry their text in body, and can collect threaded replies for discussion.

Create an annotation

createDocumentMessage creates a single document message anchored to a subdocument path. For an annotation, pass type: "annotation", your surface’s markType, and the note text in body.
mutation CreateDocumentMessage($input: CreateDocumentMessageInput!) {
  createDocumentMessage(input: $input) {
    id
    documentPath
    type
    markType
    anchorPoint
    body
    createdBy
    createdAt
  }
}

Input: CreateDocumentMessageInput

input CreateDocumentMessageInput {
  clientId: ID!
  documentPath: String!
  type: DocumentMessageType!
  markType: String!
  anchorPoint: JSON!
  body: String
  taskId: ID
  taggedUserIds: [ID!]
}
clientId
ID!
required
The client whose binder this message belongs to.
documentPath
String!
required
The subdocument path the message is anchored to.
type
DocumentMessageType!
required
annotation, activity, or missing_document. Use annotation for notes and flags, activity for sign-offs.
markType
String!
required
A label for the kind of mark. Use "note" or "flag" for annotations, and "signoff" for a sign-off.
anchorPoint
JSON!
required
A JSON object describing where the mark is anchored. The shape is surface-defined (for example { page, coordinates: { x, y } } for an annotation, or { page, coordinates: { x, y }, level, user_role } for a sign-off).
body
String
The message body text. Required for prose annotations; null for activity messages such as sign-offs that have no prose.
taskId
ID
Optional task ID to associate the message with (for example, the review task it was created during).
taggedUserIds
[ID!]
Optional list of workspace user IDs to tag on the message.

Returns: DocumentMessage!

The created DocumentMessage. Save its id to later update or hide it.
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 } }",
    "variables": {
      "input": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "documentPath": "binder/jane/2025/w2.pdf#page=1",
        "type": "annotation",
        "markType": "note",
        "anchorPoint": { "page": 1, "coordinates": { "x": 120, "y": 340 } },
        "body": "Box 1 total matches the 1099-INT sum, but box 2 looks high. Recheck."
      }
    }
  }'
{
  "data": {
    "createDocumentMessage": {
      "id": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
      "documentPath": "binder/jane/2025/w2.pdf#page=1",
      "type": "annotation",
      "markType": "note",
      "anchorPoint": { "page": 1, "coordinates": { "x": 120, "y": 340 } },
      "body": "Box 1 total matches the 1099-INT sum, but box 2 looks high. Recheck.",
      "createdBy": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
      "createdAt": "2026-07-04T16:10:00.000Z"
    }
  }
}

Update a document message

updateDocumentMessage edits the body, anchor point, or tagged users on an existing document message. markType and type are not mutable.
mutation UpdateDocumentMessage($id: ID!, $input: UpdateDocumentMessageInput!) {
  updateDocumentMessage(id: $id, input: $input) {
    id
    body
    anchorPoint
    updatedAt
  }
}

Input: UpdateDocumentMessageInput

input UpdateDocumentMessageInput {
  body: String
  anchorPoint: JSON
  taggedUserIds: [ID!]
}
id
ID!
required
The document message to update.
input.body
String
The new body text.
input.anchorPoint
JSON
The new anchor point object.
input.taggedUserIds
[ID!]
The complete list of tagged user IDs (replaces the previous list).

Returns: DocumentMessage!

The updated DocumentMessage.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation UpdateDocumentMessage($id: ID!, $input: UpdateDocumentMessageInput!) { updateDocumentMessage(id: $id, input: $input) { id body anchorPoint updatedAt } }",
    "variables": {
      "id": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
      "input": { "body": "Box 1 confirmed. Box 2 is high, needs a corrected 1099-INT." }
    }
  }'
{
  "data": {
    "updateDocumentMessage": {
      "id": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
      "body": "Box 1 confirmed. Box 2 is high, needs a corrected 1099-INT.",
      "anchorPoint": { "page": 1, "coordinates": { "x": 120, "y": 340 } },
      "updatedAt": "2026-07-04T16:30:00.000Z"
    }
  }
}

Hide and unhide a document message

Hiding a document message is the soft-delete the binder uses to dismiss an annotation or to undo a sign-off (see Sign-offs). The message is retained with hiddenAt and hiddenBy set, and excluded from default reads unless filter.includeHidden: true is passed.
mutation HideDocumentMessage($id: ID!) {
  hideDocumentMessage(id: $id) {
    id
    hiddenAt
    hiddenBy
  }
}

mutation UnhideDocumentMessage($id: ID!) {
  unhideDocumentMessage(id: $id) {
    id
    hiddenAt
    hiddenBy
  }
}
id
ID!
required
The document message to hide or unhide.

Returns: DocumentMessage!

The updated DocumentMessage. After hideDocumentMessage, hiddenAt and hiddenBy are populated. After unhideDocumentMessage, both are null again.
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 hiddenAt hiddenBy } }",
    "variables": { "id": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70" }
  }'
{
  "data": {
    "hideDocumentMessage": {
      "id": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
      "hiddenAt": "2026-07-04T16:45:00.000Z",
      "hiddenBy": "019f0fb6-37b1-7800-b7bc-0d11288504b1"
    }
  }
}

Threads (replies)

Threads are replies on a DocumentMessage. A thread reply is its own DocumentMessageThread object, created under a parent message ID. Use threads for the back-and-forth discussion that grows under an annotation.

Create a thread reply

mutation CreateDocumentMessageThread($input: CreateDocumentMessageThreadInput!) {
  createDocumentMessageThread(input: $input) {
    id
    documentMessageId
    contentPath
    body
    createdBy
    createdAt
    updatedAt
  }
}
Input: CreateDocumentMessageThreadInput
input CreateDocumentMessageThreadInput {
  documentMessageId: ID!
  body: String!
  taggedUserIds: [ID!]
}
documentMessageId
ID!
required
The parent DocumentMessage.id to reply under.
body
String!
required
The reply body text.
taggedUserIds
[ID!]
Optional list of workspace user IDs to tag on the reply.
Returns: DocumentMessageThread!
The created DocumentMessageThread.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation CreateDocumentMessageThread($input: CreateDocumentMessageThreadInput!) { createDocumentMessageThread(input: $input) { id documentMessageId contentPath body createdBy createdAt updatedAt } }",
    "variables": {
      "input": {
        "documentMessageId": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
        "body": "Pulled the corrected 1099-INT from the broker portal, box 2 now matches."
      }
    }
  }'
{
  "data": {
    "createDocumentMessageThread": {
      "id": "019f0fb6-5b3d-7900-9c01-2b3c4d5e6f71",
      "documentMessageId": "019f0fb6-4a2c-7900-9c01-2b3c4d5e6f70",
      "contentPath": "",
      "body": "Pulled the corrected 1099-INT from the broker portal, box 2 now matches.",
      "createdBy": "019f0fb6-37b1-7800-b7bc-0d11288504b1",
      "createdAt": "2026-07-04T16:50:00.000Z",
      "updatedAt": "2026-07-04T16:50:00.000Z"
    }
  }
}

Update a thread reply

mutation UpdateDocumentMessageThread($id: ID!, $input: UpdateDocumentMessageThreadInput!) {
  updateDocumentMessageThread(id: $id, input: $input) {
    id
    body
    updatedAt
  }
}
Input: UpdateDocumentMessageThreadInput
input UpdateDocumentMessageThreadInput {
  body: String!
}
id
ID!
required
The thread reply to update.
input.body
String!
required
The new reply body text.
Returns: DocumentMessageThread!
The updated DocumentMessageThread.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation UpdateDocumentMessageThread($id: ID!, $input: UpdateDocumentMessageThreadInput!) { updateDocumentMessageThread(id: $id, input: $input) { id body updatedAt } }",
    "variables": {
      "id": "019f0fb6-5b3d-7900-9c01-2b3c4d5e6f71",
      "input": { "body": "Corrected 1099-INT uploaded, box 2 now matches. Resolving." }
    }
  }'
{
  "data": {
    "updateDocumentMessageThread": {
      "id": "019f0fb6-5b3d-7900-9c01-2b3c4d5e6f71",
      "body": "Corrected 1099-INT uploaded, box 2 now matches. Resolving.",
      "updatedAt": "2026-07-04T16:52:00.000Z"
    }
  }
}

Delete a thread reply

deleteDocumentMessageThread permanently removes a thread reply. It returns the deleted reply’s ID.
mutation DeleteDocumentMessageThread($id: ID!) {
  deleteDocumentMessageThread(id: $id)
}
id
ID!
required
The thread reply to delete.
Returns: ID!
The ID of the deleted thread reply.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation DeleteDocumentMessageThread($id: ID!) { deleteDocumentMessageThread(id: $id) }",
    "variables": { "id": "019f0fb6-5b3d-7900-9c01-2b3c4d5e6f71" }
  }'
{
  "data": {
    "deleteDocumentMessageThread": "019f0fb6-5b3d-7900-9c01-2b3c4d5e6f71"
  }
}

Sign-offs

A sign-off is a DocumentMessage with type: "activity" and markType: "signoff", anchored to the subdocument path being signed off. The review flow records one sign-off per subdocument. Undoing a sign-off is a soft-hide of the sign-off DocumentMessage via hideDocumentMessage.
There is no signOffSubDocuments mutation. The schema defines an input type called SignOffSubDocumentsInput, but no field on Mutation is wired to it. The real sign-off write surface is createDocumentMessage with type: "activity" and markType: "signoff", one call per subdocument you are signing off on. Do not look for a signOffSubDocuments mutation, it does not exist.
For the end-to-end review recipe that ties leadsheets, sign-offs, and refetch together, see Review a return and sign off. For the sign-off read surface on leadsheets (the signOffs field on Leadsheet and LeadsheetFieldRow), see Leadsheets and review.

Sign off on a sheet or row

createDocumentMessage records a sign-off. Pass type: "activity", markType: "signoff", the subdocument path being signed off as documentPath, and an anchorPoint that carries the reviewer’s level and user_role so the UI renders the sign-off with the right label. The web app signs off one subdocument at a time, one createDocumentMessage call per subdocument.
mutation CreateDocumentMessage($input: CreateDocumentMessageInput!) {
  createDocumentMessage(input: $input) {
    id
    documentPath
    type
    markType
    anchorPoint
    body
    createdBy
    createdAt
    hiddenAt
  }
}
The input is the same CreateDocumentMessageInput documented under Create an annotation; only the type, markType, and anchorPoint values differ for a sign-off.
{
  "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"
    }
  }
}
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"
        }
      }
    }
  }'
{
  "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
    }
  }
}
Save the returned id. You pass it to hideDocumentMessage in the next step if you ever need to undo the sign-off.
documentPath for a subdocument sign-off is the subdocument’s ID (the same value you read as a LeadsheetFieldRow parent path, or the anchor a LeadsheetSheetIssue is tied to). The web app signs off one subdocument at a time, one createDocumentMessage call per subdocument.

Undo a sign-off

Undoing a sign-off is a soft-hide of the sign-off DocumentMessage via hideDocumentMessage (see Hide and unhide a document message). The sign-off row stays in history with hiddenAt set, and the leadsheets query’s resolved and issueCount recomputation backs it out.
mutation HideDocumentMessage($id: ID!) {
  hideDocumentMessage(id: $id) {
    id
    documentPath
    type
    markType
    hiddenAt
    hiddenBy
  }
}
{
  "id": "018f9c2c-2b3c-7f40-9b55-7e6f70829001"
}
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" }
  }'
{
  "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"
    }
  }
}
After hiding or unhiding a sign-off, refetch any open leadsheets query so the server recomputes LeadsheetSheetIssue.resolved and Leadsheet.issueCount. See Review a return and sign off for the recipe step.

See also

  • Leadsheets and review for the Leadsheets, Leadsheet, LeadsheetSheetIssue, LeadsheetFieldRow, and LeadsheetTrace type definitions, and the leadsheets sign-off read surface (Leadsheet.signOffs, LeadsheetFieldRow.signOffs).
  • Review a return and sign off for the end-to-end recipe that ties reading leadsheets, recording sign-offs, and refetching together.
  • Tasks for the polling mechanics behind the review task whose taskId you can associate a document message with.