# Update a satisfaction survey

> PUT /v1/satisfaction/{id} · Settings and configurations API

Source: https://developer.kustomer.com/api/settings-and-configurations/update-a-satisfaction-survey

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

## Authorization

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

Updates a satisfaction survey by ID.

Enabling a disabled survey or changing the channel of an enabled survey enforces the 30 enabled-surveys-per-channel limit. Disabled surveys do not count toward this limit. Returns `422 Unprocessable Entity` when the update would exceed channel capacity.

Use criteria in the request body to control the survey's audience rules.

Keep the existing audience rules: omit criteria entirely.

```json
{
  "name": "Post-conversation survey"
}
```

Clear the existing audience rules: send criteria: null.

```json
{
  "criteria": null
}
```

Or send an empty rule object:

```json
{
  "criteria": {
    "and": [],
    "or": []
  }
}
```

Both clearing forms remove the stored audience rules.

## About Satisfaction surveys

Satisfaction surveys (CSAT) ask customers to rate their experience after a conversation. A survey defines the question, rating scale, channel and audience; each time one is sent, Kustomer creates a satisfaction response that tracks delivery and stores the customer's rating and comments.

CSAT measures how customers feel about the help they got. Admins build surveys in the Kustomer admin settings; customers answer them in the chat widget or in a message on email, SMS and other channels; agents and managers see the results on the conversation and in reporting. The API splits this into two objects: the survey (type `satisfaction`), which is configuration, and the satisfaction response (type `satisfaction_response`), which is one survey sent to one customer about one conversation.

#### Surveys

Key survey fields:

- `channel`: where the survey is sent, such as `email`, `sms`, `chat`, `voice`, `facebook` or `whatsapp`. `from` picks the sending account for that channel.
- `formType`: always `csat`.
- `scale.options`: how many rating choices the customer sees, `2`, `3`, `5` or `10`, with optional `labelLow` and `labelHigh`.
- `introduction` and `ratingPrompt`: the text around the rating question. `questions` adds follow-up questions, each either a free-text `response` or a `property` question tied to a conversation attribute named in `property`.
- `delayTime`: how long after the conversation closes to send the survey, in milliseconds. Chat surveys are offered right away.
- `criteria`: audience rules in the same `and`/`or` format as queue rules. Omit it on update to keep the current rules; send `null` or empty lists to clear them.

Each channel can have up to 30 enabled surveys, and creating or enabling one beyond that returns `422`. Disabled surveys don't count.

#### Which survey a conversation gets

When a conversation is marked done, or a chat ends, Kustomer looks at the enabled surveys for the channel of the conversation's latest message and keeps those whose `criteria` match. If several match, the one with the most criteria rules wins, and the survey order breaks ties. If the conversation is reopened before a scheduled survey goes out, the survey is canceled.

#### Responses

| `status` | Meaning |
| --- | --- |
| `scheduled` | Created and waiting for `scheduledFor`. |
| `offered` | Sent to the customer (`sentAt`). |
| `rated` | The customer gave a rating. |
| `commented` | The customer also left a comment or answered follow-up questions. |
| `unresponded` | No answer was received. |
| `canceled` | Not sent, for example because the conversation reopened. |

`rating` is the customer's raw choice on the survey's scale, and `score` is the derived result used for CSAT reporting, `1` for a satisfied rating and `0` otherwise. `answers` holds follow-up answers, and responses stop accepting changes after `lockedAt`. Each response links to its `satisfaction` survey, `customer` and `conversation`.

#### Which endpoint to use

