# CFM Survey Response APIs V3 — Developer Guide

- **Applies to:** Sprinklr Customer Feedback Management (CFM) Survey Response APIs V3 (`/api/v3/surveyResponse`)
- **V2 API reference:** [CFM Survey Response APIs | Sprinklr Developer Portal](https://dev.sprinklr.com/cfm-survey-response-apis) · [Fetch Survey Response (V2)](https://dev.sprinklr.com/fetch-survey-response)
- **Module overview:** [Customer Feedback Management | Sprinklr Developer Portal](https://dev.sprinklr.com/customer-feedback-management)
- **Product guide:** [Integrating Responses Through APIs | Sprinklr Help Center](https://www.sprinklr.com/help/articles/integrating-responses-through-apis/integrating-responses-through-apis/68dcf428d65a5d6e7e63fa17)
- **OpenAPI tag:** `Survey Response V3` — *"Manage survey responses. Supports fetching by ID, ingesting a response, updating response custom fields, deleting responses, and searching responses."*


## 1. Overview

A **survey response** is a single completed submission against a Sprinklr CFM survey. It records what the respondent answered, who they were, how and when they answered, and any custom metadata attached to the response, the responder's profile, or the transaction that triggered the survey.

The CFM Survey Response APIs let you move response data programmatically in both directions. Per the Sprinklr Help Center: *"You can leverage the Standard CRUD APIs for survey responses to programmatically create, retrieve, update, or delete response data within the Sprinklr system. This capability facilitates seamless integration with external systems and supports automated workflows."*

Five operations are exposed across **two paths** — four methods on `/api/v3/surveyResponse`, plus a dedicated search path:

| Operation | Method | Path | operationId |
|  --- | --- | --- | --- |
| Import (ingest) a survey response | `POST` | `/api/v3/surveyResponse` | `SurveyResponseApiV3_ingestSurveyResponse` |
| Fetch survey response(s) by ID | `GET` | `/api/v3/surveyResponse?id=` | `SurveyResponseApiV3_fetchSurveyResponses` |
| Search survey responses | `POST` | `/api/v3/surveyResponse/search?surveyId=` | `SurveyResponseApiV3_searchSurveyResponses` |
| Update response custom fields | `PUT` | `/api/v3/surveyResponse?id=` | `SurveyResponseApiV3_updateSurveyResponse` |
| Delete survey response(s) by ID | `DELETE` | `/api/v3/surveyResponse?id=` | `SurveyResponseApiV3_deleteSurveyResponses` |


This single-path, method-differentiated shape is the central design change in V3, and it matches the pattern already used by Profile V3 (`/api/v3/profile`) and Survey Transactions V3 (`/api/v3/surveyTransactions`). V2 spread the same capabilities across separately-named paths such as `/api/v2/survey-response/{survey-response-id}`.

> **⚠️ Conflict to resolve — resource path.** The IN-12886 description documents these operations at `/api/v3/cfm/surveyResponses` (plural, `cfm/` prefixed). Both `sprinklr-v3.yaml` and every QA-verified cURL in the ticket use `/api/v3/surveyResponse` (**singular**, no `cfm/` prefix). Separately, all five supplied endpoint specifications carry an "API Endpoint" heading of `https://api3.sprinklr.com/{env}/api/v3/survey-response/{survey-response-id}` — kebab-case with a path parameter, which is the **V2** URL shape with the version number changed. This guide follows the OpenAPI specification and the QA cURLs. See [§15, Q1](#15-questions-for-the-api-owner).


> **⚠️ Conflict to resolve — `PATCH` is documented but not implemented.** The IN-12886 description lists `PATCH /api/v3/cfm/surveyResponses?id=…` as a sixth operation. The QA verification comment (Santhosh M., 2026-08-20) states explicitly: *"For CFM Survey Response, the **PATCH API is not available in the latest PR**. The current PR includes GET, POST, PUT, DELETE, and POST `/search`."* No `patch` operation exists on `/surveyResponse` in `sprinklr-v3.yaml`. **`PATCH` is therefore not documented in this guide.** Confirm whether it is deferred to a later release or dropped. See [§15, Q2](#15-questions-for-the-api-owner).


### 1.1 The survey response data model

Response data is modeled in three layers. Understanding this model is the fastest way to understand every endpoint in this guide.

| Layer | Object | What it holds |
|  --- | --- | --- |
| Identity and context | `surveyId`, `responseId`, `responseStatus`, `surveyName`, `createdTime`, `surveyLanguage`, `surveyMode`, `surveyResponseType`, `responseType`, `distributionEntityId`, `surveyStartTime`, `surveyOpenTime`, `tags` | Which survey, which response, when, in what mode and language |
| Responder and device | `responderSnId`, `responderSnType`, `browser`, `browserVersion`, `operatingSystem`, `deviceType`, `responseQuality` | Who answered, on what, and how trustworthy the answer is judged to be |
| Answers and metadata | `questionResponses`, `customProperties`, `profileCustomProperties`, `transactionCustomProperties` | The answers themselves, plus three separate namespaces of custom metadata |


The three custom-metadata namespaces are the part most developers get wrong:

| Namespace | Scope | Configured in |
|  --- | --- | --- |
| Response custom fields | Specific to this one response | **Global Settings → Response Custom Fields** |
| Profile custom fields | Attached to the responder's audience profile | Profile custom field configuration |
| Transaction custom fields | Attached to the CFM transaction that triggered the survey | **Global Settings → Transaction Fields** |


> **⚠️ Naming asymmetry — read this before writing a client.** Requests use **`…CustomFields`** (`responseCustomFields`, `profileCustomFields`, `transactionCustomFields`). Responses use **`…CustomProperties`** (`customProperties`, `profileCustomProperties`, `transactionCustomProperties`) — and note that the response-scoped one drops the `response` prefix entirely, becoming just `customProperties`. A round-trip mapper that assumes symmetry will silently drop data. This is confirmed in both `sprinklr-v3.yaml` (`SurveyResponseIngestionRequest` versus `SurveyData`) and every supplied example. See [§15, Q3](#15-questions-for-the-api-owner).


### 1.2 Addressing a survey response

A response is addressed by its **`responseId`** — for example `6a8c451269baf80612fb6417` — passed as the `id` query parameter.

`sprinklr-v3.yaml` describes `id` on `GET` and `DELETE` as a *"Comma-separated list of survey response ids"*, so both operations are **bulk-capable**. On `PUT` the description is singular: *"Survey response id."*

**To find a `responseId` in the Sprinklr UI:**

1. Open the **Customer Feedback Management (CFM)** persona app in Sprinklr.
2. In the **Programs** tab, locate the survey whose responses you want.
3. Hover over the survey. A **View** button is displayed. Click **View**.
4. On the top bar, click **Responses**.
5. The **Response Id** column values are the `responseId`.


**To find a custom field's internal name:**

1. Open the CFM persona app and navigate to the **Programs** tab.
2. Hover over the survey and click **View**.
3. From the top navigation bar, click **Settings**.
4. Scroll to **Survey Custom Fields**. It contains **Transaction Fields** and **Response Custom Fields**.
5. Click **View Fields** next to the category you need.
6. Find the field, click the three-dot menu (⋮), and select **Copy Field Name** to copy the internal field ID used in API requests.


Internal field names follow the pattern `_c_<24-hex-id>`, for example `_c_67cb50793cae6f06c0c41de0`.

### 1.3 Prerequisites

1. **CFM must be enabled in your environment.** Per the Developer Portal: *"CFM must be enabled in your environment before you can utilize these APIs. Please contact your Success Manager for more details."*
2. **Permissions.** Per the Help Center: *"You would need **View and Edit Response and Analytics permissions at the Survey Level**."* The API user must be able to access the survey's analytics in the UI.
3. **You must know the `surveyId`** for the survey you are integrating with, and the specific `responseId` values for read, update, and delete operations.
4. **Request a sample payload from Sprinklr Support.** All five supplied endpoint specifications carry the same Dev Note: *"To use this API, you will need values for the parameters mentioned below. To obtain these values, reach out to Sprinklr Support at tickets@sprinklr.com."*


## 2. Base URLs and environments

**All API calls are sent to the production endpoint:**

```
https://api3.sprinklr.com/{env}/api/v3
```

So the survey response resource in production is:

```
https://api3.sprinklr.com/{env}/api/v3/surveyResponse
```

Replace `{env}` with your assigned environment identifier (`prod0`, `prod2`, `prod3`, `prod4`, `prod5`, `prod6`, `prod8`, `prod11`, `prod12`, `prod15`–`prod19`, `prod21`, `prod24`, `production`, `spr-uat`, `azrqa` — see [APIs | Sprinklr Developer Portal](https://dev.sprinklr.com/apis) for the authoritative list).

QA and pre-production environments use a different host shape, in which the environment is a **hostname prefix** rather than a path segment:

| Environment | Base URL | Survey response resource |
|  --- | --- | --- |
| Production | `https://api3.sprinklr.com/{env}/api/v3` | `https://api3.sprinklr.com/{env}/api/v3/surveyResponse` |
| QA6 (internal) | `https://qa6-api2-v3.sprinklr.com/api/v3` | `https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse` |


Do not hard-code either host. Make the base URL a single configuration value so promotion from QA to production is a config change, not a code change.

> The examples in this guide use the **QA6** form, because that is the host used verbatim in every QA-verified cURL on IN-12886. Substitute the production form before going live.


## 3. Authentication and common headers

All CFM Survey Response API calls are authenticated with OAuth 2.0. See [API Overview](https://dev.sprinklr.com/api-overview) for portal registration, API key and secret generation, and the Authorize flow.

| Header | Value | Purpose | Required on |
|  --- | --- | --- | --- |
| `Authorization` | `Bearer {{accessToken}}` | Authenticates the user with the server. For generating the authorization token, see the **Authorize** section on the developer portal. | All requests |
| `Key` | `{{apiKey}}` | Authenticates the application with the server. For generating an API key, see the **Getting Started** guide. | All requests |
| `Content-Type` | `application/json` | Declares the request body media type | `POST`, `PUT` |
| `Accept` | `application/json` | Declares the acceptable response type | All requests |


> **⚠️ Conflict to resolve — is `Key` mandatory?** The header table in all five supplied specifications marks `Key` as required, but **none of the five QA-verified cURLs on IN-12886 sends it** — every one carries only `Authorization`, `Content-Type`, and `Accept`, and all five were recorded as verified. The same discrepancy appears on the CFM Workflow and CFM Transaction endpoints. Until this is confirmed, **send `Key` on every request** — that is the documented contract and the safe default. See [§15, Q4](#15-questions-for-the-api-owner).


> **Do not send a `Cookie` header.** The supplied `GET` and `DELETE` cURLs carry `Cookie: JSESSIONID=…`, and the Fetch and Delete parameter tables even document `Cookie` as a request parameter. That is a browser-session artefact left over from manual testing, not part of the API contract. It has been stripped from every example in this guide, and it should be removed from the published specifications. See [§15, Q5](#15-questions-for-the-api-owner).


> **Credential hygiene.** Never commit an access token or API key to source control, never paste one into a ticket or chat, and never log the `Authorization` or `Key` header values. Every credential in this guide is a placeholder.
**Action required — still open.** The QA verification comment on IN-12886 contains three live-looking QA6 secrets in plaintext: two `Authorization` values and one `Key`. Those credentials must be **revoked and regenerated**, and the comment redacted, before this guide or the ticket is shared outside the immediate team. This action was first raised against the CFM Workflow guide on 2026-08-31 and remains unaddressed.


## 4. Import a survey response

Imports a survey response from any external source — a CRM, an internal application, or an offline data-collection tool — into Sprinklr CFM.

```
POST /api/v3/surveyResponse
```

**Request schema:** `SurveyResponseIngestionRequest` — *"Survey Response Ingestion Request"*

### 4.1 Request body

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `surveyId` | — | String | Unique identifier of the survey for which the response is being submitted. |
| `questionResponses` | — | Object | Contains the responses provided for survey questions. |
| `questionResponses` | `<QUESTION_ID>` | Array[String] | Maps a survey question ID to one or more response values. |
| `responseTime` | — | Long | Timestamp in milliseconds since Unix epoch, representing when the survey response was submitted. |
| `tags` | — | Array[String] | List of tags associated with the survey response. |
| `distributionChannel` | — | String | Channel through which the survey response was collected. For example, `EXTERNAL_APPLICATION`. |
| `responseCustomFields` | — | Object | Custom fields associated with the survey response. Values are arrays of strings. |
| `profileCustomFields` | — | Object | Custom fields associated with the responder's profile. Values are arrays of strings. |
| `transactionCustomFields` | — | Object | Custom fields associated with the survey transaction. Values are arrays of strings. |
| `responderSnId` | — | String | Unique identifier of the responder in the source system. Can be `null` if not available. |
| `responderSnType` | — | String | Type of source network associated with the responder. Can be `null` if not available. |
| `surveyLanguage` | — | String | Language in which the survey response was submitted. Can be `null` if not specified. |
| `elapsedTime` | — | Long | Time taken by the responder to complete the survey, in milliseconds. Can be `null` if not available. |
| `allCustomFields` | — | Object | Consolidated object containing all custom fields associated with the response, profile, and transaction. |


`sprinklr-v3.yaml` declares no `required` array on `SurveyResponseIngestionRequest`, so no field is formally mandatory. In practice `surveyId` and `questionResponses` are the minimum meaningful payload.

> **⚠️ Conflict to resolve — `allCustomFields` is undeclared.** `allCustomFields` appears in the supplied request-parameter table **and** in the QA-verified cURL (`"allCustomFields": {}`), but it is **not declared** in `SurveyResponseIngestionRequest` in `sprinklr-v3.yaml`. Its relationship to the three specific `…CustomFields` objects is also unstated — whether it is an alternative to them, a superset, or read-only. Do not populate it until this is clarified. See [§15, Q6](#15-questions-for-the-api-owner).


> **⚠️ Conflict to resolve — `surveyId` documented as a path parameter.** The supplied Import specification includes a **"Path Parameters"** table listing `surveyId` as Required. `sprinklr-v3.yaml` declares **no** path or query parameters on `POST /surveyResponse`, and both the QA cURL and the specification's own request-parameter table place `surveyId` in the body. This guide sends it in the body. See [§15, Q7](#15-questions-for-the-api-owner).


> **Migration note.** In V2 the `surveyId` was passed **in the request header**, and the three custom-field objects were nested inside a `customFields` wrapper. V3 moves `surveyId` into the body and **flattens** the wrapper. See [§10.3](#103-request-body-restructuring).


### 4.2 Example request

```bash
curl --location 'https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "surveyId": "6a840fb9a64a419291aacd53",
    "questionResponses": {
      "422131e5-67e9-4e7d-8edf-448c7e455f0e": ["Neutral"]
    },
    "responseTime": 1738281600000,
    "tags": ["tag1"],
    "distributionChannel": "EXTERNAL_APPLICATION",
    "responseCustomFields": {
      "_c_67cb50793cae6f06c0c41de0": ["5"]
    },
    "profileCustomFields": {
      "_c_64cbf526fd8b0e259d72470a": ["20"]
    },
    "transactionCustomFields": {
      "_c_67056c1056182e36198d8eea": ["WHATSAPP"]
    },
    "responderSnId": null,
    "responderSnType": null,
    "surveyLanguage": null,
    "elapsedTime": null
  }'
```

*Illustrative example. Structure and `surveyId`/`questionResponses` identifiers are reproduced from the QA-verified cURL on IN-12886; the custom-field identifiers are reproduced from the Help Center sample payload. Credentials are placeholders.*

Note that every custom-field value is an **array of strings**, even for a single scalar value — `["5"]`, not `"5"`. This is declared in the schema (`additionalProperties: {type: array, items: {type: string}}`) and confirmed by the Help Center sample.

### 4.3 Example response

```json
{
  "data": {
    "responseId": "6a8c451269baf80612fb6417"
  },
  "errors": []
}
```

### 4.4 Response parameters

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `data` | — | Object | Contains details of the created survey response. |
| `data` | `responseId` | String | Unique identifier of the survey response created successfully. |
| `errors` | — | Array | Contains details of any errors encountered while processing the request. Returns an empty array when the request is successful. |


Persist `data.responseId` — it is the only handle for later fetch, update, or delete.

## 5. Fetch survey response(s)

Retrieves detailed information about a specific survey response using its unique ID.

```
GET /api/v3/surveyResponse?id={responseId}
```

**Response schema (per `sprinklr-v3.yaml`):** array of `SurveyResponseDTO`

### 5.1 Request parameters

| Parameter | In | Type | Required (spec) | Description |
|  --- | --- | --- | --- | --- |
| `id` | query | String | `required: false` in the OpenAPI specification; **Required** in the supplied endpoint specification | Comma-separated list of survey response IDs to retrieve. |


> **⚠️ Conflict to resolve — bulk versus single, and is `id` required?** `sprinklr-v3.yaml` describes `id` as a *"Comma-separated list of survey response ids"* and marks it `required: false`. The supplied specification documents only a single ID and marks it Required. No source states the maximum number of IDs per call, or the behaviour when `id` is omitted entirely. Treat `id` as **mandatory** in client code. See [§15, Q8](#15-questions-for-the-api-owner) and [§15, Q9](#15-questions-for-the-api-owner).


### 5.2 Example request

```bash
curl --location 'https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse?id=6a8c451269baf80612fb6417' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Accept: application/json'
```

### 5.3 Example response

```json
{
  "data": [
    {
      "hasMore": false,
      "responses": [
        {
          "surveyId": "6a840fb9a64a419291aacd53",
          "responseId": "6a8c451269baf80612fb6417",
          "responseStatus": "COMPLETE_RESPONSES",
          "createdTime": 1738281600000,
          "surveyMode": "STANDARD",
          "surveyResponseType": "STANDARD",
          "responderSnId": "6a8c451269baf80612fb6416",
          "responderSnType": "EXTERNAL_APPLICATION",
          "responseQuality": "NA",
          "questionResponses": {
            "422131e5-67e9-4e7d-8edf-448c7e455f0e": ["Neutral"]
          },
          "customProperties": {},
          "profileCustomProperties": {},
          "transactionCustomProperties": {}
        }
      ],
      "metadata": {
        "customFieldLookup": {},
        "questionLookup": {
          "422131e5-67e9-4e7d-8edf-448c7e455f0e": "Question"
        }
      }
    }
  ],
  "errors": []
}
```

*Illustrative response, reproduced from the supplied specification with the free-text answer replaced by a realistic value.*

### 5.4 Response parameters

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `data` | — | Array[Object] | Contains the survey response details and related metadata. |
| `data` | `hasMore` | Boolean | Indicates whether additional survey responses are available for retrieval. |
| `data` | `responses` | Array[Object] | Contains the list of survey responses. |
| `data.responses` | `surveyId` | String | Unique identifier of the survey associated with the response. |
| `data.responses` | `responseId` | String | Unique identifier of the survey response. |
| `data.responses` | `responseStatus` | String | Status of the survey response. See [§11.1](#111-responsestatus). |
| `data.responses` | `responseType` | String | Response type. Declared in the schema; not present in the supplied sample. |
| `data.responses` | `surveyName` | String | Name of the survey. Declared in the schema; not present in the supplied sample. |
| `data.responses` | `createdTime` | Long | Timestamp in milliseconds since Unix epoch, indicating when the response was created. |
| `data.responses` | `surveyLanguage` | String | Language in which the survey was taken. |
| `data.responses` | `surveyMode` | String | Mode in which the survey was conducted. See [§11.2](#112-surveymode). |
| `data.responses` | `surveyResponseType` | String | Type of survey response, for example `STANDARD`. |
| `data.responses` | `distributionEntityId` | String | ID of the distribution entity. |
| `data.responses` | `responderSnId` | String | Unique identifier of the responder in the source system. |
| `data.responses` | `responderSnType` | String | Source network or channel through which the response was received. |
| `data.responses` | `surveyStartTime` | Long | Timestamp at which the respondent started the survey. Declared in the schema; not present in the supplied sample. |
| `data.responses` | `surveyOpenTime` | Long | Timestamp at which the survey was opened. Declared in the schema; not present in the supplied sample. |
| `data.responses` | `tags` | Array[String] | Tags associated with the response. |
| `data.responses` | `browser` | String | Browser used by the respondent. |
| `data.responses` | `browserVersion` | String | Version of the browser. |
| `data.responses` | `operatingSystem` | String | Operating system of the responder. |
| `data.responses` | `deviceType` | String | Type of device used. See [§11.3](#113-devicetype). |
| `data.responses` | `responseQuality` | String | Quality evaluation of the survey response. See [§11.4](#114-responsequality). |
| `data.responses` | `questionResponses` | Object | Contains responses mapped to survey question identifiers. |
| `data.responses.questionResponses` | `<QUESTION_ID>` | Array[String] | Response value(s) submitted for the corresponding survey question. |
| `data.responses` | `customProperties` | Object | Custom properties associated with the survey response. |
| `data.responses` | `profileCustomProperties` | Object | Custom properties associated with the responder's profile. |
| `data.responses` | `transactionCustomProperties` | Object | Custom properties associated with the survey transaction. |
| `data` | `metadata` | Object | Contains metadata related to the survey response. |
| `errors` | — | Array | Contains details of any errors encountered. Empty array when the request is successful. |


### 5.5 Metadata parameters

The `metadata` object is what makes a response payload human-readable: it maps opaque identifiers back to display names.

| Parameter | Type | Description |
|  --- | --- | --- |
| `metadata` | Object | Contains supplementary information used to interpret and map survey response data. |
| `metadata.questionLookup` | Object | Maps survey question identifiers to their display names. |
| `metadata.questionLookup.<QUESTION_ID>` | String | Human-readable name of the corresponding survey question. |
| `metadata.customFieldLookup` | Object | Maps custom field identifiers to their corresponding field names or definitions. |
| `metadata.customFieldLookup.<CUSTOM_FIELD_ID>` | String | Human-readable name of the corresponding custom field. Returned when custom fields are available in the response. |


Always resolve `questionResponses` keys through `questionLookup` rather than hard-coding question IDs — question IDs change when a survey is edited or cloned.

> **⚠️ Conflict to resolve — the `SurveyResponseDTO` schema does not match the payload.** Three separate mismatches:
1. **Envelope.** `sprinklr-v3.yaml` declares the `200` response as a bare `type: array, items: $ref SurveyResponseDTO` — **not** wrapped in an `APIResponse` envelope. Both the supplied sample and the V2 page clearly show a `data`/`errors` wrapper.
2. **`metadata` type.** `SurveyResponseDTO.metadata` is declared as `$ref: Metadata_models`, whose properties are `sourceIp`, `version`, `sequence`, `start`, `duration`, `projectId`, `userId`, `sessionId`, `pageNum`, and `upload` — **none** of which is `customFieldLookup` or `questionLookup`. The actual payload nests those two under `metadata`, while the schema declares them as **siblings** of `metadata` at the top level of `SurveyResponseDTO`. Generated clients will fail to deserialize this response.
3. **`data` cardinality.** V3 returns `data` as an **array** containing one wrapper object; V2 returned `data` as a **single object**. This is a breaking change for every V2 client.

See [§15, Q10](#15-questions-for-the-api-owner), [§15, Q11](#15-questions-for-the-api-owner), and [§15, Q12](#15-questions-for-the-api-owner).


## 6. Search survey responses

Searches survey responses for a specific survey, with pagination and filtering.

```
POST /api/v3/surveyResponse/search?surveyId={surveyId}
```

**Request schema (per `sprinklr-v3.yaml`):** `SurveyResponseFetchRequest` — *"Survey Response Fetch Request"*

Per the Help Center, this operation exists to *"Obtain a list of survey responses that fulfill particular filtering criteria, including survey custom properties or responder characteristics… Collect survey responses in large quantities according to specific criteria for the purpose of reporting or integration."*

### 6.1 Query parameters

| Parameter | In | Type | Required (spec) | Description |
|  --- | --- | --- | --- | --- |
| `surveyId` | query | String | `required: false` | Unique identifier of the survey whose responses are searched. |


> `surveyId` is declared `required: false` but the operation is defined as searching *"a survey response for a specific survey."* Treat it as mandatory. In V2 this value was passed **in the request header**, not the query string.


### 6.2 Request body

**Two incompatible request-body shapes are documented.** Both are reproduced here because neither can be dismissed from the available evidence.

**Shape A — flat pagination (QA-verified cURL and the supplied specification):**

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `page` | — | Integer | Specifies the page number to retrieve. Pagination starts from `0`. |
| `pageSize` | — | Integer | Specifies the maximum number of records to return per page. |


**Shape B — structured pagination and filters (`sprinklr-v3.yaml`, `SurveyResponseFetchRequest`):**

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `page` | — | `Page` object | Pagination descriptor. |
| `page` | `start` | Integer (int32) | Start of the page in search results. |
| `page` | `size` | Integer (int32) | Size of the page in search results. |
| `page` | `cursor` | String | Cursor for resuming a paginated search. |
| `filters` | — | Array[`Filter_shifu`] | Filters to apply to the search. |
| `filters[]` | `field` | String | Specifies the field on which the filter is applied. |
| `filters[]` | `filterType` | String | Filter type, for example `IN`. |
| `filters[]` | `values` | Array[Object] | Specifies values for this filter. |
| `filters[]` | `details` | Object | Additional details for the filter — for example `{"fieldName": "_c_67ce77b1ddff1d4b9bc831ce"}`. |
| `filters[]` | `filters` | Array[`Filter_shifu`] | Nested filters. |


> **⚠️ Conflict to resolve — the search request body.** `page` is an **integer** in the QA cURL and the supplied specification, but a **`Page` object** with `start`/`size`/`cursor` in the OpenAPI specification. `pageSize` exists only in the flat shape; `filters` exists only in the structured shape. The two are mutually exclusive and a server cannot accept both without a custom deserializer. **This is blocking** — the search endpoint cannot be published until it is settled. See [§15, Q13](#15-questions-for-the-api-owner).
Weighing the evidence: the **structured shape is corroborated by V2**, whose documented filter payload is `{"filters": [{"field": "SURVEY_CUSTOM_PROPERTY", "filterType": "IN", "values": [...], "details": {"fieldName": "_c_…"}}]}` — a field-for-field match with `Filter_shifu`. The **flat shape is corroborated by the QA cURL**, which was actually executed. The most likely explanation is that the QA cURL exercised only default pagination and never sent a filter.


> **⚠️ Conflict to resolve — filter field naming.** The supplied specification's "Filter Types" table names the filter properties **`dimension`** and **`filterValues`**. Neither exists in `Filter_shifu`, which uses **`field`** and **`values`** — and V2 uses `field` and `values` too. The specification table appears to have been written against a different filter model. See [§15, Q14](#15-questions-for-the-api-owner).


### 6.3 Documented filter types

| Search value type | Filter type | Description |
|  --- | --- | --- |
| String | `IN` | Matches records where the field value exists in the specified list of values. |
| String | `TOPIC` | Filters data using one or more Topic IDs provided in the filter values. |


Values in the supplied table listed as filter types but which are in fact **properties** of a filter, not types:

| Property | Type | Description |
|  --- | --- | --- |
| `filterValues` (spec: `values`) | Array[String] | List of values used for filtering. For example, topic IDs, category IDs, or other dimension values. |
| `dimension` (spec: `field`) | String | Specifies the dimension or field on which the filter is applied. |


V2 documents `SURVEY_CUSTOM_PROPERTY` as a `field` value, paired with `details.fieldName` carrying the internal custom-field ID.

### 6.4 Example request

Using **Shape A**, exactly as QA verified it:

```bash
curl --location 'https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse/search?surveyId=6a840fb9a64a419291aacd53' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "page": 0,
    "pageSize": 20
  }'
```

Using **Shape B**, as the OpenAPI specification declares it — filtering on a survey custom property, modelled on the V2 sample:

```bash
curl --location 'https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse/search?surveyId=6a840fb9a64a419291aacd53' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "page": {
      "start": 0,
      "size": 20
    },
    "filters": [
      {
        "field": "SURVEY_CUSTOM_PROPERTY",
        "filterType": "IN",
        "values": ["Promoter"],
        "details": {
          "fieldName": "_c_67ce77b1ddff1d4b9bc831ce"
        }
      }
    ]
  }'
```

> **Shape B has not been verified against a running server.** It is constructed from the OpenAPI schema and the V2 documented payload. Verify with the API owner before building against it.


### 6.5 Example response

```json
{
  "data": {
    "hasMore": false
  },
  "errors": []
}
```

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `data` | — | Object | Contains the response details returned by the API. |
| `data` | `hasMore` | Boolean | Indicates whether additional records are available for retrieval. `false` means there are no more records. |
| `errors` | — | Array | Contains details of any errors encountered. Empty array when the request is successful. |


> **Specification gap.** The supplied sample response contains **only** `hasMore` — no `responses` array and no `metadata`, even though `sprinklr-v3.yaml` declares the `200` response as a `SurveyResponseDTO` (which carries `responses` and `metadata`). Either the sample was captured from a search that matched zero records, or the search response genuinely omits the payload. A sample with at least one matching record is needed. See [§15, Q15](#15-questions-for-the-api-owner).


> **Specification gap — cursor pagination.** `Page.cursor` exists in the schema but no source documents where a cursor is returned to the client, or how to pass it back. Only offset pagination can be documented from the available sources. See [§15, Q16](#15-questions-for-the-api-owner).


## 7. Update survey response custom fields

Updates the custom fields on an existing survey response. Per the Help Center, use it to *"Change particular areas in a survey response, including the modification of tags, custom fields, or metadata… Improve or enhance current replies by adding missing information or making corrections. Utilize this API for performing bulk updates through scripts."*

```
PUT /api/v3/surveyResponse?id={responseId}
```

**Request schema:** `SurveyResponseUpdateV3Request` — *"Request body for updating CFM survey response custom fields via API v3."*

### 7.1 Request parameters

| Parameter | In | Type | Required (spec) | Description |
|  --- | --- | --- | --- | --- |
| `id` | query | String | `required: false` | Survey response ID. |


### 7.2 Request body

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `responseCustomFields` | — | Object | Survey response custom fields to update. |
| `responseCustomFields` | `<CUSTOM_FIELD_KEY>` | Array[String] | Custom field identifier and its corresponding value. |
| `profileCustomFields` | — | Object | Profile custom fields to update. |
| `profileCustomFields` | `<CUSTOM_FIELD_KEY>` | Array[String] | Custom profile field identifier and its corresponding value. |
| `transactionCustomFields` | — | Object | Transaction custom fields to update. |
| `transactionCustomFields` | `<CUSTOM_FIELD_KEY>` | Array[String] | Custom transaction field identifier and its corresponding value. |


**Only custom fields can be updated.** Answers (`questionResponses`), `responseStatus`, `tags`, and every system field are not writable through this endpoint. Despite the method being `PUT`, the operation summary in `sprinklr-v3.yaml` is *"Update survey response custom fields by id"* — it is scoped to the three custom-field namespaces only.

> **⚠️ Conflict to resolve — custom field value type.** The supplied specification's parameter table types custom field values as **"String / Object / Array"**. `sprinklr-v3.yaml` declares them unambiguously as `additionalProperties: {type: array, items: {type: string}}` — an **array of strings only**. The V2 samples also use arrays of strings exclusively. Send arrays of strings. See [§15, Q17](#15-questions-for-the-api-owner).


> **⚠️ Semantics of `PUT` are unspecified.** No source states whether `PUT` **replaces** the entire custom-field map (deleting keys not present in the request, which is standard `PUT` semantics) or **merges** the supplied keys into the existing map. The distinction is destructive: a partial payload under replace semantics silently erases every unlisted custom field. The absence of a working `PATCH` makes this urgent. **Do not run a partial update against production data until this is answered.** See [§15, Q18](#15-questions-for-the-api-owner).


### 7.3 Example request

```bash
curl --location --request PUT 'https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse?id=6a8c451269baf80612fb6417' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "responseCustomFields": {
      "_c_67cb50793cae6f06c0c41de0": ["5"]
    },
    "profileCustomFields": {
      "_c_64cbf526fd8b0e259d72470a": ["20"]
    },
    "transactionCustomFields": {
      "_c_67056c1056182e36198d8eea": ["WHATSAPP"]
    }
  }'
```

### 7.4 Example response

The sources disagree.

| Source | Declared response |
|  --- | --- |
| `sprinklr-v3.yaml` | `200 Success`, `application/json`, `$ref: APIResponse` |
| Supplied specification — "Example Response" | `HTTP/1.1 204 (No Content)` |


> **⚠️ Conflict to resolve — `PUT` response.** `204 No Content` and a JSON `APIResponse` body are mutually exclusive; a `204` response must not carry a body. Client code should treat any `2xx` as success and must not assume a parseable body until this is settled. See [§15, Q19](#15-questions-for-the-api-owner).


## 8. Delete survey response(s)

Permanently deletes a survey response using its unique ID. Per the Help Center, use it to *"Remove any duplicate, test, or incorrect entries from the system."*

```
DELETE /api/v3/surveyResponse?id={responseId}
```

### 8.1 Request parameters

| Parameter | In | Type | Required (spec) | Description |
|  --- | --- | --- | --- | --- |
| `id` | query | String | `required: false` in the OpenAPI specification; **Required** in the supplied endpoint specification | Comma-separated list of survey response IDs to delete. |


> **⚠️ Safety — `id` is declared optional on a `DELETE`.** `sprinklr-v3.yaml` marks `id` as `required: false` on `DELETE /surveyResponse`. If the server interprets a missing `id` as "all responses," an omitted parameter becomes an unbounded destructive operation. **Always send `id`, and validate it client-side before dispatching the request.** Confirm the no-`id` behaviour with the API owner as a priority. See [§15, Q9](#15-questions-for-the-api-owner).


### 8.2 Example request

```bash
curl --location --request DELETE 'https://qa6-api2-v3.sprinklr.com/api/v3/surveyResponse?id=6a8c451269baf80612fb6417' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Accept: application/json'
```

### 8.3 Example response

| Source | Declared response |
|  --- | --- |
| `sprinklr-v3.yaml` | `200 Success`, `application/json`, `$ref: APIResponse` |
| Supplied specification — "Example Response" | `HTTP/1.1 204 (No Content)` |


> **⚠️ Conflict to resolve — `DELETE` response.** The same `200`-versus-`204` contradiction as `PUT`. The supplied specification additionally documents a **"Request Parameters"** table that describes request headers (`Authorization`, `Content-Type`, `Accept`, `Cookie`) rather than response fields — that table is mislabelled and should be removed or relabelled. See [§15, Q19](#15-questions-for-the-api-owner) and [§15, Q5](#15-questions-for-the-api-owner).


> **Specification gap.** No source states whether deletion is permanent or reversible, or whether a deleted response is excluded from analytics immediately. The Help Center describes it as *"Delete a survey response permanently."* Note that `ARCHIVED_RESPONSES` is a valid `responseStatus`, so archiving and deleting are distinct operations — but no API is documented for archiving. See [§15, Q20](#15-questions-for-the-api-owner).


## 9. Response format and status codes

### 9.1 The response envelope

Survey Response V3 responses use the standard `APIResponse` envelope declared in `sprinklr-v3.yaml`:

| Field | Type | Description |
|  --- | --- | --- |
| `data` | Object or Array | The operation payload. An **object** with `responseId` on `POST`; an **array** of wrapper objects on `GET`; an **object** with `hasMore` on `POST /search`. Unconfirmed on `PUT` and `DELETE`. |
| `errors` | Array of `Error` | The list of errors. Empty on success. |
| `metadata` | `ResponseMetadata` | Response metadata. |


> **Specification gap.** `ResponseMetadata` is declared with an empty `properties: {}` block, so no field can be documented, and it does not appear in any supplied sample. Do not confuse it with the `data.metadata` object on `GET`, which is an entirely different thing — the `questionLookup`/`customFieldLookup` map described in [§5.5](#55-metadata-parameters).


### 9.2 Status codes

Declared in `sprinklr-v3.yaml` for all five operations:

| Code | Meaning | Typical cause |
|  --- | --- | --- |
| `200` | Success | The request was processed. Inspect `errors` before assuming complete success. |
| `400` | Bad Request | Malformed JSON, an unknown `surveyId`, an unrecognised custom-field key, a custom-field value sent as a bare string instead of an array, or a `responseTime` sent as a formatted string instead of epoch milliseconds. |
| `401` | Unauthorized | Missing, expired, or invalid `Authorization` token. |
| `403` | Forbidden | The caller lacks **View and Edit Response and Analytics** permission at the survey level, or CFM is not enabled for the environment. |
| `404` | Not Found | No survey response exists for the supplied `id`, or the `surveyId` does not exist. |


Per the Help Center's testing guidance, a successful ingestion should return **`200 OK`** and the response should then be visible in the Sprinklr UI under the **Survey Responses** tab.

> **Specification gap.** `500 Internal Server Error` and `429 Too Many Requests` are **not declared** on any of the five operations. This omission is consistent across the Listening, CFM Workflow, and CFM Transaction paths in `sprinklr-v3.yaml`. Client code should handle `5xx` defensively regardless. See [§15, Q21](#15-questions-for-the-api-owner).


## 10. Migrating from V2

### 10.1 Endpoint mapping

| V2 operation | V2 path | V3 equivalent |
|  --- | --- | --- |
| Ingest response (bulk or single) | `POST /api/v2/survey-response` (`surveyId` in header) | `POST /api/v3/surveyResponse` (`surveyId` in body) |
| Get responses using filters | `POST` with `filters` body (`surveyId` in header) | `POST /api/v3/surveyResponse/search?surveyId=` |
| Get response by ID | `GET /api/v2/survey-response/{survey-response-id}` | `GET /api/v3/surveyResponse?id=` |
| Update response by ID | `PUT` with `updates` wrapper (`responseID` in header) | `PUT /api/v3/surveyResponse?id=` |
| Delete response by ID | `DELETE` (`responseID` in header) | `DELETE /api/v3/surveyResponse?id=` |


### 10.2 Identifier passing — the biggest behavioural change

This is the change most likely to break a V2 client silently.

| Identifier | V2 | V3 |
|  --- | --- | --- |
| `surveyId` on ingest | Request **header** | Request **body** |
| `surveyId` on search | Request **header** | **Query parameter** `?surveyId=` |
| `responseId` on get | **Path segment** `/survey-response/{id}` | **Query parameter** `?id=` |
| `responseId` on update | Request **header** | **Query parameter** `?id=` |
| `responseId` on delete | Request **header** | **Query parameter** `?id=` |


V2 routinely carried business identifiers in headers. V3 moves every one of them into the URL or the body. A client that keeps setting the V2 headers will send them harmlessly but will address no resource, producing `400` or `404`.

### 10.3 Request body restructuring

**Ingest — the `customFields` wrapper is removed.**

V2:

```json
{
  "questionResponses": { "questionID": ["Free-text answer"] },
  "responseTime": "2025-03-06 15:00:00",
  "customFields": {
    "responseCustomFields": { "_c_67cb50793cae6f06c0c41de0": ["5"] },
    "transactionCustomFields": { "_c_67056c1056182e36198d8eea": ["WHATSAPP"] },
    "profileCustomFields": { "_c_64cbf526fd8b0e259d72470a": ["20"] }
  }
}
```

V3 — the three objects move to the **top level**, and `surveyId` joins them:

```json
{
  "surveyId": "6a840fb9a64a419291aacd53",
  "questionResponses": { "questionID": ["Free-text answer"] },
  "responseTime": 1738281600000,
  "responseCustomFields": { "_c_67cb50793cae6f06c0c41de0": ["5"] },
  "transactionCustomFields": { "_c_67056c1056182e36198d8eea": ["WHATSAPP"] },
  "profileCustomFields": { "_c_64cbf526fd8b0e259d72470a": ["20"] }
}
```

**Update — the `updates` wrapper is removed.**

V2 wrapped the payload: `{"updates": {"responseCustomFields": {...}, …}}`.
V3 sends the three objects at the top level, with no wrapper.

### 10.4 Type and shape changes

| Field / area | V2 | V3 | Impact |
|  --- | --- | --- | --- |
| `responseTime` | String, `"2025-03-06 15:00:00"` | Long, epoch milliseconds (`1738281600000`) | **Breaking.** A V2-formatted string will be rejected. |
| `data` on fetch | Single object | **Array** of objects | **Breaking.** V2 clients reading `data.responses` must now read `data[0].responses`. |
| Version segment | `/api/v2/` | `/api/v3/` | Breaking. |
| Path casing | `survey-response` (kebab) | `surveyResponse` (camel) | Breaking. |
| Envelope | `data` / `errors` | `data` / `errors` / `metadata` | Compatible; `metadata` is additive. |
| Auth | OAuth 2.0 `Authorization` + `Key` | Unchanged | No change. |
| Bulk fetch / delete | Single ID per call | Comma-separated ID list | Additive capability. |


Response field names, enum values, and the `metadata` lookup structure are otherwise **unchanged** between V2 and V3 — the V2 response-parameter documentation remains an accurate description of the objects inside `responses[]`.

### 10.5 Migration checklist

- [ ] Confirm CFM is enabled in the target environment and the API user holds **View and Edit Response and Analytics** at survey level.
- [ ] Confirm the resource path — `/api/v3/surveyResponse` versus `/api/v3/cfm/surveyResponses` in the Jira ticket ([§15, Q1](#15-questions-for-the-api-owner)). **Blocking.**
- [ ] Move the base URL to configuration; replace `/api/v2/` with `/api/v3/` and `survey-response` with `surveyResponse`.
- [ ] Move `surveyId` out of the request header — into the **body** on ingest, into the **query string** on search.
- [ ] Move `responseId` out of the path segment and out of the request header, into the `?id=` query parameter.
- [ ] Unwrap `customFields` on ingest; move the three objects to the top level.
- [ ] Unwrap `updates` on update; move the three objects to the top level.
- [ ] Convert `responseTime` from a formatted string to epoch milliseconds.
- [ ] Update fetch deserializers: `data` is now an **array**, so read `data[0].responses`, not `data.responses`.
- [ ] Ensure every custom-field value is an **array of strings**, even for single scalars.
- [ ] Map request `…CustomFields` to response `…CustomProperties` explicitly — the names are not symmetrical, and the response-scoped one is just `customProperties`.
- [ ] Resolve `questionResponses` keys through `metadata.questionLookup` rather than hard-coding question IDs.
- [ ] Remove any `PATCH`-based partial-update logic — `PATCH` is not implemented ([§15, Q2](#15-questions-for-the-api-owner)).
- [ ] Confirm whether `PUT` replaces or merges custom fields before running any partial update against production ([§15, Q18](#15-questions-for-the-api-owner)). **Blocking.**
- [ ] Confirm the search request-body shape before building search integrations ([§15, Q13](#15-questions-for-the-api-owner)). **Blocking.**
- [ ] Send `Key` on every request, including `GET` and `DELETE`.
- [ ] Remove any `Cookie` header inherited from manual testing or from the V2 examples.
- [ ] Always send `id` on `DELETE`, and validate it client-side.
- [ ] Handle `400`, `401`, `403`, `404` explicitly, and `5xx` defensively even though undeclared.
- [ ] Re-run the integration against QA6 with a test survey and a test responder address (for example `test@sprinklr.com`) before promoting to production.


## 11. Reference values

The enum values below are documented on the V2 **Fetch Survey Response** reference page. The corresponding fields in `sprinklr-v3.yaml` are declared as unconstrained `type: string` with **no enum**, so these values are carried forward from V2 documentation rather than from the V3 specification. They should be added as enums to the V3 schema — see [§15, Q22](#15-questions-for-the-api-owner).

### 11.1 `responseStatus`

| Value | Meaning |
|  --- | --- |
| `COMPLETE_RESPONSES` | The respondent completed the survey. |
| `ARCHIVED_RESPONSES` | The response has been archived. |
| `IMPORTED_RESPONSES` | The response was imported from an external source. |
| `LIVE_RESPONSES` | The response is live. |
| `TEST_RESPONSES` | The response was captured during testing. |


### 11.2 `surveyMode`

| Value | Meaning |
|  --- | --- |
| `STANDARD` | Standard survey presentation. |
| `CONVERSATIONAL` | Conversational survey presentation. |


### 11.3 `deviceType`

| Value | Meaning |
|  --- | --- |
| `COMPUTER` | Desktop or laptop computer. |
| `MOBILE` | Mobile phone. |
| `TABLET` | Tablet. |
| `GAME_CONSOLE` | Games console. |
| `DMR` | Digital media receiver. |
| `WEARABLE` | Wearable device. |
| `UNKNOWN` | Device type could not be determined. |


### 11.4 `responseQuality`

AI-generated quality assessment of the response.

| Value | Meaning |
|  --- | --- |
| `N/A` | Quality assessment has not been generated yet. Returned as `NA` in the V3 sample payload. |
| `High` | High-quality response. |
| `Medium` | Medium-quality response. |
| `Low` | Low-quality response. |
| `Bot` | The system has flagged the submission as a bot response. |


> **⚠️ Value formatting inconsistency.** V2 documents this value as `N/A`, but both the V3 and V2 sample payloads return `NA` without the slash. Confirm the exact token. Note also that these values are **mixed-case** (`High`, `Bot`), unlike every other enum in this API, which is `UPPER_SNAKE_CASE`. See [§15, Q23](#15-questions-for-the-api-owner).


### 11.5 `distributionChannel` and `responderSnType`

| Value | Seen in |
|  --- | --- |
| `EXTERNAL_APPLICATION` | V3 QA cURL (`distributionChannel`), V3 and V2 samples (`responderSnType`) |
| `EMAIL` | V2 Help Center sample payload (`responderSnType`, `distributionChannel`) |


> **Specification gap.** No complete enumeration of `distributionChannel` or `responderSnType` values exists in any source. CFM supports Email, SMS, and WhatsApp distributions, so further values almost certainly exist. Do not guess. See [§15, Q24](#15-questions-for-the-api-owner).


### 11.6 Filter fields

| Value | Meaning |
|  --- | --- |
| `SURVEY_CUSTOM_PROPERTY` | Filter on a survey custom property. Pair with `details.fieldName` carrying the internal custom-field ID. Source: V2 Help Center sample. |


## 12. Use cases

1. **Bulk import of historical responses from a legacy platform.** Iterate a legacy export and `POST` each response with its original `responseTime` and `surveyLanguage`. Per the Help Center this *"Facilitates the automatic integration of large quantities of survey responses from external systems into Sprinklr."*
2. **Real-time sync from an offline collection tool.** Kiosk or field-agent applications collect feedback without connectivity, then ingest queued responses when the device reconnects, preserving the true `responseTime` rather than the upload time.
3. **CRM-triggered feedback capture.** When a CRM records a closed case, submit the associated survey response with `transactionCustomFields` carrying the case attributes, so the response is analysable alongside the interaction that produced it.
4. **Feeding a downstream analytics warehouse.** Page through `POST /search` on a schedule, resolve question IDs via `metadata.questionLookup`, and load flattened rows into a warehouse. This *"Enables downstream analytics tools to retrieve or modify response data as required."*
5. **Backfilling custom fields on historical responses.** A new segmentation attribute is defined after responses were collected. Script a `PUT` across the affected `responseId` values to populate it — the Help Center's documented pattern of *"bulk updates on older responses in light of the latest information for analytical purposes."*
6. **Enriching responses with profile data resolved later.** A response arrives anonymously and the responder is identified afterwards. Use `PUT` to attach `profileCustomFields` once the identity is known.
7. **Correcting a mis-mapped custom field.** An ingestion job wrote the wrong internal field name. Fetch the affected responses, then `PUT` the corrected mapping — subject to confirming replace-versus-merge semantics ([§15, Q18](#15-questions-for-the-api-owner)).
8. **Purging test and duplicate submissions before reporting.** Search for responses with `responseStatus` of `TEST_RESPONSES`, then `DELETE` them so they do not distort dashboards.
9. **Filtering out low-quality and bot responses.** Use `responseQuality` to identify `Bot` and `Low` submissions and either delete them or tag them for exclusion, protecting score integrity.
10. **Auditing a single response after a customer complaint.** Fetch by `responseId` to inspect exactly what was answered, on what device and browser, and at what time — the audit use case the Help Center describes as *"Obtain a particular reply for examination, auditing, or modification."*
11. **Data-subject erasure.** Search for a responder's submissions, then delete each by ID. Confirm the permanence of deletion before relying on this for a compliance workflow ([§15, Q20](#15-questions-for-the-api-owner)).


## 13. Caveats and best practices

**Verify required fields before ingestion.** The Help Center is explicit: *"Always verify the necessary fields prior to ingestion."* `SurveyResponseIngestionRequest` declares no `required` array, so the server will accept structurally valid but semantically empty payloads. Validate client-side.

**Batch large ingestions.** Per the Help Center: *"Utilize APIs in groups for large-scale data ingestion to minimize overhead."* No maximum batch size or request-size limit is documented, so keep batches conservative until one is confirmed.

**Filter searches narrowly.** Per the Help Center: *"Utilize filtering with appropriate fields to refine GET APIs and prevent timeouts."* An unfiltered search over a high-volume survey is the most likely cause of a timeout on this API.

**Custom field values are always arrays of strings.** `["5"]`, never `"5"` and never `5`. This applies on ingest and on update, across all three namespaces.

**Request and response custom-field names differ.** Requests use `responseCustomFields` / `profileCustomFields` / `transactionCustomFields`; responses use `customProperties` / `profileCustomProperties` / `transactionCustomProperties`. Write an explicit bidirectional mapping.

**Never hard-code question IDs.** Resolve them through `metadata.questionLookup` on every fetch. Question IDs change when a survey is edited or cloned, and some IDs are compound (`<questionId>_<optionId>`, as in the V2 sample `c958057e-…_778dcad1-…`).

**`PUT` is not a general-purpose update.** It writes custom fields only. Answers, status, and system fields cannot be modified through this API.

**Treat `PUT` as potentially destructive until proven otherwise.** With replace semantics, a payload containing one custom field could erase all the others. Test on a disposable response in QA6 first.

**`PATCH` does not exist.** Do not build against the `PATCH` operation listed in the Jira description — QA confirmed it is absent from the merged PR.

**Guard `DELETE` client-side.** `id` is declared optional on a destructive operation. Assert it is non-empty before dispatching, and consider requiring an explicit confirmation flag in any script that deletes in bulk.

**Test the round trip in the UI.** The Help Center recommends starting with a test survey and a test responder address such as `test@sprinklr.com`, confirming the API returns `200 OK`, and then *"verify that the response is visible in the Sprinklr UI under the Survey Responses tab."* An API success does not by itself prove the response was indexed.

**Do not assume idempotency.** No idempotency key, deduplication window, or uniqueness constraint is documented for ingestion. A retried `POST` will most likely create a duplicate response and distort your scores. Deduplicate at the client.

**Rate limits are undocumented.** No rate-limit headers, quotas, or `429` responses are declared. Build in client-side throttling and exponential backoff regardless.

**Timestamps are epoch milliseconds in V3.** `responseTime`, `createdTime`, `surveyStartTime`, `surveyOpenTime`, and `elapsedTime` are all `int64`. V2's formatted-string `responseTime` will be rejected.

**Keep credentials and session artefacts out of examples.** Strip `Cookie: JSESSIONID` headers, and never paste live tokens into tickets or shared Postman collections.

## 14. Quick reference

### 14.1 Operations

| Operation | Method | Path | Required input | Success payload |
|  --- | --- | --- | --- | --- |
| Import response | `POST` | `/api/v3/surveyResponse` | Body: `surveyId`, `questionResponses` | `200` — `data.responseId` |
| Fetch response(s) | `GET` | `/api/v3/surveyResponse?id=` | Query: `id` (comma-separated) | `200` — `data[]` with `responses[]` and `metadata` |
| Search responses | `POST` | `/api/v3/surveyResponse/search?surveyId=` | Query: `surveyId`; Body: pagination ([shape unsettled](#62-request-body)) | `200` — `data.hasMore` |
| Update custom fields | `PUT` | `/api/v3/surveyResponse?id=` | Query: `id`; Body: three custom-field objects | `200` or `204` ([unsettled](#74-example-response)) |
| Delete response(s) | `DELETE` | `/api/v3/surveyResponse?id=` | Query: `id` (comma-separated) | `200` or `204` ([unsettled](#83-example-response)) |
| Partial update | `PATCH` | — | **Not implemented.** Listed in IN-12886 but absent from the merged PR. | — |


### 14.2 Related CFM V3 endpoints not covered by this guide

| Endpoint | Method | Purpose |
|  --- | --- | --- |
| `/api/v3/cfmWorkflow/trigger` | `POST` | Trigger a CFM workflow for an existing profile. See the CFM Workflow API V3 developer guide. |
| `/api/v3/cfmWorkflow/triggerWithNewProfile` | `POST` | Create or update a profile and trigger a CFM workflow. See the CFM Workflow API V3 developer guide. |
| `/api/v3/surveyTransactions` | `POST`, `GET`, `DELETE` | Manage CFM transactions. See the CFM Transaction APIs V3 developer guide. |
| `/api/v3/surveyDistribution/transactions/link` | `POST` | Create a personalized survey transaction link. |


### 14.3 Required headers

```
Authorization: Bearer {{accessToken}}
Key: {{apiKey}}
Content-Type: application/json      # POST and PUT only
Accept: application/json
```

## 15. Questions for the API owner

Ownership drawn from IN-12886: assignee **Aman Joshi**, reporter/creator **Ruchika Grover**, reviewer **Prateek Agrawal**, QA **Santhosh M.**

| # | Question | Why it blocks | Suggested owner |
|  --- | --- | --- | --- |
| 1 | Is the resource path `/api/v3/surveyResponse` (singular, spec + QA cURLs), `/api/v3/cfm/surveyResponses` (plural, Jira description), or `/api/v3/survey-response/{id}` (kebab + path param, all five endpoint specs)? | **Blocking.** Every example, SDK method, and client integration depends on it. Three different forms are currently documented. | Aman Joshi |
| 2 | Is `PATCH` deferred to a later release or dropped? QA confirms it is absent from the merged PR, but it is in the ticket description. | Determines whether V2 clients can plan for partial updates or must use `PUT`. | Aman Joshi |
| 3 | Is the request/response naming asymmetry (`…CustomFields` versus `…CustomProperties`, and `responseCustomFields` versus plain `customProperties`) intentional? | It is a persistent source of client bugs. Confirm before it is locked into published SDKs. | Prateek Agrawal |
| 4 | Is the `Key` header mandatory? All five QA-verified cURLs omit it, yet all five specifications mark it required. | Same open question as CFM Workflow and CFM Transaction. Affects auth handling everywhere. | Aman Joshi |
| 5 | Can `Cookie` be removed from the published request-parameter tables and the example cURLs? And can the mislabelled "Request Parameters" table on the Delete specification be corrected? | A session cookie is not part of the API contract and must not be published. | Ruchika Grover |
| 6 | Should `allCustomFields` be declared in `SurveyResponseIngestionRequest`? What is its relationship to the three specific `…CustomFields` objects — alternative, superset, or read-only? | It appears in the QA cURL and the spec table but not in the schema. Clients cannot use it safely. | Prateek Agrawal |
| 7 | Does `POST /surveyResponse` accept `surveyId` anywhere other than the body? The Import specification has a "Path Parameters" table listing it as Required. | Determines whether that table should be published or removed. | Aman Joshi |
| 8 | What is the maximum number of comma-separated IDs accepted by `GET` and `DELETE`? | Needed to size batch reads and deletes. | Aman Joshi |
| 9 | `id` is `required: false` on `GET`, `PUT`, and `DELETE`. What happens when it is omitted? Specifically, **does `DELETE` without `id` delete everything?** | **Blocking, safety-critical.** An optional identifier on a destructive operation is a serious hazard. | Aman Joshi |
| 10 | Does `GET` return a bare array of `SurveyResponseDTO` (as the spec declares) or a `data`/`errors` envelope (as the sample and V2 show)? | **Blocking.** The two are incompatible and generated clients will fail. | Aman Joshi |
| 11 | `SurveyResponseDTO.metadata` is typed as `Metadata_models` (`sourceIp`, `version`, `sequence`, …), but the payload returns `{customFieldLookup, questionLookup}`, which the schema declares as top-level siblings instead. Can the schema be corrected? | **Blocking.** The declared type does not match the wire format at all. | Prateek Agrawal |
| 12 | Is `data` returning as an **array** on `GET` (V3) rather than an **object** (V2) intentional? | It is an unannounced breaking change for every V2 client. If unintentional, fix before release. | Aman Joshi |
| 13 | What is the correct search request body — flat `{page: 0, pageSize: 20}` (QA cURL + spec doc) or structured `{page: {start, size, cursor}, filters: [...]}` (`SurveyResponseFetchRequest`)? | **Blocking.** The search endpoint cannot be documented or used until this is settled. | Aman Joshi |
| 14 | Are the filter properties named `field`/`values` (OpenAPI `Filter_shifu`, and V2) or `dimension`/`filterValues` (the supplied Filter Types table)? | The specification table appears to describe a different filter model. | Prateek Agrawal |
| 15 | Can a search sample response be supplied that contains at least one matching record? The current sample returns only `hasMore`. | The actual search payload shape is undocumented. | Santhosh M. |
| 16 | How does cursor pagination work? `Page.cursor` exists in the schema but no source shows where a cursor is returned or how to pass it back. | Only offset pagination can currently be documented. | Aman Joshi |
| 17 | Are custom-field values strictly `Array[String]` (spec, V2) or can they be "String / Object / Array" as the Update specification table states? | Wrong typing produces `400` responses or silent data loss. | Prateek Agrawal |
| 18 | Does `PUT` **replace** the entire custom-field map or **merge** the supplied keys? | **Blocking, destructive.** Under replace semantics a partial payload silently erases unlisted fields. Critical given `PATCH` does not exist. | Aman Joshi |
| 19 | Do `PUT` and `DELETE` return `200` with an `APIResponse` body (spec) or `204 No Content` (endpoint specs)? | **Blocking.** The two are mutually exclusive; a `204` must not carry a body. | Aman Joshi |
| 20 | Is `DELETE` permanent or reversible? Is a deleted response removed from analytics immediately? Is there an API to set `ARCHIVED_RESPONSES` status instead of deleting? | Determines suitability for compliance workflows and for pre-reporting cleanup. | Ruchika Grover |
| 21 | Should `500` and `429` be declared on these operations? | Undeclared error responses lead to unhandled paths in generated clients. | Prateek Agrawal |
| 22 | Can the enum values in [§11](#11-reference-values) — `responseStatus`, `surveyMode`, `deviceType`, `responseQuality` — be added as `enum` blocks in `sprinklr-v3.yaml`? They are currently documented only on the V2 page. | Without enums the generated V3 reference is materially less useful than the V2 page it replaces. | Prateek Agrawal |
| 23 | Is `responseQuality` returned as `N/A` (V2 documentation) or `NA` (both sample payloads)? Are the mixed-case values `High`/`Medium`/`Low`/`Bot` correct, given every other enum is `UPPER_SNAKE_CASE`? | Exact-match comparisons in client code will fail on the wrong token. | Santhosh M. |
| 24 | What is the complete accepted set of `distributionChannel` and `responderSnType` values? | Only `EXTERNAL_APPLICATION` and `EMAIL` are evidenced; CFM also supports SMS and WhatsApp. | Aman Joshi |
| 25 | Is ingestion idempotent? Is there a deduplication key or window? Is bulk ingestion atomic, or can it partially succeed — and how is that signalled in `errors`? | Determines retry safety on high-volume ingestion pipelines. | Aman Joshi |
| 26 | What are the maximum request-body size, batch size, and rate limits for these operations? | The Help Center advises batching but gives no numbers. | Aman Joshi |
| 27 | Can the plaintext QA6 credentials in the IN-12886 QA comment be revoked, regenerated, and redacted? | **Security action, still open** since 2026-08-31. Live-looking tokens are exposed in the ticket. | Santhosh M. |
| 28 | Can the V2 `cfm-survey-response-apis` landing page be corrected? Its body currently describes the Survey Builder module rather than the response APIs. | Affects the V2 page that developers will consult during migration. | Ruchika Grover |


*All examples in this guide are illustrative and were assembled from the five supplied endpoint specifications, `sprinklr-v3.yaml`, Jira IN-12886 (description and QA verification comment), the Sprinklr Developer Portal V2 reference pages, and the Sprinklr Help Center. All credentials are placeholders. No API call was executed and no specification linting or validation was run to produce this document. Items marked as conflicts or specification gaps must be resolved by the API owner before publication.*