# Campaign API V3 — Developer Guide

- **Applies to:** Sprinklr Campaign API V3 (`/api/v3/campaign`, `/api/v3/campaign/brief`)
- **V2 API reference:** [Campaign | Sprinklr Developer Portal](https://dev.sprinklr.com/campaign)


## 1. Overview

Campaigns are used in Sprinklr as **primary tags to categorize outbound messages for reporting**. They can align with your traditional marketing or PR campaigns and can be customized to give you the outbound categories you need for Reporting. Campaigns that you create through the API appear as selectable options when you create and publish outbound content in Sprinklr. Sprinklr Campaign Management APIs let you create, update, retrieve, and delete campaigns.

Campaign V3 exposes full CRUD on a **single resource path** — `/api/v3/campaign` — differentiated by HTTP method, plus one sub-resource for briefs:

| Operation | Method | Path |
|  --- | --- | --- |
| Create campaign or sub-campaign | `POST` | `/api/v3/campaign` |
| List all campaigns | `GET` | `/api/v3/campaign` |
| Fetch by campaign ID | `GET` | `/api/v3/campaign?campaignId={id}` |
| Fetch by external source | `GET` | `/api/v3/campaign?externalSource={source}&externalSourceId={id}` |
| Update campaign (full) | `PUT` | `/api/v3/campaign?campaignId={id}` |
| Update external campaign (full) | `PUT` | `/api/v3/campaign?externalSource={source}&externalSourceId={id}` |
| Update campaign (partial) | `PATCH` | `/api/v3/campaign?campaignId={id}` or `?externalSource=&externalSourceId=` |
| Delete campaign | `DELETE` | `/api/v3/campaign?campaignId={id}` |
| Delete external campaign | `DELETE` | `/api/v3/campaign?externalSource={source}&externalSourceId={id}` |
| Add campaign brief | `POST` | `/api/v3/campaign/brief?campaignId={id}` |


### 1.1 The campaign data model

| Layer | Field(s) | What it holds |
|  --- | --- | --- |
| Identity | `id`, `displayId`, `parentCampaignId`, `externalSource`, `externalSourceId` | How the campaign is addressed inside Sprinklr and in your system. |
| Descriptive | `name`, `description`, `startDate`, `endDate`, `tags`, `status` | The campaign itself |
| Custom data | `partnerCustomFields` / `partnerCustomProperties`, `clientCustomFields` / `clientCustomProperties` | Partner-level and client (workspace)-level custom fields, each a map of field ID → list of string values. |
| Sharing | `visibility.globallyVisible`, `visibility.visibilityConfig[]` | Who can see the campaign |
| Lifecycle | `archived`, `deleted`, `createdTime`, `modifiedTime`, `owner` | System-managed state. |
| Connectors | `externalCampaignDetails[]` | External connector IDs per channel. |


> Note:  A sub-campaign is a campaign that includes `parentCampaignId`. As in V2, pass `parentCampaignId` in the create payload to nest a campaign under a parent. `displayId` is populated by the platform for sub-campaigns.


### 1.2 Addressing a campaign

You can address a campaign in two mutually exclusive ways:

- **By Sprinklr campaign ID** — `campaignId`, for example `66001165_7624` or `1_7714`
- **By external key** — The pair `externalSource` and `externalSourceId`, for example `CAMPAIGN_PORTAL` and `dgsg3456y45tygvcd3456tyhgfde3456yhfgfd_02`


Campaign IDs are opaque **strings** that follow the format `{partnerId}_{sequence}`. For example, `66001165_7623`, `66001165_7624`, and `66001165_7629`. Never parse or arithmetically manipulate them, and never store them in a numeric column.

> Notes:
- `GET`, `PUT`, `PATCH`, and `DELETE` all accept both forms.
- `POST` takes no identifier — identity comes from the body. `POST /campaign/brief` accepts `campaignId` only.



## 2. Base URLs and environments

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

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

So the campaign resource is:

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

Replace `{env}` with your assigned environment identifier. The V3 specification enumerates: `prod`, `prod0`, `prod2`, and so on. See [APIs | Sprinklr Developer Portal](https://dev.sprinklr.com/apis) for the current environment list.

## 3. Authentication and common headers

All Campaign API calls are authenticated with OAuth 2.0 (`bearerAuth`, HTTP bearer, JWT) combined with an API key header (`apiKeyAuth`, header name `Key`). See [Developer Tools in Sprinklr](https://www.sprinklr.com/help/articles/developer-tools/developer-tools-in-sprinklr/692e8b39f0afa271d18a5929) and the Authorize section of the developer portal for API key and secret generation.

| Header | Value | Purpose | Required on |
|  --- | --- | --- | --- |
| `Authorization` | `Bearer {{accessToken}}` | Authenticates the user with the server. | All requests |
| `Key` | `{{apiKey}}` | Authenticates the application with the server. | All requests |
| `Content-Type` | `application/json` | Declares the request body media type. | `POST`, `PUT`, `PATCH` |
| `Content-Type` | `multipart/form-data` | Declares the brief upload media type. | `POST /campaign/brief` |
| `Accept` | `application/json` | Declares the acceptable response type. | All requests |


**Permissions**: Users in your Sprinklr environment need the appropriate roles and permissions to create and view campaigns. Missing permissions return a `403 Forbidden` response. For more information, see [Campaign Permissions](https://www.sprinklr.com/help/articles/campaigns-and-subcampaigns-overview/campaign-permissions/63ff052632d12b63c5f55c71)

## 4. Write operations

### 4.1 Create a campaign or sub-campaign

**`POST /api/v3/campaign`**

Use this endpoint to create an internal campaign, external campaign, or sub-campaign.

#### Request body

| Parameter | Sub-parameter | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `name` |  | **Required** | String | Name of the campaign. |
| `status` |  | **Required** | String | Status of the campaign. |
| `description` |  | Optional | String | Description of the campaign. |
| `parentCampaignId` |  | Optional | String | Parent campaign ID for a sub-campaign. |
| `startDate` |  | Optional | Integer (int64) | Start date of the campaign, in epoch milliseconds. |
| `endDate` |  | Optional | Integer (int64) | End date of the campaign, in epoch milliseconds. |
| `tags` |  | Optional | Array[String] | Tags associated with the campaign. |
| `archived` |  | Optional | Boolean | Specifies whether the campaign is archived.  **Default**: `false` |
| `partnerCustomFields` |  | Optional | Map<String, List> | Partner-level custom fields, keyed by custom field ID. |
| `clientCustomFields` |  | Optional | Map<String, List> | Client-level custom fields, keyed by custom field ID. |
| `externalSource` |  | **Required to create an external campaign.** | String | Name of the external system. Provide this parameter with `externalSourceId` to create an external campaign. |
| `externalSourceId` |  | **Required to create an external campaign.** | String | Identifier of the campaign in the external system. Provide this parameter with `externalSource` to create an external campaign. |
| `visibility` |  | Optional | Object | Campaign visibility settings. |
|  | `globallyVisible` | Optional | Boolean | Specifies whether the campaign is globally visible. |
|  | `visibilityConfig` | Optional | Array[Object] | Sharing configuration entries. |
|  | `visibilityConfig[].type` | **Required** within the entry | String | Share target type.  For example `CLIENT`, `CLIENT_GROUP`, `USER`, `USER_GROUP`, `GLOBAL` |
|  | `visibilityConfig[].ids` | Optional | Array[String] | Identifiers associated with the visibility target type. |
| `externalCampaignDetails` |  | Optional | Array[Object] | External connector details for a campaign. |
|  | `connector` | Optional | Enum (`ChannelType`) | Channel or connector associated with the external identifiers. |
|  | `externalIds` | Optional | Array[String] | External identifiers associated with the connector. |


#### Request — Internal campaign

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/campaign' \
--header 'Authorization: Bearer {{token}}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
    "name": "SampleCampaign",
    "description": "Sample description",
    "startDate": 1560159652000,
    "endDate": 2209563421000,
    "tags": ["Int Tag","Campaign Tag"],
    "status": "APPROVED",
    "partnerCustomFields": {"5c35cbf2e4b0e1b05edd1b01": ["1"]},
    "clientCustomFields": {"5ad59e6fe4b024f15e8384ef": ["ABC"]},
    "visibility": {
        "globallyVisible": "false",
        "visibilityConfig": [{type": "CLIENT","ids": ["2"]},
            {"type": "CLIENT_GROUP","ids": ["60740690d4644c337059b7a1"]},
            {"type": "USER","ids": ["600000003"]},
            {"type": "USER_GROUP","ids": ["61923d665f0ec3151749d108"]}
        ]
    }
}'
```

#### Request — External campaign

To create an external campaign, include both `externalSource` and `externalSourceId` in the request body.

```bash
curl --location 'https://qa6-api2-v3.sprinklr.com/api/v3/campaign' \
--header 'Authorization: Bearer {{token}}' \
--header 'accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
    "name": "SampleExtCampaign",
    "description": "Testing",
    "externalSource": "DCM",
    "externalSourceId": "69b11ab96fd8b8456bc1e12d",
    "tags": ["Int Tag","Campaign Tag"],
    "status": "APPROVED",
    "partnerCustomFields": {"5c35cbf2e4b0e1b05edd1b01": ["1"]},
    "clientCustomFields": {"5ad59e6fe4b024f15e8384ef": ["ABC"]},
    "visibility": {
        "globallyVisible": "false",
        "visibilityConfig": [{type": "CLIENT","ids": ["2"]},
            {"type": "CLIENT_GROUP","ids": ["60740690d4644c337059b7a1"]},
            {"type": "USER","ids": ["600000003"]},
            {"type": "USER_GROUP","ids": ["61923d665f0ec3151749d108"]}
        ]
    }
}'
```

#### Request — Sub-campaign

To create a sub-campaign, include `parentCampaignId` in the request body.

```bash
curl --location 'https://qa6-api2-v3.sprinklr.com/api/v3/campaign' \
--header 'Authorization: Bearer {{token}}' \
--header 'accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
    "name": "SampleSubCampaign",
    "description": "Testing06",
    "parentCampaignId": "66000002_8163",
    "startDate": 1560159652000,
    "endDate": 2209563421000,
    "tags": ["Int Tag","Campaign Tag"],
    "status": "APPROVED",
    "partnerCustomFields": {"5c35cbf2e4b0e1b05edd1b01": ["1"]},
    "clientCustomFields": {"5ad59e6fe4b024f15e8384ef": ["ABC"]},
    "visibility": {
        "globallyVisible": "false",
        "visibilityConfig": [{type": "CLIENT","ids": ["2"]},
            {"type": "CLIENT_GROUP","ids": ["60740690d4644c337059b7a1"]},
            {"type": "USER","ids": ["600000003"]},
            {"type": "USER_GROUP","ids": ["61923d665f0ec3151749d108"]}
        ]
    }
}'
```

#### Response

A successful request returns **201 Created** and campaign details in the data object. The response also returns an errors array.

The response structure is the same for Internal campaigns, sub-campaigns, and external campaigns. Additional fields are returned based on the campaign type:

- Sub-campaigns include `parentCampaignId` and `displayId`.
- External campaigns include `externalSource` and `externalSourceId`.


For a complete list of response fields, see [§6.2](#62-the-campaign-response-object).

```json
{
"data": {
    "id": "66001165_8318",
    "name": "SampleCampaign",
    "description": "Sample description",
    "createdTime": 1788218719757,
    "modifiedTime": 1788218719014,
    "startDate": 1560159652000,
    "endDate": 2209563421000,
    "tags": ["Int Tag", "Campaign Tag"],
    "owner": 66077412,
    "partnerCustomProperties": {
        "5c35cbf2e4b0e1b05edd1b01": ["1"]
    },
    "clientCustomProperties": {
        "5ad59e6fe4b024f15e8384ef": ["ABC"]
    },
    "status": "APPROVED",
    "archived": false,
    "deleted": false
},
"errors": []
}
```

> **Note the field-name asymmetry.** You *write* `partnerCustomFields` / `clientCustomFields` and you *read back* `partnerCustomProperties` / `clientCustomProperties`. This asymmetry exists in V2 and is carried into the V3 `Campaign` and `CampaignRequest` schemas. Do not round-trip a response object straight back into a `PUT` body without remapping these two keys.


#### Duplicate external campaigns

Creating a campaign with an `externalSourceId` that already exists returns an error in the `errors` array.

```json
{
  "errors": [
    {
      "id": "5df8fcdec6e1aa000166cc8c",
      "code": 400,
      "message": "Campaign already exists with external details : CAMPAIGN_PORTAL - dgsg3456y45tygvcd3456tyhgfde3456yhfgfd_02"
    }
  ]
}
```

### 4.2 Update a campaign — Full update

- **`PUT /api/v3/campaign?campaignId={id}`**
- **`PUT /api/v3/campaign?externalSource={source}&externalSourceId={id}`**


Use this endpoint to update an existing campaign. Provide the complete campaign definition in the request body. The API replaces the existing campaign with the payload you provide.

#### Query parameters

| Parameter | Type | Required | Description | Example |
|  --- | --- | --- | --- | --- |
| `campaignId` | String | Optional | Identifier of the campaign to update. | `1_7714` |
| `externalSource` | String | Optional | Name of the external system associated with the campaign. Use with `externalSourceId`. | `CAMPAIGN_PORTAL` |
| `externalSourceId` | String | Optional | Identifier of the campaign in the external system. Use with `externalSource`. | `dgsg3456y45tygvcd3456tyhgfde3456yhfgfd_02` |


Supply **either** `campaignId` **or** the `externalSource` and `externalSourceId` pair.

#### Request body

**Update campaign by Campaign ID**

```bash
curl --location --request PUT 'https://api3.sprinklr.com/{env}/api/v3/campaign?campaignId=66001165\_8318' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "name": "SampleCampaign",
    "description": "Updated campaign description",
    "startDate": 1560159652000,
    "endDate": 2209563421000,
    "tags": ["Int Tag","Campaign Tag"],
    "status": "APPROVED"
}'
```

**Update campaign by External Source**

```bash
curl --location --request PUT 'https://api3.sprinklr.com/{env}/api/v3/campaign?externalSource=DCM&externalSourceId=69b11ab96fd8b8456bc1e12d' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "name": "SampleExtCampaign",
    "description": "Updated external campaign",
    "status": "APPROVED"
}'
```

#### ⚠️ `PUT` replaces — it does not merge

Any field you omit from a `PUT` body — `tags`, `description`, `partnerCustomFields`, `visibility` — is at risk of being cleared. Before issuing a `PUT`:

1. `GET` the current campaign.
2. Remap `partnerCustomProperties` → `partnerCustomFields` and `clientCustomProperties` → `clientCustomFields`.
3. Merge your changes into the result.
4. `PUT` the merged document back.


If you need to update only a few fields, use `PATCH` ([§4.3](#43-update-a-campaign--partial-update)) instead.

**Response:** A successful request returns **200 OK** and the updated campaign object. For information about the response parameters, see [§6.2](#62-the-campaign-response-object).

### 4.3 Update a campaign — Partial update

- **`PATCH /api/v3/campaign?campaignId={id}`**
- **`PATCH /api/v3/campaign?externalSource={source}&externalSourceId={id}`**


Use this API to update specific fields in an existing campaign. Unlike [Update a campaign](#_Update_Campaign), you do not need to provide the complete campaign definition in the request body. Include only the fields that you want to update.

#### Query parameters

| **Parameter** | **Type** | **Description** |
|  --- | --- | --- |
| `campaignId` | String | Identifier of the campaign to update. |
| `externalSource` | String | Name of the external system associated with the campaign. Use with `externalSourceId`. |
| `externalSourceId` | String | Identifier of the campaign in the external system. Use with `externalSource`. |


#### Request body

Campaign partial update (PATCH). Send only fields to change. **No field is required.**

| Parameter | Type | Description |
|  --- | --- | --- |
| `name` | String | Name of the campaign. |
| `description` | String | Description of the campaign. |
| `startDate` | Integer (int64) | Start date, epoch milliseconds |
| `endDate` | Integer (int64) | End date, epoch milliseconds |
| `tags` | Array[String] | Tags on the campaign |
| `partnerCustomFields` | Map<String, List> | Partner-level custom fields on the campaign |
| `clientCustomFields` | Map<String, List> | Client-level custom fields on the campaign |
| `externalSource` | String | External system name |
| `externalSourceId` | String | External system ID |
| `visibility` | Object | Visibility of the campaign (`globallyVisible`, `visibilityConfig[]`) |
| `status` | String (`CampaignStatus`) | Status of the campaign |
| `archived` | Boolean | `true` if the campaign is archived |
| `externalCampaignDetails` | Array[Object] | External connector details (`connector`, `externalIds[]`) |


> **Note**: `PATCH` **cannot** change `parentCampaignId`. Re-parenting a sub-campaign requires a `PUT`.


```bash
curl --location --request PATCH 'https://api3.sprinklr.com/{env}/api/v3/campaign?campaignId=66001165_8318' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "name": "Updated Campaign Name",
    "status": "APPROVED"
}
```

**Response:** A successful request returns **200 OK** and the updated campaign object. For information about the response parameters, see [§6.2](#62-the-campaign-response-object).

### 4.4 Delete a campaign

- **`DELETE /api/v3/campaign?campaignId={id}`**
- **`DELETE /api/v3/campaign?externalSource={source}&externalSourceId={id}`**


**Delete Campaign by Campaign ID**

```bash
curl --location --request DELETE 'https://api3.sprinklr.com/{env}/api/v3/campaign?campaignId=66001165\_8318' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

**Delete Campaign by External Source**

```bash
curl --location --request DELETE 'https://api3.sprinklr.com/{env}/api/v3/campaign?externalSource=DCM&externalSourceId=69b11ab96fd8b8456bc1e12d' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

**Response:** A successful request returns **204 No Content**, indicating that the campaign was deleted successfully.

> **Note**: Use the value of `archived` as **true** when you want to archive a campaign without deleting it. Use the Delete Campaign API when you want to remove a campaign.


### 4.5 Add a campaign brief

**`POST /api/v3/campaign/brief?campaignId={id}`**

Attaches a campaign brief, uploaded as an HTML file, to an existing campaign.

Campaign Briefs are attached to campaigns to give the agency or internal creative team enough information to strategize the concept for the campaign. You can add multiple briefs to campaigns and sub-campaigns.

#### Query parameters

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `campaignId` | String | Optional in the spec; required in practice | Identifier of the campaign to which the brief is attached. |


#### Form-data parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `briefName` | **Required** | String | Name of the campaign brief. |
| `htmlFile` | **Required** | `.html` file | HTML file that contains the campaign brief content.The HTML can include all tags except <link>. |


```bash
curl --location --request POST 'https://api3.sprinklr.com/{env}/api/v3/campaign/brief?campaignId=66001165\_8318' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json' \
--form 'briefName="Spring Product Launch"' \
--form 'htmlFile=@"/path/to/brief.html"'
```

**Response:** A successful request returns **200 OK** along with a confirmation message indicating that the brief was added successfully.

```json
{
"data": "Successfully added the requested brief: \"Spring Product Launch\" to the campaign \"66001165_8318\".",
"errors": []
}
```

### 4.6 Method comparison — when to use which

|  | `POST` | `PUT` | `PATCH` | `DELETE` |
|  --- | --- | --- | --- | --- |
| Purpose | Create a campaign, sub-campaign, or external campaign | Replace the full campaign document | Change selected fields | Remove the campaign |
| Addressing | None — identity comes from the body | `?campaignId=` or `?externalSource=&externalSourceId=` | Same as `PUT` | Same as `PUT` |
| Body schema | `CampaignRequest` | `CampaignRequest` | `CampaignPatchRequestV3DTO` | None |
| Required body fields | `name`, `status` | `name`, `status` | None | — |
| Unspecified fields | N/A | **Replaced/cleared** | **Preserved** | — |
| Can set `parentCampaignId` | Yes | Yes | **No** | — |
| Response body | `Campaign` object | `string` | `string` | `string` |
| Safe for incremental sync | — | No, without read-modify-write | Yes | — |


## 5. Read operations

### 5.1 `GET /api/v3/campaign` — List or fetch campaigns

This is a **multi-mode** endpoint that replaces three separate V2 reads:

| V2 endpoint | V3 equivalent |
|  --- | --- |
| [Fetch All Campaign](https://dev.sprinklr.com/fetch-all-campaign) — `GET /api/v2/campaign` | `GET /api/v3/campaign` |
| [Read Campaign](https://dev.sprinklr.com/read-campaign) — `GET /api/v2/campaign/{campaignId}` | `GET /api/v3/campaign?campaignId={id}` |
| [External Read Campaign](https://dev.sprinklr.com/external-read-campaign) — `GET /api/v2/campaign/{externalSource}/{externalId}` | `GET /api/v3/campaign?externalSource={source}&externalSourceId={id}` |


#### Query parameters

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `campaignId` | String | Optional | Identifier of the campaign to retrieve. |
| `externalSource` | String | Optional | Name of the external system associated with the campaign. |
| `externalSourceId` | String | Optional | Identifier of the campaign in the external system. |
| `pageNumber` | String | Optional | Page number (**0-based**); use with `pageSize` for the partner-wide list |
| `pageSize` | String | Optional | Page size (**default 20, maximum 100**) |
| `sinceTime` | String | Optional | Alternative pagination cursor; see API v3 docs |


```bash
# Mode 1 — all campaigns, first page
curl --location 'https://api3.sprinklr.com/{env}/api/v3/campaign?pageNumber=0&pageSize=100' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'

# Mode 2 — by campaign ID
curl --location 'https://api3.sprinklr.com/{env}/api/v3/campaign?campaignId=1_7714' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'

# Mode 3 — by external key
curl --location 'https://api3.sprinklr.com/{env}/api/v3/campaign?externalSource=CAMPAIGN_PORTAL&externalSourceId=dgsg3456y45tygvcd3456tyhgfde3456yhfgfd_02' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

**Response:** A successful request returns **200 OK** and the requested campaign data. Depending on the retrieval method, the response contains either a list of campaigns or a single campaign.

```json
{
    "data": [
        {
            "id": "66001165_8318",
            "name": "SampleCampaign",
            "description": "Testing06",
            "createdTime": 1788218719757,
            "modifiedTime": 1788218719014,
            "startDate": 1560159652000,
            "endDate": 2209563421000,
            "tags": ["Int Tag", "Campaign Tag"],
            "owner": 66077412,
            "partnerCustomProperties": { "5c35cbf2e4b0e1b05edd1b01": ["1"]},
            "clientCustomProperties": { "5ad59e6fe4b024f15e8384ef": ["ABC"]},
            "status": "APPROVED",
            "visibility": {
                "globallyVisible": false
                },
            "archived": false,
            "deleted": false
        }
    ],
    "errors": []
}
```

> **Do not mix addressing modes.** Send `campaignId`, or the `externalSource` and `externalSourceId` pair, or the pagination parameters and not a combination. Mixed parameter sets are not defined in any source and should be treated as unsupported.


## 6. Response format and status codes

### 6.1 Envelopes

V3 Campaign responses are **not uniformly enveloped**, and this is the most important behavioural detail to plan for:

| Operation | Declared response |
|  --- | --- |
| `GET /campaign` | `array` of `Campaign` — bare array, no envelope |
| `POST /campaign` | `Campaign` object — bare object, no envelope |
| `PUT`, `PATCH`, `DELETE /campaign` | `string` |
| `POST /campaign/brief` | `APIResponse` envelope: `{ "data": {}, "errors": [], "metadata": {} }` |
| All `4xx` responses | `ErrorResponse` envelope: `{ "data": null, "errors": [], "metadata": {} }` |


`Error` objects inside `errors[]` carry three required fields:

| Field | Type | Description |
|  --- | --- | --- |
| `id` | String | 24-character hex ObjectId |
| `code` | Integer | Numeric error code |
| `message` | String | Dotted error key, for example `account.not.found` |


### 6.2 The `Campaign` response object

| **Parameter** | **Sub Parameter** | **Type** | **Description** |
|  --- | --- | --- | --- |
| `id` |  | String | Unique identifier of the campaign. |
| `name` |  | String | Name of the campaign. |
| `displayId` |  | Long | Display identifier for a sub-campaign. |
| `parentCampaignId` |  | String | Identifier of the parent campaign. Returned for sub-campaigns. |
| `description` |  | String | Description of the campaign. |
| `createdTime` |  | Long | Time when Sprinklr creates the campaign, in epoch milliseconds. |
| `modifiedTime` |  | Long | Time when Sprinklr last modifies the campaign, in epoch milliseconds. |
| `startDate` |  | Long | Start date of the campaign, in epoch milliseconds. |
| `endDate` |  | Long | End date of the campaign, in epoch milliseconds. |
| `tags` |  | Array[String] | Tags associated with the campaign. |
| `owner` |  | Long | Owner of the campaign. |
| `partnerCustomProperties` |  | Map[String, List[String]] | Partner-level custom properties associated with the campaign. |
| `clientCustomProperties` |  | Map[String, List[String]] | Client-level custom properties associated with the campaign. |
| `status` |  | String | Status of the campaign. |
| `visibility` |  | Object | Visibility of the campaign. |
|  | `globallyVisible` | Boolean | Specifies whether the campaign is globally visible. |
|  | `visibilityConfig` | Array[Object] | Visibility configuration entries. |
| `externalSource` |  | String | Name of the external system associated with the campaign. Returned for external campaigns. |
| `externalSourceId` |  | String | Identifier of the campaign in the external system. Returned for external campaigns. |
| `externalCampaignDetails` |  | Array[Object] | External connector details associated with the campaign. |
|  | `connector` | String | Channel or connector associated with the external identifiers. |
|  | `externalIds` | Array[String] | External identifiers associated with the connector. |
| `archived` |  | Boolean | Specifies whether the campaign is archived. |
| `deleted` |  | Boolean | Specifies whether the campaign is deleted. |


> **Note**:`status` is the **only required field** on the `Campaign` schema. Every other field may be absent from a response — code defensively.


### 6.3 Response codes

| HTTP code | Scenario | Description |
|  --- | --- | --- |
| `200 Success` | Success | Declared success response for every Campaign V3 operation, including create. |
| `400 Bad request` | Invalid parameters | Missing required fields, invalid parameter combinations, or a duplicate external campaign. |
| `401 Missing or invalid authentication` | Authentication failed | Invalid or missing `Authorization` token or `Key` header. |
| `403 Insufficient permissions` | Permission gap | The user lacks campaign create/view permissions. |
| `404 Resource not found` | Not found | No campaign matches the supplied identifier. |


## 7. V2 → V3 migration

### 7.1 Endpoint mapping

| V2 | V3 |
|  --- | --- |
| `POST /api/v2/campaign` (Create Campaign) | `POST /api/v3/campaign` |
| `POST /api/v2/campaign` (Create External Campaign) | `POST /api/v3/campaign` with `externalSource` and `externalSourceId` in the body |
| `GET /api/v2/campaign` (Fetch All Campaign) | `GET /api/v3/campaign` |
| `GET /api/v2/campaign/{campaignId}` (Read Campaign) | `GET /api/v3/campaign?campaignId={id}` |
| `GET /api/v2/campaign/{externalSource}/{externalId}` (External Read Campaign) | `GET /api/v3/campaign?externalSource={source}&externalSourceId={id}` |
| `PUT /api/v2/campaign/{campaignId}` (Update Campaign) | `PUT /api/v3/campaign?campaignId={id}` |
| Update Campaign by external source and source ID | `PUT /api/v3/campaign?externalSource={source}&externalSourceId={id}` |
| Delete Campaign | `DELETE /api/v3/campaign?campaignId={id}` |
| External Delete Campaign | `DELETE /api/v3/campaign?externalSource={source}&externalSourceId={id}` |
| *No V2 equivalent* | `PATCH /api/v3/campaign` — **new in V3** |
| `POST /api/v2/campaign/{campaignId}/brief` (Create Campaign Brief) | `POST /api/v3/campaign/brief?campaignId={id}` |


### 7.2 What changes structurally

| Aspect | API V2 | API V3 | Impact |
|  --- | --- | --- | --- |
| Base path | `/api/v2/campaign` | `/api/v3/campaign` | Update every URL. |
| Campaign addressing | Path segment — `/campaign/{campaignId}` | Query parameter — `/campaign?campaignId={id}` | URL construction changes; no more path-encoding of IDs. |
| External addressing | Separate path — `/campaign/{externalSource}/{externalId}` | Same base path, `externalSource` and `externalSourceId` query parameters | Two code paths collapse into one. |
| Brief path | `/campaign/{campaignId}/brief` | `/campaign/brief?campaignId={id}` | Sub-resource moves from path to query. |
| Create | Two documented flows on one `POST` | One `POST`; body content selects the flow | Payload logic unchanged, docs consolidated. |
| Partial update | Not available | `PATCH` with `CampaignPatchRequestV3DTO` | Removes the read-modify-write cycle. |
| List pagination | None | `pageNumber`, `pageSize` (default 20, max 100), `sinceTime` | Large partners must use pagination. |
| Response envelope | `{ "data": …, "errors": [] }` | Bare array/object on `GET`/`POST`; `string` on `PUT`/`PATCH`/`DELETE`; `APIResponse` on brief |  |


### 7.3 Field mapping

Field names carry over unchanged between V2 and V3 — `name`, `description`, `parentCampaignId`, `startDate`, `endDate`, `tags`, `status`, `archived`, `partnerCustomFields`, `clientCustomFields`, `visibility`, `externalSource`, `externalSourceId`.

Two additions to be aware of on V3:

| Field | Where | Notes |
|  --- | --- | --- |
| `externalCampaignDetails[]` | `CampaignRequest`, `CampaignPatchRequestV3DTO`, `Campaign` | External connector details: `connector` (channel type enum) plus `externalIds[]` |
| `displayId` | `Campaign` (read-only) | Typed `integer (int64)` in V3; V2's Fetch All Campaign example showed it as a string |


### 7.4 Migration steps

1. **Update the base URL** to `https://api3.sprinklr.com/{env}/api/v3`.
2. **Move identifiers from the path to the query string.** `/campaign/{id}` → `/campaign?campaignId={id}`; `/campaign/{externalSource}/{externalId}` → `/campaign?externalSource=&externalSourceId=`; `/campaign/{id}/brief` → `/campaign/brief?campaignId={id}`.
3. **Merge your two create paths.** Internal and external creates now hit the same `POST /api/v3/campaign`.
4. **Split your write path by method.** Route creates to `POST`, authoritative full syncs to `PUT`, and incremental changes to `PATCH`.
5. **Introduce pagination on the list call.** V2's Fetch All returned everything; V3 pages at a default of 20 and a maximum of 100.
6. **Adjust response parsing.** Handle a bare array from `GET`, a bare object from `POST`, and a plain `string` from `PUT`/`PATCH`/`DELETE`.
7. **Keep the custom-field remap.** Requests use `partnerCustomFields` / `clientCustomFields`; responses return `partnerCustomProperties` / `clientCustomProperties`.
8. **Re-test duplicate-external-ID handling.** The `400` "Campaign already exists with external details" error remains the signal to switch from create to update.


## 8. Field reference — supporting objects

### 8.1 `visibility`

| Field | Type | Description |
|  --- | --- | --- |
| `globallyVisible` | Boolean | `true` if the asset is globally visible. |
| `visibilityConfig` | Array[`AssetShareConfig`] | Config to share the asset. |
| `visibilityConfig[].type` | String — **required** | Type, for example `USER`, `GLOBAL`, `CLIENT`. V2 documented `CLIENT`, `CLIENT_GROUP`, `USER`, `USER_GROUP`. |
| `visibilityConfig[].ids` | Array[String] | IDs based on `type`. |


All `visibilityConfig` entries are optional and can be used as required by your use case.

### 8.2 `externalCampaignDetails`

| Field | Type | Description |
|  --- | --- | --- |
| `connector` | Enum (`ChannelType`) | The channel/connector the external IDs belong to. |
| `externalIds` | Array[String] | External connector IDs for the campaign. |


### 8.3 Custom fields

Both `partnerCustomFields` and `clientCustomFields` are `Map<String, List<String>>` — keyed by the custom field ID, always taking a **list** of string values even for single-valued fields:

```json
{
  "partnerCustomFields": { "5c35cbf2e4b0e1b05edd1b01": ["1"] },
  "clientCustomFields":  { "5ad59e6fe4b024f15e8384ef": ["ABC"] }
}
```

Use the Bootstrap API for configuration information — it is how you resolve custom field IDs, as documented on the V2 Create Campaign reference.

## 9. Use cases

### 9.1 Create a campaign and immediately attach its brief

**Scenario:** a marketing tool creates a campaign and uploads the creative brief in one workflow.

1. `POST /api/v3/campaign` with `name` and `status`. Capture `id` from the response.
2. `POST /api/v3/campaign/brief?campaignId={id}` as `multipart/form-data` with `briefName` and `htmlFile`.


Strip `link` tags from the HTML before upload — they are the one unsupported tag.

### 9.2 Keep an external campaign portal in sync

**Scenario:** a third-party campaign portal owns campaign records and pushes changes to Sprinklr.

Do not store the Sprinklr `id`; address everything by your own key:

```
POST   /api/v3/campaign                                          (body carries externalSource and externalSourceId)
GET    /api/v3/campaign?externalSource=…&externalSourceId=…
PATCH  /api/v3/campaign?externalSource=…&externalSourceId=…
DELETE /api/v3/campaign?externalSource=…&externalSourceId=…
```

This is the single largest ergonomic win in V3 — in V2 the external flows lived on a different path shape from the internal ones.

### 9.3 Idempotent create-or-update from an external system

**Scenario:** your sync job does not know whether a campaign already exists in Sprinklr.

1. `GET /api/v3/campaign?externalSource=…&externalSourceId=…`
2. On a result, `PATCH` the changed fields.
3. On `404`, `POST` the campaign.


Alternatively, `POST` first and treat the `400` "Campaign already exists with external details" error as the signal to fall back to `PATCH`.

### 9.4 Flip a campaign's status at launch

**Scenario:** a scheduler approves campaigns at their start date.

```bash
curl --location --request PATCH 'https://api3.sprinklr.com/{env}/api/v3/campaign?campaignId=1_7714' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--data '{ "status": "APPROVED" }'
```

Using `PUT` requires you to reconstruc `name`, `tags`, custom fields, and visibility just to change one string.

### 9.5 Build a sub-campaign hierarchy

**Scenario:** one global campaign with per-region children.

1. `POST /api/v3/campaign` for the parent; capture `id`.
2. `POST /api/v3/campaign` once per region with `parentCampaignId` set to that `id`.
3. Read the children back with `GET /api/v3/campaign?pageNumber=0&pageSize=100` and group by `parentCampaignId`.


To move a sub-campaign to another parent, use `PUT`.

### 9.6 Page through every campaign in a large partner

```javascript
let pageNumber = 0;
const pageSize = 100;                 // maximum permitted
let all = [];

while (true) {
  const page = await get(
    `/api/v3/campaign?pageNumber=${pageNumber}&pageSize=${pageSize}`
  );

  if (!Array.isArray(page) || page.length === 0) break;

  all = all.concat(page);

  if (page.length < pageSize) break;  // short page ⇒ last page
  pageNumber += 1;
}
```

The `Campaign` array carries no `hasMore` flag, so terminate on a short or empty page.

### 9.7 Archive rather than delete at end of quarter

**Scenario:** campaigns must stop appearing in publishing pickers but remain available for reporting.

```json
{ "archived": true }
```

Send this payload in a `PATCH` request. Reserve `DELETE` for records created in error.

## 10. Caveats and best practices

**Method selection**

- Default to `PATCH`. Reach for `PUT` only when you hold the complete authoritative document.
- `PUT` replaces. Any field absent from the body is at risk.
- `PUT` requires `name` and `status` on every call; `PATCH` requires nothing.
- `PATCH` cannot set `parentCampaignId`.


**Identifiers**

- Provide either `campaignId` or the `externalSource` and `externalSourceId` pair.
- `externalSourceId` must be unique. A duplicate returns `400` with the message `Campaign already exists with external details : {source} - {id}`.
- URL-encode `externalSourceId` values containing reserved characters — external IDs are frequently long opaque strings.
- Campaign IDs are opaque strings shaped `{partnerId}_{sequence}` (for example `66001165_7623`). Store them as strings; do not parse, increment, or coerce them to numbers.
- Identifiers travel in the **query string**, not the request body — this is the form every QA request uses.


**Field naming**

- Write `partnerCustomFields` / `clientCustomFields`; read `partnerCustomProperties` / `clientCustomProperties`. Do not feed a `GET` response directly back into a `PUT`.
- Custom field values are always **lists of strings**, even when single-valued.


**Dates**

- `startDate`, `endDate`, `createdTime`, and `modifiedTime` are all epoch **milliseconds** as `int64`. Do not send seconds.


**Pagination**

- Pages are **0-based**; the first page is `0`.
- Default `pageSize` is **20**, maximum is **100**.
- `pageNumber` and `pageSize` are typed as `string` in the specification — send them as query strings, not JSON numbers.
- No `hasMore` flag is returned; stop on a short or empty page.


**Briefs**

- `briefName` and `htmlFile` are both required, sent as `multipart/form-data`.
- All HTML tags are supported **except the `link` tag**.
- Create the campaign before you attach a brief.


**Response handling**

- `status` is the only required field on `Campaign`. Treat every other field as optional.
- Success is `200` on all five operations, including create.
- Always inspect `errors[]` where an envelope is returned; each `Error` carries `id`, `code`, and `message`.
- Parse defensively for the envelope. Because the spec and the ticket's acceptance criteria disagree and QA captured no responses, branch on the presence of a `data` key rather than assuming either shape.


**Values**

- `status`: `APPROVED` is confirmed accepted. The schema imposes no enum, so validate against your own allow-list rather than trusting the API to reject a typo.
- `visibility.globallyVisible`: send a JSON boolean (`false`), not the string `"false"`, even though QA sent the string form.


**Permissions**

- Set up the appropriate roles and grant permissions for users who create and view campaigns. A `403` almost always means a permission gap, not a malformed request.