| To | Call |
| --- | --- |
| List the org's surveys | [`GET /v1/satisfaction`](https://developer.kustomer.com/api/core-resources/get-satisfactions) |
| Create a survey | [`POST /v1/satisfaction`](https://developer.kustomer.com/api/settings-and-configurations/create-a-satisfaction-survey) |
| Change, enable or disable a survey | [`PUT /v1/satisfaction/{id}`](https://developer.kustomer.com/api/settings-and-configurations/update-a-satisfaction-survey) |
| Set the survey order used to break ties | [`PUT /v1/satisfaction/order`](https://developer.kustomer.com/api/settings-and-configurations/update-satisfaction-survey-order) |
| Read one customer's response and rating | [`GET /v1/satisfaction-responses/{id}`](https://developer.kustomer.com/api/core-resources/get-satisfaction-response) |

To find responses in bulk, search conversations by the `conversation_satisfaction_*` attributes, such as rating, score or status. If you run your own chat UI, record a rating with the [chat satisfaction response endpoint](https://developer.kustomer.com/api/chat/update-a-chat-satisfaction-response). `PUT /v1/satisfaction/order` takes the full ordered list of survey IDs in `satisfactionForms`, up to 500.

Related: [Server chat API](https://developer.kustomer.com/docs/chat/server-chat-api), [Expressions reference](https://developer.kustomer.com/docs/workflows/expressions-reference), [Search](https://developer.kustomer.com/docs/concepts/search)

### Recipes

#### 1. Read the CSAT result for a conversation

Show a conversation's survey rating and comments in your own tool, such as a CRM or QA dashboard.

**Step 1.** [`GET /v1/conversations/{id}`](https://developer.kustomer.com/api/core-resources/get-conversation-by-id.md). Read the [conversation](https://developer.kustomer.com/api/core-resources#conversations). Its `satisfactionLevel` has the response's `status`, `rating` and `score`, and the satisfaction response ID in `formResponse`. If there's no `formResponse`, there's no response to read yet.

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

```bash title="kusty"
RESPONSE_ID=$(kusty api GET /v1/conversations/$CONVERSATION_ID \
  | jq -r '.data.attributes.satisfactionLevel.formResponse // empty')
```

**Step 2.** [`GET /v1/satisfaction-responses/{id}`](https://developer.kustomer.com/api/core-resources/get-satisfaction-response.md). Read the response for the full detail: when it was sent, the follow-up `answers`, and the survey it came from. `rating` is the customer's choice on the survey's scale, and `score` is `1` for a satisfied rating and `0` otherwise.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/satisfaction-responses/$RESPONSE_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/satisfaction-responses/$RESPONSE_ID \
  --fields attributes.status,attributes.rating,attributes.score,attributes.answers,relationships.satisfaction
```

#### 2. Report CSAT for a date range

Work out the share of satisfied customers for last month, overall and per survey, for a weekly report.

**Step 1.** [`POST /v1/customers/search`](https://developer.kustomer.com/api/core-resources/create-customer-search.md). [Search](https://developer.kustomer.com/api/core-resources#search) for conversations with an answered survey: `conversation_satisfaction_status` of `rated` or `commented`, and `conversation_satisfaction_created_at`, when the survey response was created, in the window. For a quick number, run it with `pageSize=1` as shown, run it again with `{"conversation_satisfaction_score": {"equals": 1}}` added, and divide the second `meta.total` by the first.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/search?pageSize=1&trackTotalHits=true" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "queryContext": "conversation",
    "and": [
      {"conversation_satisfaction_status": {"in": ["rated", "commented"]}},
      {"conversation_satisfaction_created_at": {"gte": "2026-09-01T00:00:00Z"}},
      {"conversation_satisfaction_created_at": {"lt": "2026-10-01T00:00:00Z"}}
    ]
  }' | jq '.meta.total'
```

```bash title="kusty"
kusty api POST /v1/customers/search --query pageSize=500 --all --output jsonl \
  --data @csat-september.json > rated.jsonl
```

**Step 2.** To break the result down, export the matches and group them by survey locally. Each conversation's `satisfactionLevel` carries the `form` (survey ID) and `score`.

```bash title="kusty"
jq -s 'map(.data.attributes.satisfactionLevel)
  | group_by(.form)
  | map({survey: .[0].form, responses: length, csat: ((map(.score) | add) / length * 100 | round)})' rated.jsonl
```

> One search can only page through its first 10,000 matches. For a busy org, split the range into shorter windows and combine the results.

#### 3. Launch a new survey and give it priority

Add a survey for a channel, put it ahead of existing ones in the tie-break order, and only then turn it on.

**Step 1.** [`POST /v1/satisfaction`](https://developer.kustomer.com/api/settings-and-configurations/create-a-satisfaction-survey.md). Create the survey. It starts disabled unless you send `enabled: true`, which gives you time to set its order first. `formType` and `scale.options` are required.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/satisfaction" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "SMS survey",
    "formType": "csat",
    "channel": "sms",
    "scale": {"options": 5, "type": "number", "labelLow": "Terrible", "labelHigh": "Amazing"},
    "ratingPrompt": "How satisfied are you with your interaction?",
    "delayTime": 3600000
  }'
```

```bash title="kusty"
SURVEY_ID=$(kusty api POST /v1/satisfaction --data @sms-survey.json | jq -r '.data.id')
```

**Step 2.** [`GET /v1/satisfaction`](https://developer.kustomer.com/api/core-resources/get-satisfactions.md). List every survey. The list comes back in the current order, with new surveys added at the end.

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

```bash title="kusty"
kusty api GET /v1/satisfaction --all | jq -r '.data.id' > survey-order.txt
```

**Step 3.** [`PUT /v1/satisfaction/order`](https://developer.kustomer.com/api/settings-and-configurations/update-satisfaction-survey-order.md). Send the full list with the new survey first. It must include every survey in the org, or the request fails with `400`. The order only matters when several enabled surveys with the same number of criteria rules match a conversation.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/satisfaction/order" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"satisfactionForms\": [\"$SURVEY_ID\", \"$OTHER_SURVEY_ID\"]}"
```

```bash title="kusty"
ORDER=$( (echo "$SURVEY_ID"; grep -v "$SURVEY_ID" survey-order.txt) | jq -R . | jq -sc .)
kusty api PUT /v1/satisfaction/order --field "satisfactionForms:=$ORDER"
```

**Step 4.** [`PUT /v1/satisfaction/{id}`](https://developer.kustomer.com/api/settings-and-configurations/update-a-satisfaction-survey.md). Turn it on. Leaving `criteria` out keeps the survey's current audience rules. Enabling fails with `422` if the channel already has 30 enabled surveys.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/satisfaction/$SURVEY_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"enabled": true}'
```

```bash title="kusty"
kusty api PUT /v1/satisfaction/$SURVEY_ID --field enabled:=true
```

### Example object

A real record from [`POST /v1/satisfaction`](https://developer.kustomer.com/api/settings-and-configurations/create-a-satisfaction-survey.md):

```json
{
  "type": "satisfaction",
  "id": "5daf7921124be4f2960cb197",
  "attributes": {
    "name": "Satisfaction Form for Email",
    "formType": "csat",
    "enabled": true,
    "channel": "email",
    "description": "This survey will be sent when email conversations are marked done.",
    "from": {
      "channel": "email",
      "account": "support@test.mail.kustomerapp.com",
      "app": "postmark"
    },
    "delayTime": 0,
    "scale": {
      "options": 5,
      "type": "number",
      "labelLow": "Terrible",
      "labelHigh": "Amazing"
    },
    "introduction": "Tell us about your experience.",
    "ratingPrompt": "How satisfied are you with your interaction?",
    "questions": [],
    "criteria": {
      "and": [],
      "or": []
    },
    "createdAt": "2019-08-15T14:25:30.715Z",
    "updatedAt": "2022-07-11T19:52:48.296Z",
    "modifiedAt": "2022-07-11T19:52:48.296Z"
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
      },
      "data": {
        "type": "org",
        "id": "57f29863a1dbf61100e6aa92"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/58190b991f2932100010d683"
      },
      "data": {
        "type": "user",
        "id": "58190b991f2932100010d683"
      }
    },
    "modifiedBy": {
      "links": {
        "self": "/v1/users/58190b991f2932100010d683"
      },
      "data": {
        "type": "user",
        "id": "58190b991f2932100010d683"
      }
    }
  }
}
```

Fields:

- `attributes.name` (string): The survey's name in the admin settings. Like the other text fields, it can be a snippet reference.
- `attributes.formType` (string): Always `csat`.
- `attributes.enabled` (boolean): Whether Kustomer sends the survey. New surveys are disabled unless you send `enabled` set to `true`, and each channel can have at most 30 enabled surveys.
- `attributes.channel` (string): The channel the survey goes out on, such as `email`, `sms`, `chat`, `voice`, `facebook` or `whatsapp`. Kustomer matches it against the channel of the conversation's latest message.
- `attributes.description` (string): Notes for admins about when the survey is used.
- `attributes.from` (object): The account the survey is sent from, as `{ channel, app, account }`, for example a support address for email.
- `attributes.from.account` (string): The sending address or account on the survey's channel, such as `support@example.com`.
- `attributes.delayTime` (number): How long after the conversation is marked done to send the survey, in milliseconds. It defaults to 24 hours (`86400000`); chat surveys are offered right away.
- `attributes.scale` (object): The rating question's scale. It's required when you create a survey, with at least `options`.
- `attributes.scale.options` (number): How many rating choices the customer sees, `2`, `3`, `5` or `10`.
- `attributes.scale.type` (string): How the choices are drawn, `emoji`, `number`, `thumb` or `custom`.
- `attributes.scale.labelLow` (string): The label at the low end of the scale, such as "Terrible".
- `attributes.scale.labelHigh` (string): The label at the high end of the scale, such as "Amazing".
- `attributes.introduction` (string): Text shown before the rating question. It can be a snippet reference such as `[[sn.org.csat.intro]]`, filled in with the customer's language.
- `attributes.ratingPrompt` (string): The rating question itself, such as "How satisfied are you with your interaction?".
- `attributes.questions` (array): Follow-up questions shown after the rating. Each has a `type` and a `prompt`, where `type` is `response` for free text or `property` for a question tied to the conversation attribute named in `property`.
- `attributes.criteria` (object): Audience rules, in the same `and`/`or` format as queue rules, that decide which conversations on the channel get this survey. On `PUT`, omit it to keep the current rules, or send `null` or empty lists to clear them.

### Relationships

- Points to **Customers** via `relationships.customer`: The customer a satisfaction response was sent to. Each response, read with `GET /v1/satisfaction-responses/{id}`, links to one customer, one conversation and the survey it came from.
- Points to **Conversations** via `relationships.conversation`: The conversation a satisfaction response is about. The conversation's `satisfactionLevel` holds the response ID in `formResponse`, along with its `status`, `rating` and `score`.

### Permissions

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

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Satisfaction survey ID. |

## Request body

Content type: `application/json` (required)

- `name` (string): (min length 1; max length 256)
- `description` (string): (max length 10240)
- `subjectOverride` (string): (max length 10240)
- `from` (object)
  - `account` (string)
  - `app` (string)
  - `channel` (string)
- `channel` (string): One of: `email`, `sms`, `chat`, `voice`, `facebook`.
- `enabled` (boolean): Setting `enabled` to `true` on a disabled survey enforces the per-channel enabled survey limit.
- `delayTime` (integer): (≥ 0; ≤ 86400000)
- `formType` (string): One of: `csat`.
- `scale` (object)
  - `options` (integer, required): One of: `2`, `3`, `5`, `10`.
  - `type` (string)
  - `labelHigh` (string)
  - `labelLow` (string)
- `introduction` (string): (max length 256)
- `ratingPrompt` (string): (max length 256)
- `questions` (array<SatisfactionSurveyQuestion>): (max items 1)
  - `id` (string)
  - `type` (string, required): One of: `response`, `property`.
  - `prompt` (string, required)
  - `property` (string)
- `criteria` (object, nullable): Omit to leave unchanged; send null or an empty and/or object to clear audience rules.
  - `and` (array<object>): (unique items)
  - `or` (array<object>): (unique items)
- `template` (string<object-id>)

Example:

```json
{
  "name": "example_name",
  "description": "Example text",
  "subjectOverride": "string",
  "from": {
    "account": "string",
    "app": "string",
    "channel": "string"
  },
  "channel": "email",
  "enabled": true
}
```

## Responses

### 200

OK

- `data` (object, required)
  - `type` (string, required): One of: `satisfaction`.
  - `id` (string, required)
  - `attributes` (object, required)
    - `name` (string)
    - `formType` (string): One of: `csat`.
    - `enabled` (boolean)
    - `channel` (string): One of: `email`, `sms`, `chat`, `voice`, `facebook`, `whatsapp`.
    - `description` (string)
    - `from` (object)
    - `delayTime` (number)
    - `scale` (object)
    - `introduction` (string)
    - `ratingPrompt` (string)
    - `questions` (array<SatisfactionSurveyQuestion>)
    - `criteria` (object)
    - `createdAt` (string<date-time>)
    - `updatedAt` (string<date-time>)
    - `modifiedAt` (string<date-time>)
  - `relationships` (object)
    - `org` (object)
    - `createdBy` (object)
    - `modifiedBy` (object)

```json
{
  "data": {
    "type": "satisfaction",
    "id": "5daf7921124be4f2960cb197",
    "attributes": {
      "name": "Satisfaction Form for Email",
      "formType": "csat",
      "enabled": true,
      "channel": "email",
      "description": "This survey will be sent when email conversations are marked done.",
      "delayTime": 0,
      "scale": {
        "options": 5,
        "type": "number",
        "labelLow": "Terrible",
        "labelHigh": "Amazing"
      },
      "introduction": "Tell us about your experience.",
      "ratingPrompt": "How satisfied are you with your interaction?",
      "questions": [],
      "criteria": {
        "and": [],
        "or": []
      },
      "createdAt": "2019-08-15T14:25:30.715Z",
      "updatedAt": "2022-07-11T19:52:48.296Z",
      "modifiedAt": "2022-07-11T19:52:48.296Z"
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
        },
        "data": {
          "type": "org",
          "id": "57f29863a1dbf61100e6aa92"
        }
      },
      "modifiedBy": {
        "links": {
          "self": "/v1/users/58190b991f2932100010d683"
        },
        "data": {
          "type": "user",
          "id": "58190b991f2932100010d683"
        }
      }
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

### 422

Unprocessable Entity

## Example request

```bash
curl --request PUT \
  --url 'https://api.kustomerapp.com/v1/satisfaction/5daf7921124be4f2960cb197' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "example_name",
  "description": "Example text",
  "subjectOverride": "string",
  "from": {
    "account": "string",
    "app": "string",
    "channel": "string"
  },
  "channel": "email",
  "enabled": true
}'
```
