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

# Annotation - Create or Update

> Create or update an annotation for an agent message. Uses upsert logic based on messageId.

This endpoint creates a new annotation or updates an existing one for a message. It supports:

* Creating annotations for agent messages
* Updating existing annotations (upsert by messageId)
* Associating tags with annotations
* Public agent access (no authentication required for public agents)
* Automatic organization and agent resolution from message

<Note>
  Authentication is **optional** for public agents. If the message belongs to a public agent, you can create annotations without authentication. Otherwise, authentication is required.
</Note>

<Warning>
  Only **agent messages** can be annotated. Attempting to annotate a user or contact message will result in a 400 error.
</Warning>

### Body

#### Required

<ParamField body="messageId" type="string" required>
  The ID of the message to annotate (CUID format). The message must be from an agent (not a user or contact).
</ParamField>

<ParamField body="sentiment" type="string" required>
  Sentiment of the annotation. Valid values: `good`, `bad`.
</ParamField>

<ParamField body="comment" type="string" required>
  Comment or feedback text for the annotation. Cannot be empty or whitespace-only.
</ParamField>

#### Optional

<ParamField body="tagIds" type="array">
  Array of tag IDs (CUID format) to associate with the annotation. If provided, replaces any existing tags. If omitted or empty, removes all tags.
</ParamField>

### Response

<ResponseField name="id" type="string">
  Unique identifier of the annotation (CUID format).
</ResponseField>

<ResponseField name="messageId" type="string">
  ID of the message this annotation refers to.
</ResponseField>

<ResponseField name="conversationId" type="string">
  ID of the conversation this annotation belongs to.
</ResponseField>

<ResponseField name="agentId" type="string">
  ID of the agent associated with this annotation.
</ResponseField>

<ResponseField name="organizationId" type="string">
  ID of the organization the annotation belongs to.
</ResponseField>

<ResponseField name="comment" type="string">
  Comment or feedback text for the annotation.
</ResponseField>

<ResponseField name="sentiment" type="string">
  Sentiment of the annotation (e.g., `good`, `bad`).
</ResponseField>

<ResponseField name="status" type="string">
  Current status of the annotation.
</ResponseField>

<ResponseField name="createdById" type="string">
  ID of the user who created or last updated the annotation.
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 timestamp of when the annotation was created.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 timestamp of when the annotation was last updated.
</ResponseField>

<ResponseField name="tags" type="array">
  Tags associated with the annotation.

  <Expandable title="Tags array items">
    <ResponseField name="tag" type="object">
      Tag information.

      <Expandable title="Tag object">
        <ResponseField name="id" type="string">
          Tag ID.
        </ResponseField>

        <ResponseField name="name" type="string">
          Tag name.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

### Error Responses

| Status Code | Type             | Description                                                                                                                                              |
| ----------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400         | Bad Request      | Comment is empty or whitespace-only, or attempting to annotate a non-agent message.                                                                      |
| 401         | UNAUTHORIZED     | Missing or invalid authentication session, and the message does not belong to a public agent, or the message's organization does not match your session. |
| 404         | NOT\_FOUND       | Message with the specified ID does not exist.                                                                                                            |
| 400         | INVALID\_REQUEST | Message does not have an associated agent ID.                                                                                                            |

<RequestExample>
  ```bash cURL (create new annotation) theme={null}
  curl --location --request POST 'https://dashboard.laburen.com/api/annotations' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <API_KEY>' \
  --data-raw '{
      "messageId": "clxxxxxxxxxxxxxxxxx",
      "sentiment": "bad",
      "comment": "The agent provided incorrect information about pricing",
      "tagIds": ["clxxxxxxxxxxxxxxxxx"]
  }'
  ```

  ```bash cURL (update existing annotation) theme={null}
  curl --location --request POST 'https://dashboard.laburen.com/api/annotations' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <API_KEY>' \
  --data-raw '{
      "messageId": "clxxxxxxxxxxxxxxxxx",
      "sentiment": "good",
      "comment": "Updated: Actually, the response was correct after clarification",
      "tagIds": ["clxxxxxxxxxxxxxxxxx", "clxxxxxxxxxxxxxxxxx"]
  }'
  ```

  ```bash cURL (public agent, no auth) theme={null}
  curl --location --request POST 'https://dashboard.laburen.com/api/annotations' \
  --header 'Content-Type: application/json' \
  --data-raw '{
      "messageId": "clxxxxxxxxxxxxxxxxx",
      "sentiment": "good",
      "comment": "Great response!"
  }'
  ```

  ```javascript JavaScript theme={null}
  const apiUrl = 'https://dashboard.laburen.com/api';
  const apiKey = '<API_KEY>';

  const response = await fetch(`${apiUrl}/annotations`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      messageId: 'clxxxxxxxxxxxxxxxxx',
      sentiment: 'bad',
      comment: 'The agent provided incorrect information about pricing',
      tagIds: ['clxxxxxxxxxxxxxxxxx'],
    }),
  });

  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  api_url = "https://dashboard.laburen.com/api"
  api_key = "<API_KEY>"

  response = requests.post(
      f"{api_url}/annotations",
      headers={
          "Content-Type": "application/json",
          "Authorization": f"Bearer {api_key}",
      },
      json={
          "messageId": "clxxxxxxxxxxxxxxxxx",
          "sentiment": "bad",
          "comment": "The agent provided incorrect information about pricing",
          "tagIds": ["clxxxxxxxxxxxxxxxxx"],
      },
  )

  data = response.json()
  print(data)
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "id": "clxxxxxxxxxxxxxxxxx",
    "messageId": "clxxxxxxxxxxxxxxxxx",
    "conversationId": "clxxxxxxxxxxxxxxxxx",
    "agentId": "clxxxxxxxxxxxxxxxxx",
    "organizationId": "clxxxxxxxxxxxxxxxxx",
    "comment": "The agent provided incorrect information about pricing",
    "sentiment": "bad",
    "status": "pending",
    "createdById": "clxxxxxxxxxxxxxxxxx",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z",
    "tags": [
      {
        "tag": {
          "id": "clxxxxxxxxxxxxxxxxx",
          "name": "bug"
        }
      }
    ]
  }
  ```
</ResponseExample>
