# Knowledge Base Articles API V3 — Developer Guide

- **Applies to:** Sprinklr Knowledge Base API V3 (`/api/v3/knowledgebase`)
- **V2 API reference:** [Knowledge Base | Sprinklr Developer Portal](https://dev.sprinklr.com/knowledge-base)


## 1. Overview

Sprinklr's unified **Knowledge Base** stores the articles that power agent-assist, community, live chat, and public self-service surfaces. Articles created through this API can be surfaced to customer care agents by the AI-powered **Smart Comprehend** feature in the Agent Console, and can be published to end customers.
Knowledge Base V3 collapses the verb-in-path design of V2 into **three resources**, differentiated by HTTP method:

| Operation | Method | Path |
|  --- | --- | --- |
| Create article | `POST` | `/api/v3/knowledgebase/article` |
| Read article by content ID | `GET` | `/api/v3/knowledgebase/article?contentIds=` |
| Read article by migration details | `GET` | `/api/v3/knowledgebase/article?migratedId=&migratedFrom=` |
| Update article | `PUT` | `/api/v3/knowledgebase/article?contentId=` |
| Delete article | `DELETE` | `/api/v3/knowledgebase/article?contentId=&isLngVariant=` |
| Search articles | `POST` | `/api/v3/knowledgebase/article/search` |
| Find linked assets | `POST` | `/api/v3/knowledgebase/linked-asset?linkedAssetType=` |
| Get version history by version ID | `GET` | `/api/v3/knowledgebase/version-history?versionHistoryId=` |
| Search version history | `POST` | `/api/v3/knowledgebase/version-history/search` |


### 1.1 The Knowledge Base content data model

Every article is returned as one **Knowledge Base content object**, organized in five layers. Understanding these layers is the fastest way to understand every endpoint in this guide.

| Layer | Object | What it holds | Scope |
|  --- | --- | --- | --- |
| Body | `content` | `contentType`, `contentSubType`, `title`, `markUpText` | One per article |
| Placement | `folderMetadata`, `tags`, `locale`, `countryCodes`, `publicContent`, `externalPermalink` | Where the article lives and who can see it | One per article |
| Variants | `lngVariants`, `inactiveLngVariants`, `countryVariants`, `publishedCountryVariants`, `countryBaseContent`, `languageSettingId` | Language and country translations, each a separate content ID linked to the parent | One entry per locale / country |
| Engagement | `stats`, `favourite`, `translationProcess` | 16 usage and feedback counters, plus translation workflow state | One per article |
| Governance | `status`, `version`, `contributors`, `grants`, `ownerUserId`, `clientId`, `createdTime`, `modifiedTime`, `lastModifiedUserId`, `canEdit`, `deleted` | Workflow state, ownership, and audit fields | One per article |


A sixth structure, `linkedAssets[]`, records the Content Variables, Content Blocks, and hyperlinks embedded in `markUpText`.

**Language variants are first-class articles.** When you pass `lngVariants: ["fr_FR", "de_DE"]` on create, Sprinklr generates *empty placeholder articles* for those locales, each with its own content ID, and links them to the parent. The create response returns the mapping:

```json
"lngVariants": {
  "de_DE": "6a8c34439549b75aa81ac014",
  "fr_FR": "6a8c34439549b75aa81ac013"
}
```

You then populate each variant by calling the update endpoint with that variant's content ID.

### 1.2 Addressing an article

An article is addressed in four ways, and the choice determines which endpoint you call:

| Addressing mode | Parameter(s) | Endpoint | When to use |
|  --- | --- | --- | --- |
| By Sprinklr content ID | `contentIds` (read) / `contentId` (update, delete) | `GET`, `PUT`, `DELETE /knowledgebase/article` | You already hold the Sprinklr ID |
| By migration identity | `migratedId` + `migratedFrom` | `GET /knowledgebase/article` | Reconciling content imported from a legacy KB |
| By filter expression | `filters[]` in the body | `POST /knowledgebase/article/search` | Discovery, reporting, and bulk export |
| By version history ID | `versionHistoryId` | `GET /knowledgebase/version-history` | Retrieving one historical revision |


**Steps to extract a folder ID from the Sprinklr UI** (needed for `folderMetadata.folderId` on create):

1. Search **Knowledge Base** from the universal search bar on the Sprinklr homepage.
2. Click the Knowledge Base category/folder you need the ID for.
3. The category/folder ID is the last segment of the browser URL.
4. For example, if the URL is `https://sprinklr.com/care/knowledge-base/categories/65aa485c36fd937eb0a1d815`, the folder ID is `65aa485c36fd937eb0a1d815`.


## 2. Base URLs and environments

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

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

So the Knowledge Base article resource is:

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

Replace `{env}` with your assigned environment identifier (`prod0`, `prod2`, `prod11`, and so on — see [APIs | Sprinklr Developer Portal](https://dev.sprinklr.com/apis) for the environment list).

## 3. Authentication and common headers

All Knowledge Base API calls are authenticated with OAuth 2.0. For generating the authorization token, see the [Authorize](https://dev.sprinklr.com/authorize) section on the developer portal. For generating the API key, see the [Getting Started](https://dev.sprinklr.com/api-key-and-secret-generation) guide.

| Header | Value | Purpose | Required on |
|  --- | --- | --- | --- |
| `Authorization` | `******` | Credential used by the API to authenticate a user with the server | All requests |
| `Key` | `api-key` | API key that authenticates the application with the server | All requests |
| `Content-Type` | `application/json` | Determines the type of data (media/resource) present in the request body | `POST`, `PUT`, `DELETE` |
| `Accept` | `application/json` | Determines the acceptable response type from the server | All requests |


The header table is identical across all nine Knowledge Base V3 reference pages. Note that the Delete reference page lists `Content-Type: application/json` even though `DELETE` sends no body.

## 4. Write operations

### 4.1 Create a Knowledge Base article

**`POST /api/v3/knowledgebase/article`**

Creates a Knowledge Base article in a specified folder, optionally generating empty language-variant placeholders at the same time.

#### Request body

| Parameter | Sub-parameter | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `tags` |  | Optional | List of Strings | List of tags you want to apply on the article |
| `folderMetadata` |  | Required | Object | Object containing the details about the folder the article is added to |
|  | `folderId` | Required | String | Unique identifier for the folder where the article will be added. See [§1.2](#12-addressing-an-article) for the UI extraction steps. |
| `content` |  | Required | Object | Object containing the content details of the article |
|  | `title` | Optional | String | Title of the article. **Recommended** — a title clarifies the article's intent. |
|  | `contentType` | Required | String | Template of the article. Supported value: `KNOWN_ISSUE` |
|  | `markUpText` | Required | String | The content of the article in HTML format |
| `locale` |  | Required | String | Language code of the article, for example `en_US` |
| `lngVariants` |  | Optional | List[String] | Required if you wish to translate the article into other languages. Empty placeholder articles are generated for the specified [language codes](https://www.sprinklr.com/help/articles/translating-content/autotranslate-an-article/641adba2a1367f1be7db82d4#fcd36636-ec32-4947-b31f-b9a993825515) and linked to the parent article. Populate them later with the update endpoint, referencing their variant content IDs. |
| `publicContent` |  | Optional | Boolean | If `true`, the article is visible to the end customer |
| `originType` |  | Optional | String | `EXTERNAL` — articles imported into Sprinklr can only be viewed. `IMPORTED` — articles imported into Sprinklr but can be edited. **Defaults to `SPRINKLR` if not passed.** |
| `externalPermalink` |  | Optional | URL | The article's URL |
| `status` |  | Optional | String | `APPROVED` or `DRAFT`. `APPROVED` — ready for publishing and included in ML training for smart recommendation. `DRAFT` — needs work before publishing and not included in ML training. **Defaults to `DRAFT` if not passed.** |


#### Request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/article' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}' \
--data '{
    "tags": ["hello v3"],
    "folderMetadata": {
        "folderId": "69ca1b75063c432f16e271d2"
    },
    "content": {
        "title": "Article 1 v3 Test",
        "contentType": "KNOWN_ISSUE",
        "markUpText": "<p>Hello world v3 Text</p>"
    },
    "locale": "en_US",
    "lngVariants": ["fr_FR", "de_DE"],
    "publicContent": false,
    "originType": "EXTERNAL",
    "externalPermalink": "",
    "status": "APPROVED"
}'
```

#### Response

```json
{
  "data": {
    "id": "6a8c34439549b75aa81ac003",
    "version": 1,
    "contributors": [66011271],
    "tags": ["hello v3"],
    "content": {
      "contentType": "KNOWN_ISSUE",
      "contentSubType": "KB_ARTICLE",
      "title": "Article 1 v3 Test",
      "markUpText": "<p>Hello world v3 Text</p>"
    },
    "publicContent": false,
    "hasConditionalSection": false,
    "externalContent": false,
    "originType": "EXTERNAL",
    "favourite": false,
    "textModifiedTime": "Aug 24, 2026, 12:08:35 PM",
    "lngVariants": {
      "de_DE": "6a8c34439549b75aa81ac014",
      "fr_FR": "6a8c34439549b75aa81ac013"
    },
    "inactiveLngVariants": {},
    "countryVariants": {},
    "publishedCountryVariants": {},
    "externalPermalink": "",
    "stats": {
      "recommendCount": 0,
      "usageCount": 0,
      "ratingCount": 0,
      "ratingAvg": 0,
      "agentViewCount": 0,
      "communityViewCount": 0,
      "livechatViewCount": 0,
      "helpfulCount": 0,
      "notHelpfulCount": 0,
      "communityHelpfulCount": 0,
      "communityNotHelpfulCount": 0,
      "livechatHelpfulCount": 0,
      "livechatNotHelpfulCount": 0,
      "externalViewCount": 0,
      "externalHelpfulCount": 0,
      "externalNotHelpfulCount": 0
    },
    "status": "APPROVED",
    "saveInLngVariantEsEnabled": false,
    "locale": "en_US",
    "countryCodes": ["global"],
    "countryBaseContent": false,
    "linkedAssets": [],
    "translationProcess": {
      "updateTime": 1787573315063,
      "newContentAvailableForTranslation": true,
      "authorId": 66011271
    },
    "grants": [
      "USER/66011271/OWNERSHIP",
      "CLIENT/66001165/OWNERSHIP"
    ],
    "clientId": 66001165,
    "ownerUserId": 66011271,
    "createdTime": "Aug 24, 2026, 12:08:35 PM",
    "modifiedTime": "Aug 24, 2026, 12:08:35 PM",
    "lastModifiedUserId": 66011271,
    "deleted": false,
    "folderMetadata": {
      "folderId": "69ca1b75063c432f16e271d2",
      "confidential": false
    },
    "canEdit": false
  },
  "errors": []
}
```

**Note**: `data.id` is the content ID you will use on every subsequent read, update, and delete and `data.lngVariants` are the content IDs of the placeholder translations.

#### 4.1.1 The Knowledge Base content object

This object shape is returned by **create, read by content ID, read by migration details, search, and find linked assets**. It is documented once here and referenced from the other sections.

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `id` |  | String | Unique identifier of the content |
| `version` |  | Integer | Version number of the content |
| `contributors` |  | Array of Integers | List of user IDs who contributed to the content. May include system users. |
| `tags` |  | Array of Strings | Tags associated with the content |
| `content` |  | Object | Core content details such as type, title, and body |
|  | `contentType` | String | High-level content type, for example `KNOWN_ISSUE` |
|  | `contentSubType` | String | Content subtype, for example `KB_ARTICLE` |
|  | `title` | String | Title of the content |
|  | `markUpText` | String | HTML-encoded rich text body of the content |
| `publicContent` |  | Boolean | Indicates whether the content is publicly accessible |
| `hasConditionalSection` |  | Boolean | Indicates whether the content includes conditional sections |
| `externalContent` |  | Boolean | Indicates whether the content originates from an external source |
| `originType` |  | String | Source of the content, for example `SPRINKLR` |
| `favourite` |  | Boolean | Indicates whether the content is marked as a favorite |
| `textModifiedTime` |  | String | Timestamp of the last text modification |
| `lngVariants` |  | Map of Objects | Language-to-content-ID mapping for language variants |
| `inactiveLngVariants` |  | Map of Objects | Language variants that are currently inactive |
| `countryVariants` |  | Map of Objects | Country-specific variants of the content |
| `publishedCountryVariants` |  | Map of Objects | Published country-specific content variants |
| `stats` |  | Object | Usage and feedback statistics. See [§4.1.2](#412-the-stats-object). |
| `status` |  | String | Current status of the content, for example `DRAFT` |
| `saveInLngVariantEsEnabled` |  | Boolean | Indicates whether saving in the ES language variant is enabled |
| `locale` |  | String | Primary locale of the content, for example `en_US` |
| `countryCodes` |  | Array of Strings | List of country codes where the content applies |
| `countryBaseContent` |  | Boolean | Indicates whether the content is country-base content |
| `linkedAssets` |  | Array of Objects | Assets linked to the content. Empty if none exist. |
|  | `assetId` | String | Unique identifier for the linked asset |
|  | `assetType` | String | Type of linked asset, such as URL, Content Variable, Content Block, and so on |
|  | `assetCategory` | String | Classification of the linked asset, indicating which family of Sprinklr asset the reference points to |
|  | `resolvable` | Boolean | Indicates whether the linked asset reference can be resolved to a live asset when the article is rendered |
| `translationProcess` |  | Object | Metadata related to translation workflow |
|  | `updateTime` | Integer (Epoch ms) | Timestamp of the last translation update |
|  | `newContentAvailableForTranslation` | Boolean | Indicates whether new content is available for translation |
|  | `authorId` | Integer | User ID of the author who triggered the translation update |
| `grants` |  | Array of Strings | Permissions granted on the content |
| `clientId` |  | Integer | Client identifier |
| `ownerUserId` |  | Integer | User ID of the content owner |
| `createdTime` |  | String | Content creation timestamp |
| `modifiedTime` |  | String | Last modification timestamp |
| `lastModifiedUserId` |  | Integer | User ID of the last editor |
| `deleted` |  | Boolean | Indicates whether the content is deleted |
| `folderMetadata` |  | Object | Folder and confidentiality details |
|  | `folderId` | String | Identifier of the folder containing the content |
|  | `confidential` | Boolean | Indicates whether the folder is confidential |
| `canEdit` |  | Boolean | Indicates whether the current user can edit the content |


#### 4.1.2 The `stats` object

All 16 counters are returned on every read. They are integers except `ratingAvg`.

| Field | Type | Description |
|  --- | --- | --- |
| `recommendCount` | Integer | Number of recommendations |
| `usageCount` | Integer | Number of times the content was used |
| `ratingCount` | Integer | Total number of ratings received |
| `ratingAvg` | Number | Average rating value |
| `agentViewCount` | Integer | Number of agent views |
| `communityViewCount` | Integer | Number of community views |
| `livechatViewCount` | Integer | Number of live chat views |
| `helpfulCount` | Integer | Number of helpful votes |
| `notHelpfulCount` | Integer | Number of not helpful votes |
| `communityHelpfulCount` | Integer | Helpful votes from the community |
| `communityNotHelpfulCount` | Integer | Not helpful votes from the community |
| `livechatHelpfulCount` | Integer | Helpful votes from live chat |
| `livechatNotHelpfulCount` | Integer | Not helpful votes from live chat |
| `externalViewCount` | Integer | Number of external views |
| `externalHelpfulCount` | Integer | Helpful votes from external users |
| `externalNotHelpfulCount` | Integer | Not helpful votes from external users |


### 4.2 Update content by content ID

**`PUT /api/v3/knowledgebase/article?contentId={contentId}`**

Updates an existing article. The update is **action-driven**: you declare what you are changing in `updateActions`, and supply only the matching payload fields.

#### Query parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `contentId` | Required | String | Unique identifier for the content (article). Pass the base article ID to update the base article, or a language-variant ID to update that variant. |


#### Request body

| Parameter | Sub-parameter | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `updateActions` |  | Required | List[String] | Type of update action. See the action matrix in [§4.2.1](#421-the-updateactions-matrix). |
| `content` |  | Required | Object | Object containing the content details of the article. Use with the `CONTENT` action. |
|  | `contentSubType` | Optional | String | Subtype of the content. Default: `KB_ARTICLE` |
|  | `contentType` | Required | String | Template of the article. Supported value: `KNOWN_ISSUE` |
|  | `title` | Optional | String | Title of the article |
|  | `markUpText` | Required | String | The updated content of the article in HTML format |
| `status` |  | Optional | String | `APPROVED` or `DRAFT`. Use with the `STATUS` action. If not passed, it is set to the default. |
| `externalPermalink` |  | Optional | URL | The external URL of the article. Use with the `EXTERNAL_PERMALINK` action. |
| `syncedTags` |  | Optional | Array of Strings | Tags to associate with the article. Use with the `SYNC_TAGS` action. |
| `selectedCustomProperties` |  | Optional | Object | Custom properties applicable at the global level, keyed by the custom field's internal identifier (`_c_<id>`) with array values. Use with the `SYNC_SELECTED_CUSTOM_PROPERTIES` action. |
| `lngVariants` |  | Optional | List[String] | Language codes for new variants. Required if you want to add language variants/translations to the parent article. Use with the `ADD_LANGUAGE_VARIANT` action. |
| `lngVariantOriginType` |  | Optional | String | Origin type of the language variants created for the selected base article: `EXTERNAL` or `SPRINKLR`. **Does not update the origin type of existing variants** — it applies only when creating new ones. |


#### 4.2.1 The `updateActions` matrix

The reference page describes these as "**some of** the supported update actions," so treat the list as non-exhaustive.

| Action | Field it drives | Semantics |
|  --- | --- | --- |
| `CONTENT` | `content` | Updates the content of Knowledge Base articles |
| `STATUS` | `status` | Updates the status of Knowledge Base articles, such as `DRAFT`, `APPROVED` |
| `EXTERNAL_PERMALINK` | `externalPermalink` | Updates the external permalink of Knowledge Base articles |
| `SYNC_TAGS` | `syncedTags` | Updates the tags associated with Knowledge Base articles |
| `SYNC_SELECTED_CUSTOM_PROPERTIES` | `selectedCustomProperties` | Updates the Custom Field values associated with Knowledge Base articles |
| `ADD_LANGUAGE_VARIANT` | `lngVariants`, `lngVariantOriginType` | Adds language variants/translations to Knowledge Base articles |


Actions are composable — the canonical example sends `["CONTENT", "STATUS"]` in one call.

#### ⚠️ `SYNC_TAGS` overwrites — it does not append

- If tags already exist, `syncedTags` **overrides the existing tags and does not append new ones**. Read the current `tags` array first and send the union if you intend to add.
- **If the new Tag Manager is enabled for your account, you cannot use this API to add or update tags.**


`SYNC_SELECTED_CUSTOM_PROPERTIES` behaves the same way: it replaces the values of the listed custom properties.

`ADD_LANGUAGE_VARIANT` is additive and idempotent-by-skip: **specify only the language codes for the new variants you want to add. If the request includes language variants that already exist, the API skips those entries and continues processing. Only new language variants are accepted and processed.**

#### Request

```bash
curl --location --request PUT 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/article?contentId=68bebd3aaeb2ed12b188ba1d' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}' \
--data '{
    "updateActions": [
        "CONTENT",
        "STATUS"
    ],
    "content": {
        "contentSubType": "KB_ARTICLE",
        "contentType": "KNOWN_ISSUE",
        "title": "Tiered Approval Test v3 Test",
        "markUpText": "<p>This is a new sentence specifically to test the v3 API configurations.</p>"
    },
    "status": "APPROVED"
}'
```

#### Per-action payload examples

Update the external permalink:

```json
{
  "updateActions": ["EXTERNAL_PERMALINK"],
  "externalPermalink": "https://help.sprinklr.com/new-external-permalink"
}
```

Replace the tag set:

```json
{
  "updateActions": ["SYNC_TAGS"],
  "syncedTags": ["test manually", "api test"]
}
```

Set a Custom Field value:

```json
{
  "updateActions": ["SYNC_SELECTED_CUSTOM_PROPERTIES"],
  "selectedCustomProperties": {
    "_c_666c4fe839e2966639eaa2b2": ["value"]
  }
}
```

Add language variants:

```json
{
  "updateActions": ["ADD_LANGUAGE_VARIANT"],
  "lngVariants": ["en", "ar"],
  "lngVariantOriginType": "SPRINKLR"
}
```

### 4.3 Delete a Knowledge Base article

**`DELETE /api/v3/knowledgebase/article?contentId={contentId}&isLngVariant={boolean}`**

Deletes a Knowledge Base article, or one of its language variants.

#### Query parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `contentId` | Required | String | Unique identifier for the content (article) |
| `isLngVariant` | Required | Boolean | If `true`, you are deleting a language variant of the article. Otherwise the base article is deleted. |


#### Request

```bash
curl --location --request DELETE 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/article?contentId=68064635419fa452f2065ba6&isLngVariant=false' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}'
```

#### Response

```
204 No Content
```

## 5. Read operations

### 5.1 Read content by content ID

**`GET /api/v3/knowledgebase/article?contentIds={contentIds}`**

Fetches Knowledge Base content along with details about the content variable, content block, featured image, and content description.

#### Query parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `contentIds` | Required | String | Unique identifier for the content (article). The OpenAPI specification describes this as **comma-separated content IDs**, so more than one ID can be requested per call. |


#### Request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/article?contentIds=684aa7de2224f872685e54f8' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}'
```

#### Response (abridged)

```json
{
  "data": [
    {
      "id": "684aa7de2224f872685e54f8",
      "version": 7,
      "contributors": [66004233, 66010022, 66015956],
      "tags": [],
      "content": {
        "contentType": "KNOWN_ISSUE",
        "contentSubType": "KB_ARTICLE",
        "title": "GreenBuild Depot Solar Lights",
        "markUpText": "<div>...</div>"
      },
      "publicContent": false,
      "hasConditionalSection": false,
      "externalContent": false,
      "originType": "IMPORTED",
      "favourite": false,
      "textModifiedTime": "Aug 21, 2026, 07:15:59 AM",
      "lngVariants": {},
      "inactiveLngVariants": {},
      "countryVariants": {},
      "publishedCountryVariants": {},
      "stats": { "...": "see §4.1.2" },
      "migrationDetails": {},
      "languageSettingId": "6a3502e712283169da6b37f9",
      "folderMetadata": { "folderId": "...", "confidential": false },
      "canEdit": false
    }
  ],
  "errors": []
}
```

`data` is an **array** on this endpoint, even for a single content ID. Field-by-field descriptions are in [§4.1.1](#411-the-knowledge-base-content-object).

### 5.2 Get content by migration details

**`GET /api/v3/knowledgebase/article?migratedId={migratedId}&migratedFrom={migratedFrom}`**

Fetches Knowledge Base article content from the migration details of an existing, migrated article. Migration details are set on the article when it is created.

#### Query parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `migratedId` | Required | String | Unique identifier for the migration ID that was configured while creating the article |
| `migratedFrom` | Required | String | The source from which the article was migrated, configured while creating the article |


#### Request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/article?migratedId=hi_IN_test_migrated_id_api3&migratedFrom=SPRINKLR' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}'
```

#### Response (abridged)

```json
{
  "data": [
    {
      "id": "643ef350a56aae2db6e83b36",
      "version": 0,
      "contributors": [600038885],
      "migrationDetails": {
        "migratedFrom": "SPRINKLR",
        "migratedId": "hi_IN_test_migrated_id_api3"
      }
    }
  ],
  "errors": []
}
```

`data` is an array of Knowledge Base content objects. The `migrationDetails` object carries `migratedId` and `migratedFrom` back to you, which is what makes this endpoint the reconciliation key for bulk imports.

### 5.3 Search Knowledge Base articles

**`POST /api/v3/knowledgebase/article/search`**

Searches Knowledge Base articles using filters. Use it to find articles by title and underlying content, by language, or by category, and to fetch every article in the instance.

#### Request body

| Parameter | Sub-parameter | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `filters` |  | Required | Array | Array of filter objects that scope the search. **Send an empty object `{}` in the request body to fetch all content available in the Sprinklr instance.** |
|  | `filterType` | Optional | String | Operator applied to `field` and `values`. See [§8.5](#85-filter-operators-filtertype). |
|  | `field` | Optional | String | Content attribute the filter applies to. See [§8.6](#86-filterable-fields-field). |
|  | `values` | Optional | List[String/Integer] | Values evaluated against `field` using `filterType`. Supply one value for single-value operators such as `EQUALS`, `GT`, and `LT`, and one or more values for set operators such as `IN` and `NIN`. |
| `page` |  | Optional | Object | Object controlling pagination of the search results. Omit it to use the API's default paging behavior. |
|  | `page` | Optional | Integer | Zero-based index of the results page to return. Set `0` for the first page, `1` for the second, and so on. Use with `size`, and check `data.hasMore` in the response to determine whether further pages exist. |
|  | `size` | Optional | Integer | Maximum number of content items to return in a single response. Compare against `data.totalHits` to calculate the total number of pages. |


#### Request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/article/search' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}' \
--data '{
    "filters": [
        {
            "filterType": "IN",
            "field": "KB_CONTRIBUTOR",
            "values": [
                "66011271"
            ]
        }
    ],
    "page": {
        "page": 0,
        "size": 2
    }
}'
```

#### Response (abridged)

```json
{
  "data": {
    "searchResults": [
      {
        "id": "68bebd3aaeb2ed12b188ba1d",
        "version": 106,
        "contributors": [66011530, 66066902, 66011271, 66015956, 66067647],
        "tags": [
          "6a5f20048488c270e94f019f",
          "6811cbc77085764e3e512dfc",
          "6a67a90f9ade0be93322522d"
        ],
        "content": { "...": "see §4.1.1" },
        "canEdit": false
      }
    ],
    "hasMore": true,
    "totalHits": 73,
    "sortValues": {
      "KB_MODIFIED_TIME": 1787675543039
    }
  },
  "errors": []
}
```

#### Response schema

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `data` |  | Object | Search response containing results and metadata |
|  | `searchResults` | Array of Objects | List of Knowledge Base articles that match the search criteria. Each entry uses the object in [§4.1.1](#411-the-knowledge-base-content-object). |
|  | `hasMore` | Boolean | Indicates whether more results are available for pagination |
|  | `totalHits` | Integer | Total number of matching results for the search query |
|  | `sortValues` | Object | Map of sort-field name to the sort value of the last item on the current page |
| `errors` |  | Array of Objects | List of errors encountered during the search operation. Empty if no errors occur. |


### 5.4 Search version history of an article

**`POST /api/v3/knowledgebase/version-history/search`**

Searches the version history of a Knowledge Base article. Each result is a point-in-time snapshot of the article body, title, status, and custom-field values.

#### Request body

| Parameter | Sub-parameter | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `contentIds` |  | Required | List of Strings | Unique identifier of the article whose version history you want to retrieve. **Specify exactly one content ID per request.** |
| `sort` |  | Optional | Object | Object containing sorting conditions |
|  | `key` | Optional | String | The property on which you want to perform the search, for example `version` |
|  | `order` | Optional | String | Order in which results appear: `ASC` or `DESC` |
| `page` |  | Optional | Object | Object controlling pagination of the search results. Omit it to use the API's default paging behavior. |
|  | `page` | Optional | String | Zero-based index of the results page to return |
|  | `size` | Optional | String | Maximum number of content items to return in a single response |


#### Request

```bash
curl -X POST \
'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/version-history/search' \
-H 'Authorization: ******' \
-H 'Key: {{apiKey}}' \
-H 'Content-Type: application/json' \
-d '{
    "contentIds": [
        "63cc0f1fddf30d04d8dfc19d"
    ],
    "sort": {
        "key": "version",
        "order": "DESC"
    },
    "page": {
        "page": 0,
        "size": 1
    }
}'
```

#### Response (abridged)

```json
{
  "data": {
    "searchResults": [
      {
        "id": "6a8f6c94d66e5431dde60516",
        "contentId": "68bebd3aaeb2ed12b188ba1d",
        "version": 45,
        "hierarchicalVersion": "8.34",
        "title": "Tiered Approval Test v3 Test",
        "markUpText": "<p class=\"export-block__parent \">...</p>",
        "status": "DRAFT",
        "publicContent": false,
        "partnerCustomProperties": {
          "_c_64ddebe659f4f4171e2ed8a1": ["2"],
          "_c_68d6613fc4174114a1ed4307": ["jyfuygluy"]
        },
        "ownerUserId": 66011530,
        "createdTime": "Aug 26, 2026, 10:45:39 PM",
        "countryCodes": ["global"],
        "locale": "en_GB",
        "processTypes": []
      }
    ],
    "hasMore": true,
    "totalHits": 45
  },
  "errors": []
}
```

#### Response schema

| Parameter | Sub-parameter | Type | Description |
|  --- | --- | --- | --- |
| `data` |  | Object | Search response containing results and metadata |
|  | `searchResults` | Array of Objects | List of version history IDs that match the search criteria |
|  | `hasMore` | Boolean | Indicates whether more results are available for pagination |
|  | `totalHits` | Integer | Total number of matching results for the search query |
| `errors` |  | Array of Objects | List of errors encountered during the search operation. Empty if no errors occur. |


#### 5.4.1 The version history object

Returned by both [§5.4](#54-search-version-history-of-an-article) and [§5.5](#55-get-version-history-by-version-id).

| Parameter | Type | Description |
|  --- | --- | --- |
| `id` | String | Unique identifier of the version history |
| `contentId` | String | Unique identifier of the article |
| `version` | Integer | Version number of the article |
| `hierarchicalVersion` | String | Dotted version identifier that expresses the version's position in a major/minor revision hierarchy, for example `8.34` |
| `title` | String | Title of the article |
| `markUpText` | String | HTML-encoded rich text body of the article |
| `status` | String | Current status of the article, for example `DRAFT` |
| `publicContent` | Boolean | Indicates whether the article is publicly accessible |
| `partnerCustomProperties` | Object | Custom Field values captured with this version, keyed by the custom field's internal identifier, with each value returned as an array |
| `ownerUserId` | Integer | User ID of the article owner |
| `createdTime` | String | Article creation timestamp |
| `countryCodes` | Array of Strings | List of country codes where the article applies |
| `locale` | String | Primary locale of the article, for example `en_US` |
| `processTypes` | Array | *No description supplied in the source documentation.* See [§11](#11-questions-for-the-api-owner). |


Note that a version history entry carries **`version` and `hierarchicalVersion` in parallel** — `version` is the flat monotonic counter (45), `hierarchicalVersion` is the major/minor label (`8.34`). Display `hierarchicalVersion` to end users and sort on `version`.

### 5.5 Get version history by version ID

**`GET /api/v3/knowledgebase/version-history?versionHistoryId={versionHistoryId}`**

Retrieves a single revision of a Knowledge Base article by its version history ID.

#### Query parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `versionHistoryId` | Required | String | Unique identifier of the version history. Obtain it from the Search Version History API ([§5.4](#54-search-version-history-of-an-article)). |


#### Request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/version-history?versionHistoryId=6a8f6c94d66e5431dde60516' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}'
```

#### Response (abridged)

```json
{
  "data": {
    "id": "6a8f6c94d66e5431dde60516",
    "contentId": "68bebd3aaeb2ed12b188ba1d",
    "version": 45,
    "hierarchicalVersion": "8.34",
    "title": "Tiered Approval Test v3 Test",
    "markUpText": "<p class=\"export-block__parent \">...</p>",
    "status": "DRAFT",
    "publicContent": false,
    "partnerCustomProperties": {
      "_c_64ddebe659f4f4171e2ed8a1": ["2"],
      "_c_68d6613fc4174114a1ed4307": ["jyfuygluy"]
    },
    "ownerUserId": 66011530,
    "createdTime": "Aug 26, 2026, 10:45:39 PM",
    "countryCodes": ["global"],
    "locale": "en_GB",
    "processTypes": []
  },
  "errors": []
}
```

`data` here is a **single object**, not a `searchResults` wrapper — the pagination fields are absent because there is exactly one result. Field descriptions are in [§5.4.1](#541-the-version-history-object).

### 5.6 Find linked assets

**`POST /api/v3/knowledgebase/linked-asset?linkedAssetType={type}`**

Fetches the Knowledge Base articles or assets in which Content Variables and Content Blocks are present. Use it for impact analysis before you change a shared asset.

#### Query parameters

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `linkedAssetType` | Required | String | Type of linked asset you want to fetch. `spr-content-variable` for Content Variables. `spr-content-block` for Reusable Content Blocks. |


#### Request body

| Parameter | Required | Type | Description |
|  --- | --- | --- | --- |
| `assetIds` | Required | List of Strings | List containing the unique identifiers for the linked assets. You can find linked asset IDs using any content read or search API, in the `linkedAssets[].assetId` field. |


#### Request — Content Blocks

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/linked-asset?linkedAssetType=spr-content-block' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}' \
--data '["6994852301de1f1e6c70ebdf",
"68c29da3fae3f43736b05961"]'
```

#### Request — Content Variables

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/knowledgebase/linked-asset?linkedAssetType=spr-content-variable' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ******' \
--header 'Key: {{apiKey}}' \
--data '["6994852301de1f1e6c70ebdf"]'
```

#### Response (abridged)

```json
{
  "data": {
    "6994852301de1f1e6c70ebdf": {
      "id": "6994852301de1f1e6c70ebdf",
      "version": 29,
      "contributors": [66010022, 66015300],
      "tags": [
        "6a5f20048488c270e94f019f",
        "6811cbc77085764e3e512dfb",
        "6811cbc77085764e3e512dfc"
      ],
      "content": {
        "contentType": "KNOWN_ISSUE",
        "contentSubType": "REUSABLE_CONTENT",
        "title": "Ashish Test Main CB",
        "markUpText": "<p class=\"export-block__parent \">this is for CB Testing.</p>..."
      },
      "publicContent": false,
      "hasConditionalSection": false,
      "externalContent": false,
      "originType": "SPRINKLR",
      "favourite": false,
      "textModifiedTime": "Feb 17, 2026, 03:12:06 PM",
      "linkedAssets": [
        {
          "assetId": "662a0c006...",
          "assetType": "...",
          "assetCategory": "MARK_UP_TEXT",
          "resolvable": false
        }
      ],
      "languageSettingId": "6870bb01a04f816797f4f9c8"
    }
  },
  "errors": []
}
```

**`data` is a map keyed by asset ID**, not an array. Each value is a Knowledge Base content object ([§4.1.1](#411-the-knowledge-base-content-object)).

## 6. Response format and status codes

### 6.1 The V3 envelope

Every Knowledge Base V3 endpoint returns `data` and `errors`. **The shape of `data` varies by endpoint family** — this is the single most important parsing detail in this guide.

| Endpoint family | `data` shape | Endpoints |
|  --- | --- | --- |
| Create | Single object | `POST /knowledgebase/article` |
| Direct read | **Array** of objects | `GET /knowledgebase/article` (by `contentIds` and by migration details) |
| Keyed read | **Map** keyed by asset ID | `POST /knowledgebase/linked-asset` |
| Single read | Single object | `GET /knowledgebase/version-history` |
| Search | Object with `searchResults` | `POST /knowledgebase/article/search`, `POST /knowledgebase/version-history/search` |
| Delete | No body (`204`) | `DELETE /knowledgebase/article` |


Direct-read envelope:

```json
{
  "data": [ { "id": "...", "content": { } } ],
  "errors": []
}
```

Search envelope:

```json
{
  "data": {
    "searchResults": [ { } ],
    "hasMore": true,
    "totalHits": 73,
    "sortValues": {
      "KB_MODIFIED_TIME": 1787675543039
    }
  },
  "errors": []
}
```

| Field | Type | Description |
|  --- | --- | --- |
| `data` | Object / Array / Map | Response payload. See the shape table above. |
| `data.searchResults` | Array of Objects | Items matching the search criteria (search endpoints only) |
| `data.hasMore` | Boolean | Indicates whether more results are available for pagination |
| `data.totalHits` | Integer | Total number of matching results for the search query |
| `data.sortValues` | Object | Map of sort-field name to the sort value of the last item on the current page. Returned by article search; not returned by version-history search. |
| `errors` | Array of Objects | List of errors encountered during the operation. Empty if no errors occur. |


### 6.2 Response codes

These are the status codes declared for every Knowledge Base V3 operation in the OpenAPI specification.

| HTTP Code | Scenario | Description |
|  --- | --- | --- |
| `200 OK` | Success | Request completed and the payload was returned |
| `204 No Content` | Success | Documented on the reference page for `DELETE /knowledgebase/article`. The specification declares `200` for this operation instead — treat both as success. |
| `400 Bad Request` | BadRequest | Missing required parameters or invalid parameter combinations |
| `401 Unauthorized` | Unauthorized | Invalid or missing `Authorization` token |
| `403 Forbidden` | Forbidden | The caller lacks permission on the Knowledge Base content or folder |
| `404 Not Found` | NotFound | No content matches the supplied identifier |


No `500` response is declared for the Knowledge Base V3 operations in the specification. Do not assume `2xx` means every item succeeded — always inspect the `errors` array.

## 8. Supported values

### 8.1 `status`

| Value | Meaning |
|  --- | --- |
| `APPROVED` | Articles that are ready for publishing and will be included in ML training for smart recommendation |
| `DRAFT` | Articles that need work before publishing and will not be included in ML training for smart recommendation |


If `status` is not passed on create, it is automatically set to `DRAFT`.

The broader workflow status vocabulary, filterable via `KB_CONTENT_STATUS`, is: `DRAFT`, `APPROVED`, `IN_APPROVAL`, `REJECTED`, `IN_TRANSLATION`, `SENT_FOR_TRANSLATION`, `SCHEDULED`, `PUBLISHED`, `EXPIRED`, `ARCHIVED`, `READY_TO_PUBLISH`, `IN_ACTIVE`, `DELETED`.

### 8.2 `originType`

| Value | Meaning |
|  --- | --- |
| `SPRINKLR` | Default when `originType` is not passed in the request payload |
| `EXTERNAL` | Articles imported into Sprinklr can only be viewed |
| `IMPORTED` | Articles imported into Sprinklr but can be edited |
| `NOMINATED` | Filterable via `KB_ORIGIN_TYPE`; not documented as a create-time value |


`lngVariantOriginType` accepts `EXTERNAL` or `SPRINKLR`, and applies only when creating new language variants.

### 8.3 `contentType` and `contentSubType`

| Field | Values |
|  --- | --- |
| `contentType` | `KNOWN_ISSUE` |
| `contentSubType` | `KB_ARTICLE` (default). `REUSABLE_CONTENT` is returned for Content Blocks by the linked-asset endpoint. |


### 8.4 `linkedAssetType`

| Value | Meaning |
|  --- | --- |
| `spr-content-variable` | Content Variables |
| `spr-content-block` | Reusable Content Blocks |


### 8.5 Filter operators (`filterType`)

| Operator | Semantics |
|  --- | --- |
| `AND` | All nested conditions must match |
| `OR` | At least one nested condition must match |
| `NOT` | Excludes content matching the condition |
| `IN` | Field value matches any entry in `values` |
| `NIN` | Field value matches no entry in `values` |
| `EQUALS` | Field value equals the single supplied value |
| `NOT_EQUALS` | Field value differs from the supplied value |
| `GT` | Field value is greater than the supplied value |
| `GTE` | Field value is greater than or equal to the supplied value |
| `LT` | Field value is less than the supplied value |
| `LTE` | Field value is less than or equal to the supplied value |
| `CONTAINS` | Field value contains the supplied text |


### 8.6 Filterable fields (`field`)

| Field | Maps to | Description |
|  --- | --- | --- |
| `KB_CONTRIBUTOR` | `contributors` | User IDs of users who edited the article |
| `KB_CONTENT_ID` | `id` | Unique identifier of the Knowledge Base content |
| `KB_CONTENT_TYPE` | `content.contentType` | High-level content type, for example `KNOWN_ISSUE` |
| `KB_CONTENT_SUB_TYPE` | `content.contentSubType` | Content subtype, for example `KB_ARTICLE` |
| `KB_CONTENT_STATUS` | `status` | Workflow status. Allowed values: `DRAFT`, `APPROVED`, `IN_APPROVAL`, `REJECTED`, `IN_TRANSLATION`, `SENT_FOR_TRANSLATION`, `SCHEDULED`, `PUBLISHED`, `EXPIRED`, `ARCHIVED`, `READY_TO_PUBLISH`, `IN_ACTIVE`, `DELETED` |
| `KB_TAGS` | `tags` | Tags applied to the article |
| `PUBLIC_CONTENT` | `publicContent` | Whether the article is visible to end customers. `true`/`false`, default `false`. |
| `MAPPED_PROJECT_ID` | — | Identifier of the project the content is mapped to |
| `KB_FAVOURITE` | `favourite` | Whether the article is marked as a favorite. `true`/`false`, default `false`. |
| `KB_CREATED_TIME` | `createdTime` | Article creation timestamp. Use with `GT`, `GTE`, `LT`, or `LTE`. |
| `KB_MODIFIED_TIME` | `modifiedTime` | Last modification timestamp. Use with `GT`, `GTE`, `LT`, or `LTE`. |
| `KB_ORIGIN_TYPE` | `originType` | Origin of the content. Allowed values: `SPRINKLR`, `IMPORTED`, `NOMINATED`, `EXTERNAL`. `EXTERNAL` articles can only be viewed; `IMPORTED` articles can be edited in Sprinklr. |
| `KB_MIGRATED_ID` | `migrationDetails` | Identifier of the content in the source system it was migrated from |
| `KB_MIGRATED_FROM` | `migrationDetails` | Source system the content was migrated from |
| `KB_EXPORT_IMPORT_ID` | — | Identifier of the export/import job associated with the content |
| `KB_BASE_LNG_CONTENT_ID` | `lngVariants` | Content ID of the base-language article for a language variant |
| `KB_BASE_COUNTRY_CONTENT_ID` | `countryBaseContent` | Content ID of the base-country article |
| `KB_CONTENT_SCHEDULED_STATUS` | `contentScheduledDetails` | Scheduled publish/unpublish state |
| `KB_MAP_SCHEDULED_DATE` | — | Scheduled date on which the content is mapped (published) to a category |
| `KB_UN_MAP_SCHEDULED_DATE` | — | Scheduled date on which the content is unmapped (unpublished) |
| `KB_LINKED_ASSET_ID` | `linkedAssets` | Identifier of an asset linked to the content |
| `KB_TITLE` | `content.title` | Article title. Commonly used with `CONTAINS`. |
| `KB_MARK_UP_TEXT` | `content.markUpText` | Article body as escaped HTML markup. Commonly used with `CONTAINS`. |
| `KB_LOCALE` | `locale` | Language/locale code of the article, for example `en_US` |


## 9. Use cases

### 9.1 Publish a new article with translations in one call

Send a single `POST /knowledgebase/article` with `lngVariants: ["fr_FR", "de_DE"]`, `locale: "en_US"`, `status: "APPROVED"`, and `publicContent: true`. The response returns the base article ID plus a `lngVariants` map of the placeholder variant IDs. Store the map; you will need it in [§9.2](#92-fill-in-a-translation-once-the-copy-comes-back).

### 9.2 Fill in a translation once the copy comes back

For each variant content ID from `lngVariants`, call:

```
PUT /knowledgebase/article?contentId={variantContentId}
```

with `updateActions: ["CONTENT"]` and the translated `content.markUpText`. Passing a variant content ID updates that variant only; passing the base content ID updates the base article.

### 9.3 Add languages to an article that is already live

```
PUT /knowledgebase/article?contentId={baseContentId}
```

with `updateActions: ["ADD_LANGUAGE_VARIANT"]`, `lngVariants: ["en", "ar"]`, and optionally `lngVariantOriginType`. Already-existing variants are skipped, so this call is safe to re-run — it will only create what is missing.

### 9.4 Move an article from draft to published

```
PUT /knowledgebase/article?contentId={contentId}
```

with `updateActions: ["STATUS"]` and `status: "APPROVED"`. Only `APPROVED` articles are included in ML training for smart recommendation, so this is the call that makes an article eligible for Smart Comprehend.

### 9.5 Reconcile a bulk migration from a legacy Knowledge Base

For each record in your legacy export, call:

```
GET /knowledgebase/article?migratedId={legacyId}&migratedFrom={sourceSystem}
```

An empty `data` array means the record was never imported — re-create it. A populated array returns the Sprinklr content ID, which you write back into your legacy system as the cross-reference. Because migration details are set at create time, this is the only stable way to map legacy IDs to Sprinklr IDs after the fact.

Alternatively, search on `KB_MIGRATED_FROM` with `filterType: "EQUALS"` to page through everything you imported from one source system.

### 9.6 Export the entire Knowledge Base

Send `POST /knowledgebase/article/search` with an empty object `{}` in the request body to fetch all content available in the Sprinklr instance, then page with `page.page` and `page.size`, following `data.hasMore`.

### 9.7 Find every article a contributor has touched

```json
{
  "filters": [
    {
      "filterType": "IN",
      "field": "KB_CONTRIBUTOR",
      "values": ["66011271"]
    }
  ],
  "page": { "page": 0, "size": 50 }
}
```

Useful for offboarding reviews and content-ownership audits.

### 9.8 Find stale content for a refresh cycle

Filter on `KB_MODIFIED_TIME` with `LT` and an epoch cutoff, combined with `KB_CONTENT_STATUS` `IN` `["PUBLISHED", "APPROVED"]`. The result is your review queue.

### 9.9 Full-text discovery across titles and bodies

Use `CONTAINS` against `KB_TITLE` for title matching and `KB_MARK_UP_TEXT` for body matching. Combine them under an `OR` filter to reproduce a simple search box.

### 9.10 Build an article audit trail

Call `POST /knowledgebase/version-history/search` with the article's content ID, `sort: {"key": "version", "order": "DESC"}`, and a page size that suits your UI. Each result carries `hierarchicalVersion`, `title`, `markUpText`, `status`, `partnerCustomProperties`, `ownerUserId`, and `createdTime` — enough to render a full revision timeline and diff two revisions client-side. To display one revision in full, call `GET /knowledgebase/version-history?versionHistoryId=` with the `id` from the search results.

### 9.11 Impact analysis before changing a shared Content Block

Before editing a Reusable Content Block, call:

```
POST /knowledgebase/linked-asset?linkedAssetType=spr-content-block
```

with the block's asset ID in the body array. The response map tells you every article that embeds it, so you can assess blast radius. Repeat with `spr-content-variable` for Content Variables. Asset IDs come from the `linkedAssets[].assetId` field on any read or search result, or you can search articles directly on `KB_LINKED_ASSET_ID`.

### 9.12 Retire an article and its translations

Delete each language variant first with `isLngVariant=true`, then delete the base article with `isLngVariant=false`. Enumerate the variant IDs from the base article's `lngVariants` map before you start — once the base article is gone you lose the mapping.

### 9.13 Sync tags from an external taxonomy

```
PUT /knowledgebase/article?contentId={contentId}
```

with `updateActions: ["SYNC_TAGS"]` and the **complete** desired tag set in `syncedTags`. Because `syncedTags` overwrites, read the current `tags` array first and send the union if you are adding rather than replacing. This will not work if the new Tag Manager is enabled on your account.

## 10. Caveats and best practices

**Method and routing**

- The verb is now the HTTP method, not a path segment. `POST` to `/knowledgebase/article` creates; it does not update.
- Search lives on a separate sub-path (`/article/search`, `/version-history/search`) and always uses `POST` with a JSON body.
- The Search Article reference page's example curl omits `/search`. Use the endpoint given in that page's **API Endpoint** section and in the OpenAPI specification: `/knowledgebase/article/search`.


**Identifiers**

- Read uses `contentIds` (plural, comma-separated). Update and delete use `contentId` (singular). The one-character difference is the most common porting bug.
- `isLngVariant` is required on delete. Getting it wrong deletes the base article instead of the variant.
- Version history has two identifiers: `contentId` (the article) and `versionHistoryId` (one revision of it). They are not interchangeable.
- Every query parameter is declared `required: false` in the OpenAPI specification, but the reference pages mark most Required. Follow the reference pages.


**Update semantics**

- `updateActions` is required. A payload field without its matching action will not be applied.
- `SYNC_TAGS` **overwrites** the tag set. `SYNC_SELECTED_CUSTOM_PROPERTIES` **replaces** the listed property values.
- `ADD_LANGUAGE_VARIANT` skips existing variants — safe to retry.
- `lngVariantOriginType` does not retro-fit existing variants.
- If the new Tag Manager is enabled for your account, you cannot add or update tags through this API.


**Content**

- `markUpText` is HTML and is returned escaped. Sanitize on ingest and on render.
- Language variants created via `lngVariants` are **empty placeholders** — they carry no content until you `PUT` into each variant's content ID.
- `contentType` is `KNOWN_ISSUE`; `contentSubType` defaults to `KB_ARTICLE`. Content Blocks come back as `REUSABLE_CONTENT`.


**Pagination**

- Pages are **0-based**. The first page is `0`.
- Use `data.hasMore` to decide whether to request another page; use `data.totalHits` to compute the total page count.
- Article search additionally returns `data.sortValues` — the sort value of the last item on the page. Prefer it over growing offsets for deep paging.
- Version-history search returns `hasMore` and `totalHits` but **no `sortValues`**.
- Version-history search accepts **exactly one content ID per request**.


**Response parsing**

- Branch on endpoint family: `data` is an array on direct reads, an object on create and single version reads, a map keyed by asset ID on linked assets, and an object with `searchResults` on searches.
- Always inspect `errors` even on a `2xx`.
- Accept both `200` and `204` from `DELETE`.


**Operational**

- `APPROVED` is what makes an article eligible for ML training; `DRAFT` content is excluded.
- Folder IDs are not discoverable through a published V3 endpoint — extract them from the Sprinklr UI URL, or use the V2 folder APIs.
- Category, folder, and Content Variable **write** operations have no published V3 equivalent. Plan for a hybrid V2/V3 integration.