Skip to content
Last updated

Create Listening Theme — API V3 Developer Guide

  • Applies to: Sprinklr Listening Theme API, version 3 (POST /api/v3/listening/theme), OpenAPI tag Listening Theme V3
  • V2 API reference: Create Theme 2.0 — part of the Listening 2.0 suite (Create Theme · Read Theme · Update Theme · Delete Theme)

Prerequisites

  1. A registered API application with a service account — see Getting Started on the developer portal.

  2. An OAuth access token and an API key.

  3. The Create Theme permission. Per How to access Listening Themes?, Theme permissions sit under permission category Conversations, permission type Listening Settings:

    PermissionAllows
    Create ThemeCreate new Listening Themes
    Edit ThemeEdit existing Listening Themes
    Delete ThemeDelete existing Themes
    View ThemeView Themes
  4. Server routing records the server type and backing store for each Listening V3 entity:

    V3 APIModule / Entity (UI)Permission (UI name)Server typeCollection / store
    Listening TopicListening SettingsViewLISTENINGlisteningTopic
    Listening Topic GroupListening SettingsViewLISTENINGlisteningGroup
    Listening ThemeListening SettingsTheme ViewLISTENINGlisteningTheme
    Listening BackfillListening SettingsView (or Topic Backfill)LISTENINGhistoricBackfill
    Keyword ListListening SettingsKeyword Group ViewDEFAULTkeywordGroup
    AlertAlert ManagerView or AdminDEFAULTalert

1. Overview

1.1 What a Listening Theme is

A Listening Theme is a set of keywords and filters that can be used to sort Listening Insights data across a pre-built area of focus. Consider Themes like a sub-topic or a lens that can be associated to slice and dice your topics around different aspects and attributes

The critical property, and the reason Themes exist as a separate entity from Topics:

Listening Themes helps to visualize data without changing what a Topic pulls into the Sprinklr platform.

A Topic controls ingestion — it determines which conversations enter Sprinklr at all. A Theme is a read-time lens over data a Topic has already collected. Creating, changing, or deleting a Theme therefore never changes your ingested volume or your data bill; it changes only how existing mentions are segmented for analysis. Typical Themes are segments such as Product Quality, Customer Service, Pricing, or Delivery.

For more information, refer to Introduction to Listening Themes

1.2 Where a Theme sits in the Listening model

Keyword List ──┐
               ├──> Topic ──> ingests mentions ──> Theme ──> segments them for analysis
Boolean query ─┘                                     │
                                                     └──> applied to dashboards, widgets,
                                                          alerts, and rules (via Theme Tags)

Themes are step 5 — applied after Topics exist and are collecting.

1.3 When to use the API instead of the UI

The UI offers two creation routes, per Create Listening Themes: the Theme builder, and the Theme Library, which contains "5000+ readily available Themes based on various categories and different industries." There is also Bulk Import Themes and AI+ automatic Theme framework creation.

Reach for the API when you need to:

  • Provision programmatically — create the same Theme taxonomy across many environments or client accounts.
  • Sync from an external source — mirror a product taxonomy, a CRM category tree, or a support-ticket schema into Themes.
  • Version-control your taxonomy — keep Theme definitions in Git and apply them via CI.
  • Generate Themes — create one Theme per SKU, region, or campaign from a data source, at a volume that makes manual creation impractical.

The UI is better when a human needs the Theme Editor's live preview, which the API has no equivalent of: "the Theme Editor UI has preview support, meaning social posts, conversations, smart clusters, etc., can be previewed immediately while creating the Themes." There is no API dry-run.

1.4 Conventions in this guide

  • {base} stands for the host and environment prefix resolved.
  • Credentials always appear as {{accessToken}} and {{apiKey}} placeholders. Never paste a real key into a shared document or a ticket.
  • Fields are shown exactly as they appear in sprinklr-v3.yaml; casing is significant.

2. Base URLs and environments

{base} = https://{host}/{env}
SegmentValue
{host}Issued with your application registration. The V2 Create Theme page documents api3.sprinklr.com.
{env}Your environment prefix.

Full create URL:

POST https://api3.sprinklr.com/{env}/api/v3/listening/theme

3. Authentication and common headers

All API calls are authenticated with OAuth 2.0. See Developer Tools in Sprinklr for API key and secret generation, and the Authorize flow.

HeaderValuePurposeRequired on
AuthorizationBearer {{accessToken}}Authenticates the user with the serverAll requests
Key{{apiKey}}The API key acts as both a unique identifier and a secret token for authentication to a set of access rightsAll requests
Content-Typeapplication/jsonRequest format should be JSON, as the endpoint expects a JSON bodyPOST, PUT
Acceptapplication/jsonDeclares the acceptable response typeAll requests

4. POST: Create Listening Theme

Summary. Create a Listening Theme. operationId. ListeningThemeApiV3_createTheme Request body. ListeningTheme, required. Response. 200 with a ListeningTheme. Declared errors. 400, 401, 403, 404. V2 reference. Create Theme 2.0

The V2 page describes the contract precisely, and it holds in V3:

"You can create a Listening Theme via this API call and you will get the complete Listening Theme object along with unique Theme Id as response after making the request."

So the response is the full object, not just an id — read id from it and store that as your handle for later GET, PATCH, and DELETE calls.

4.1 Parameters

None. The create operation declares no path, query, header, or parameters. Everything travels in the body.

This differs from the other three methods on this path, all of which take an id query parameter.

4.2 Required fields

ListeningTheme declares exactly two required properties:

required:
- name
- filters

Everything else is optional. A minimum viable create body is:

{
  "name": "customer_service",
  "filters": [
    {
      "filterType": "IN",
      "field": "QUERY",
      "values": [
        {
          "advanceQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")",
          "textQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")"
        }
      ]
    }
  ]
}

4.3 Example request

curl -X POST \
  'https://api3.sprinklr.com/{env}/api/v3/listening/theme' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "name": "ListeningThemeAPITest",
    "displayName": "Customer Service",
    "color": "#4CAF50",
    "iconUrl": "https://cdn.example.com/icons/customer-service.png",
    "description": "Customer service related conversations and inquiries",
    "filters": [
      {
        "filterType": "IN",
        "field": "QUERY",
        "values": [
          {
            "advanceQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")",
            "textQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")"
          }
        ]
      }
    ],
    "tags": ["cs", "support", "priority"],
    "shareConfigs": [
      { "type": "USER", "ids": ["600000000"] }
    ],
    "permissions": [
      { "type": "USER", "ids": ["600000000"] }
    ]
  }'

Illustrative example. Values are placeholders; substitute your own ids, keyword-list ids, and credentials.

4.4 Example response

{
  "id": "60486cef06ebe67ed55c4140",
  "name": "ListeningThemeAPITest",
  "displayName": "Customer Service",
  "color": "#4CAF50",
  "iconUrl": "https://cdn.example.com/icons/customer-service.png",
  "description": "Customer service related conversations and inquiries",
  "filters": [
    {
      "filterType": "IN",
      "field": "QUERY",
      "values": [
        {
          "advanceQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")",
          "textQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")"
        }
      ],
      "details": {}
    }
  ],
  "tags": ["cs", "support", "priority"],
  "shareConfigs": [
    { "type": "USER", "ids": ["600000000"] }
  ],
  "permissions": [
    { "type": "USER", "ids": ["600000000"] }
  ]
}

Note what the V2 response echoed back that the V3 schema does not model: V2 returned filter sub-fields userFilter, allValuesAllowed, favourite, mandatory, locked, and details populated with server defaults. Filter_shifu models only filterType, field, values, details, and filters. If the server still returns those extra keys, your JSON parser must tolerate unmodelled properties.


5. The ListeningTheme object

This is the create request body and the response body. Unusually for the V3 contract, every property carries a description in the spec.

FieldTypeRequiredDescription (from sprinklr-v3.yaml)
idstring—Unique identifier of the Listening Theme in Sprinklr. Server-assigned; omit on create.
namestringRequiredListening Theme name.
displayNamestringOptionalListening Theme display name.
iconUrlstringOptionalListening Theme icon url.
imageUrlstringOptionalListening Theme image url.
colorstringOptionalListening Theme identification color.
filtersarray of Filter_shifuRequiredFilter list to filter out required data.
descriptionstringOptionalListening Theme description.
tagsarray of stringOptionalTags to group Listening Theme based on use case.
shareConfigsarray of AssetShareConfigOptionalShare config to view Listening Theme.
permissionsarray of AssetShareConfigOptionalPermission config to edit or update Listening Theme.

Schema description: "Unified view of a Listening Theme in Sprinklr."

5.1 Name versus DisplayName

The spec distinguishes them but does not say how they differ in behaviour. The V2 example is instructive: "name": "ListeningThemeAPITest" alongside "displayName": "sampleCreateThemeDisplayName1" — a machine-ish identifier and a human label.Read Theme example follows the same pattern: "name": "customer_service", "displayName": "Customer Service".

Recommended convention: use name as a stable, lower-case, underscore-separated key you control, and displayName as the label analysts see. Whether name must be unique per environment is unspecified.

