Summary
Allow POST /v2/orgs/:org/brands/:brand/serenity/tags to create generic tags — arbitrary <dimension>:<value> tags — instead of only the category / topic open dimensions. In particular, support creating tags under the type: dimension (and other prefixes), which the endpoint currently rejects.
Current behavior
createTag (added in #2730) is deliberately narrow:
src/support/serenity/prompt-tags.js — CREATABLE_TAG_DIMENSIONS = [CATEGORY, TOPIC]. The other dimensions in TAG_DIMENSION (source, intent, type) are closed taxonomies with fixed enums (SOURCE_TAG / INTENT_TAG / TYPE_TAG) and are not freely creatable.
src/support/serenity/handlers/tags.js — parseCreateTagBody:
type must be in CREATABLE_TAG_DIMENSIONS → a type: (or any non-category/topic) value is a 400.
name must not contain : → you can't pass a fully-qualified type:branded as the name either (it would otherwise double-prefix to category:type:branded).
- The stored tag is
tagFor(type, name) = "<type>:<name>".
So today there is no way to register e.g. type:<something> or any generic <prefix>:<value> tag through this endpoint.
Requested change
Support generic tags on the same endpoint. Two design options (pick one — needs a decision):
- Widen the dimension allow-list. Add
type (and optionally source / intent) to CREATABLE_TAG_DIMENSIONS, keeping the type + name (no-colon) composition. Simple, but it conflates "open" and "closed" taxonomies — the closed ones have fixed enums for a reason (classification semantics, the onboarding INTENT_MAP, etc.), so freely creating arbitrary type:foo values may be undesirable.
- Add an explicit generic/raw mode. Allow the caller to pass a full tag string (e.g.
name: "type:branded" with a generic/raw type, or a dedicated field), relaxing the no-colon rule for that mode and storing the name verbatim. Keeps category/topic strict while making genericness opt-in and unambiguous.
Either way, update:
parseCreateTagBody validation (and the name must not contain ":" rule),
CREATABLE_TAG_DIMENSIONS / the SerenityCreatableTagType mirror in elmo (project-elmo-ui src/types/serenity.ts),
docs/openapi/serenity-api.yaml (the type enum / request schema),
- unit + IT tests.
Mock support (spacecat-shared project-engine-client)
Likely no mock change needed — the mock already stores arbitrary tag names verbatim:
Caveats to verify if option 2 (colons in names) is chosen:
- Confirm Counterfact request validation against the overlayed OpenAPI doesn't constrain the
names string format.
- Confirm
tagId collision behavior and GET /aio/tags search filtering still behave for prefix:value names.
Out-of-scope but must be checked first: real Semrush contract
The mock is permissive, but the gating question is whether the live Semrush AIO gateway accepts arbitrary <prefix>:<value> tag names — especially under the reserved type: / source: / intent: dimensions, which may have upstream semantics. Validate against live before widening, so the mock and prod don't diverge.
Context
Summary
Allow
POST /v2/orgs/:org/brands/:brand/serenity/tagsto create generic tags — arbitrary<dimension>:<value>tags — instead of only thecategory/topicopen dimensions. In particular, support creating tags under thetype:dimension (and other prefixes), which the endpoint currently rejects.Current behavior
createTag(added in #2730) is deliberately narrow:src/support/serenity/prompt-tags.js—CREATABLE_TAG_DIMENSIONS = [CATEGORY, TOPIC]. The other dimensions inTAG_DIMENSION(source,intent,type) are closed taxonomies with fixed enums (SOURCE_TAG/INTENT_TAG/TYPE_TAG) and are not freely creatable.src/support/serenity/handlers/tags.js—parseCreateTagBody:typemust be inCREATABLE_TAG_DIMENSIONS→ atype:(or any non-category/topic) value is a400.namemust not contain:→ you can't pass a fully-qualifiedtype:brandedas the name either (it would otherwise double-prefix tocategory:type:branded).tagFor(type, name)="<type>:<name>".So today there is no way to register e.g.
type:<something>or any generic<prefix>:<value>tag through this endpoint.Requested change
Support generic tags on the same endpoint. Two design options (pick one — needs a decision):
type(and optionallysource/intent) toCREATABLE_TAG_DIMENSIONS, keeping thetype+name(no-colon) composition. Simple, but it conflates "open" and "closed" taxonomies — the closed ones have fixed enums for a reason (classification semantics, the onboardingINTENT_MAP, etc.), so freely creating arbitrarytype:foovalues may be undesirable.name: "type:branded"with a generic/rawtype, or a dedicated field), relaxing the no-colon rule for that mode and storing the name verbatim. Keepscategory/topicstrict while making genericness opt-in and unambiguous.Either way, update:
parseCreateTagBodyvalidation (and thename must not contain ":"rule),CREATABLE_TAG_DIMENSIONS/ theSerenityCreatableTagTypemirror in elmo (project-elmo-ui src/types/serenity.ts),docs/openapi/serenity-api.yaml(thetypeenum / request schema),Mock support (spacecat-shared project-engine-client)
Likely no mock change needed — the mock already stores arbitrary tag names verbatim:
POST .../aio/tags(createProjectTags) takes{ names: string[] }with no per-name format constraint and persists them (stateful since 1.4.0, feat(project-engine-client): make project AIO tags a stateful mock resource spacecat-shared#1752).tag-<urlencoded name>), which already handles a:in the name, andGET .../aio/tagslists them back — so a generic name round-trips.Caveats to verify if option 2 (colons in names) is chosen:
namesstring format.tagIdcollision behavior andGET /aio/tagssearchfiltering still behave forprefix:valuenames.Out-of-scope but must be checked first: real Semrush contract
The mock is permissive, but the gating question is whether the live Semrush AIO gateway accepts arbitrary
<prefix>:<value>tag names — especially under the reservedtype:/source:/intent:dimensions, which may have upstream semantics. Validate against live before widening, so the mock and prod don't diverge.Context