# Content Template API v3 — Developer Guide

**Applies to:** Sprinklr API v3 (`Content Template V3` tag) — `GET /contentTemplate`, `POST /contentTemplate/search`

**V2 API reference:** [Content Template](https://dev.sprinklr.com/content-template) on the Sprinklr Developer Portal [doc:turn1doc1]

## 1. Overview

Sprinklr **Content Template Builder** powers cross-channel content management: it lets users consolidate images, videos, and content for non-social channels such as websites, blogs, and email into reusable templates. The Content Template APIs let you fetch details for user-created and standard content templates, and search templates using sorts, filters, and pagination [doc:turn1doc1].

The v3 Content Template API replaces four separate v2 endpoints with a single resource path plus a search sub-resource.

### 1.1 Operations

| # | Operation | Method and path | operationId | Declared 200 schema |
|  --- | --- | --- | --- | --- |
| 1 | Fetch standard content template details | `GET /contentTemplate` | `ContentTemplateApiV3_fetchContentTemplates` | `APIResponse` |
| 2 | Fetch content template details using sorting and filters | `POST /contentTemplate/search` | `ContentTemplateApiV3_searchContentTemplates` | `PaginatedSearchResponse` |


Both operations are tagged **`Content Template V3`** and declare the four shared error responses: `400 BadRequest`, `401 Unauthorized`, `403 Forbidden`, `404 NotFound`.

For more details refer to the [Content Template V3 API Reference](/apis/sprinklr-v3/#tag/Content-Template-V3).

### 1.2 Data model and workflow

A content template is identified by a 24-character hexadecimal `id`. Beyond `id` and `name`, the returned object is **template-type dependent**.

> **Note (from the source operation document).** *"The response parameters vary from template to template. The details depend on the template details present on the backend."* Treat every field other than `id` and `name` as optional and defensively parse the payload.


Fields observed in the reference `GET` response for an email content template:

| Field | Type | Observed value / meaning |
|  --- | --- | --- |
| `id` | String | `6a2bfe33703a82b2e91dc02a` — template identifier |
| `name` | String | `manual ctm template` |
| `channelType` | String | `EMAIL` |
| `accountTypes` | Array[String] | `["BULK_EMAIL"]` |
| `content` | String | Stringified JSON describing the template component tree (Canvas → Subject, EmailTitle, PostGallery …), fonts, merge tags, and languages. Parse as JSON *after* unescaping. |
| `smartTemplate` | Boolean | `false` |
| `isAdvocacyTemplate` | Boolean | `true` |
| `templateType` | String | `CONTENT_TEMPLATE` |
| `advocacyProjectId` | String (UUID) | `f5122f12-f3fb-4ef9-87f5-56b05b2ba4f9` |
| `height` / `width` | Integer | `1457` / `600` (pixels) |
| `projectId` | String (UUID) | `d5b402ad-cb5f-4976-8a23-0ea316767890` |
| `locationUrl` | String (URL) | Location of the rendered publisher bundle |
| `publisherStatus` | String | `completed` |
| `contentType` | String | `PUBLISHING` |
| `version` | Integer | `0` |
| `templateVersionInfo` | String | `0.0.1` |
| `editDisabled` | Boolean | `true` |
| `archived` | Boolean | `false` |
| `isPurelyAIGenerated` | Boolean | `false` |
| `report` | String | `CONTENT_TEMPLATE` |
| `governance.visibility.globallyVisible` | Boolean | `true` — the v3 equivalent signal of the v2 `fromGlobal` flag |
| `clientId` | Integer | `66000002` |
| `ownerUserId` | Integer | `66009128` |
| `createdTime` / `modifiedTime` | String | `Jun 12, 2026, 12:40:19 PM` / `Jul 29, 2026, 09:29:56 AM` — **formatted date strings, not epoch milliseconds** |
| `lastModifiedUserId` | Integer | `66009128` |
| `deleted` | Boolean | `false` |
| `canEdit` | Boolean | `false` |


Typical workflow:

1. Call `POST /contentTemplate/search` with filters (for example `TEMPLATE_TYPE IN [CONTENT_TEMPLATE]`) and a page size to obtain a lightweight list of `{id, name}` pairs plus `hasMore` and `totalHits`.
2. Call `GET /contentTemplate?id={templateId}` for each identifier you need in order to retrieve the full template body.


## 2. Base URLs and environments

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

`env` is a required path variable. Default: `prod`. Allowed values declared in the specification:

`prod`, `prod0`, `prod2`, `prod3`, `prod4`, `prod5`, `prod6`, `prod8`, `prod11`, `prod12`, `prod15`, `prod16`, `prod17`, `prod18`, `prod19`, `prod21`, `prod25`

Full endpoint URLs:

```
GET  https://api3.sprinklr.com/{env}/api/v3/contentTemplate
POST https://api3.sprinklr.com/{env}/api/v3/contentTemplate/search
```

> **Important.** Use your own partition's environment token.


## 3. Authentication and common headers

| Key | Value | Description |
|  --- | --- | --- |
| `Authorization` | `Bearer {{accessToken}}` | Credential used by the API to authenticate a user with the server. For generating an authorization token, refer to the Authorize section on the developer portal. |
| `Key` | `{{apiKey}}` | API key helps authenticate the application with the server. For generating an API key, refer to the Getting Started guide. |
| `Content-Type` | `application/json` | Representation header that determines the type of data (media/resource) present in the request body. Required for `POST /contentTemplate/search`. |
| `Accept` | `application/json` | Determines the acceptable response type from the server. |


## 4. Fetch standard content template details

**`GET /api/v3/contentTemplate`** · operationId `ContentTemplateApiV3_fetchContentTemplates`

Fetches standard content template details using the template id.

### 4.1 Request

Endpoint form given in the source operation document:

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

Endpoint form given in the Jira description:

```
GET /api/v3/contentTemplate?id=templateId1,templateId2
```

### 4.2 Query parameters

| Parameter | Required / Optional | Description | Type |
|  --- | --- | --- | --- |
| `id` | Required | Unique identifier of the content template to retrieve. Example: `6a2bfe33703a82b2e91dc02a`. Use a comma-separated list form (`id=templateId1,templateId2`) for bulk fetch | String |


Additional query parameters named in the Jira description as replacements for v2 URL variants, **not present in the specification and not exercised in the operation document**:

| Parameter | Source | Status |
|  --- | --- | --- |
| `fromGlobal` | Jira description — "`fromGlobal=true` … moved into query params on the standard GET" | Undeclared in `sprinklr-v3.yaml`; unverified |
| `channelTypes` | Jira description — "`channelTypes` filter moved into query params on the standard GET" | Undeclared in `sprinklr-v3.yaml`; unverified |


### 4.3 Example request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/contentTemplate?id=6a2bfe33703a82b2e91dc02a' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

*Illustrative example. Replace `{env}`, `{{accessToken}}`, `{{apiKey}}`, and the template id with your own values.*

### 4.4 Example response (abridged)

The `content` field in the real response is a very large escaped-JSON string containing the full component tree and embedded `@font-face` declarations. It is elided below as `"…"`; everything else is reproduced from the source document.

```json
{
  "data": {
    "id": "6a2bfe33703a82b2e91dc02a",
    "name": "manual ctm template",
    "channelType": "EMAIL",
    "accountTypes": [
      "BULK_EMAIL"
    ],
    "content": "…",
    "smartTemplate": false,
    "isAdvocacyTemplate": true,
    "templateType": "CONTENT_TEMPLATE",
    "advocacyProjectId": "f5122f12-f3fb-4ef9-87f5-56b05b2ba4f9",
    "height": 1457,
    "width": 600,
    "projectId": "d5b402ad-cb5f-4976-8a23-0ea316767890",
    "locationUrl": "https://storage.googleapis.com/…/spx-publisher/d5b402ad-cb5f-4976-8a23-0ea316767890/1785317396873",
    "publisherStatus": "completed",
    "contentType": "PUBLISHING",
    "version": 0,
    "templateVersionInfo": "0.0.1",
    "editDisabled": true,
    "archived": false,
    "isPurelyAIGenerated": false,
    "report": "CONTENT_TEMPLATE",
    "governance": {
      "visibility": {
        "globallyVisible": true
      }
    },
    "clientId": 66000002,
    "ownerUserId": 66009128,
    "createdTime": "Jun 12, 2026, 12:40:19 PM",
    "modifiedTime": "Jul 29, 2026, 09:29:56 AM",
    "lastModifiedUserId": 66009128,
    "deleted": false,
    "canEdit": false
  },
  "errors": []
}
```

### 4.5 Response fields

`data` carries the template object described in [§1.2](#12-data-model-and-workflow). `errors` is an array of `Error` objects; an empty array indicates that no errors occurred. See [§6](#6-response-format-and-status-codes).

## 5. Fetch content template details using sorting and filters

**`POST /api/v3/contentTemplate/search`** · operationId `ContentTemplateApiV3_searchContentTemplates`

Fetches content template `id` and `name` details using sorting and filter values. The request body is **required** and the declared body schema is `Request`; the declared 200 schema is `PaginatedSearchResponse`.

### 5.1 Request body parameters

| Parameter | Sub-parameter | Required / Optional | Definition | Type |
|  --- | --- | --- | --- | --- |
| `sorts` |  | Optional | Sorting filter that specifies the arrangement of data in the response. | Array |
|  | `key` | — | Field used for sorting the data. Example: `MODIFIED_TIME` sorts the response by each template's modified time. | String |
|  | `order` | — | Order of data in the response. Example: `ASC` for ascending, `DESC` for descending. | String |
| `page` |  | Optional | Limits the number of items returned when the response set is large. | Object |
|  | `page` | — | Page number to return. If the field has no value, the first page of results is returned. | Integer |
|  | `size` | — | Number of results to return. | Integer |
| `filters` |  | **Required** | Information used to filter the data in the response. | List[Object] |
|  | `field` | Optional | Field on which the data is filtered. Example: `TEMPLATE_TYPE`, `PUBLISHER_STATUS`. | String |
|  | `filterType` | Optional | Type of filter applied. Supported values per the operation document: `IN`, `GT`, `GTE`, `LT`, `LTE`, `NIN`, `BETWEEN`, `STARTS_WITH`, `CONTAINS`, `EQUALS`, `FILTER`, `EXISTS`. | String |
|  | `values` | Optional | Values against which the data is compared. Example: `CONTENT_TEMPLATE`. | List[String, Integer] |


> **Discrepancy — `filters` requiredness.** The operation document marks `filters` **Required** while marking every one of its sub-fields Optional, and the same document's dev note says an empty body `{}` is valid (see [§5.4](#54-empty-request-body-behaviour)). Those two statements cannot both hold. The specification marks the *body* required but declares no `required` array inside `Request`, so `filters` is optional at the schema level. Confirm the intended contract.


> **Discrepancy — `filterType` enum.** In the specification, `filters[]` resolves to `Filter_request`, whose `filterType` is the enum `FilterType_Filter` with values `FILTER`, `LIMIT`, `SEARCH`, `MATCH_PHRASE_PREFIX`, `ADHOC_SEARCH`, `EXPRESSION`, `GEO_DISTANCE`, `ADVANCED_QUERY`, `MATCH_NONE`, `MATCH_ALL`. Only `FILTER` appears in both lists. The value used in the working example — `IN` — is **not** in the specification enum, yet QA verified the call. Treat the operation document's list as the behavioural truth and the specification enum as stale, but confirm before generating client code from the spec.


> **Note — `Request` is a shared, very wide schema.** `Request` is Sprinklr's generic search/reporting request object. It declares roughly 40 top-level properties (`key`, `timeFilter`, `previousTimeFilter`, `query`, `queries`, `filters`, `filter`, `postFilters`, `projectionFilters`, `groupBys`, `projections`, `report`, `permission`, `tzOffset`, `timezone`, `sorts`, `page`, `pageForDocuments`, `sortsForDocuments`, `merge`, `excludeFields`, `additional`, and more). Only `sorts`, `page`, and `filters` are documented for this operation. The remaining properties are **not** documented as supported for content template search — do not assume they are honoured.


Related schema details from `sprinklr-v3.yaml`:

- `sorts[]` → `Sort_request`: `type` (string), `key` (string), `order` (`Order_enums` — a bare string with **no enum values declared**), `additional`, `postProjectionSort`.
- `page` → `Page_request`: `page` (int32), `size` (int32), `skip` (int32), `cursor` (string). Only `page` and `size` are documented for this operation.
- `filters[]` → `Filter_request`: `field`, `filterType`, `values` (array of object), plus `displayName`, `negativeFilter`, `isCompoundFilter`, `accessibleValues`, `userFilter`, `allValuesAllowed`, `lockedWithValues`, `hidden`, `favourite`, `mandatory`, `locked`, `disabled`, `details` — all undocumented for this operation.


### 5.2 Example request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/contentTemplate/search' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
    "sorts": [
        {
            "key": "MODIFIED_TIME",
            "order": "DESC"
        }
    ],
    "page": {
        "page": 0,
        "size": 5
    },
    "filters": [
        {
            "field": "TEMPLATE_TYPE",
            "filterType": "IN",
            "values": [
                "CONTENT_TEMPLATE"
            ]
        }
    ]
}'
```

*Illustrative example, reproduced from the source operation document with credentials replaced by placeholders.*

### 5.3 Example response

```json
{
  "data": {
    "searchResults": [
      {
        "id": "6a214d2e1d46a76667fd4b9c",
        "name": "pragya testing "
      },
      {
        "id": "6a883d0595239a114d6dbf47",
        "name": "Link"
      },
      {
        "id": "6a883d05e7f1f360e3d4f1c2",
        "name": "Media"
      },
      {
        "id": "6a883d05e7f1f360e3d4f1c3",
        "name": "Text"
      },
      {
        "id": "6a7afc34dff6d5eda924d20e",
        "name": "Thread"
      }
    ],
    "hasMore": true,
    "totalHits": 287
  },
  "errors": []
}
```

Response fields:

| Parameter | Sub-parameter | Required / Optional | Definition | Type |
|  --- | --- | --- | --- | --- |
| `data` |  | Required | Contains the search results and pagination information returned by the API. | Object |
|  | `searchResults` | Required | List of templates matching the specified search criteria. | Array |
|  | `searchResults[].id` | Required | Unique identifier of the template. | String |
|  | `searchResults[].name` | Required | Name of the template. | String |
|  | `hasMore` | Required | Indicates whether additional results are available beyond the current response. `true` means another page can be requested. | Boolean |
|  | `totalHits` | Required | Total number of records matching the search criteria. `287` means 287 templates match the applied filters. | Integer |
| `errors` |  | Required | Details of any errors encountered while processing the request. An empty array indicates that no errors occurred. | Array |


> **Discrepancy — field names.** The declared 200 schema `PaginatedSearchResponse` uses **abbreviated property names**: `sR` (array), `tBC` (int64), `iBC` (string), `hM` (boolean), `tH` (int64), `fR` (array of `SearchFacetResponse`), `topHits` (map of `Tuple_Long_List_T`). The verified response instead uses `searchResults`, `hasMore`, and `totalHits`, and is wrapped in a `data` envelope that `PaginatedSearchResponse` does not declare. This is almost certainly a serialization-alias mismatch in the published spec. **Code against the documented names** (`data.searchResults`, `data.hasMore`, `data.totalHits`) and treat the abbreviated spec names as an outstanding spec bug. Also note `sR[]` is typed "Unresolved type (T)", so the `{id, name}` shape comes only from the example.


### 5.4 Empty request body behaviour

> **Dev note.** *"If the request has no sorting or filters applied and has a pair of empty curly brackets, i.e., `{}`, then the response will show details for all the content templates available on a single page. By default, the size of response per page is equal to 100."*


So:

- Body `{}` → all available content templates, one page.
- Default page size when `page` is omitted → **100**.
- Use `hasMore` and `totalHits` to decide whether to request the next page.


```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/contentTemplate/search' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{}'
```

## 6. Response format and status codes

### 6.1 Status codes

| Code | Meaning | Declared schema |
|  --- | --- | --- |
| `200` | Success | `APIResponse` (GET) / `PaginatedSearchResponse` (POST search) |
| `400` | Bad Request — shared `BadRequest` response | `ErrorResponse` |
| `401` | Unauthorized — shared `Unauthorized` response | `ErrorResponse` |
| `403` | Forbidden — shared `Forbidden` response | `ErrorResponse` |
| `404` | Not Found — shared `NotFound` response | `ErrorResponse` |


No other status codes are declared for either operation.

### 6.2 Success envelope

`APIResponse`:

```json
{
  "data": {},
  "errors": [],
  "metadata": {}
}
```

| Field | Type | Notes |
|  --- | --- | --- |
| `data` | object | **Untyped in the specification.** The actual shape is documented only by the examples in [§4.4](#44-example-response-abridged) and [§5.3](#53-example-response). |
| `errors` | array of `Error` | Empty on success. |
| `metadata` | `ResponseMetadata` | Declared by `APIResponse`; absent from the captured examples. |


### 6.3 Error envelope

`ErrorResponse`:

```json
{
  "data": null,
  "errors": [
    {
      "id": "6a2bfe33703a82b2e91dc02a",
      "code": 404,
      "message": "content.template.not.found"
    }
  ],
  "metadata": {}
}
```

`Error` requires all three fields:

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


> **Note.** The `message` value shown above is an *illustrative* key following the platform's dotted-key convention. The specification does not enumerate content-template-specific error codes or keys. Log `code` and `message` verbatim; do not pattern-match on message text.


## 7. V2 → V3 migration

### 7.1 Endpoint map

| v2 operation | v2 endpoint | v3 equivalent | Notes |
|  --- | --- | --- | --- |
| Fetch User-Created Content Template Details | `GET /api/v2/contentTemplate/{templateId}` | `GET /api/v3/contentTemplate?id={templateId}` | Path segment becomes a query parameter. |
| Fetch Standard Content Template Details | `GET /api/v2/contentTemplate/{templateId}?fromGlobal=true` | `GET /api/v3/contentTemplate?id={templateId}` (+ `fromGlobal` per the Jira description) | The v3 operation document covers **this** operation ("Fetch Standard Content Template Details"). `fromGlobal` is named only in the Jira description and is undeclared in the spec. The returned object exposes `governance.visibility.globallyVisible`. |
| Fetch Content Template Details Using Sorting and Filters | `POST /api/v2/contentTemplate/searchContentTemplates` | `POST /api/v3/contentTemplate/search` | Sub-path renamed from `searchContentTemplates` to `search`. |
| Fetch Content Template Details by Channel Type | `GET /api/v2/contentTemplate/findAll?channelTypes={channelType}` [doc:turn1doc4] | `GET /api/v3/contentTemplate` (+ `channelTypes` per the Jira description) | `findAll` is removed. `channelTypes` is named only in the Jira description and is undeclared in the spec. |
| — (did not exist) | — | **Not delivered** | The Jira description announces `PUT`, `PATCH`, and `DELETE` on `/api/v3/contentTemplate`. QA confirmed they are out of scope and the spec does not define them. |


### 7.2 Field and payload changes

| Area | v2 | v3 |
|  --- | --- | --- |
| Base path | `/api/v2/contentTemplate/…` | `/api/v3/contentTemplate` |
| Template addressing | Path segment `/{templateId}` | Query parameter `?id=` |
| Global/standard templates | Distinct URL variant `?fromGlobal=true` | Same `GET`; `governance.visibility.globallyVisible` on the returned object |
| Channel filtering | Dedicated `/findAll?channelTypes=` endpoint | Per the Jira description, a query parameter on the standard `GET` (undeclared in the spec) — or use `POST /search` with a `filters` entry |
| Search sub-path | `searchContentTemplates` | `search` |
| `findAll` response shape | `{"data": [{"id": …, "name": …}], "errors": []}` — `data` is a flat array [doc:turn1doc4] | `{"data": {"searchResults": [{"id": …, "name": …}], "hasMore": …, "totalHits": …}, "errors": []}` — `data` is an object with pagination metadata |
| Response envelope | `data` + `errors` | `data` + `errors` (+ `metadata` per `APIResponse`); all responses wrapped in a `{ "data": … }` envelope per v3 convention |
| Pagination signal | Not present on `findAll` | `hasMore`, `totalHits`, default page size 100 |


### 7.3 Migration steps

1. **Change the base path** from `/api/v2/` to `/api/v3/` and confirm your `{env}` token is in the v3 environment enum ([§2](#2-base-urls-and-environments)).
2. **Move the template id out of the path.** Replace `GET /api/v2/contentTemplate/{templateId}` with `GET /api/v3/contentTemplate?id={templateId}`.
3. **Retire the `?fromGlobal=true` URL variant.** Call the single `GET` and read `governance.visibility.globallyVisible` from the response. Confirm with the API owner whether a `fromGlobal` query parameter is actually accepted before depending on it.
4. **Retire `/findAll?channelTypes=`.** Replace it with `POST /api/v3/contentTemplate/search` using a `filters` entry, or confirm the `channelTypes` query parameter with the API owner.
5. **Rename the search sub-path** from `searchContentTemplates` to `search`. The `sorts` / `page` / `filters` body structure is unchanged in the documented examples.
6. **Update your search response parser.** `data` is now an object, not an array. Read `data.searchResults` instead of `data`, and start consuming `hasMore` / `totalHits`.
7. **Adopt pagination.** v2 `findAll` returned an unpaginated list. In v3, omitting `page` returns 100 results; loop on `hasMore` and increment `page.page`.
8. **Harden your `GET` parser.** The full template object varies by template type, and `content` is an escaped JSON string that must be unescaped before parsing.
9. **Update error handling** to the `ErrorResponse` / `Error` shape (`id`, `code`, `message`) and to the 400/401/403/404 set.
10. **Do not plan on v3 write operations.** `PUT`, `PATCH`, and `DELETE` are not delivered. Keep using existing platform mechanisms for template mutation.


## 8. Reference

### 8.1 Documented filter fields

| Field | Source | Example values |
|  --- | --- | --- |
| `TEMPLATE_TYPE` | Operation document | `CONTENT_TEMPLATE` |
| `PUBLISHER_STATUS` | Operation document | Not exemplified; the `GET` response exposes `publisherStatus: "completed"` |


No complete list of filterable fields is published in any source.

### 8.2 Filter types

| Source | Values |
|  --- | --- |
| Operation document (behavioural) | `IN`, `GT`, `GTE`, `LT`, `LTE`, `NIN`, `BETWEEN`, `STARTS_WITH`, `CONTAINS`, `EQUALS`, `FILTER`, `EXISTS` |
| `sprinklr-v3.yaml` — `FilterType_Filter` | `FILTER`, `LIMIT`, `SEARCH`, `MATCH_PHRASE_PREFIX`, `ADHOC_SEARCH`, `EXPRESSION`, `GEO_DISTANCE`, `ADVANCED_QUERY`, `MATCH_NONE`, `MATCH_ALL` |


The two lists overlap only on `FILTER`. See the discrepancy note in [§5.1](#51-request-body-parameters).

### 8.3 Sort keys and order

| Item | Documented values | Specification |
|  --- | --- | --- |
| `sorts[].key` | `MODIFIED_TIME` (only example given) | `Sort_request.key` — bare string, no enum |
| `sorts[].order` | `ASC`, `DESC` | `Order_enums` — bare string, **no enum values declared** |


### 8.4 Observed enumerated values in the `GET` response

These values are observed in the reference example only; none is declared as an enum in the specification.

| Field | Observed value |
|  --- | --- |
| `channelType` | `EMAIL` |
| `accountTypes[]` | `BULK_EMAIL` |
| `templateType` | `CONTENT_TEMPLATE` |
| `contentType` | `PUBLISHING` |
| `publisherStatus` | `completed` |
| `report` | `CONTENT_TEMPLATE` |


### 8.5 Schema index

| Schema | Used by | Notes |
|  --- | --- | --- |
| `APIResponse` | `GET /contentTemplate` 200 | `{data, errors, metadata}`; `data` untyped |
| `PaginatedSearchResponse` | `POST /contentTemplate/search` 200 | Abbreviated property names — see [§5.3](#53-example-response) |
| `Request` | `POST /contentTemplate/search` body | Generic, wide search/reporting request |
| `Sort_request` | `Request.sorts[]` |  |
| `Page_request` | `Request.page` | `page`, `size`, `skip`, `cursor` |
| `Filter_request` | `Request.filters[]` | `field`, `filterType`, `values`, + many undocumented flags |
| `FilterType_Filter` | `Filter_request.filterType` | Enum — conflicts with the documented list |
| `ErrorResponse` / `Error` | 400/401/403/404 |  |


## 9. Use cases and best practices

### 9.1 Use cases

- **Template catalogue sync.** Page through `POST /contentTemplate/search` with `{}` or a `TEMPLATE_TYPE` filter to mirror the id/name catalogue into an external CMS or DAM, then fetch full bodies on demand.
- **Most-recently-edited view.** Sort by `MODIFIED_TIME DESC` with a small `page.size` to power a "recently updated templates" panel.
- **Channel-scoped picker.** Filter the search results to the channel your integration publishes to, replacing the v2 `findAll?channelTypes=` call.
- **Template rendering/preview.** Retrieve the full object with `GET`, unescape `content`, and hand the component tree to your renderer; use `height`, `width`, and `locationUrl` for layout and asset resolution.
- **Governance auditing.** Read `governance.visibility.globallyVisible`, `editDisabled`, `canEdit`, `archived`, `deleted`, `ownerUserId`, and `lastModifiedUserId` to report on template access and lifecycle.


### 9.2 Best practices

1. **Search first, fetch second.** The search response is a lightweight `{id, name}` list; the `GET` response can be hundreds of kilobytes because of the embedded `content` string. Do not fetch full bodies to build a list view.
2. **Always send `page`.** Relying on the default of 100 makes your integration silently sensitive to a server-side default change.
3. **Loop on `hasMore`, verify against `totalHits`.** Stop when `hasMore` is `false`; use `totalHits` for progress and sanity checks.
4. **Unescape `content` before parsing.** It is a JSON *string*, not a nested object.
5. **Parse the `GET` response defensively.** Per the source dev note, fields vary by template. Guard every field except `id` and `name`.
6. **Treat `createdTime` / `modifiedTime` as formatted strings.** They arrive as `Jun 12, 2026, 12:40:19 PM`, not epoch milliseconds. Parse with an explicit format and confirm the emitting timezone with the API owner.
7. **Do not send `Cookie: JSESSIONID=…`.** Authenticate with `Authorization` + `Key` only.
8. **Do not use undocumented `Request` properties.** Only `sorts`, `page`, and `filters` are documented for this operation.
9. **Filter on the server.** Send `filters` rather than pulling all templates and filtering client-side.
10. **Handle `errors` even on `200`.** The envelope carries an `errors` array independently of the HTTP status; check that it is empty before trusting `data`.
11. **Pin the environment.** Ensure `{env}` matches your partition; a wrong environment yields 401/403 rather than empty results.