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:
- Annotations, free-text notes and flags a reviewer leaves on a document
(
type: "annotation"), with threaded replies for back-and-forth discussion.
- 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
}
The message’s unique identifier. Save this to later
update or hide
it.
The workspace that owns the message.
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.
annotation for free-text annotations, activity for activity events such as
sign-offs, missing_document for missing-document flags.
A free-form label for the kind of mark. Sign-offs use "signoff". Annotations
are surface-defined (for example "note", "flag").
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.
Optional path into the document content that the mark references.
The message body text. Annotations carry their note text here. null for
activity messages such as sign-offs that have no prose.
ISO 8601 timestamp when the message was soft-hidden via
hideDocumentMessage. null while the
message is visible.
The user who hid the message. null while the message is visible.
The user who created the message.
ISO 8601 timestamp of creation.
ISO 8601 timestamp of the last mutation (edit, hide, unhide).
threads
[DocumentMessageThread!]!
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!]!
}
The thread reply’s unique identifier.
The parent DocumentMessage.id.
Path into the document content the reply is anchored to.
The reply body. null when empty.
The user who posted the reply.
ISO 8601 timestamp of creation.
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!
}
The tag record’s unique identifier.
The workspace user who was tagged.
Set when the tag is on a top-level DocumentMessage. null when the tag is on
a thread reply.
Set when the tag is on a DocumentMessageThread reply. null when the tag is
on a top-level message.
ISO 8601 timestamp of the tag.
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
}
Return only messages created in the context of this task ID.
Return only messages anchored to this subdocument path.
Return only messages whose markType is in this list (for example
["signoff"] for sign-offs, ["note"] for notes).
Return only messages whose type is in this list.
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 {
clientId: ID!
documentPath: String!
type: DocumentMessageType!
markType: String!
anchorPoint: JSON!
body: String
taskId: ID
taggedUserIds: [ID!]
}
The client whose binder this message belongs to.
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.
A label for the kind of mark. Use "note" or "flag" for annotations, and
"signoff" for a sign-off.
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).
The message body text. Required for prose annotations; null for activity
messages such as sign-offs that have no prose.
Optional task ID to associate the message with (for example, the
review task it was created during).
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 {
body: String
anchorPoint: JSON
taggedUserIds: [ID!]
}
The document message to update.
The new anchor point object.
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
}
}
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!]
}
The parent DocumentMessage.id to reply under.
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!
}
The thread reply to update.
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)
}
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.