# Bulk batch update notes

> PUT /v1/notes/bulk · Core resources API

Source: https://developer.kustomer.com/api/core-resources/bulk-batch-update-notes

```http
PUT https://{orgname}.api.kustomerapp.com/v1/notes/bulk
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.note.update`; or legacy `org.user.note.write`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Updates a bulk batch of notes.

Use the `ids` query param to update multiple notes in bulk with the same data.

> **Note:** `_clientRef` is supported when submitting an array-shaped request body,
> but is **not** supported when using the `?ids=` query param mode. See
> [Bulk create customers](https://developer.kustomer.com/api/core-resources/bulk-create-customers) for full `_clientRef` documentation.

## About Notes

A note is an internal comment that your team leaves on a conversation. Customers never see notes; agents use them to hand off context, record what they checked, and @mention teammates.

In the agent desktop, notes appear in a conversation's thread next to its [messages](https://developer.kustomer.com/api/core-resources#messages) but are styled as internal and are never sent to the customer. Every note belongs to one [conversation](https://developer.kustomer.com/api/core-resources#conversations), and through it to that conversation's customer, so you can read notes per conversation or across everything you know about a customer. Agents write notes by hand; integrations and workflows use them to drop in context such as an order lookup result or a fraud check, without the customer seeing it.

Notes attach to conversations. Internal comments on custom objects, such as a task or an order, are a separate resource called [comments](https://developer.kustomer.com/api/core-resources#comments).

#### Key fields

| Field | Meaning |
| --- | --- |
| `body` | The note text, 1 to 10,240 characters. Required. |
| `conversation` | The conversation the note belongs to. Required when you create a note with `POST /v1/notes` or in bulk; the customer comes from the conversation. |
| `userMentions` | Up to 20 `{ user }` or `{ team }` entries. Mentioned users are notified, the same as an @mention typed in the agent desktop. |
| `attachments` | Files uploaded beforehand with [`POST /v1/attachments`](https://developer.kustomer.com/api/core-resources/create-attachment), passed as `{ _id, name, contentType, contentLength }`. |
| `externalId` | Your own ID for the note, up to 256 characters. |

The conversation's `noteCount` tracks how many notes it has. An unsent note is a different object: a [draft](https://developer.kustomer.com/api/core-resources#drafts) with `channel: "note"`, limited to one per conversation.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Add a note to a conversation | [`POST /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/create-note-within-conversation), or [`POST /v1/notes`](https://developer.kustomer.com/api/core-resources/create-note) with `conversation` in the body |
| Read, edit or delete a note | [`GET`](https://developer.kustomer.com/api/core-resources/get-note-by-id), [`PUT`](https://developer.kustomer.com/api/core-resources/update-note-by-id), [`PATCH`](https://developer.kustomer.com/api/core-resources/update-note-attributes-by-id) or [`DELETE /v1/notes/{id}`](https://developer.kustomer.com/api/core-resources/delete-note-by-id) |
| List a conversation's notes | [`GET /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/get-notes-by-conversation) |
| List every note about a customer | [`GET /v1/customers/{id}/notes`](https://developer.kustomer.com/api/core-resources/get-notes-by-customer-id) |
| Page through all notes | [`GET /v1/notes`](https://developer.kustomer.com/api/core-resources/get-notes) |
| Import or update up to 100 notes per request | [`POST`](https://developer.kustomer.com/api/core-resources/bulk-create-notes) or [`PUT /v1/notes/bulk`](https://developer.kustomer.com/api/core-resources/bulk-batch-update-notes) |

A client can create 120 notes per customer per minute. Only tokens with the `org.admin` role can set `createdAt`, `createdBy`, `modifiedAt` and `modifiedBy`, which you need when importing history so notes keep their original author and time. `PUT` merges your changes unless you add `replace=true`, in which case `conversation` and `body` are both required. Bulk jobs run asynchronously; see [Bulk operations](https://developer.kustomer.com/docs/guides/bulk-operations).

Related: [Conversations](https://developer.kustomer.com/api/core-resources#conversations), [Messages](https://developer.kustomer.com/api/core-resources#messages), [Comments on custom objects](https://developer.kustomer.com/api/core-resources#comments), [Conversations and messages](https://developer.kustomer.com/docs/concepts/conversations-and-messages)

### Recipes

#### 1. Add an internal note and @mention a teammate

Hand off context on a conversation, such as a fraud check result or an escalation reason, and notify the person who needs to act on it.

**Step 1.** [`GET /v1/users`](https://developer.kustomer.com/api/access-management/get-users.md). Mentions take user or team IDs, not names. Find the user by email and keep their `id`.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users?email=$TEAMMATE_EMAIL" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/users --query email=$TEAMMATE_EMAIL | jq -r '.data[0].id'
```

**Step 2.** [`POST /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/create-note-within-conversation.md). Post the note on the conversation. `body` is required and up to 10,240 characters. `userMentions` takes up to 20 entries, each `{ "user": … }` or `{ "team": … }`, and mentioned users are notified just as if an agent had typed the @mention. The customer never sees the note.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID/notes" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"body\": \"Fraud check flagged this order (score 0.91). Please verify the shipping address before refunding.\", \"userMentions\": [{\"user\": \"$USER_ID\"}]}"
```

```bash title="kusty"
kusty api POST /v1/conversations/$CONVERSATION_ID/notes \
  --field body="Fraud check flagged this order (score 0.91). Please verify the shipping address before refunding." \
  --field userMentions.0.user=$USER_ID
```

> To mention a whole team, look it up with [`GET /v1/teams`](https://developer.kustomer.com/api/access-management/get-teams) and send `{ "team": "<team id>" }`. To attach a file, upload it first and pass it in `attachments`; see [Attachments](https://developer.kustomer.com/api/core-resources#attachments).

#### 2. Post an integration note once per event

Drop the result of a lookup, such as an order status or a CRM summary, into a conversation without repeating it when your webhook handler is retried.

**Step 1.** [`GET /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/get-notes-by-conversation.md). There's no lookup by a note's `externalId`, so list the conversation's notes and check whether one already carries your event ID.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID/notes?pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/conversations/$CONVERSATION_ID/notes --all \
  | jq -r --arg ref "$EVENT_ID" 'select(.data.attributes.externalId == $ref) | .data.id'
```

**Step 2.** [`POST /v1/conversations/{id}/notes`](https://developer.kustomer.com/api/core-resources/create-note-within-conversation.md). If nothing matched, create the note with your event ID as `externalId` (up to 256 characters), so the next retry finds it.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID/notes" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"externalId\": \"$EVENT_ID\", \"body\": \"Order 4417: shipped 2 days ago via UPS, tracking 1Z999AA10123456784.\"}"
```

```bash title="kusty"
kusty api POST /v1/conversations/$CONVERSATION_ID/notes \
  --field externalId=$EVENT_ID \
  --field body="Order 4417: shipped 2 days ago via UPS, tracking 1Z999AA10123456784."
```

> A client can create 120 notes per customer per minute. If your integration can fire many times for one customer, batch the results into a single note.

#### 3. Export every note about a customer

Pull all internal notes across a customer's conversations, for an account review, a handover to another team or a data request.

**Step 1.** [`GET /v1/customers/{id}/notes`](https://developer.kustomer.com/api/core-resources/get-notes-by-customer-id.md). Page through the customer's notes. Each one has `relationships.conversation` and `relationships.createdBy`, so you can group by conversation and show the author.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/notes?pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/customers/$CUSTOMER_ID/notes --all > notes.jsonl
```

**Step 2.** Write one CSV row per note. Authors are user IDs; resolve them with [`GET /v1/users/{id}`](https://developer.kustomer.com/api/access-management/get-user-by-id) and cache the names.

```bash title="kusty"
jq -r '[.data.attributes.createdAt, .data.relationships.conversation.data.id,
        .data.relationships.createdBy.data.id, .data.attributes.body] | @csv' notes.jsonl > notes.csv
```

> Notes can be deleted with [`DELETE /v1/notes/{id}`](https://developer.kustomer.com/api/core-resources/delete-note-by-id). Messages can't, so for a full data request also list the customer's [messages](https://developer.kustomer.com/api/core-resources/get-messages-by-customer-id).

### Example object

A real record from [`GET /v1/notes/{id}`](https://developer.kustomer.com/api/core-resources/get-note-by-id.md):

```json
{
  "type": "note",
  "id": "57cf1d65079e780006e34f79",
  "attributes": {
    "body": "Hey",
    "createdAt": "2016-09-06T19:47:49.235Z",
    "updatedAt": "2016-09-06T19:47:49.236Z"
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57bb5d2e32e83a130052e94e"
      },
      "data": {
        "type": "org",
        "id": "57bb5d2e32e83a130052e94e"
      }
    },
    "customer": {
      "links": {
        "self": "/v1/customers/57c08ccde595371400462df2"
      },
      "data": {
        "type": "customer",
        "id": "57c08ccde595371400462df2"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/57bb5d2e32e83a130052e94f"
      },
      "data": {
        "type": "user",
        "id": "57bb5d2e32e83a130052e94f"
      }
    },
    "conversation": {
      "links": {
        "self": "/v1/conversations/57ce17d2369f251400fc8e89"
      },
      "data": {
        "type": "conversation",
        "id": "57ce17d2369f251400fc8e89"
      }
    }
  },
  "links": {
    "self": "/v1/notes/57cf1d65079e780006e34f79"
  }
}
```

Fields:

- `attributes.body` (string): The note text, 1 to 10,240 characters. Required. Agents see it in the conversation thread, styled as internal; the customer never does.
- `attributes.createdAt` (string): When the note was written. Only tokens with the `org.admin` role can set it, which you need when importing old notes so they keep their original time.
- `relationships.customer` (object): The customer the note is about. Kustomer copies it from the conversation, so you never set it yourself.
- `relationships.createdBy` (object): The note's author. It's the user or API key that created it, unless an `org.admin` token set `createdBy` during an import.
- `relationships.conversation` (object): The conversation the note belongs to. Required when you create a note with `POST /v1/notes` or in bulk.

### Relationships

- Points to **Customers** via `relationships.customer`: The customer a note is about, taken from its conversation. List every note about a customer with `GET /v1/customers/{id}/notes`.
- Points to **Conversations** via `relationships.conversation`: The conversation a note is left on. Every note belongs to one conversation, which counts its notes in `noteCount`.

### Permissions

Fewest roles for a read-only integration: `org.permission.note.read`. For every endpoint: `org.permission.note.read`, `org.permission.note.create`, `org.permission.note.update`, `org.permission.note.delete`.

### Events

Workflow triggers:

- `kustomer.note.create` (Note Created): An agent or workflow adds a note
- `kustomer.note.update` (Note Updated): A note is edited

Workflow actions:

- `kustomer.note.create` (Note: Create): required inputs `conversation`, `body`
- `kustomer.note.update` (Note: Update): required inputs `id`
- `kustomer.note.find-by-id` (Note: Lookup (By ID)): required inputs `id`
- `kustomer.note.find-by-external` (Note: Lookup (By External ID)): required inputs `identifier`

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ids` | string | no | Comma-separated list of unique note IDs. |

## Request body

Content type: `application/json` (required)

- `id` (string, required)
- `conversation` (string)
- `externalId` (string, nullable): (max length 256)
- `body` (string): (min length 1; max length 10240)
- `userMentions` (array<object>)
  - `user` (string)
  - `team` (string)
- `deleted` (boolean, nullable)
- `attachments` (array<attachment>): (unique items)
  - `_id` (string<object-id>, required)
  - `name` (string, required)
  - `contentType` (string, required)
  - `contentLength` (number, required)
  - `sourceId` (string<object-id>)
- `createdAt` (string<date-time>)
- `modifiedAt` (string<date-time>)
- `updatedAt` (string<date-time>)
- `createdBy` (string<object-id>)
- `modifiedBy` (string<object-id>, nullable)
- `lang` (string, nullable): (max length 100)
- `_clientRef` (string): Optional client-supplied correlation token. Omit the field to rely on the existing `objectIds` and `errors` fields. When included, it must be present on every item in the request (all-or-nothing), and must be unique within the request array. See [Bulk create customers](https://developer.kustomer.com/api/core-resources/bulk-create-customers) for full documentation of rules, the async poll flow, and the `results[]` response shape. (min length 1; max length 255)

Example:

```json
[
  {
    "id": "65f2c0a1b4e3d2001234abcd",
    "externalId": "crm-10293",
    "body": "Example text"
  }
]
```

## Responses

### 201

Created

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `ids` (array<any>, required)
    - `context` (string, required)
    - `status` (string, required)
    - `v2` (boolean, required)
    - `payload` (object, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `completed` (boolean, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `createdBy` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "bulk",
    "id": "5b3bddb67f8469003df370a4",
    "attributes": {
      "ids": [],
      "context": "api.sobjects.notes.bulk-create",
      "status": "complete",
      "v2": true,
      "payload": {
        "api": {
          "data": [
            {
              "id": "5b3bddb67f8469003df370a1",
              "tags": [
                "5b3bddb67f8469003df370a5"
              ]
            },
            {
              "id": "5b3bddb67f8469003df370a2",
              "tags": [
                "5b3bddb67f8469003df370a5"
              ]
            }
          ],
          "path": "/v1/notes",
          "user": {
            "id": "5a96dac46dd73800112fab30",
            "userType": "user",
            "roles": [
              "org"
            ],
            "orgName": "mycompany",
            "subject": "5a96dac46dd73800112fab30",
            "org": "5a96dac36dd73800112fab2e"
          },
          "requestId": "5b3bddb6b6e494001779fa38",
          "service": "api",
          "method": "POST"
        }
      },
      "createdAt": "2018-07-03T20:33:58.486Z",
      "updatedAt": "2018-07-03T20:34:03.873Z",
      "completed": true
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/5a96dac36dd73800112fab2e"
        },
        "data": {
          "type": "org",
          "id": "5a96dac36dd73800112fab2e"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/5a96dac46dd73800112fab30"
        },
        "data": {
          "type": "user",
          "id": "5a96dac46dd73800112fab30"
        }
      }
    },
    "links": {
      "self": "/v1/bulk/5b3bddb67f8469003df370a4"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

## Example request

```bash
curl --request PUT \
  --url 'https://api.kustomerapp.com/v1/notes/bulk' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '[
  {
    "id": "65f2c0a1b4e3d2001234abcd",
    "externalId": "crm-10293",
    "body": "Example text"
  }
]'
```