5.2 Tags

Plain array of strings on the wire. In the product these become Theme Tags, which are considerably more powerful than the flat type suggests. Per Using Themes:

"Theme Tags can be used across the Listening module to group and use multiple Themes at once. They can be used as — Filters in dashboards, Dimensions in widgets, Filters in alerts, Conditions in rules."

Tag deliberately. A tag applied at create time is what later lets a dashboard filter, a widget dimension, an alert, or a rule address a whole family of Themes at once, without enumerating ids.

5.3 Color, IconUrl, Image Url

Presentation metadata surfaced in the Themes record manager and in dashboards. color is documented as the "identification color"; the V2 example uses "#FFFFF" — five hex digits, which is not a valid CSS hex colour. Read example uses a valid six-digit "#4CAF50". This guide uses six digits. Whether the field is validated is unknown. See §18, Q9.


6. Filters

filters is one of the two required fields and the one most likely to cause a 400. It is an array of Filter_shifu.

6.1 Filter_shifu schema

Schema description: "Describes the schema for filters in Sprinklr."

FieldTypeDescription
filterTypestringFilter type.
fieldstringSpecifies field on which filter is applied.
valuesarray of objectSpecifies values for this filter.
detailsobject (string → object)Additional details for filter.
filtersarray of Filter_shifuNested filters — the schema is recursive.

6.2 The values that actually work

Specification gap — no enums anywhere. filterType and field are declared as bare string with no enum, and values is an array of untyped object. The V3 contract will accept any string for either. The only known-good combination in any source is the one from the V2 Create Theme example, reproduced below.

The V2 page's working example:

{
  "filterType": "IN",
  "field": "QUERY",
  "values": [
    {
      "advanceQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")",
      "textQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")"
    }
  ]
}

Reading that: field: "QUERY" says filter on the query expression; filterType: "IN" says match against the supplied values; and each value object carries a paired advanceQuery and textQuery, both set to the same expression. The expression keyword_list_or("<keywordListId>") matches a mention if any keyword in that Keyword List matches — which is how a Theme reuses a Keyword List rather than restating its keywords.

Note the pairing of advanceQuery and textQuery. The V2 example sets both to the identical string, and this guide does the same. Their intended difference is undocumented. See §18, Q10.

6.3 The enum that exists but does not apply

The PATCH request schema uses a different filter type, Filter_request, whose filterType does carry an enum — FilterType_Filter:

FILTER · LIMIT · SEARCH · MATCH_PHRASE_PREFIX · ADHOC_SEARCH ·
EXPRESSION · GEO_DISTANCE · ADVANCED_QUERY · MATCH_NONE · MATCH_ALL

Filter_request also carries fifteen properties Filter_shifu lacks — displayName, isCompoundFilter, negativeFilter, accessibleValues, userFilter, allValuesAllowed, lockedWithValues, hidden, favourite, mandatory, locked, ignoreAddingToDimensionFilter, disabled, and advancedFilters. Several of those (userFilter, allValuesAllowed, favourite, mandatory, locked) are exactly the keys the V2 create response echoed back, which suggests Filter_request is closer to the real internal model and Filter_shifu is a reduced projection.

6.4 Nested filters

Both filter schemas are recursive via their own filters property, so compound logic is expressible by nesting. No source supplies a worked nested example, and Filter_shifu has no negativeFilter or isCompoundFilter flag to declare how children combine — Filter_request has both. How nested Filter_shifu children are combined (AND, OR) is unspecified. Prefer a single advanceQuery expression with Boolean operators over nesting until this is confirmed.

6.5 Location-based Themes

The Help Center documents Creating location-based Themes:

"In Listening Themes, you have the ability to filter data on the basis of the location of a message. Messages can be filtered out for a specific location by selecting the latitude, longitude, and radius of the search (in kilometers or miles). Note: To learn more about getting this capability enabled…"

FilterType_Filter includes GEO_DISTANCE, which is plainly the mechanism. But no source documents the field name or the values shape (latitude, longitude, radius, unit) for a geo filter, and the capability requires enablement. Build geo Themes in the UI and read them back with GET to learn the exact shape.


7. Sharing and permissions

Two optional arrays control who can see and who can edit a Theme. Both use the same AssetShareConfig schema.

FieldPurpose (from the spec)
shareConfigs"Share config to view Listening Theme"
permissions"Permission config to edit or update Listening Theme"

7.1 AssetShareConfig

Schema description: "Describes the schema for Asset Share config."

FieldTypeRequiredDescription
typestringRequiredType e.g. USER, GLOBAL, CLIENT.
idsarray of stringOptionalIDs based on type.

type is required whenever you supply an entry. The description gives three examples — USER, GLOBAL, CLIENT — introduced with *"e.g.", so the list is illustrative, not exhaustive, and there is no enum. The V2 model additionally distinguished user groups and client groups; whether V3 expresses those as further type values (USER_GROUP, CLIENT_GROUP) is not stated anywhere. See §18, Q14.

"shareConfigs": [
  { "type": "USER",   "ids": ["600000000", "600037831"] },
  { "type": "CLIENT", "ids": ["66000002"] }
]

7.2 Sharing with everyone

V2 had an explicit boolean, shareConfig.shareWithEveryOne. AssetShareConfig has no boolean at all. The likely V3 equivalent is {"type": "GLOBAL"} — possibly with ids omitted, since ids is optional and a global share has no specific target. This is inference from the type name, not documented behaviour.

7.3 Id types changed

In V2, user and client ids were List<Long> — JSON numbers. In V3, AssetShareConfig.ids is an array of string. Quote your ids:

- "userIds": [600000000]
+ "ids": ["600000000"]

A client that forwards V2 numeric ids into the V3 array will send numbers where strings are expected. Whether the server coerces them is unverified — quote them and avoid the question.


8. Response format and status codes

8.1 The envelope question

Note also the errors array. V2 returned "errors": [] on success, which implies a partial-success mode where a 200 carries errors. Always inspect errors even on 200. Its element shape is undocumented. See §18, Q17.

8.2 Declared status codes

CodeMeaningDeclared?
200Success — theme created, full object returned.Yes
400Bad Request — malformed body, missing name or filters, invalid filter.Yes
401Unauthorized — missing, expired, or invalid token.Yes
403Forbidden — authenticated but lacking the Create Theme permission.Yes
404Not Found — declared on create, though its meaning there is unclear.Yes
429Rate limited.Not declared
500Server error.Not declared

8.3 Error responses

All four operations return the same error schema for 400, 401, 403, and 404 — the shared ErrorResponse component.

ResponseDescription (from sprinklr-v3.yaml)Schema
BadRequest (400)Bad requestErrorResponse
Unauthorized (401)Missing or invalid authenticationErrorResponse
Forbidden (403)Insufficient permissionsErrorResponse
NotFound (404)Resource not foundErrorResponse

ErrorResponse:

FieldTypeDescription
dataobject, nullableNull or empty on an error.
errorsarray of ErrorThe failures.
metadataobjectUndescribed in the spec.

Error — all three properties are required:

FieldTypeDescription (from sprinklr-v3.yaml)
idstring24-character hex ObjectId
codeinteger— (no description)
messagestringDotted error key (e.g. account.not.found)
{
  "data": null,
  "errors": [
    {
      "id": "66badc8a6df5bf43f6c8f374",
      "code": 400,
      "message": "listening.theme.name.required"
    }
  ],
  "metadata": {}
}

Illustrative — the shape is from the schema, the literal values are invented. No source publishes the code values or the message keys for Listening Theme.

Specification gap — no error catalogue. No source publishes the code values or message keys this API emits, so you cannot write exhaustive error handling in advance. Log the full errors array on every failure and build your catalogue from observed traffic. See §16, Q33.


9. GET: Fetch Listening Themes

Summary. Fetch Listening Theme(s) by id. Supports bulk fetch. operationId. ListeningThemeApiV3_getThemes Request body. None. Response. 200 with an array of ListeningTheme. Declared errors. 400, 401, 403, 404. V2 reference. Read Theme 2.0, part of the Listening 2.0 suite.

This is the only Theme operation with a Jira description, a worked example, and recorded production verification. It is also, paradoxically, the operation whose documented response least resembles the OpenAPI schema.

9.1 Parameters

ParameterInTypeRequired (spec)Description (from sprinklr-v3.yaml)
idquerystringfalseComma-separated list of Listening Theme id

9.2 Bulk fetch

The parameter description — "Comma-separated list of Listening Theme id" — is what makes this operation bulk-capable, and the response is typed as an array even for a single id. Always iterate the result; never index [0] blindly.

curl -X GET \
  'https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=66badc8a6df5bf43f6c8f374,60486cef06ebe67ed55c4140' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Accept: application/json'

No source states an upper bound on how many ids one call accepts, whether the ids must all resolve, or what happens when one id in the list is invalid — does the whole call 404, or does it return the ones that resolved and report the rest in errors?

Nor is there any pagination, sorting, or filtering mechanism on this operation: no page, limit, offset, sort, or name-search parameter is declared. If you do not already know a Theme's id, the V3 API as specified gives you no documented way to discover it.

