> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Comment Annotations

> Create V2 comment annotations via REST: plain comments, agent findings, suggestions with a proposed-change payload, and private annotations.

Use this API to add comment annotations to a document within an organization.

* You can add comments on an element, text or page.
* You can provide HTML or text content.
* Additional filters can be applied using location IDs.

# Endpoint

`POST https://api.velt.dev/v2/commentannotations/add`

# Headers

**x-velt-api-key** `string` *required*

Your API key.

**x-velt-auth-token** `string` *required*

Your [Auth Token](https://velt.dev/docs/security/auth-tokens.md).

# Body

#### Params

**data** `object` *required*

**properties**

**organizationId** `string` *required*

Organization ID

**documentId** `string` *required*

Document ID

**createOrganization** `boolean`

If set to true, a new organization will be created (if it doesn't exist) before the annotation is added.

**createDocument** `boolean`

If set to true, a new document will be created (if it doesn't exist) before the annotation is added.

**verifyUserPermissions** `boolean`

When enabled, verifies the user has access to the document before creating comment annotations.
This ensures annotations respect document access permissions configured via Access Control or Permission Provider.

Default: `false`

**commentAnnotations** `object[]` *required*

**properties**

**annotationId** `string`

Custom Annotation ID. If not provided, Velt will generate a unique ID for the annotation.

**location** `object`

**properties**

**id** `string` *required*

Location ID

**locationName** `string`

Location Name

**targetElement** `object`

Target Element

**properties**

**elementId** `string`

Element DOM Id. When used with `targetText`, defines the text search boundaries for locating text comments within the specified element.

**targetText** `string`

Target Text. Provide this if you want to add comment annotation on the provided text content.

**occurrence** `number`

Occurrence. Provide this if you want to add comment annotation on a text content.

**selectAllContent** `boolean`

Select All Content. Provide this if you want to select and add comment annotation on the entire text content of the target elementId.

**commentData** `object[]` *required*

Array of Comment Data

**properties**

**commentId** `number`

Custom Comment ID. If not provided, Velt will generate a unique ID for the comment.

**commentText** `string`

Comment content in plain text string. To tag users, wrap their userId in double curly braces like `{{userId}}`. The frontend will replace this with the user's name.

**commentHtml** `string`

Comment content in HTML string. To tag users, wrap their userId in double curly braces like `{{userId}}`. The frontend will replace this with the user's name.

**context** `object`

Custom key/value metadata object. This is used to store any additional information about the comment.

**isCommentResolverUsed** `boolean`

Use this for self-hosting comments data. Set this as true if you are comments resolver data provider in the SDK.

**isCommentTextAvailable** `boolean`

Use this for self-hosting comments data. Set this as true if this comment will have text content. Sometimes, comments might only have attachments and in that case, set this as false.

**progress** `CommentProgress`

Turns this comment into a live progress row while `progress.state` is `active`. Absence leaves the comment unaffected. See [CommentProgress](https://velt.dev/docs/api-reference/sdk/models/data-models.md#commentprogress) for the full shape. `steps` is capped at 100 entries.

**actions** `CommentAction[]`

Customer-defined action chips rendered on this comment row. Overrides the annotation-level `actions` default for this row. Capped at 20 entries. See [CommentAction](https://velt.dev/docs/api-reference/sdk/models/data-models.md#commentaction) for the full shape.

**triggerNotification** `boolean`

When enabled, adding comments via the REST API will trigger in-app notifications, email notifications to relevant users and also trigger webhooks matching the SDK’s native notification behavior. Default: false.

**triggerActivities** `boolean`

When enabled, adding comments via the REST API will create activity log records matching the SDK’s native activity log behavior. Default: false.

**from** `User` *required*

User object from whom the comment is added

**createdAt** `number`

Created At timestamp (in milliseconds since epoch).

**lastUpdated** `number`

Last Updated timestamp (in milliseconds since epoch).

**taggedUserContacts** `object[]`

Array of tagged user contacts. Use this field to provide information about users tagged in the comment. Each userId wrapped in `{{userId}}` in commentText or commentHtml should have a corresponding entry here so the frontend can replace it with the user's name.

**properties**

**userId** `string` *required*

User ID of the tagged user. This should match the userId used in `{{userId}}` syntax within commentText or commentHtml.

**contact** `object` *required*

Contact information for the tagged user

**properties**

**userId** `string` *required*

User ID of the tagged user

**name** `string`

Name of the tagged user

**email** `string`

Email of the tagged user

**agent** `object`

Marks this comment as agent-authored (an agent finding or an agent reply). V2 only. When set, the server stamps `sourceType: "agent"` on the comment. When set on the root comment (`commentData[0]`), the server also generates the annotation-level agent block and stamps `sourceType: "agent"` on the annotation. Discriminated on `agentSource`.

**properties**

**agentSource** `string` *required*

Origin of the agent. Must be one of: `velt` or `external`.

**agentId** `string` *required*

Agent ID. Must be non-empty for both sources. When `agentSource` is `velt`, it is a built-in agent like `spell-check` or a custom agent ID that is verified server-side (unknown IDs return `NOT_FOUND`). When `agentSource` is `external`, it is your own identifier — opaque and never validated against any registry.

**executionId** `string`

Execution / run ID for this agent invocation.

**agentName** `string`

Display name for the agent. **Required when `agentSource` is `external`** (the only source of truth for an external agent's name). Not used for `velt` agents (their name is resolved server-side).

**url** `string`

Page URL associated with the finding.

**reason** `object` *required*

Finding details produced by the agent. Additional custom fields are preserved.

**properties**

**title** `string` *required*

Short finding title.

**description** `string` *required*

Finding description.

**severity** `string` *required*

Severity of the finding. Must be one of: `critical`, `high`, `medium`, `low`, or `info`.

**findingId** `string`

Unique identifier for the finding.

**findingType** `string`

Type of finding. Must be one of: `text`, `pin`, or `page`.

**issueType** `string`

Custom issue classification.

**confidence** `number`

Confidence score for the finding. Integer between 0 and 100.

**suggestion** `string`

Suggested change in plain text.

**suggestedFix** `string`

Suggested fix for the finding.

**htmlSnippet** `string`

Relevant HTML snippet for the finding.

**htmlSelector** `string`

CSS/HTML selector pointing to the finding location.

**source** `string`

Provenance of the rule that triggered the finding. Must be one of: `instructions` or `knowledge`.

**knowledgeSection** `string`

Knowledge section that triggered the finding.

**status** `Status`

Status

**properties**

**id** `string` *required*

Status ID

**name** `string` *required*

Status Name

**type** `string` *required*

Type. Must be one of: `default`, `ongoing`, or `terminal`.

**color** `string` *required*

Text and comment pin color

**lightColor** `string` *required*

Background color on the status indicator

**svg** `string`

Raw SVG of the icon. Either `svg` or `iconUrl` is required.

**iconUrl** `string`

Icon URL. Either `svg` or `iconUrl` is required.

**assignedTo** `User`

User object to whom the comment is assigned

**context** `object`

Custom key/value metadata object

**createdAt** `number`

Created At timestamp (in milliseconds since epoch).

**lastUpdated** `number`

Last Updated timestamp (in milliseconds since epoch).

**priority** `object`

Priority

**properties**

**id** `string` *required*

Priority ID

**color** `string` *required*

Priority Color

**name** `string` *required*

Priority Name

**lightColor** `string` *required*

Priority Light Color

**type** `string`

Annotation kind. Must be one of: `comment` or `suggestion`. This is the source of truth for the suggestion classification; agent-produced findings should set `suggestion`. Defaults to `comment` when omitted.

**suggestion** `object`

Proposed-change payload for `type: "suggestion"` annotations. Persisted verbatim so reviewers (and your app's accept handler) can round-trip the target and the old/new values. Ignored when `type` is not `suggestion`. See [Suggestions](https://velt.dev/docs/async-collaboration/suggestions/overview.md) for the full frontend lifecycle.

The schema is open: any additional custom fields you include are stored as-is alongside the fields below. `status` is server-owned: any caller-supplied value is dropped and the server stamps `pending` at create time (and `accepted` / `rejected` on the corresponding lifecycle action).

**properties**

**targetId** `string`

Stable ID of the thing being changed (for example, the record or field ID the suggestion applies to). Your accept handler uses this to locate what to update.

**targetType** `string`

Kind of target the suggestion applies to (for example, `custom`, `text`, or a value your app defines).

**oldValue** `any`

The value at the time the suggestion was created. Can be any JSON-serializable value.

**newValue** `any`

The proposed new value. Can be any JSON-serializable value. Your app writes this into its own state or backend when the suggestion is accepted.

**summary** `string | null`

Optional human-readable summary of the change (for example, `"row.123: 205 → 305"`).

**driftDetected** `boolean`

Set to `true` when the live target value at accept time differs from `oldValue`. Informational; it does not block the accept.

**commentType** `string`

Caller-driven comment classification. Must be one of: `manual` or `chart`. Defaults to `manual` when omitted.

> [!NOTE]
>
> The legacy value `commentType: "suggestion"` is still accepted and preserved for backward compatibility, but it no longer drives the suggestion classification. Use the annotation-level `type: "suggestion"` field instead.

**visibility** `object`

Controls who can see the annotation. Discriminated on `type`. When omitted, the annotation defaults to `public`.

**properties**

**type** `string` *required*

Visibility type. Must be one of: `public`, `organizationPrivate`, or `restricted`.

* `public`: Visible to everyone.
* `organizationPrivate`: Visible only to users in the specified organization. Requires `organizationId`.
* `restricted`: Visible only to the specified users (private comment). Requires `userIds`.

**organizationId** `string`

Organization ID whose users can see the annotation. Required when `type` is `organizationPrivate`.

**userIds** `string[]`

User IDs allowed to see the annotation. Required (non-empty) when `type` is `restricted`.

**actions** `CommentAction[]`

Per-row default action chips, inherited by every comment on the annotation that doesn't set its own `commentData[].actions`. Capped at 20 entries. See [CommentAction](https://velt.dev/docs/api-reference/sdk/models/data-models.md#commentaction) for the full shape.

## **Example Requests**

#### Add comment annotation by organizationId, documentId and location

```JSON theme={null}
{
  "data": {
    "organizationId": "yourOrganizationId",
    "documentId": "yourDocumentId",
    "commentAnnotations": [
      {
        "location": {
          "id": "yourLocationId",
          "locationName": "yourLocationName"
        },
        "targetElement": {
          "elementId": "yourElementId",
          "targetText": "Your Target Text",
          "occurrence": 1,
          "selectAllContent": false
        },
        "commentData": [
          {
            "commentText": "Sample Comment",
            "commentHtml": "<div>Sample Comment</div>",
            "from": {
              "userId": "yourUserId",
              "name": "yourUserName",
              "email": "yourUserEmail",
            }
          }
        ]
      }
    ]
  }
}
```

#### Add comment annotation with user tagging

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "commentData": [
          {
            "triggerNotification": true,
            "commentText": "Hey {{user_sarah_chen}}, can you review this color scheme? I think we should use a darker shade for better contrast.",
            "commentHtml": "<p>Hey {{user_sarah_chen}}, can you review this color scheme? I think we should use a darker shade for better contrast.</p>",
            "from": {
              "userId": "user_john_smith",
              "name": "John Smith",
              "email": "john.smith@acme-corp.com"
            },
            "taggedUserContacts": [
              {
                "userId": "user_sarah_chen",
                "contact": {
                  "userId": "user_sarah_chen",
                  "name": "Sarah Chen",
                  "email": "sarah.chen@acme-corp.com"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}
```

In this example, `{{user_sarah_chen}}` in the commentText will be replaced with "Sarah Chen" on the frontend, displaying as "Hey Sarah Chen, can you review this color scheme? I think we should use a darker shade for better contrast."

#### Add comment annotation with permission verification

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "verifyUserPermissions": true,
    "commentAnnotations": [
      {
        "commentData": [
          {
            "commentText": "Please review this section",
            "from": {
              "userId": "user_john_smith",
              "name": "John Smith",
              "email": "john.smith@acme-corp.com"
            }
          }
        ]
      }
    ]
  }
}
```

> [!NOTE]
>
> When `verifyUserPermissions` is enabled, the API verifies the user has access to the document before creating the comment annotation. If verification fails, the request will be rejected.

#### Add comment annotation with activity tracking

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "commentData": [
          {
            "commentText": "This needs review",
            "triggerNotification": true,
            "triggerActivities": true,
            "from": {
              "userId": "user-1",
              "email": "user@example.com",
              "name": "User Name"
            }
          }
        ]
      }
    ]
  }
}
```

#### Add a content-less progress comment with action chips

Create a comment with no text and `progress.state: "active"` to reserve a spot for a running task. Push step updates with subsequent [Update Comments](https://velt.dev/docs/api-reference/rest-apis/v2/comments-feature/comments/update-comments.md) calls on the same `commentId`, then finish by writing the real content with `state: "completed"`. `actions` renders customer-defined chips; Velt performs no write of its own when one is clicked.

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "commentData": [
          {
            "isCommentTextAvailable": false,
            "progress": {
              "state": "active",
              "steps": [
                { "label": "Fetching data", "state": "active" }
              ]
            },
            "actions": [
              { "id": "approve", "label": "Approve" }
            ],
            "from": {
              "userId": "agent-1",
              "name": "Review Agent"
            }
          }
        ]
      }
    ]
  }
}
```

#### Add comment annotation with access context

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "analytics-dashboard",
    "commentAnnotations": [
      {
        "context": {
          "access": {
            "entityId": "numberOfVisitors",
            "dashboardId": "myDashboard"
          }
        },
        "commentData": [
          {
            "commentText": "Traffic numbers look unusual today",
            "from": {
              "userId": "user_john_smith",
              "name": "John Smith",
              "email": "john.smith@acme-corp.com"
            }
          }
        ]
      }
    ]
  }
}
```

#### Add an agent suggestion (finding)

Set `type: "suggestion"` and attach an `agent` block to the root comment (`commentData[0]`). The server generates the annotation-level agent block and stamps `sourceType: "agent"`.

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "type": "suggestion",
        "commentData": [
          {
            "commentText": "Heading has a spelling mistake: 'Welcom' should be 'Welcome'.",
            "from": {
              "userId": "spell-check",
              "name": "Spell Check Agent"
            },
            "agent": {
              "agentSource": "velt",
              "agentId": "spell-check",
              "executionId": "exec_123",
              "url": "https://example.com/design-mockup-v2",
              "reason": {
                "title": "Spelling error",
                "description": "The word 'Welcom' is misspelled.",
                "severity": "low",
                "findingType": "text",
                "confidence": 98,
                "suggestedFix": "Welcome",
                "source": "instructions"
              }
            }
          }
        ]
      }
    ]
  }
}
```

To attach a finding from an agent run through your own framework, use `agentSource: "external"` and supply your own `agentName` and `agentId` (`executionId` is optional):

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "type": "suggestion",
        "commentData": [
          {
            "commentText": "This button has insufficient color contrast.",
            "from": {
              "userId": "a11y-bot"
            },
            "agent": {
              "agentSource": "external",
              "agentName": "Accessibility Bot",
              "agentId": "a11y-bot",
              "reason": {
                "title": "Low color contrast",
                "description": "Contrast ratio is 2.1:1, below the 4.5:1 WCAG AA threshold.",
                "severity": "high",
                "findingType": "pin"
              }
            }
          }
        ]
      }
    ]
  }
}
```

#### Add a suggestion with a proposed-change payload

Set `type: "suggestion"` and attach a `suggestion` object describing what should change. The server persists the payload verbatim and stamps `suggestion.status: "pending"`; any caller-supplied `status` is dropped. Additional custom fields inside `suggestion` are stored as-is.

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "type": "suggestion",
        "suggestion": {
          "targetId": "row.123",
          "targetType": "custom",
          "oldValue": { "x": 205 },
          "newValue": { "x": 305 },
          "summary": "row.123: 205 → 305",
          "driftDetected": false,
          "myCustomField": "kept as-is"
        },
        "commentData": [
          {
            "commentText": "Bump x from 205 to 305 to match the spec.",
            "from": {
              "userId": "user_sarah_chen",
              "name": "Sarah Chen"
            }
          }
        ]
      }
    ]
  }
}
```

#### Add a private (restricted) comment annotation

Use `visibility` to limit who can see the annotation.

```JSON theme={null}
{
  "data": {
    "organizationId": "acme-corp",
    "documentId": "design-mockup-v2",
    "commentAnnotations": [
      {
        "visibility": {
          "type": "restricted",
          "userIds": ["user_sarah_chen", "user_john_smith"]
        },
        "commentData": [
          {
            "commentText": "Internal note: don't ship this section yet.",
            "from": {
              "userId": "user_john_smith",
              "name": "John Smith",
              "email": "john.smith@acme-corp.com"
            }
          }
        ]
      }
    ]
  }
}
```

# Response

`data` is a map of results, keyed per annotation, not an array.

#### Success Response

```JSON theme={null}
{
   "result": {
       "status": "success",
       "message": "Comment Annotation(s) added successfully.",
       "data": {
           "-O0mpUziLcBwzREvZKs6": {
               "success": true,
               "annotationId": "-O0mpUziLcBwzREvZKs6",
               "commentIds": [
                   126535
               ],
               "message": "Added Successfully"
           }
       }
   }
}

```

#### Result fields

**success** `boolean`

Outcome for this annotation. The envelope reports `success` only when every entry succeeded and no user was permission denied.

**annotationId** `string`

The annotation ID. `annotationId` is optional on input, so the server generates one when you do not supply it.

**commentIds** `number[]`

IDs of the comments created inside this annotation.

**message** `string`

Success message, or the reason this annotation failed.

**findingId** `string`

Agent annotations only. Echoes back `commentData[0].agent.reason.findingId` exactly as you sent it. The field is omitted for non-agent annotations.

#### Correlating results back to your own records

Read `entry.annotationId` from each result, never the map key.

The server generates `annotationId`, and results are written into the map as each write completes, so the map order does not match the order you submitted. For agent annotations, `findingId` is the reliable way to match a created annotation back to the record it came from.

Entries for annotations rejected by permission checks are recorded before an ID exists. When you did not supply an `annotationId` for one of those, its key falls back to `__velt_denied:<index>`, where `<index>` is its position in the `commentAnnotations` array you sent, and the entry has no `annotationId` field to read. That key is reserved: annotation IDs cannot contain a colon, so it never collides with one of yours.

#### Failure Response

```JSON theme={null}
{
  "error": {
    "message": "ERROR_MESSAGE",
    "status": "INVALID_ARGUMENT"
  }
}
```

When some annotations fail or a user is permission denied, the error carries the same per-annotation map in `details`. Entries marked `"success": true` inside `details` **were created**, so treat a failed request as a partial write and read `details` instead of assuming nothing was saved.

```JSON theme={null}
{
  "error": {
    "message": "ERROR_MESSAGE",
    "status": "PERMISSION_DENIED",
    "details": {
        "-O0mpUziLcBwzREvZKs6": {
            "success": true,
            "annotationId": "-O0mpUziLcBwzREvZKs6",
            "commentIds": [
                126535
            ],
            "message": "Added Successfully"
        },
        "deniedAnnotation-1": {
            "success": false,
            "message": "ERROR_MESSAGE"
        }
    }
  }
}
```

```js theme={null}
{
   "result": {
       "status": "success",
       "message": "Comment Annotation(s) added successfully.",
       "data": {
           "-O0mpUziLcBwzREvZKs6": {
               "success": true,
               "annotationId": "-O0mpUziLcBwzREvZKs6",
               "commentIds": [
                   126535
               ],
               "message": "Added Successfully"
           }
       }
   }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.