From 42a15fbe3d46d62b87ec9ced927c0ecbd34acabb Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Wed, 22 Jul 2026 14:32:26 +0530 Subject: [PATCH 1/3] feat: add content types in SR URL Inspector Filters response --- src/controllers/elements.js | 2 +- .../elements/definitions/content-types.js | 83 ++++++++++ src/support/elements/definitions/index.js | 4 + src/support/elements/element-ids.js | 1 + src/support/elements/elements-service.js | 10 +- .../definitions/content-types.test.js | 142 ++++++++++++++++++ .../support/elements/elements-service.test.js | 38 ++++- 7 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 src/support/elements/definitions/content-types.js create mode 100644 test/support/elements/definitions/content-types.test.js diff --git a/src/controllers/elements.js b/src/controllers/elements.js index b1d64eb252..fd88e74061 100644 --- a/src/controllers/elements.js +++ b/src/controllers/elements.js @@ -398,7 +398,7 @@ export default function ElementsController(context, log, env) { * GET /v2/orgs/:spaceCatId/brands/:brandId/serenity/brand-presence * /url-inspector/filter-dimensions * Returns filter dimensions for the URL Inspector dashboard - * (brands, regions, topics, categories, page_intents, origins), scoped to + * (brands, regions, topics, categories, page_intents, origins, content_types), scoped to * that single brand. */ const listUrlInspectorFilterDimensions = async (ctx) => { diff --git a/src/support/elements/definitions/content-types.js b/src/support/elements/definitions/content-types.js new file mode 100644 index 0000000000..c3752838a8 --- /dev/null +++ b/src/support/elements/definitions/content-types.js @@ -0,0 +1,83 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { resolveElementModel } from '../constants.js'; + +// Legacy default window is a rolling 28 days (see defaultDateRange in +// llmo-brand-presence.js / cited-domains.js). Kept inline here so this definition +// stays pure and does not import controller code. +const DEFAULT_WINDOW_DAYS = 28; + +function defaultDateRange() { + const end = new Date(); + const start = new Date(); + start.setDate(start.getDate() - DEFAULT_WINDOW_DAYS); + return { + startDate: start.toISOString().slice(0, 10), + endDate: end.toISOString().slice(0, 10), + }; +} + +/** + * Builds the payload for the Content Types filter-dimensions element (d1f9e6ec). + * Returns the available `domain_type` values (Owned/Other/Social/Earned/Benchmark + * Competitors) for the given model + date window — powers the Content Type filter + * dropdown. + * + * @param {object} [params] + * @param {string} [params.model] - AI model (Semrush engine name or SpaceCat/UI platform code). + * Translated to a Semrush model + validated via {@link resolveElementModel} (default search-gpt). + * @param {string} [params.platform] - Legacy alias for `model`; `model` takes precedence. + * @param {string} [params.startDate] - ISO date (YYYY-MM-DD). Defaults to 28 days ago. + * @param {string} [params.endDate] - ISO date (YYYY-MM-DD). Defaults to today. + */ +export function buildContentTypesPayload({ + model, platform, startDate, endDate, +} = {}) { + const resolvedModel = resolveElementModel(model || platform); + const defaults = defaultDateRange(); + const start = startDate || defaults.startDate; + const end = endDate || defaults.endDate; + + return { + comparison_data_formatting: 'union', + filters: { + simple: {}, + advanced: { + op: 'and', + filters: [ + { op: 'eq', val: resolvedModel, col: 'CBF_model' }, + { op: 'gte', val: start, col: 'CBF_date__start' }, + { op: 'lte', val: end, col: 'CBF_date__end' }, + ], + }, + }, + }; +} + +/** + * Transforms the raw Semrush Content Types element response into URL Inspector + * filter-dimension content types. `id` is derived from `label` (lowercased, + * spaces → underscores) — Semrush has no stable content-type ID. + * + * @param {object} raw - Raw response from the Elements API. + * @returns {Array<{id: string, label: string}>} + */ +export function transformContentTypesToFilterDimensions(raw) { + return (raw?.blocks?.value ?? []) + .map((item) => String(item.value ?? '')) + .filter((label) => label !== '') + .map((label) => ({ + id: label.toLowerCase().replace(/\s+/g, '_'), + label, + })); +} diff --git a/src/support/elements/definitions/index.js b/src/support/elements/definitions/index.js index 0133d795b7..91b52c3bfa 100644 --- a/src/support/elements/definitions/index.js +++ b/src/support/elements/definitions/index.js @@ -12,6 +12,10 @@ export { buildBrandsPayload, transformBrandsToFilterDimensions } from './brands.js'; export { buildMarketsPayload, transformMarketsToFilterDimensions } from './markets.js'; +export { + buildContentTypesPayload, + transformContentTypesToFilterDimensions, +} from './content-types.js'; export { buildTopicsPayload, transformTopicsForFilterDimensions, diff --git a/src/support/elements/element-ids.js b/src/support/elements/element-ids.js index d5e3d5e55f..2f771a9ac1 100644 --- a/src/support/elements/element-ids.js +++ b/src/support/elements/element-ids.js @@ -20,6 +20,7 @@ export const ELEMENT_IDS = Object.freeze({ BRANDS: 'b178ce4e-6471-4430-9a32-8228ce72b2e6', MARKETS: '478968a7-8851-4daf-83f7-2e8fb6185ddc', TOPICS: 'ba3b19c1-22d4-460a-8dc3-1ff05c360852', + CONTENT_TYPES: 'd1f9e6ec-9128-4b1b-b767-d86f93f54237', // Superseded by a new element with the same semantics but a shape aligned to // Mentions/Visibility/Citations (CBF_ws_brand + CBF_model + optional // CBF_project OR-list, all wrapped in their own `or` blocks) — see diff --git a/src/support/elements/elements-service.js b/src/support/elements/elements-service.js index a239703df7..47d118c629 100644 --- a/src/support/elements/elements-service.js +++ b/src/support/elements/elements-service.js @@ -19,6 +19,8 @@ import { transformBrandsToFilterDimensions, buildMarketsPayload, transformMarketsToFilterDimensions, + buildContentTypesPayload, + transformContentTypesToFilterDimensions, buildTopicsPayload, transformTopicsForFilterDimensions, transformCategoriesToFilterDimensions, @@ -81,10 +83,15 @@ export function createElementsService(transport, log) { spacecatBrands = [], brandSemrushProjects = [], ) { - const [rawTopics, rawBrands, rawMarkets] = await Promise.all([ + const [rawTopics, rawBrands, rawMarkets, rawContentTypes] = await Promise.all([ transport.fetchElement(workspaceId, ELEMENT_IDS.TOPICS, buildTopicsPayload(params)), transport.fetchElement(workspaceId, ELEMENT_IDS.BRANDS, buildBrandsPayload(params)), transport.fetchElement(workspaceId, ELEMENT_IDS.MARKETS, buildMarketsPayload({})), + transport.fetchElement( + workspaceId, + ELEMENT_IDS.CONTENT_TYPES, + buildContentTypesPayload(params), + ), ]); const result = { brands: transformBrandsToFilterDimensions(rawBrands, spacecatBrands), @@ -93,6 +100,7 @@ export function createElementsService(transport, log) { categories: transformCategoriesToFilterDimensions(rawTopics), page_intents: transformIntentsToFilterDimensions(rawTopics), origins: transformOriginsToFilterDimensions(rawTopics), + content_types: transformContentTypesToFilterDimensions(rawContentTypes), }; // Merge any tag types not covered above (e.g. `type:branded`) under their own // prefix key, and plain prefix-less tags under `tags` — see diff --git a/test/support/elements/definitions/content-types.test.js b/test/support/elements/definitions/content-types.test.js new file mode 100644 index 0000000000..1a02d98a93 --- /dev/null +++ b/test/support/elements/definitions/content-types.test.js @@ -0,0 +1,142 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { expect } from 'chai'; +import { + buildContentTypesPayload, + transformContentTypesToFilterDimensions, +} from '../../../../src/support/elements/definitions/content-types.js'; +import { DEFAULT_ELEMENT_MODEL } from '../../../../src/support/elements/constants.js'; + +describe('content-types definitions', () => { + describe('buildContentTypesPayload', () => { + it('uses the default model when no params are provided', () => { + const payload = buildContentTypesPayload(); + expect(payload.filters.advanced.filters[0]).to.deep.equal({ + op: 'eq', + val: DEFAULT_ELEMENT_MODEL, + col: 'CBF_model', + }); + }); + + it('uses the default model when model is not in ELEMENT_MODELS', () => { + const payload = buildContentTypesPayload({ model: 'unknown-model' }); + expect(payload.filters.advanced.filters[0].val).to.equal(DEFAULT_ELEMENT_MODEL); + }); + + it('uses the provided model when it is in ELEMENT_MODELS', () => { + const payload = buildContentTypesPayload({ model: 'perplexity' }); + expect(payload.filters.advanced.filters[0].val).to.equal('perplexity'); + }); + + it('translates a SpaceCat/UI platform code to the Semrush model', () => { + const payload = buildContentTypesPayload({ model: 'copilot' }); + expect(payload.filters.advanced.filters[0].val).to.equal('microsoft-copilot'); + }); + + it('accepts the platform alias and translates it', () => { + const payload = buildContentTypesPayload({ platform: 'openai' }); + expect(payload.filters.advanced.filters[0].val).to.equal('chatgpt-paid'); + }); + + it('uses the provided startDate and endDate', () => { + const payload = buildContentTypesPayload({ startDate: '2026-06-07', endDate: '2026-06-14' }); + expect(payload.filters.advanced.filters[1]).to.deep.equal({ + op: 'gte', + val: '2026-06-07', + col: 'CBF_date__start', + }); + expect(payload.filters.advanced.filters[2]).to.deep.equal({ + op: 'lte', + val: '2026-06-14', + col: 'CBF_date__end', + }); + }); + + it('defaults to a rolling 28-day window when no dates are provided', () => { + const payload = buildContentTypesPayload(); + const start = payload.filters.advanced.filters[1]; + const end = payload.filters.advanced.filters[2]; + expect(start.col).to.equal('CBF_date__start'); + expect(end.col).to.equal('CBF_date__end'); + const startMs = new Date(start.val).getTime(); + const endMs = new Date(end.val).getTime(); + const daysApart = Math.round((endMs - startMs) / (24 * 60 * 60 * 1000)); + expect(daysApart).to.equal(28); + }); + + it('sets comparison_data_formatting to union', () => { + const payload = buildContentTypesPayload(); + expect(payload.comparison_data_formatting).to.equal('union'); + }); + + it('uses AND operator in the advanced filter', () => { + const payload = buildContentTypesPayload(); + expect(payload.filters.advanced.op).to.equal('and'); + }); + + it('includes an empty simple filter object', () => { + const payload = buildContentTypesPayload(); + expect(payload.filters.simple).to.deep.equal({}); + }); + }); + + describe('transformContentTypesToFilterDimensions', () => { + it('returns an empty array when raw is null', () => { + expect(transformContentTypesToFilterDimensions(null)).to.deep.equal([]); + }); + + it('returns an empty array when raw has no blocks', () => { + expect(transformContentTypesToFilterDimensions({})).to.deep.equal([]); + }); + + it('derives id from a single-word label', () => { + const raw = { blocks: { value: [{ value: 'Owned' }] } }; + const [item] = transformContentTypesToFilterDimensions(raw); + expect(item).to.deep.equal({ id: 'owned', label: 'Owned' }); + }); + + it('derives id from a multi-word label by replacing spaces with underscores', () => { + const raw = { blocks: { value: [{ value: 'Benchmark Competitors' }] } }; + const [item] = transformContentTypesToFilterDimensions(raw); + expect(item).to.deep.equal({ id: 'benchmark_competitors', label: 'Benchmark Competitors' }); + }); + + it('filters out entries with an empty/missing value', () => { + const raw = { blocks: { value: [{ value: '' }, { }, { value: 'Social' }] } }; + const result = transformContentTypesToFilterDimensions(raw); + expect(result).to.deep.equal([{ id: 'social', label: 'Social' }]); + }); + + it('handles the full known set of content types, preserving element order', () => { + const raw = { + blocks: { + value: [ + { value: 'Other' }, + { value: 'Social' }, + { value: 'Earned' }, + { value: 'Owned' }, + { value: 'Benchmark Competitors' }, + ], + }, + }; + const result = transformContentTypesToFilterDimensions(raw); + expect(result).to.deep.equal([ + { id: 'other', label: 'Other' }, + { id: 'social', label: 'Social' }, + { id: 'earned', label: 'Earned' }, + { id: 'owned', label: 'Owned' }, + { id: 'benchmark_competitors', label: 'Benchmark Competitors' }, + ]); + }); + }); +}); diff --git a/test/support/elements/elements-service.test.js b/test/support/elements/elements-service.test.js index 1a78d0f89c..bd03ec3c30 100644 --- a/test/support/elements/elements-service.test.js +++ b/test/support/elements/elements-service.test.js @@ -54,6 +54,18 @@ const RAW_TOPICS = { }, }; +const RAW_CONTENT_TYPES = { + blocks: { + value: [ + { value: 'Other' }, + { value: 'Social' }, + { value: 'Earned' }, + { value: 'Owned' }, + { value: 'Benchmark Competitors' }, + ], + }, +}; + describe('createElementsService', () => { let transport; let service; @@ -74,25 +86,41 @@ describe('createElementsService', () => { transport.fetchElement .withArgs('ws-1', ELEMENT_IDS.MARKETS, sinon.match.any) .resolves(RAW_MARKETS); + transport.fetchElement + .withArgs('ws-1', ELEMENT_IDS.CONTENT_TYPES, sinon.match.any) + .resolves(RAW_CONTENT_TYPES); }); - it('calls fetchElement three times (topics, brands, markets)', async () => { + it('calls fetchElement four times (topics, brands, markets, content types)', async () => { await service.getUrlInspectorFilterDimensions('ws-1', {}); - expect(transport.fetchElement).to.have.been.calledThrice; + expect(transport.fetchElement.callCount).to.equal(4); }); - it('fetches TOPICS, BRANDS and MARKETS in parallel', async () => { + it('fetches TOPICS, BRANDS, MARKETS and CONTENT_TYPES in parallel', async () => { await service.getUrlInspectorFilterDimensions('ws-1', {}); const calledIds = transport.fetchElement.getCalls().map((c) => c.args[1]); expect(calledIds).to.include(ELEMENT_IDS.TOPICS); expect(calledIds).to.include(ELEMENT_IDS.BRANDS); expect(calledIds).to.include(ELEMENT_IDS.MARKETS); + expect(calledIds).to.include(ELEMENT_IDS.CONTENT_TYPES); }); - it('returns an object with brands, regions, topics, categories, page_intents, origins, type, tags keys', async () => { + it('returns an object with brands, regions, topics, categories, page_intents, origins, content_types, type, tags keys', async () => { const result = await service.getUrlInspectorFilterDimensions('ws-1', {}); expect(result).to.have.all.keys([ - 'brands', 'regions', 'topics', 'categories', 'page_intents', 'origins', 'type', 'tags', + 'brands', 'regions', 'topics', 'categories', 'page_intents', 'origins', + 'content_types', 'type', 'tags', + ]); + }); + + it('content_types contains the transformed content-type filter dimensions', async () => { + const result = await service.getUrlInspectorFilterDimensions('ws-1', {}); + expect(result.content_types).to.deep.equal([ + { id: 'other', label: 'Other' }, + { id: 'social', label: 'Social' }, + { id: 'earned', label: 'Earned' }, + { id: 'owned', label: 'Owned' }, + { id: 'benchmark_competitors', label: 'Benchmark Competitors' }, ]); }); From 4f9905485cb0e5bfaa8dc15d304d3d69e7bcb53a Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Wed, 22 Jul 2026 17:09:08 +0530 Subject: [PATCH 2/3] update openapi docs --- docs/index.html | 90 ++++++++++++++++++++++------------ docs/openapi/schemas.yaml | 33 ++++++++----- docs/openapi/serenity-api.yaml | 28 ++++++++--- 3 files changed, 102 insertions(+), 49 deletions(-) diff --git a/docs/index.html b/docs/index.html index 7d7cb39423..8d05c208f7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -567,7 +567,7 @@ 55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864 -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688, -104.0616 -231.873,-231.248 z - " fill="currentColor">

SpaceCat API (1.667.0)

Download OpenAPI specification:

SpaceCat API (1.676.0)

Download OpenAPI specification:

The SpaceCat API is used to manage edge delivery site directory and obtain audit information.

Authentication

api_key

DEPRECATED, use scoped_api_key or ims_key. (Used for read-only operations.)

@@ -5755,7 +5755,9 @@ " class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

The search query prompt text

name
string

Display name (defaults to prompt slice)

-
regions
Array of strings
Default: []
categoryId
string
topicId
string
status
string
Default: "active"
Enum: "active" "pending" "deleted"
origin
string
Default: "human"
Enum: "ai" "human"
source
string
Default: "config"
regions
Array of strings
Default: []
categoryId
string
topicId
string
status
string
Default: "active"
Enum: "active" "pending" "deleted"
origin
string
Default: "human"
Enum: "ai" "human"

Who authored the prompt's text. SERVICE-PRINCIPAL-ONLY and read-only for end users: a user-authenticated request (IMS/JWT) has this field IGNORED (never rejected) and the value derived as human; only a service principal (e.g. the generation pipeline) may assert it, validated against the enum. It is never patched on update — it is fixed by the writer that created the row (origin-dimension.md §3).

+
source
string
Default: "config"

The source system or process that created the prompt

intent
string

Optional search-intent bucket for the prompt. Normalized on write: the value is lowercased, legacy aliases are remapped (statistical/navigational -> informational, commercial -> transactional), and the result is validated against the canonical buckets. An absent, empty, or (post-remap) invalid value is stored as null (NULL) — it is NOT coerced to a default bucket. Canonical buckets (mirror DRS prompt generation): informational, instructional, comparative, transactional, planning, delegation. No enum constraint is applied to the input: legacy aliases (statistical, navigational, commercial) are accepted and normalized on write.

@@ -5787,7 +5789,9 @@ " class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

Development server

https://spacecat.experiencecloud.live/api/ci/v2/orgs/{spaceCatId}/brands/{brandId}/prompts/{promptId}

Production server

-
https://spacecat.experiencecloud.live/api/v1/v2/orgs/{spaceCatId}/brands/{brandId}/prompts/{promptId}

Response samples

Content type
application/json
{
  • "id": "photoshop-prompt-1",
  • "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f",
  • "prompt": "string",
  • "name": "string",
  • "regions": [
    ],
  • "categoryId": "337f5e5d-288b-40d5-be14-901cc3acacc0",
  • "topicId": "string",
  • "status": "active",
  • "origin": "ai",
  • "source": "config",
  • "intent": "informational",
  • "updatedAt": "2024-01-19T14:20:30Z",
  • "updatedBy": "string",
  • "brandId": "0e9bcbb3-096e-49f9-aeea-7a13a201eff5",
  • "brandName": "string",
  • "category": {
    },
  • "topic": {
    }
}

Update a single prompt

Authorizations:
ims_keyapi_key
path Parameters
spaceCatId
required
string <uuid>
brandId
required
string
promptId
required
string
https://spacecat.experiencecloud.live/api/v1/v2/orgs/{spaceCatId}/brands/{brandId}/prompts/{promptId}

Response samples

Content type
application/json
{
  • "id": "photoshop-prompt-1",
  • "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f",
  • "prompt": "string",
  • "name": "string",
  • "regions": [
    ],
  • "categoryId": "337f5e5d-288b-40d5-be14-901cc3acacc0",
  • "topicId": "string",
  • "status": "active",
  • "origin": "ai",
  • "source": "config",
  • "intent": "informational",
  • "updatedAt": "2024-01-19T14:20:30Z",
  • "updatedBy": "string",
  • "brandId": "0e9bcbb3-096e-49f9-aeea-7a13a201eff5",
  • "brandName": "string",
  • "category": {
    },
  • "topic": {
    }
}

Update a single prompt

Updates a single prompt. origin is NOT patchable — it is fixed by the writer that created the row and is never re-derived on update (origin-dimension.md §3 item 3); an origin in the body is ignored, leaving the stored value untouched.

+
Authorizations:
ims_keyapi_key
path Parameters
spaceCatId
required
string <uuid>
brandId
required
string
promptId
required
string

prompt_id (business key)

Request Body schema: application/json
id
string

Optional business key; auto-generated if omitted

@@ -5795,7 +5799,9 @@ " class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

The search query prompt text

name
string

Display name (defaults to prompt slice)

-
regions
Array of strings
Default: []
categoryId
string
topicId
string
status
string
Default: "active"
Enum: "active" "pending" "deleted"
origin
string
Default: "human"
Enum: "ai" "human"
source
string
Default: "config"
regions
Array of strings
Default: []
categoryId
string
topicId
string
status
string
Default: "active"
Enum: "active" "pending" "deleted"
origin
string
Default: "human"
Enum: "ai" "human"

Who authored the prompt's text. SERVICE-PRINCIPAL-ONLY and read-only for end users: a user-authenticated request (IMS/JWT) has this field IGNORED (never rejected) and the value derived as human; only a service principal (e.g. the generation pipeline) may assert it, validated against the enum. It is never patched on update — it is fixed by the writer that created the row (origin-dimension.md §3).

+
source
string
Default: "config"

The source system or process that created the prompt

intent
string

Optional search-intent bucket for the prompt. Normalized on write: the value is lowercased, legacy aliases are remapped (statistical/navigational -> informational, commercial -> transactional), and the result is validated against the canonical buckets. An absent, empty, or (post-remap) invalid value is stored as null (NULL) — it is NOT coerced to a default bucket. Canonical buckets (mirror DRS prompt generation): informational, instructional, comparative, transactional, planning, delegation. No enum constraint is applied to the input: legacy aliases (statistical, navigational, commercial) are accepted and normalized on write.

@@ -10371,8 +10377,10 @@

Behaviour notes

" class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

Akamai contract ID.

groupId
required
string^grp_[A-Za-z0-9]+$

Akamai group ID.

-
insertIndex
integer >= 0

Optional position among the existing (non-managed) top-level rules to insert the managed wrapper at. Defaults to 0 (before everything); clamped to the number of existing children.

+
insertIndex
integer >= 0

Optional position among the existing (non-managed) top-level rules to insert the managed wrapper at. Defaults to appending AFTER all existing children (so the Optimize-at-Edge origin/cacheId win, since Akamai is last-match-wins); an explicit value is clamped to the number of existing children.

+
baseVersion
integer >= 1

(deploy only) Optional property version to copy the new version from. Defaults to the property's latest version.

Responses

Production server

-
https://spacecat.experiencecloud.live/api/v1/sites/{siteId}/llmo/cdn-onboard/akamai/plan

Request samples

Content type
application/json
{
  • "propertyId": "prp_1253269",
  • "contractId": "ctr_1-ABC123",
  • "groupId": "grp_18385",
  • "insertIndex": 0
}

Response samples

Content type
application/json
{
  • "propertyId": "string",
  • "latestVersion": 0,
  • "ruleFormat": "string",
  • "managedRules": [
    ],
  • "currentChildRules": [
    ],
  • "mergedChildRules": [
    ],
  • "validated": true,
  • "errors": [
    ],
  • "warnings": [
    ],
  • "merged": { }
}

Create a new property version with the Edge Optimize rules

https://spacecat.experiencecloud.live/api/v1/sites/{siteId}/llmo/cdn-onboard/akamai/plan

Request samples

Content type
application/json
{
  • "propertyId": "prp_1253269",
  • "contractId": "ctr_1-ABC123",
  • "groupId": "grp_18385",
  • "insertIndex": 0,
  • "baseVersion": 1
}

Response samples

Content type
application/json
{
  • "propertyId": "string",
  • "latestVersion": 0,
  • "ruleFormat": "string",
  • "managedRules": [
    ],
  • "currentChildRules": [
    ],
  • "mergedChildRules": [
    ],
  • "validated": true,
  • "errors": [
    ],
  • "warnings": [
    ],
  • "merged": { }
}

Create a new property version with the Edge Optimize rules

Creates a NEW property version from the latest, merges the managed Edge Optimize rules into -it, and PUTs the rule tree with Akamai-side validation. Does NOT activate — activation is a +" class="sc-iJuXkV sc-cBNeAB iNuSsz jtfGmi">

Creates a NEW property version from baseVersion (default: latest), merges the managed Edge +Optimize rules into it, and PUTs the rule tree with Akamai-side validation. Only supported for +properties whose default origin uses "Choose Your Own" (CUSTOM) SSL verification; a property on +"Use Platform Settings" is rejected with 400. Does NOT activate — activation is a separate, explicit step. Guarded so the target property must serve the site's own domain on an active hostname. Idempotent by rule name: the merge replaces any previously-managed rules rather than duplicating them.

@@ -10427,8 +10439,10 @@

Behaviour notes

" class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

Akamai contract ID.

groupId
required
string^grp_[A-Za-z0-9]+$

Akamai group ID.

-
insertIndex
integer >= 0

Optional position among the existing (non-managed) top-level rules to insert the managed wrapper at. Defaults to 0 (before everything); clamped to the number of existing children.

+
insertIndex
integer >= 0

Optional position among the existing (non-managed) top-level rules to insert the managed wrapper at. Defaults to appending AFTER all existing children (so the Optimize-at-Edge origin/cacheId win, since Akamai is last-match-wins); an explicit value is clamped to the number of existing children.

+
baseVersion
integer >= 1

(deploy only) Optional property version to copy the new version from. Defaults to the property's latest version.

Responses

Production server

-
https://spacecat.experiencecloud.live/api/v1/sites/{siteId}/llmo/cdn-onboard/akamai/deploy

Request samples

Content type
application/json
{
  • "propertyId": "prp_1253269",
  • "contractId": "ctr_1-ABC123",
  • "groupId": "grp_18385",
  • "insertIndex": 0
}

Response samples

Content type
application/json
{
  • "propertyId": "string",
  • "baseVersion": 0,
  • "newVersion": 0,
  • "managedRules": [
    ],
  • "warnings": [
    ]
}

Activate a property version to STAGING or PRODUCTION

https://spacecat.experiencecloud.live/api/v1/sites/{siteId}/llmo/cdn-onboard/akamai/deploy

Request samples

Content type
application/json
{
  • "propertyId": "prp_1253269",
  • "contractId": "ctr_1-ABC123",
  • "groupId": "grp_18385",
  • "insertIndex": 0,
  • "baseVersion": 1
}

Response samples

Content type
application/json
{
  • "propertyId": "string",
  • "baseVersion": 0,
  • "newVersion": 0,
  • "managedRules": [
    ],
  • "warnings": [
    ]
}

Activate a property version to STAGING or PRODUCTION

Behaviour notes " class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

Akamai contract ID.

groupId
required
string^grp_[A-Za-z0-9]+$

Akamai group ID.

-
insertIndex
integer >= 0

Optional position among the existing (non-managed) top-level rules to insert the managed wrapper at. Defaults to 0 (before everything); clamped to the number of existing children.

+
insertIndex
integer >= 0

Optional position among the existing (non-managed) top-level rules to insert the managed wrapper at. Defaults to appending AFTER all existing children (so the Optimize-at-Edge origin/cacheId win, since Akamai is last-match-wins); an explicit value is clamped to the number of existing children.

+
baseVersion
integer >= 1

(deploy only) Optional property version to copy the new version from. Defaults to the property's latest version.

network
string
Default: "STAGING"
Enum: "STAGING" "PRODUCTION"

Activation network. Defaults to STAGING.

version
integer >= 1
Behaviour notes " class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

Development server

https://spacecat.experiencecloud.live/api/ci/sites/{siteId}/llmo/cdn-onboard/akamai/activate

Production server

-
https://spacecat.experiencecloud.live/api/v1/sites/{siteId}/llmo/cdn-onboard/akamai/activate

Request samples

Content type
application/json
{
  • "propertyId": "prp_1253269",
  • "contractId": "ctr_1-ABC123",
  • "groupId": "grp_18385",
  • "insertIndex": 0,
  • "network": "STAGING",
  • "version": 1
}

Response samples

Content type
application/json
{
  • "propertyId": "string",
  • "version": 0,
  • "network": "STAGING",
  • "activationId": "atv_123",
  • "activationLink": "string"
}

Check the status of an Akamai activation

https://spacecat.experiencecloud.live/api/v1/sites/{siteId}/llmo/cdn-onboard/akamai/activate

Request samples

Content type
application/json
{
  • "propertyId": "prp_1253269",
  • "contractId": "ctr_1-ABC123",
  • "groupId": "grp_18385",
  • "insertIndex": 0,
  • "baseVersion": 1,
  • "network": "STAGING",
  • "version": 1
}

Response samples

Content type
application/json
{
  • "propertyId": "string",
  • "version": 0,
  • "network": "STAGING",
  • "activationId": "atv_123",
  • "activationLink": "string"
}

Check the status of an Akamai activation

Returns the status of an activation. With activationId, checks that specific activation @@ -14794,30 +14810,34 @@

Filters

https://spacecat.experiencecloud.live/api/ci/v2/orgs/{spaceCatId}/brands/{brandId}/serenity/deactivate

Production server

https://spacecat.experiencecloud.live/api/v1/v2/orgs/{spaceCatId}/brands/{brandId}/serenity/deactivate

Response samples

Content type
application/json
{
  • "brandId": "0e9bcbb3-096e-49f9-aeea-7a13a201eff5",
  • "status": "pending"
}

Filter dimensions for the Serenity URL Inspector dashboard

Returns the option universe for the URL Inspector Brand/Region/Topic/ -Category/Intent/Origin filter dropdowns, sourced from the Semrush Brands, -Markets, and Topics elements (not from Adobe Postgres). Unlike the -cascading /brand-presence/filter-dimensions endpoint, this read is not -itself filtered by any of the dimensions it returns — it defines what -those dropdowns can show.

+Category/Intent/Origin/Content-Type filter dropdowns, sourced from the +Semrush Brands, Markets, Topics, and Content Types elements (not from +Adobe Postgres). Unlike the cascading /brand-presence/filter-dimensions +endpoint, this read is not itself filtered by any of the dimensions it +returns — it defines what those dropdowns can show.

topics/categories/page_intents/origins are derived from the same underlying Topics element response, split apart by their topic__/ category__/intent__/source__ tag prefix. Any other tag prefix Semrush introduces later (e.g. type__branded) surfaces automatically as an extra array property keyed by that prefix. Bare prefix declarations with no -value (e.g. a lone category row) are ignored — see +value (e.g. a lone category row) are ignored. content_types is +sourced from a separate Content Types element (Semrush domain_type +values) scoped to the same model/date window — see SerenityUrlInspectorFilterDimensions.

Authorizations:
ims_key
path Parameters
spaceCatId
required
string <uuid>

SpaceCat Organization ID (UUID)

@@ -14831,8 +14851,16 @@

Filters

server-side). Defaults to search-gpt when omitted or unrecognized.

platform
string

Legacy alias for model; model takes precedence when both are given.

-
projectId
string <uuid>
projectId
string <uuid>

Semrush project UUID to scope the Topics element to a specific market.

+
startDate
string <date>

ISO date (YYYY-MM-DD) scoping the Content Types element's date window. +Defaults to 28 days before endDate.

+
endDate
string <date>

ISO date (YYYY-MM-DD) scoping the Content Types element's date window. +Defaults to today.

Responses

Production server

-
https://spacecat.experiencecloud.live/api/v1/v2/orgs/{spaceCatId}/brands/{brandId}/serenity/brand-presence/url-inspector/filter-dimensions

Response samples

Content type
application/json
{
  • "brands": [
    ],
  • "regions": [
    ],
  • "topics": [
    ],
  • "categories": [
    ],
  • "page_intents": [
    ],
  • "origins": [
    ],
  • "tags": [
    ],
  • "property1": [
    ],
  • "property2": [
    ]
}

Weekly Market Tracking Trends for the Brand Presence Competitor Comparison chart

https://spacecat.experiencecloud.live/api/v1/v2/orgs/{spaceCatId}/brands/{brandId}/serenity/brand-presence/url-inspector/filter-dimensions

Response samples

Content type
application/json
{
  • "brands": [
    ],
  • "regions": [
    ],
  • "topics": [
    ],
  • "categories": [
    ],
  • "page_intents": [
    ],
  • "origins": [
    ],
  • "content_types": [
    ],
  • "tags": [
    ],
  • "property1": [
    ],
  • "property2": [
    ]
}

Weekly Market Tracking Trends for the Brand Presence Competitor Comparison chart

Filters " class="sc-iJuXkV sc-cBNeAB iNuSsz dyntKg">

Production server

https://spacecat.experiencecloud.live/api/v1/monitoring/drs-bp-pg-audit

Response samples

Content type
application/json
{
  • "rows": [
    ],
  • "hasMore": true
}