9.3 Example response

Per the OpenAPI document, the 200 body is an array of ListeningTheme objects:

[
  {
    "id": "66badc8a6df5bf43f6c8f374",
    "name": "customer_service",
    "displayName": "Customer Service",
    "iconUrl": "https://cdn.example.com/icons/customer-service.png",
    "color": "#4CAF50",
    "description": "Customer service related conversations and inquiries",
    "filters": [
      {
        "filterType": "IN",
        "field": "QUERY",
        "values": [
          { "advanceQuery": "support OR help OR issue", "textQuery": "support OR help OR issue" }
        ],
        "details": {}
      }
    ],
    "tags": ["cs", "support", "priority"],
    "shareConfigs": [ { "type": "USER", "ids": ["600000000"] } ],
    "permissions":  [ { "type": "USER", "ids": ["600000000"] } ]
  }
]

Illustrative, constructed from the OpenAPI schema.

9.4 The response example does not match the schema

This deserves its own subsection, because it is the sharpest source disagreement in this guide and it affects anyone writing a response parser.

Field in the ticket exampleIn ListeningTheme?Notes
appliedToTopicsNoArray of topic ids. Would be the Theme→Topic association — a genuinely useful field with no schema counterpart.
workflowNoA whole nested object, described below.
workflow.assignmentNo{assigneeId, assigneeType, assignedById, assignmentTime}.
workflow.customPropertiesNoMap of string → array of string.
workflow.permissionsNo{type, id, role} — singular id, plus a role.
workflow.tagsTop-level onlyThe ticket nests tags inside workflow; the schema has it at the top level.
workflow.shareConfigNo{sharedWithTeams, visibility}.
createdBy, modifiedByNoAudit metadata.
createdTime, modifiedTimeNoEpoch-millisecond timestamps.
activeNoBoolean.
deletedNoBoolean — the same field the V2 create example carried.

And three fields that exist but in a different shape:

  1. filters is an object, not an array. The ticket shows "filters": {"keywords": [...], "sentiment": [...], "priority": "HIGH"}. The schema says array of Filter_shifu. The V2 page shows an array of {filterType, field, values}. That is three mutually incompatible shapes for the single most important field on the entity.
  2. The id field is called themeId. The ticket's response-fields table lists themeId as "The unique identifier of the Listening Theme", while the ticket's own JSON example uses id and the schema uses id.
  3. shareConfig is singular and differently shaped — a fourth variant, {sharedWithTeams, visibility}, alongside the create model (shareConfigs array of {type, ids}), the patch model (shareConfig object of id lists), and the V2 model.

How to read this. The ticket's example JSON is also syntactically malformed — the id line reads id": "66badc8a… with the opening quote missing — which indicates it was hand-written into the ticket rather than captured from a live response. Combined with the invented workflow envelope and the themeId/id mismatch inside a single section, the example should be treated as illustrative prose, not observed behaviour.

Per this programme's precedence rule, the OpenAPI document outranks Jira description text, so this guide documents the schema shape. But the risk cuts both ways: if any part of the ticket example reflects reality, a strict parser bound to ListeningTheme will break. Parse permissively — tolerate unknown properties, and do not fail on a filters value that arrives as an object.


9.5 Verified GET scenarios

The following request and response scenarios supplement the GET contract with tested single-ID, multiple-ID, partial-success, and request-limit examples.

9.5.1 Retrieve a Theme by ID: Basic Response

Request Parameters

ParameterSub-parameterRequiredTypeDescription
idN/AYesStringUnique identifier of the Listening Theme to retrieve. Pass the theme ID as a query parameter in the request URL.

Example Request

curl --location '<{{baseUrl}}/api/v3/listening/theme?id=69957998530c7a6ee53206eb>' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}' \\

Example Response

{

"data": \[

{

"id": "69957998530c7a6ee53206eb",

"name": "Theme_Search_1771403672289",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"advanceQuery": "Google AND Facebook",

"textQuery": "(\\Google\\ AND \\Facebook\\)",

"themesInQuery": \[\]

}

\],

"details": {

"raw_search": false,

"type": "criteria"

},

"filters": \[\]

}

\],

"tags": \[

"ALERT_TAG_DND"

\],

"shareConfigs": \[

{

"type": "GLOBAL"

}

\]

}

\],

"errors": \[\]

}

Response Parameters

ParameterSub-parameterTypeDescription
dataN/AArrayList of Listening Theme objects returned by the API.
dataidStringUnique identifier of the theme.
datanameStringName of the theme.
datafiltersArrayList of filters associated with the theme.
data.filtersfilterTypeStringFilter operator applied to the field. Example: IN.
data.filtersfieldStringDimension or field on which the filter is applied.
data.filtersvaluesArrayList of values used for filtering.
data.filters.valuesadvanceQueryStringAdvanced query definition used by the theme.
data.filters.valuestextQueryStringParsed text representation of the query.
data.filters.valuesthemesInQueryArrayList of referenced themes used within the query.
data.filtersdetailsObjectAdditional filter configuration details.
data.filters.detailsraw_searchBooleanIndicates whether raw search is enabled.
data.filters.detailstypeStringType of filter criteria configured.
data.filtersfiltersArrayNested filters applied within the current filter. Returns an empty array when no nested filters exist.
datatagsArrayList of tags associated with the theme.
datashareConfigsArraySharing configuration for the theme.
data.shareConfigstypeStringSharing scope for the theme. Example: GLOBAL.
errorsN/AArrayList of errors encountered during request processing. Returns an empty array when the request is successful.

9.6 Retrieve a Theme by ID: Additional Basic Response

Example Request

curl --location '<{{baseUrl}}/api/v3/listening/theme?id=69957998530c7a6ee53206eb>' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}' \\

Request Parameters

ParameterSub-parameterRequiredTypeDescription
idN/AYesStringUnique identifier of the Listening Theme to retrieve. Pass the Theme ID as a query parameter in the request URL.

Example Response

{

"data": \[

{

"id": "69957998530c7a6ee53206eb",

"name": "Theme_Search_1771403672289",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"advanceQuery": "Google AND Facebook",

"textQuery": "(\\Google\\ AND \\Facebook\\)",

"themesInQuery": \[\]

}

\],

"details": {

"raw_search": false,

"type": "criteria"

},

"filters": \[\]

}

\],

"tags": \[

"ALERT_TAG_DND"

\],

"shareConfigs": \[

{

"type": "GLOBAL"

}

\]

}

\],

"errors": \[\]

}

Response Parameters

ParameterSub-parameterTypeDescription
dataN/AArrayList of Listening Theme objects returned by the API.
dataidStringUnique identifier of the theme.
datanameStringName of the theme.
datafiltersArrayList of filters associated with the theme.
data.filtersfilterTypeStringFilter operator applied to the field. Example: IN.
data.filtersfieldStringDimension or field on which the filter is applied.
data.filtersvaluesArrayList of values used for filtering.
data.filters.valuesadvanceQueryStringAdvanced query expression used in the theme filter.
data.filters.valuestextQueryStringText representation of the advanced query.
data.filters.valuesthemesInQueryArrayList of referenced themes used within the query.
data.filtersdetailsObjectAdditional filter configuration details.
data.filters.detailsraw_searchBooleanIndicates whether raw search mode is enabled.
data.filters.detailstypeStringType of filter configuration. Example: criteria.
data.filtersfiltersArrayNested filters defined within the current filter. Returns an empty array when no nested filters exist.
datatagsArrayList of tags associated with the theme.
datashareConfigsArraySharing configuration for the theme.
data.shareConfigstypeStringSharing scope of the theme. Example: GLOBAL.
errorsN/AArrayList of errors encountered during request processing. Returns an empty array when the request succeeds.

9.7 Retrieve a Theme by ID: Complex Filters

Example Request

curl --location --globoff '{{baseUrl}}/api/v3/listening/theme?id=6995af98d7890350f0d74510' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}'

**Request Parameters**

| **Parameter** | **Sub-parameter** | **Required** | **Type** | **Description** |
|----|----|----|----|----|
| id | N/A | Yes | String | Unique identifier of the Listening Theme to retrieve. Pass the Theme ID as a query parameter in the request URL |

Example Response

{

"data": \[

{

"id": "6995af98d7890350f0d74510",

"name": "Test-Theme-with-API",

"displayName": "Test-Theme-with-API",

"color": "#ca6969",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"orConditions": \[

{

"topicKeywords": \[\],

"operator": "AND",

"keywordGroupsRef": \[

{

"keywordGroupId": "67e657633d78d4090467b7e0",

"operator": "AND"

}

\],

"ordered": false,

"nearDistance": 0

}

\],

"andConditions": \[

{

"topicKeywords": \[\],

"operator": "OR",

"keywordGroupsRef": \[

{

"keywordGroupId": "6983038bdc0d4e0888a19de6",

"operator": "OR"

}

\],

"ordered": false,

"nearDistance": 0

}

\],

"excludeConditions": \[

{

"topicKeywords": \[

{

"term": "black",

"keywordType": "EXCLUDE_KEYWORD",

"distance": 0

}

\],

"operator": "OR",

"keywordGroupsRef": \[\],

"ordered": false,

"nearDistance": 0

}

\],

"textQuery": "(\\Rolex\\ AND (\\handcrafted clothing\\ OR \\artisan fashion\\ OR \\unique garments\\ OR \\fashion craftsmanship\\) AND NOT \\black\\)",

"themesInQuery": \[\]

}

\],

"details": {

"type": "criteria"

},

"filters": \[\]

},

{

"filterType": "IN",

"field": "POINT_RADIUS_LOCATION",

"values": \[

{

"type": "POINT_RADIUS",

"locationName": "G33W+946 Ambience Island, DLF Phase 3, Sector 24, Gurugram, Haryana, India",

"latitude": 28.5034,

"longitude": 77.0953,

"radius": 10,

"unit": "mi",

"id": "28.5034_77.0953"

}

\],

"details": {},

"filters": \[\]

}

\],

"tags": \[

"Air Travel Preferences",

"Credit Products - Retail Bank"

\]

}

\],

"errors": \[\]

}

Response Parameters

ParameterSub-parameterTypeDescription
dataN/AArrayList of Listening Theme objects returned by the API.
dataidStringUnique identifier of the theme.
datanameStringInternal name of the theme.
datadisplayNameStringDisplay name of the theme shown in the user interface.
datacolorStringHex color code associated with the theme.
datafiltersArrayList of filters associated with the theme.
data.filtersfilterTypeStringFilter operator applied to the field. Example: IN.
data.filtersfieldStringDimension or field on which the filter is applied.
data.filtersvaluesArrayList of values used for filtering.
data.filters.valuesorConditionsArrayCollection of OR conditions used in the query definition.
data.filters.values.orConditionstopicKeywordsArrayList of topic keywords included in the condition.
data.filters.values.orConditionsoperatorStringLogical operator applied to the condition.
data.filters.values.orConditionskeywordGroupsRefArrayReferences to keyword groups used in the condition.
data.filters.values.orConditions.keywordGroupsRefkeywordGroupIdStringUnique identifier of the referenced keyword group.
data.filters.values.orConditions.keywordGroupsRefoperatorStringLogical operator associated with the keyword group.
data.filters.values.orConditionsorderedBooleanIndicates whether keyword ordering is enforced.
data.filters.values.orConditionsnearDistanceIntegerDistance value used with proximity-based matching.
data.filters.valuesandConditionsArrayCollection of AND conditions used in the query definition.
data.filters.valuesexcludeConditionsArrayCollection of exclusion conditions used in the query definition.
data.filters.values.excludeConditions.topicKeywordstermStringExcluded keyword term.
data.filters.values.excludeConditions.topicKeywordskeywordTypeStringExcluded keyword type.
data.filters.values.excludeConditions.topicKeywordsdistanceIntegerDistance value for the excluded keyword.
data.filters.valuestextQueryStringHuman-readable representation of the complete query.
data.filters.valuesthemesInQueryArrayReferenced themes used within the query.
data.filtersdetailsObjectAdditional filter configuration details.
data.filters.detailstypeStringFilter configuration type.
data.filtersfiltersArrayNested filters associated with the current filter.
data.filtersfieldStringCan also represent location-based fields such as POINT_RADIUS_LOCATION.
data.filters.valuestypeStringLocation filter type. Example: POINT_RADIUS.
data.filters.valueslocationNameStringName of the configured location.
data.filters.valueslatitudeDoubleLatitude coordinate of the location.
data.filters.valueslongitudeDoubleLongitude coordinate of the location.
data.filters.valuesradiusIntegerRadius around the location.
data.filters.valuesunitStringUnit used for the radius (for example, mi).
data.filters.valuesidStringUnique identifier of the location filter.
datatagsArrayList of tags associated with the theme.
errorsN/AArrayList of errors encountered during request processing. Returns an empty array when the request succeeds

9.8 Retrieve Multiple Themes: All Found

Request Parameters

ParameterSub-parameterRequiredTypeDescription
idN/AYesStringComma-separated list of Listening Theme IDs to retrieve. The API returns details for each valid Theme identifier provided.

Example Request

curl --location --globoff '{{baseUrl}}/api/v3/listening/theme?id=69957998530c7a6ee53206eb%2C6995af98d7890350f0d74510' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}'

Example Response

{

"data": \[

{

"id": "69957998530c7a6ee53206eb",

"name": "Theme_Search_1771403672289",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"advanceQuery": "Google AND Facebook",

"textQuery": "(\\Google\\ AND \\Facebook\\)",

"themesInQuery": \[\]

}

\],

"details": {

"raw_search": false,

"type": "criteria"

},

"filters": \[\]

}

\],

"tags": \[

"ALERT_TAG_DND"

\],

"shareConfigs": \[

{

"type": "GLOBAL"

}

\]

},

{

"id": "6995af98d7890350f0d74510",

"name": "Test-Theme-with-API",

"displayName": "Test-Theme-with-API",

"color": "#ca6969",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"orConditions": \[

{

"topicKeywords": \[\],

"operator": "AND",

"keywordGroupsRef": \[

{

"keywordGroupId": "67e657633d78d4090467b7e0",

"operator": "AND"

}

\],

"ordered": false,

"nearDistance": 0

}

\],

"andConditions": \[

{

"topicKeywords": \[\],

"operator": "OR",

"keywordGroupsRef": \[

{

"keywordGroupId": "6983038bdc0d4e0888a19de6",

"operator": "OR"

}

\],

"ordered": false,

"nearDistance": 0

}

\],

"excludeConditions": \[

{

"topicKeywords": \[

{

"term": "black",

"keywordType": "EXCLUDE_KEYWORD",

"distance": 0

}

\],

"operator": "OR",

"keywordGroupsRef": \[\],

"ordered": false,

"nearDistance": 0

}

\],

"textQuery": "(\\Rolex\\ AND (\\handcrafted clothing\\ OR \\artisan fashion\\ OR \\unique garments\\ OR \\fashion craftsmanship\\) AND NOT \\black\\)",

"themesInQuery": \[\]

}

\],

"details": {

"type": "criteria"

},

"filters": \[\]

},

{

"filterType": "IN",

"field": "POINT_RADIUS_LOCATION",

"values": \[

{

"type": "POINT_RADIUS",

"locationName": "G33W+946 Ambience Island, DLF Phase 3, Sector 24, Gurugram, Haryana, India",

"latitude": 28.5034,

"longitude": 77.0953,

"radius": 10,

"unit": "mi",

"id": "28.5034_77.0953"

}

\],

"details": {},

"filters": \[\]

}

\],

"tags": \[

"Air Travel Preferences",

"Credit Products - Retail Bank"

\]

}

\],

"errors": \[\]

}

Response Parameters

ParameterSub-parameterTypeDescription
dataN/AArrayList of Listening Theme objects returned by the API.
dataidStringUnique identifier of the theme.
datanameStringName of the theme.
datadisplayNameStringDisplay name of the theme shown in the user interface.
datacolorStringHex color code associated with the theme.
datafiltersArrayList of filters associated with the theme.
data.filtersfilterTypeStringFilter operator applied to the field. Example: IN.
data.filtersfieldStringDimension or field on which the filter is applied. Examples: QUERY, POINT_RADIUS_LOCATION.
data.filtersvaluesArrayList of values used for filtering.
data.filters.valuesadvanceQueryStringAdvanced query expression used in the theme filter.
data.filters.valuestextQueryStringText representation of the query.
data.filters.valuesthemesInQueryArrayList of referenced themes used in the query.
data.filters.valuesorConditionsArrayCollection of OR conditions configured in the query.
data.filters.valuesandConditionsArrayCollection of AND conditions configured in the query.
data.filters.valuesexcludeConditionsArrayCollection of exclusion conditions configured in the query.
data.filters.values.orConditionstopicKeywordsArrayKeywords used in the OR condition.
data.filters.values.orConditionsoperatorStringLogical operator applied to the condition.
data.filters.values.orConditionskeywordGroupsRefArrayReferences to keyword groups used in the condition.
data.filters.values.orConditions.keywordGroupsRefkeywordGroupIdStringUnique identifier of the referenced keyword group.
data.filters.values.orConditions.keywordGroupsRefoperatorStringLogical operator applied to the referenced keyword group.
data.filters.values.excludeConditions.topicKeywordstermStringExcluded keyword term.
data.filters.values.excludeConditions.topicKeywordskeywordTypeStringType of excluded keyword.
data.filters.values.excludeConditions.topicKeywordsdistanceIntegerDistance value for the keyword.
data.filtersdetailsObjectAdditional filter configuration details.
data.filters.detailsraw_searchBooleanIndicates whether raw search mode is enabled.
data.filters.detailstypeStringFilter configuration type.
data.filtersfiltersArrayNested filters associated with the current filter.
data.filters.valuestypeStringLocation filter type. Example: POINT_RADIUS.
data.filters.valueslocationNameStringName of the configured location.
data.filters.valueslatitudeDoubleLatitude coordinate of the location.
data.filters.valueslongitudeDoubleLongitude coordinate of the location.
data.filters.valuesradiusIntegerRadius around the location.
data.filters.valuesunitStringUnit used for the radius.
data.filters.valuesidStringUnique identifier of the location filter.
datatagsArrayList of tags associated with the theme.
datashareConfigsArraySharing configuration for the theme.
data.shareConfigstypeStringSharing scope for the theme. Example: GLOBAL.
errorsN/AArrayList of errors encountered during request processing. Returns an empty array when the request succeeds.

9.9 Retrieve Multiple Themes: Partial Success

Request Parameters

ParameterSub-parameterRequiredTypeDescription
idN/AYesStringComma-separated list of Listening Theme IDs to retrieve. The request can include valid IDs, invalid IDs, non-existent IDs, and empty values. The API attempts to retrieve all valid themes and returns errors for invalid entries.

Example Request

curl --location --globoff '{{baseUrl}}/api/v3/listening/theme?id=69957998530c7a6ee53206eb%2C6995af98d7890350f0d74510%2CINVALID%2Cabcd%2C%2Ctest%2C6995af98d7890350f0d74511' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}'

Example Response

{

"data": \[

{

"id": "69957998530c7a6ee53206eb",

"name": "Theme_Search_1771403672289",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"advanceQuery": "Google AND Facebook",

"textQuery": "(\\Google\\ AND \\Facebook\\)",

"themesInQuery": \[\]

}

\],

"details": {

"raw_search": false,

"type": "criteria"

},

"filters": \[\]

}

\],

"tags": \[

"ALERT_TAG_DND"

\],

"shareConfigs": \[

{

"type": "GLOBAL"

}

\]

},

{

"id": "6995af98d7890350f0d74510",

"name": "Test-Theme-with-API",

"displayName": "Test-Theme-with-API",

"color": "#ca6969",

"filters": \[

{

"filterType": "IN",

"field": "QUERY",

"values": \[

{

"orConditions": \[

{

"topicKeywords": \[\],

"operator": "AND",

"keywordGroupsRef": \[

{

"keywordGroupId": "67e657633d78d4090467b7e0",

"operator": "AND"

}

\],

"ordered": false,

"nearDistance": 0

}

\],

"andConditions": \[

{

"topicKeywords": \[\],

"operator": "OR",

"keywordGroupsRef": \[

{

"keywordGroupId": "6983038bdc0d4e0888a19de6",

"operator": "OR"

}

\],

"ordered": false,

"nearDistance": 0

}

\],

"excludeConditions": \[

{

"topicKeywords": \[

{

"term": "black",

"keywordType": "EXCLUDE_KEYWORD",

"distance": 0

}

\],

"operator": "OR",

"keywordGroupsRef": \[\],

"ordered": false,

"nearDistance": 0

}

\],

"textQuery": "(\\Rolex\\ AND (\\handcrafted clothing\\ OR \\artisan fashion\\ OR \\unique garments\\ OR \\fashion craftsmanship\\) AND NOT \\black\\)",

"themesInQuery": \[\]

}

\],

"details": {

"type": "criteria"

},

"filters": \[\]

},

{

"filterType": "IN",

"field": "POINT_RADIUS_LOCATION",

"values": \[

{

"type": "POINT_RADIUS",

"locationName": "G33W+946 Ambience Island, DLF Phase 3, Sector 24, Gurugram, Haryana, India",

"latitude": 28.5034,

"longitude": 77.0953,

"radius": 10,

"unit": "mi",

"id": "28.5034_77.0953"

}

\],

"details": {},

"filters": \[\]

}

\],

"tags": \[

"Air Travel Preferences",

"Credit Products - Retail Bank"

\]

}

\],

"errors": \[

{

"id": "6995b00af58d8b601be9ed3e",

"code": 404,

"message": "Listening_theme not found for id: 'INVALID'. Verify the listening_theme exists and you have access permissions."

},

{

"id": "6995b00af58d8b601be9ed3f",

"code": 404,

"message": "Listening_theme not found for id: 'abcd'. Verify the listening_theme exists and you have access permissions."

},

{

"id": "6995b00af58d8b601be9ed3d",

"code": 400,

"message": "Invalid id format: ''. Value cannot be empty or blank."

},

{

"id": "6995b00af58d8b601be9ed40",

"code": 404,

"message": "Listening_theme not found for id: 'test'. Verify the listening_theme exists and you have access permissions."

},

{

"id": "6995b00af58d8b601be9ed41",

"code": 404,

"message": "Listening_theme not found for id: '6995af98d7890350f0d74511'. Verify the listening_theme exists and you have access permissions."

}

\]

}

**Response Parameters**

| **Parameter** | **Sub-parameter** | **Type** | **Description** |
|----|----|----|----|
| data | N/A | Array | List of Listening Theme objects successfully retrieved by the API. |
| data | id | String | Unique identifier of the theme. |
| data | name | String | Name of the theme. |
| data | displayName | String | Display name of the theme shown in the user interface. |
| data | color | String | Hex color code associated with the theme. |
| data | filters | Array | List of filters associated with the theme. |
| data.filters | filterType | String | Filter operator applied to the field. Example: IN. |
| data.filters | field | String | Dimension or field on which the filter is applied. Examples: QUERY, POINT_RADIUS_LOCATION. |
| data.filters | values | Array | List of values used for filtering. |
| data.filters.values | advanceQuery | String | Advanced query expression used in the theme filter. |
| data.filters.values | textQuery | String | Text representation of the query. |
| data.filters.values | themesInQuery | Array | List of referenced themes used within the query. |
| data.filters.values | orConditions | Array | Collection of OR conditions configured in the query. |
| data.filters.values | andConditions | Array | Collection of AND conditions configured in the query. |
| data.filters.values | excludeConditions | Array | Collection of exclusion conditions configured in the query. |
| data.filters | details | Object | Additional filter configuration details. |
| data.filters.details | raw_search | Boolean | Indicates whether raw search mode is enabled. |
| data.filters.details | type | String | Type of filter configuration. |
| data.filters | filters | Array | Nested filters associated with the current filter. |
| data | tags | Array | List of tags associated with the theme. |
| data | shareConfigs | Array | Sharing configuration for the theme. |
| data.shareConfigs | type | String | Sharing scope for the theme (for example, GLOBAL). |
| errors | N/A | Array | List of errors encountered while processing the request. |
| errors | id | String | Unique identifier associated with the error record. |
| errors | code | Integer | Error code indicating the type of validation or lookup failure. |
| errors | message | String | Detailed error message describing the issue. |

### **Error Parameters**

| **Parameter** | **Sub-parameter** | **Type** | **Description** |
|----|----|----|----|
| errors | id | String | Identifier associated with the error entry. |
| errors | code | Integer | HTTP-style error code returned by the API (400, 404). |
| errors | message | String | Human-readable error message describing the validation or lookup failure. |

#### 9.10 Retrieve More Than 50 Themes

**Request Parameters**

| **Parameter** | **Sub-parameter** | **Required** | **Type** | **Description** |
|----|----|----|----|----|
| id | N/A | Yes | String | Comma-separated list of Listening Theme IDs to retrieve. A maximum of **50 theme identifiers** can be provided in a single request. Requests exceeding this limit return a validation error. |

**Example Request**

curl --location --globoff '{{baseUrl}}/api/v3/listening/theme?id=id1%2Cid2%2Cid3%2Cid4%2Cid5%2Cid6%2Cid7%2Cid8%2Cid9%2Cid10%2Cid11%2Cid12%2Cid13%2Cid14%2Cid15%2Cid16%2Cid17%2Cid18%2Cid19%2Cid20%2Cid21%2Cid22%2Cid23%2Cid24%2Cid25%2Cid26%2Cid27%2Cid28%2Cid29%2Cid30%2Cid31%2Cid32%2Cid33%2Cid34%2Cid35%2Cid36%2Cid37%2Cid38%2Cid39%2Cid40%2Cid41%2Cid42%2Cid43%2Cid44%2Cid45%2Cid46%2Cid47%2Cid48%2Cid49%2Cid50%2Cid51' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}'

**Example Response**

{

"errors": \[

{

"id": "6995b066f58d8b601be9ed44",

"code": 400,

"message": "Maximum of 50 listening_theme identifiers supported per request. Provided: 51. Consider splitting you request in batches of 50 identifiers per request"

}

\]

}

**Response Parameters**

| **Parameter** | **Sub-parameter** | **Type** | **Description** |
|----|----|----|----|
| errors | N/A | Array | List of errors encountered while processing the request. |
| errors | id | String | Unique identifier associated with the error record. |
| errors | code | Integer | Error code indicating the type of validation failure. |
| errors | message | String | Detailed error message describing the validation error and recommended resolution. |

#### 9.11 Retrieve a Theme with Complete Sharing Configuration

 **Request Parameters**

| **Parameter** | **Sub-parameter** | **Required** | **Type** | **Description** |
|----|----|----|----|----|
| id | N/A | Yes | String | Unique identifier of the Listening Theme to retrieve. Pass the Theme ID as a query parameter in the request URL |

#### 9.12 **Example Request**
```bash
curl --location '<{{baseUrl}}/api/v3/listening/theme?id=6995af98d7890350f0d74510>' \\

--header 'Accept: application/json' \\

--header 'Authorization: Bearer {{accessToken}}' \\

--header 'Key: {{apiKey}}' \\

9.13 Example Response

{

"data": [

{

"id": "6995af98d7890350f0d74510",

"name": "Test-Theme-with-API",

"displayName": "Test-Theme-with-API",

"color": "#ca6969",

"filters": [

{

"filterType": "IN",

"field": "QUERY",

"values": [

{

"orConditions": [

{

"topicKeywords": [],

"operator": "AND",

"keywordGroupsRef": [

{

"keywordGroupId": "67e657633d78d4090467b7e0",

"operator": "AND"

}

],

"ordered": false,

"nearDistance": 0

}

],

"andConditions": [

{

"topicKeywords": [],

"operator": "OR",

"keywordGroupsRef": [

{

"keywordGroupId": "6983038bdc0d4e0888a19de6",

"operator": "OR"

}

],

"ordered": false,

"nearDistance": 0

}

],

"excludeConditions": [

{

"topicKeywords": [

{

"term": "black",

"keywordType": "EXCLUDE_KEYWORD",

"distance": 0

}

],

"operator": "OR",

"keywordGroupsRef": [],

"ordered": false,

"nearDistance": 0

}

],

"textQuery": "(\\Rolex\\ AND (\\handcrafted clothing\\ OR \\artisan fashion\\ OR \\unique garments\\ OR \\fashion craftsmanship\\) AND NOT \\black\\)",

"themesInQuery": \[\]

}

\],

"details": {

"type": "criteria"

},

"filters": \[\]

},

{

"filterType": "IN",

"field": "POINT_RADIUS_LOCATION",

"values": \[

{

"type": "POINT_RADIUS",

"locationName": "G33W+946 Ambience Island, DLF Phase 3, Sector 24, Gurugram, Haryana, India",

"latitude": 28.5034,

"longitude": 77.0953,

"radius": 10,

"unit": "mi",

"id": "28.5034_77.0953"

}

\],

"details": {},

"filters": \[\]

}

\],

"tags": \[

"Air Travel Preferences",

"Credit Products - Retail Bank"

\],

"shareConfigs": \[

{

"type": "CLIENT",

"ids": \[

"66000002"

\]

},

{

"type": "CLIENT_GROUP",

"ids": \[

"69956ee7bd0cf95d4a5b43a5"

\]

},

{

"type": "USER",

"ids": \[

"66006626",

"66044670",

"66044665",

"66007863",

"66044664",

"66062939",

"66044660"

\]

},

{

"type": "USER_GROUP",

"ids": \[\]

}

\]

}

\],

"errors": \[\]

}

Response Parameters

ParameterSub-parameterTypeDescription
dataN/AArrayList of Listening Theme objects returned by the API.
dataidStringUnique identifier of the theme.
datanameStringName of the theme.
datadisplayNameStringDisplay name of the theme.
datacolorStringHex color code associated with the theme.
datafiltersArrayList of filters associated with the theme.
data.filtersfilterTypeStringFilter operator applied to the field. Example: IN.
data.filtersfieldStringDimension or field on which the filter is applied. Examples include QUERY and POINT_RADIUS_LOCATION.
data.filtersvaluesArrayList of values used for filtering.
data.filters.valuesorConditionsArrayCollection of OR conditions configured in the query.
data.filters.valuesandConditionsArrayCollection of AND conditions configured in the query.
data.filters.valuesexcludeConditionsArrayCollection of exclusion conditions configured in the query.
data.filters.valuesadvanceQueryStringAdvanced query expression used in the theme filter.
data.filters.valuestextQueryStringHuman-readable representation of the query.
data.filters.valuesthemesInQueryArrayList of referenced themes used within the query.
data.filters.values.orConditionstopicKeywordsArrayTopic keywords included in the condition.
data.filters.values.orConditionsoperatorStringLogical operator applied to the condition.
data.filters.values.orConditionskeywordGroupsRefArrayReferenced keyword groups used in the condition.
data.filters.values.orConditions.keywordGroupsRefkeywordGroupIdStringUnique identifier of the referenced keyword group.
data.filters.values.orConditions.keywordGroupsRefoperatorStringLogical operator associated with the keyword group.
data.filters.values.excludeConditions.topicKeywordstermStringExcluded keyword term.
data.filters.values.excludeConditions.topicKeywordskeywordTypeStringType of excluded keyword.
data.filters.values.excludeConditions.topicKeywordsdistanceIntegerDistance value associated with the excluded keyword.
data.filtersdetailsObjectAdditional filter configuration details.
data.filters.detailstypeStringFilter configuration type.
data.filters.detailsraw_searchBooleanIndicates whether raw search mode is enabled.
data.filtersfiltersArrayNested filters associated with the current filter.
data.filters.valuestypeStringLocation filter type. Example: POINT_RADIUS.
data.filters.valueslocationNameStringName of the configured location.
data.filters.valueslatitudeDoubleLatitude coordinate of the location.
data.filters.valueslongitudeDoubleLongitude coordinate of the location.
data.filters.valuesradiusIntegerRadius around the location.
data.filters.valuesunitStringUnit used for the radius.
data.filters.valuesidStringUnique identifier of the location filter.
datatagsArrayList of tags associated with the theme.
datashareConfigsArraySharing configuration for the theme.
data.shareConfigstypeStringSharing scope (CLIENT, CLIENT_GROUP, USER, USER_GROUP, GLOBAL).
data.shareConfigsidsArrayIdentifiers of entities granted access to the theme.
errorsN/AArrayList of errors encountered during request processing. Returns an empty array when the request succeeds.

10. PATCH: Partially Update a Listening Theme

Summary. Partially update a Listening Theme. operationId. ListeningThemeApiV3_patchTheme Request body. ListeningThemePatchRequestDTO, required. Response. 200 with type: string. Declared errors. 400, 401, 403, 404. V2 reference. Update Theme 2.0, part of the Listening 2.0 suite. Ticket coverage. **None.**Read/Fetch-scoped. Permission. Conversations → Listening Settings → Edit Theme.

10.1 Parameters

ParameterInTypeRequired (spec)Description
idquerystringrequiredListening theme id

Note the description is singular — "Listening theme id" — with no mention of comma separation. Unlike GET and DELETE, PATCH is single-target: you can bulk-fetch and bulk-delete, but you must update Themes one at a time.

10.2 Listening Theme Patch Request DTO

Schema description: "Partial update for a Listening Theme."

FieldTypeRequiredDescription (from sprinklr-v3.yaml)
namestring—Listening Theme name
displayNamestring—Listening Theme display name
iconUrlstring—Listening Theme icon url
imageUrlstring—Listening Theme image url
colorstring—Listening Theme identification color
filtersarray of Filter_request—Filter list
descriptionstring—Description
tagsarray of string—Tags
shareConfigShareConfig (single object)—Share configuration
permissionEntityPermissionEntity (single object)—Permission configuration

The schema declares no required array at all — every field is optional, which is consistent with partial-update semantics. It also means an empty body {} satisfies the schema, and id is required: false, so PATCH /listening/theme with an empty body is a spec-valid request with entirely undefined behaviour.

Note that id is absent from the body schema — the target travels only in the query string.

10.3 Share Config and Permission Entity

Both are the V2-era shapes, and in V3 they have identical property sets — the two schemas differ only in name and description.

FieldTypeDescription
userIdsarray of integer (int64)List of User IDs, which is unique identifier for each User
userGroupIdsarray of stringList of User Group IDs, which is unique identifier for each User Group
clientIdsarray of integer (int64)List of Client IDs, which is unique identifier for Client
clientGroupIdsarray of stringList of Client Group IDs, which is unique identifier for Client Group
shareWithEveryOnebooleanSet this true, in order to share with every User

ShareConfig is described as "Describes the schema for Share Config"; PermissionEntity as "Describes the schema for Permission".

Two things follow:

  • Ids are numbers here, strings in create. ShareConfig.userIds is an array of int64; AssetShareConfig.ids is an array of string. The same user id must be serialised as 600000000 in a PATCH and "600000000" in a POST.
  • The V2 documentation understated permissionEntity. The V2 Create Theme page documented permissionEntity with only userIds and userGroupIds. The V3 PermissionEntity schema carries all five properties, including clientIds, clientGroupIds, and shareWithEveryOne. Whether the extra three are honoured on a permission entity, or simply inherited from a shared base class, is unknown.

Unlike create, PATCH retains a direct equivalent of V2's shareWithEveryOne

10.4 Filter_request — the other filter schema

PATCH filters use Filter_request, which is substantially richer than the Filter_shifu used on create:

FieldTypeNotes
filterTypeFilterType_FilterEnumerated — see below.
fieldstringField the filter applies to.
valuesarray of objectFilter values.
displayNamestringAbsent from Filter_shifu.
isCompoundFilterbooleanAbsent from Filter_shifu.
negativeFilterbooleanNegation. Absent from Filter_shifu.
accessibleValuesarray of objectAbsent from Filter_shifu.
userFilterbooleanEchoed by the V2 create response.
allValuesAllowedbooleanEchoed by the V2 create response.
lockedWithValuesbooleanAbsent from Filter_shifu.
hiddenbooleanAbsent from Filter_shifu.
favouritebooleanEchoed by the V2 create response.
mandatorybooleanEchoed by the V2 create response.
lockedbooleanEchoed by the V2 create response.
ignoreAddingToDimensionFilterbooleanAbsent from Filter_shifu.
disabledbooleanAbsent from Filter_shifu.
detailsobject (string → object)Also in Filter_shifu.
filtersarray of Filter_requestRecursive nesting.
advancedFiltersarray of AdvancedFilterRequestAbsent from Filter_shifu.

filterType here is constrained, by the FilterType_Filter enum:

FILTER · LIMIT · SEARCH · MATCH_PHRASE_PREFIX · ADHOC_SEARCH ·
EXPRESSION · GEO_DISTANCE · ADVANCED_QUERY · MATCH_NONE · MATCH_ALL

IN — the only filter type evidenced anywhere for Themes — is not in this list. So a filter that is valid on create may be rejected on patch. Unlike Filter_shifu, Filter_request has no description on any property, so the meaning of the sixteen flags is undocumented.

Because five of Filter_request's exclusive fields (userFilter, allValuesAllowed, favourite, mandatory, locked) are exactly the keys the V2 create response echoed back, Filter_request is very likely the true internal filter model, and Filter_shifu a reduced projection of it.

10.5 Partial-update semantics are undefined

The operation is named "partial update", but no source states how omitted or supplied collections are handled:

  • Omitted field — left unchanged (the normal PATCH reading), or reset to null?
  • Supplied array (filters, tags) — does it replace the existing array wholesale, or merge with it?
  • Empty array [] — clears the collection, or is it a no-op?
  • shareConfig — does supplying it replace the whole share configuration, or merge id lists?

There is no If-Match, ETag, or version field on the entity, so concurrent patches are last-write-wins with no optimistic-concurrency mechanism. The safest pattern is a serialised read-modify-write per Theme — but §10 opening callout shows you cannot mechanically round-trip a GET body into a PATCH body, so you must translate the three divergent structures yourself.

10.6 Example request

curl -X PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=66badc8a6df5bf43f6c8f374' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "displayName": "Customer Service & Support",
    "description": "Customer service conversations, inquiries, and escalations",
    "color": "#2E7D32",
    "tags": ["cs", "support", "priority", "escalations"],
    "shareConfig": {
      "userIds": [600000000, 600037831],
      "userGroupIds": ["ug-support"],
      "shareWithEveryOne": false
    },
    "permissionEntity": {
      "userIds": [600000000]
    }
  }'

10.7 Response

The 200 response is declared as type: string — a bare JSON string, not an object. No source states what it contains: the updated Theme's id, a status word such as SUCCESS, or a human-readable message.

Consequences for your client:

  • You cannot read the updated entity back from the response. Issue a follow-up GET if you need to confirm the result.
  • Do not parse the response as JSON into a model. Treat a 200 as success and the body as opaque.
  • If the envelope described applies, the body may actually be {"data": "…", "errors": []} — in which case checking errors remains essential.

11. DELETE — Delete Listening Theme(s)

Summary. Delete Listening Theme(s) by id. Supports bulk delete. V2 reference. Delete Theme 2.0, part of the Listening 2.0 suite. Permission. Conversations → Listening Settings → Delete Theme.

11.1 Parameters

ParameterInTypeRequired (spec)Description
idquerystringfalseComma-separated listening theme ids

11.2 Bulk delete

# Single
curl -X DELETE \
  'https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=66badc8a6df5bf43f6c8f374' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}'

# Bulk — comma-separated, no spaces
curl -X DELETE \
  'https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=66badc8a6df5bf43f6c8f374,60486cef06ebe67ed55c4140' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}'

Do not put spaces after the commas; no source shows whitespace being tolerated, and an unencoded space is not valid in a query string.

Partial failure is unspecified. If one id in a bulk delete does not exist, or the caller lacks permission on one of them, no source states whether the call is atomic (all-or-nothing), best-effort (deletes what it can), or fails outright. With a type: string response body there is also no per-id result structure to report outcomes. Prefer deleting in small batches, or one at a time, when you need to know exactly what happened.

11.3 Response

As with PATCH, the 200 body is declared type: string with no described content. You cannot determine from the response how many Themes were actually deleted. Confirm with a follow-up GET on the same ids and check that they no longer resolve.

11.4 Soft delete or hard delete?

Both the V2 create example and read example carry a deleted boolean on the Theme object, which strongly suggests the platform soft-deletes Themes rather than removing the record. But deleted is absent from the V3 ListeningTheme schema, so a V3 client has no way to observe the flag.

Open questions this raises:

  • After DELETE, does a subsequent GET on that id return 404, or return the object with deleted: true (a field your model does not have)?
  • Can a deleted Theme be restored, and if so, by what call?
  • Does the name become reusable for a new Theme after deletion?

None is answerable from the available sources.

11.5 What happens to things that reference the Theme

A Theme is referenced from several places in the product — dashboard filters, widget dimensions, alert conditions, and rule conditions, all of which can target Themes via Theme Tags. Read example also shows an appliedToTopics array associating a Theme with Topics.

No source documents what deleting a Theme does to those references: whether the delete is blocked while references exist, whether references are silently dropped, or whether dashboards break. Because a Theme is a read-time lens rather than an ingestion rule, deleting one cannot cause data loss in your mention store — but it can break saved dashboards and reporting that segment by it.

Recommended practice: GET the Theme and record its full definition before deleting, so it can be recreated if a dependency surfaces. There is no undo documented.


12. V2 → V3 migration

12.1 Endpoint mapping

OperationV2V3Change
Create Theme (V2)POST /api/v2/listening-themePOST /api/v3/listening/themeBreaking. Hyphenated listening-theme becomes the nested path listening/theme.
Read ThemeGET /api/v2/listening-theme/{id}GET /api/v3/listening/theme?id={id}Breaking. Id moves path → query; gains bulk support.
Update ThemePUT /api/v2/listening-theme/{id}PATCH /api/v3/listening/theme?id={id}Breaking. Method changes PUT → PATCH; id moves path → query.
Delete ThemeDELETE /api/v2/listening-theme/{id}DELETE /api/v3/listening/theme?id={id}Breaking. Id moves path → query; gains bulk support.

V2 read/update/delete path forms are inferred from the V2 suite's structure; only the create path is quoted verbatim from the V2 page.

This follows the V3 programme's consistent rewrite rule, seen across every entity documented so far: identifiers move from the path into query parameters, and hyphenated resource names become nested path segments. A v2 → v3 string replacement will not work.

12.2 The create body — what changed

V2 fieldV3 fieldChange
namenameUnchanged. Required in both.
displayNamedisplayNameUnchanged.
iconUrliconUrlUnchanged.
imageUrlimageUrlUnchanged.
colorcolorUnchanged.
descriptiondescriptionUnchanged.
filtersfiltersName unchanged. Type documentation changed — V2 documented it as List<String>, which its own example contradicts by sending objects. V3 correctly types it as an array of Filter_shifu.
tagstagsUnchanged.
shareConfig (object)shareConfigs (array)Breaking.
permissionEntity (object)permissions (array)Breaking.
deleted (in V2 example)—Removed. Not in ListeningTheme. Drop it.

The V2 page documented filters as List<String> — "The Filter list to filter out required data. List<String>" — while its own example sends an array of objects. V3's typing is the correct one; if you built against the V2 documentation's stated type rather than its example, your code was already wrong.

12.3 Sharing and permissions are restructured, not renamed

This is the largest breaking change and the easiest to get subtly wrong, because the V3 name permissions is close enough to V2's permissionEntity to look like a simple rename. It is not.

V2 — two objects, four id lists each, keyed by id type:

"shareConfig": {
  "userIds": [600000000],
  "userGroupIds": ["ug-1"],
  "clientIds": [66000002],
  "clientGroupIds": ["cg-1"],
  "shareWithEveryOne": false
},
"permissionEntity": {
  "userIds": [600000000],
  "userGroupIds": ["ug-1"]
}

V3 — two arrays of {type, ids} pairs:

"shareConfigs": [
  { "type": "USER",   "ids": ["600000000"] },
  { "type": "CLIENT", "ids": ["66000002"] }
],
"permissions": [
  { "type": "USER", "ids": ["600000000"] }
]

The model inverts: V2 keyed id lists by field name, V3 keys them by a type discriminator. Migration steps:

  1. Rename shareConfig → shareConfigs and permissionEntity → permissions, and change both from object to array.
  2. Convert each V2 id list into an array element with the matching type: userIds → {"type": "USER", …}, clientIds → {"type": "CLIENT", …}.
  3. Stringify all ids. V2 userIds and clientIds were List<Long>; V3 ids is an array of string.
  4. Map shareWithEveryOne: true to — most likely — {"type": "GLOBAL"}. Unconfirmed;
  5. Find the V3 type values for userGroupIds and clientGroupIds. Not documented;

12.4 What else to check

AreaV2V3Action
Response envelope{"data": {…}, "errors": []}Spec says bare objectUnwrap defensively.
Update methodPUTPATCHChange the verb, and send partial bodies.
Bulk operationsOne id per callGET/DELETE accept comma-separated idsBatch reads and deletes.
Filter type valueINNot in the patch enumVerify.
deleted fieldIn the V2 example bodyNot in ListeningThemeRemove from your payload.
Host / envapi3.sprinklr.com/{env}/…{env}.api.sprinklr.com/… perConfirm with your account team.

13. Worked examples

All examples are illustrative and reconstructed. Verify against your environment.

13.1 Minimal Theme

curl -X POST 'https://api3.sprinklr.com/{env}/api/v3/listening/theme' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "pricing_mentions",
    "filters": [
      {
        "filterType": "IN",
        "field": "QUERY",
        "values": [
          { "advanceQuery": "pricing OR price OR cost", "textQuery": "pricing OR price OR cost" }
        ]
      }
    ]
  }'

13.2 Theme reusing an existing Keyword List

The pattern from the V2 page — reference a Keyword List by id rather than restating its keywords, so that updating the list updates every Theme that uses it.

curl -X POST 'https://api3.sprinklr.com/{env}/api/v3/listening/theme' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "competitor_mentions",
    "displayName": "Competitor Mentions",
    "description": "Mentions matching the maintained competitor keyword list",
    "color": "#E53935",
    "tags": ["competitive-intel"],
    "filters": [
      {
        "filterType": "IN",
        "field": "QUERY",
        "values": [
          {
            "advanceQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")",
            "textQuery": "keyword_list_or(\"5f601c2728cf4e53142af770\")"
          }
        ]
      }
    ]
  }'

13.3 Theme shared with a team, editable by two users

curl -X POST 'https://api3.sprinklr.com/{env}/api/v3/listening/theme' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "customer_service",
    "displayName": "Customer Service",
    "color": "#4CAF50",
    "tags": ["cs", "support"],
    "filters": [
      {
        "filterType": "IN",
        "field": "QUERY",
        "values": [
          { "advanceQuery": "support OR help OR issue OR problem", "textQuery": "support OR help OR issue OR problem" }
        ]
      }
    ],
    "shareConfigs": [
      { "type": "CLIENT", "ids": ["66000002"] }
    ],
    "permissions": [
      { "type": "USER", "ids": ["600000000", "600037831"] }
    ]
  }'
# 1. Create, capturing the id
THEME_ID=$(curl -s -X POST 'https://api3.sprinklr.com/{env}/api/v3/listening/theme' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Content-Type: application/json' \
  -d @theme.json | python3 -c 'import sys,json; r=json.load(sys.stdin); d=r.get("data", r); d=d[0] if isinstance(d,list) else d; print(d["id"])')

# 2. Read it back
curl -s -X GET "https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=${THEME_ID}" \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' | python3 -m json.tool

The id extraction above handles all three candidate response shapes from.

13.5 Bulk read and bulk delete

# Read several Themes in one call
curl -X GET 'https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=60486cef06ebe67ed55c4140,66badc8a6df5bf43f6c8f374' \
  -H 'Authorization: Bearer {{accessToken}}' -H 'Key: {{apiKey}}'

# Delete several — always with an explicit id list
curl -X DELETE 'https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=60486cef06ebe67ed55c4140,66badc8a6df5bf43f6c8f374' \
  -H 'Authorization: Bearer {{accessToken}}' -H 'Key: {{apiKey}}'

14. Use cases

Four patterns that combine the four operations. All are illustrative.

14.1 Provision a Theme taxonomy across environments

The most common reason to reach for this API. Keep Theme definitions in version control, then apply them per environment.

  1. Store one JSON file per Theme, each a valid ListeningTheme create body, with name as the stable key.
  2. For each file, POST it and record the returned id in an environment-scoped map (name → id).
  3. On a later run, look the name up in the map: if present, PATCH by its id; if absent, POST.

Because there is no documented list-all or name-search on GET , your name → id map is the only index you have. Persist it as a build artefact; if you lose it, you cannot reliably rediscover which Themes you own. This is the strongest practical argument for treating id storage as part of the deployment, not a cache.

Remember that the create and patch bodies are not interchangeable — keep a translation function between the two shapes rather than reusing one payload.

14.2 Mirror an external taxonomy

Sync Themes from a product catalogue, CRM category tree, or support-ticket schema.

  1. Read the external taxonomy.
  2. GET your known Theme ids in one bulk call — comma-separate them rather than looping.
  3. Diff on name: create the additions, patch the changes, and collect the removals.
  4. Delete the removals in a single bulk DELETE, having first GET-ed and archived their full definitions.

Tag every Theme created by the sync with a common tag — "tags": ["src:crm"] — so automation-owned Themes are distinguishable from hand-built ones in the UI.

14.3 Audit and back up Theme configuration

Bulk GET makes this cheap, and it is the safest operation in the set.

curl -s -X GET "https://api3.sprinklr.com/{env}/api/v3/listening/theme?id=${IDS}" \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' > themes-$(date +%Y%m%d).json

Because PATCH and DELETE return only an opaque string and there is no version history on the entity, a periodic snapshot is your only rollback mechanism. Run it before any bulk change.

14.4 Roll out a segment across many Topics

Create one Theme per product attribute, region, or campaign, then apply it consistently.

  1. Build the filter once in the UI Theme Editor, using its live preview to validate that the query returns what you expect — there is no API dry-run.
  2. GET that Theme and copy its exact filters block.
  3. Generate the remaining Themes programmatically, substituting only the keyword or keyword-list id.
  4. Give them all a shared tag so dashboards, widgets, alerts, and rules can target the whole family at once.

This is the recommended way to work around the filter-vocabulary uncertainty in: let the UI produce a known-good filter, then replicate its shape rather than inventing one.


15. Caveats and best practices

15.1 Treat every write path as unverified

POST, PATCH, and DELETE have no ticket coverage and no recorded production verification in any available source. Only GET is signed off. Before running writes at volume:

  • Exercise each operation once in a non-production environment and record the actual request and response.
  • Diff the created object against what you sent to learn the server's defaulting behaviour.
  • Keep that transcript — it is better evidence than this guide.

15.2 Never issue an unparameterised DELETE

Restating the most dangerous item on the page: DELETE /listening/theme declares its only targeting parameter as optional, and it is a bulk operation (§11.1). Validate a non-empty id list client-side before the URL is built. Do not rely on the server to reject an empty target.

15.3 Do not round-trip GET into PATCH

The fetch response, the create body, and the patch body use three different structures for filters, sharing, and permissions (§0.1). Write an explicit translation layer with tests. A naive read-modify-write will either fail validation or, worse, silently drop the fields it does not recognise.

Watch the id types especially: strings in AssetShareConfig.ids on create, numbers in ShareConfig.userIds on patch.

15.4 Parse responses permissively

Given the schema/example divergence in , configure your JSON deserialiser to ignore unknown properties, and do not fail if filters arrives as an object rather than an array. Unwrap a data envelope if present, and read the errors array even on 200.

15.5 Let the UI author your filters

Filter_shifu has no enum on filterType or field, and the only evidenced values come from a V2 example whose filterType: "IN" is absent from the enum the patch schema uses. Building filter JSON from first principles is guesswork. Build in the Theme Editor, GET, and copy.

15.6 Batch reads and deletes, but not blindly

GET and DELETE accept comma-separated ids; PATCH does not. Batching cuts call volume, but no source states a maximum id count or how partial failures are reported. Start with modest batches — tens, not thousands — and reduce them if you see 400s. When you need to know exactly which ids succeeded, delete one at a time.

15.7 Assume rate limits exist even though none are declared

Neither 429 nor 500 is declared on any operation in this tag. That is a documentation gap, not a guarantee of unlimited throughput. Implement exponential backoff with jitter, honour Retry-After if present, and throttle bulk provisioning rather than firing it in parallel.

15.8 Creating a Theme cannot lose you data

Worth stating plainly, because it makes Themes far safer to automate than Topics. A Theme is a read-time lens over already-ingested data , so creating, changing, or deleting one never changes ingestion volume or your data bill. The blast radius of a mistake is broken dashboards and reporting — recoverable — not lost mentions.

The one caveat: whether a new Theme applies retroactively to already-collected mentions or only forward from creation is undocumented.

15.9 Tag deliberately at creation time

Theme Tags are how dashboards, widgets, alerts, and rules address groups of Themes. Assigning them at create time costs nothing; retrofitting them means a PATCH per Theme, since patch is single-target. Decide your tag vocabulary before you provision.

15.10 Store ids, and store them durably

There is no documented list-all, name search, or pagination on GET. The idreturned fromPOST` may be the only handle you ever get. Persist it at creation time.