# Update a campaign `PATCH /platform/v1/campaigns/{campaign_id}` Partial update. An **archived** campaign is terminal: every update is rejected with `409`, including a status change, so it can never be un-archived — create a new campaign instead. A present `rules` list fully replaces the rule set. ## Request example ```bash curl --request PATCH \ --url 'https://api.eu1.luigisbox.ai/platform/v1/campaigns/?filter_syntax=group' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "catalog_ids": [ "example" ], "catalog_tags": [ "example" ], "channel_ids": [ "example" ], "channel_tags": [ "example" ], "ends_at": "2026-04-15T12:00:00Z", "intents": [ "search" ], "match": { "conditions": [ { "field": "example", "on_missing": "fail_closed", "operator": "eq", "source": "request", "value": "example", "value_from": {} } ], "operator": "and" }, "name": "example", "rules": [ { "effect": { "content_type": "product", "decay_window": "example", "disclosure_type": "sponsored", "mode": "hard", "position": 1, "respect_user_filters": false, "type": "pin" }, "id": "example", "name": "example", "record_match": { "conditions": [ "example" ], "operator": "and" } } ], "starts_at": "2026-04-15T12:00:00Z", "status": "draft", "surface_ids": [ "example" ], "surface_tags": [ "example" ], "timezone": "example" }' ``` ## Path Parameters | Name | Type | Required | Description | Constraints | |---|---|---|---|---| | `campaign_id` | `string` | Yes | Campaign Id | — | ## Query Parameters | Name | Type | Required | Description | Constraints | |---|---|---|---|---| | `filter_syntax` | `string` | No | Syntax filter trees are rendered in. `group` is the `{operator, conditions}` form; `map` is the `$and` / `$or` / `$not` map syntax the Discovery API uses, in which an empty tree renders as null. Request bodies accept either syntax regardless of this setting. | allowed values: group, map | ## Request Body **Required.** ### application/json Schema: `object` #### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `catalog_ids` | `Array` | No | Catalog Ids | — | | `catalog_tags` | `Array` | No | Catalog Tags | — | | `channel_ids` | `Array` | No | Channel Ids | — | | `channel_tags` | `Array` | No | Channel Tags | — | | `ends_at` | `anyOf(string, null)` | No | Ends At | — | | `intents` | `Array` | No | Surface Intents this campaign targets — the kind of discovery it applies to. Empty means **every** Intent, not none. Narrowing by Intent is mutually exclusive with narrowing by `surface_ids` / `surface_tags`: an Intent targets whole families of surfaces, so combining the two is rejected with `intents_conflict`. A targeted Intent no scoped catalog hosts a surface for is rejected with `catalog_without_targeted_intent` — it could never trigger. | — | | `match` | `anyOf(oneOf(object, oneOf(object, object)), null)` | No | The campaign trigger: when the campaign fires, evaluated against the request. Every leaf `field` must carry the `trigger.` prefix. After the prefix, the field is either a catalog attribute of the matched anchor/collection document (e.g. `trigger.brand`) or one of the four request-owned fields under the reserved, closed `request:` namespace: `trigger.request:query` (the search query, analyzed — supports `matches`), `trigger.request:intent` (the surface Intent; one of `search` / `recommender` / `collection`), `trigger.request:type` (the requested object type) and `trigger.request:anchor_id` (a recommender anchor's public id). Any other `trigger.request:*` name is rejected with 422. `POST /campaigns/trigger-schema` projects the authorable fields and their operators for a targeting scope. Written in either the group syntax `{"operator": "and", "conditions": [...]}` or the map syntax `{"$and": [{"field": {"eq": ...}}]}`; an explicit null is the empty tree, which constrains nothing. Read back in the syntax the `filter_syntax` query parameter selects. | — | | `match.conditions` | `Array` | No | Leaf conditions and nested groups. | — | | `match.conditions[].field` | `string` | Yes | Object field the condition reads. | — | | `match.conditions[].on_missing` | `anyOf(string, null)` | No | With `value_from`: what the condition does when no anchor carries the attribute. | — | | `match.conditions[].operator` | `string` | Yes | Comparison operator. | allowed values: eq, neq, gt, gte, lt, lte, in, not_in, matches, starts_with, ends_with, range, exists | | `match.conditions[].source` | `string` | No | Provenance of the condition; stamped by the system, never read from a client. | allowed values: request, collection, surface, autofilter, business_rule, visibility | | `match.conditions[].value` | `any` | No | Literal to compare against, or a trigger reference `{trigger_ref: ...}`; a scalar for eq/neq and the comparisons, a non-empty list for in/not_in, absent for exists. | — | | `match.conditions[].value_from` | `anyOf(object, null)` | No | Dynamic reference read from the request's anchors instead of a literal `value`. | — | | `match.conditions[].value_from.anchor_attribute` | `string` | Yes | Anchor attribute whose value the condition compares against. | — | | `match.operator` | `string` | Yes | Boolean combinator applied to `conditions`. | allowed values: and, or, not | | `match.$and` | `Array` | No | Nodes that must all match. | >= 1 items; <= 100 items | | `match.$not` | `oneOf(object, object)` | No | A node that must not match. | — | | `match.$or` | `Array` | No | Nodes of which at least one must match. | >= 1 items; <= 100 items | | `match.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `match.*.eq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.eq.index` | `integer` | No | — | >= 0 | | `match.*.eq.n` | `integer` | No | — | >= 1 | | `match.*.eq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.eq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.eq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.exists` | `boolean` | No | — | — | | `match.*.gt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.gt.index` | `integer` | No | — | >= 0 | | `match.*.gt.n` | `integer` | No | — | >= 1 | | `match.*.gt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.gte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.gte.index` | `integer` | No | — | >= 0 | | `match.*.gte.n` | `integer` | No | — | >= 1 | | `match.*.gte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.in` | `anyOf(Array, object)` | No | — | — | | `match.*.in.index` | `integer` | No | — | >= 0 | | `match.*.in.n` | `integer` | No | — | >= 1 | | `match.*.in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.lt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.lt.index` | `integer` | No | — | >= 0 | | `match.*.lt.n` | `integer` | No | — | >= 1 | | `match.*.lt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.lte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.lte.index` | `integer` | No | — | >= 0 | | `match.*.lte.n` | `integer` | No | — | >= 1 | | `match.*.lte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.matches` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.matches.index` | `integer` | No | — | >= 0 | | `match.*.matches.n` | `integer` | No | — | >= 1 | | `match.*.matches.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.matches.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.matches.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.neq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.neq.index` | `integer` | No | — | >= 0 | | `match.*.neq.n` | `integer` | No | — | >= 1 | | `match.*.neq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.neq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.neq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.not_in` | `anyOf(Array, object)` | No | — | — | | `match.*.not_in.index` | `integer` | No | — | >= 0 | | `match.*.not_in.n` | `integer` | No | — | >= 1 | | `match.*.not_in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.not_in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.not_in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `name` | `string` | No | Name | >= 1 characters; <= 256 characters | | `rules` | `anyOf(Array, null)` | No | Rules | — | | `rules[].effect` | `oneOf(object, object, object, object)` | Yes | CampaignRuleEffect | — | | `rules[].effect.content_type` | `anyOf(string, null)` | No | Content Type | — | | `rules[].effect.decay_window` | `anyOf(string, null)` | No | Decay Window | — | | `rules[].effect.disclosure_type` | `anyOf(string, null)` | No | Disclosure Type | — | | `rules[].effect.mode` | `string` | No | Mode | allowed values: hard, soft | | `rules[].effect.position` | `integer` | Yes | Position | — | | `rules[].effect.respect_user_filters` | `boolean` | No | Respect User Filters | — | | `rules[].effect.type` | `string` | No | Type | allowed value: pin | | `rules[].effect.strength_pct` | `number` | Yes | Strength Pct | >= 0; <= 1 | | `rules[].id` | `anyOf(string, null)` | No | Id | — | | `rules[].name` | `string` | Yes | Name | >= 1 characters; <= 256 characters | | `rules[].record_match` | `anyOf(oneOf(object, oneOf(object, object)), null)` | Yes | The product selector: which records within the campaign's scope the effect hits, as plain catalog fields with no prefix. Empty selects the whole scope. Written in either the group syntax `{"operator": "and", "conditions": [...]}` or the map syntax `{"$and": [{"field": {"eq": ...}}]}`; an explicit null is the empty tree, which constrains nothing. Read back in the syntax the `filter_syntax` query parameter selects. | — | | `rules[].record_match.conditions` | `Array` | No | Leaf conditions and nested groups. | — | | `rules[].record_match.conditions[].field` | `string` | Yes | Object field the condition reads. | — | | `rules[].record_match.conditions[].on_missing` | `anyOf(string, null)` | No | With `value_from`: what the condition does when no anchor carries the attribute. | — | | `rules[].record_match.conditions[].operator` | `string` | Yes | Comparison operator. | allowed values: eq, neq, gt, gte, lt, lte, in, not_in, matches, starts_with, ends_with, range, exists | | `rules[].record_match.conditions[].source` | `string` | No | Provenance of the condition; stamped by the system, never read from a client. | allowed values: request, collection, surface, autofilter, business_rule, visibility | | `rules[].record_match.conditions[].value` | `any` | No | Literal to compare against, or a trigger reference `{trigger_ref: ...}`; a scalar for eq/neq and the comparisons, a non-empty list for in/not_in, absent for exists. | — | | `rules[].record_match.conditions[].value_from` | `anyOf(object, null)` | No | Dynamic reference read from the request's anchors instead of a literal `value`. | — | | `rules[].record_match.conditions[].value_from.anchor_attribute` | `string` | Yes | Anchor attribute whose value the condition compares against. | — | | `rules[].record_match.operator` | `string` | Yes | Boolean combinator applied to `conditions`. | allowed values: and, or, not | | `rules[].record_match.$and` | `Array` | No | Nodes that must all match. | >= 1 items; <= 100 items | | `rules[].record_match.$not` | `oneOf(object, object)` | No | A node that must not match. | — | | `rules[].record_match.$or` | `Array` | No | Nodes of which at least one must match. | >= 1 items; <= 100 items | | `rules[].record_match.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `rules[].record_match.*.eq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.eq.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.eq.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.eq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.eq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.eq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.exists` | `boolean` | No | — | — | | `rules[].record_match.*.gt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.gt.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.gt.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.gt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.gte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.gte.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.gte.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.gte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.in` | `anyOf(Array, object)` | No | — | — | | `rules[].record_match.*.in.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.in.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.lt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.lt.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.lt.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.lt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.lte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.lte.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.lte.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.lte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.matches` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.matches.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.matches.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.matches.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.matches.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.matches.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.neq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.neq.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.neq.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.neq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.neq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.neq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.not_in` | `anyOf(Array, object)` | No | — | — | | `rules[].record_match.*.not_in.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.not_in.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.not_in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.not_in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.not_in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `starts_at` | `anyOf(string, null)` | No | Starts At | — | | `status` | `string` | No | Lifecycle state of a Campaign. | allowed values: draft, active, paused, archived | | `surface_ids` | `Array` | No | Surface Ids | — | | `surface_tags` | `Array` | No | Surface Tags | — | | `timezone` | `anyOf(string, null)` | No | IANA timezone identifier (e.g. `Europe/Bratislava`) the schedule was authored in, kept so it can be re-opened in that zone. **Informational only:** `starts_at` / `ends_at` are the UTC instants that govern when the campaign runs, and this field never shifts them — use it when re-planning a schedule, never to compute execution times. Requires at least one of `starts_at` / `ends_at`, and is cleared when both are cleared. Because tz databases differ between releases and between server and browser (`Europe/Kiev` vs `Europe/Kyiv`), a client whose own database does not know the identifier should keep the string as it is rather than discard or rewrite it. A scheduled campaign always reads back a zone — `UTC` when none was authored; an unscheduled one omits the field, so a response never shows the zone-without-schedule state that writes reject. | — | #### Request body example ```json { "catalog_ids": [ "example" ], "catalog_tags": [ "example" ], "channel_ids": [ "example" ], "channel_tags": [ "example" ], "ends_at": "2026-04-15T12:00:00Z", "intents": [ "search" ], "match": { "conditions": [ { "field": "example", "on_missing": "fail_closed", "operator": "eq", "source": "request", "value": "example", "value_from": {} } ], "operator": "and" }, "name": "example", "rules": [ { "effect": { "content_type": "product", "decay_window": "example", "disclosure_type": "sponsored", "mode": "hard", "position": 1, "respect_user_filters": false, "type": "pin" }, "id": "example", "name": "example", "record_match": { "conditions": [ "example" ], "operator": "and" } } ], "starts_at": "2026-04-15T12:00:00Z", "status": "draft", "surface_ids": [ "example" ], "surface_tags": [ "example" ], "timezone": "example" } ``` ## Responses | Status | Description | Content | |---|---|---| | `200` | Successful Response | application/json: object | | `401` | Missing or invalid credentials | application/json: object | | `403` | Authenticated but not permitted | application/json: object | | `404` | Resource not found | application/json: object | | `409` | State conflict: the campaign is archived (terminal — every update is rejected, including a status change), or the requested `name` is already used by another campaign in the organization. | application/json: object | | `422` | Request validation failed. A targeting rejection lists every problem at once in `exception_details.problems`. | application/json: anyOf(object, object) | | `503` | The catalog's attribute types could not be read, so the request could not be type-checked; retryable. | application/json: object | ### 200 Successful Response #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `catalog_ids` | `Array` | Yes | Catalog Ids | — | | `catalog_tags` | `Array` | Yes | Catalog Tags | — | | `channel_ids` | `Array` | Yes | Channel Ids | — | | `channel_tags` | `Array` | Yes | Channel Tags | — | | `created_at` | `string` | Yes | Created At | format: date-time | | `created_by` | `anyOf(string, null)` | Yes | Created By | — | | `ends_at` | `anyOf(string, null)` | Yes | Ends At | — | | `id` | `string` | Yes | Id | — | | `intents` | `Array` | Yes | Surface Intents this campaign targets — the kind of discovery it applies to. Empty means **every** Intent, not none. Narrowing by Intent is mutually exclusive with narrowing by `surface_ids` / `surface_tags`: an Intent targets whole families of surfaces, so combining the two is rejected with `intents_conflict`. A targeted Intent no scoped catalog hosts a surface for is rejected with `catalog_without_targeted_intent` — it could never trigger. | — | | `match` | `anyOf(object, oneOf(object, object), null)` | Yes | The campaign trigger: when the campaign fires, evaluated against the request. Every leaf `field` must carry the `trigger.` prefix. After the prefix, the field is either a catalog attribute of the matched anchor/collection document (e.g. `trigger.brand`) or one of the four request-owned fields under the reserved, closed `request:` namespace: `trigger.request:query` (the search query, analyzed — supports `matches`), `trigger.request:intent` (the surface Intent; one of `search` / `recommender` / `collection`), `trigger.request:type` (the requested object type) and `trigger.request:anchor_id` (a recommender anchor's public id). Any other `trigger.request:*` name is rejected with 422. `POST /campaigns/trigger-schema` projects the authorable fields and their operators for a targeting scope. Written in either the group syntax `{"operator": "and", "conditions": [...]}` or the map syntax `{"$and": [{"field": {"eq": ...}}]}`; an explicit null is the empty tree, which constrains nothing. Read back in the syntax the `filter_syntax` query parameter selects. | — | | `match.conditions` | `Array` | No | Leaf conditions and nested groups. | — | | `match.conditions[].field` | `string` | Yes | Object field the condition reads. | — | | `match.conditions[].on_missing` | `anyOf(string, null)` | No | With `value_from`: what the condition does when no anchor carries the attribute. | — | | `match.conditions[].operator` | `string` | Yes | Comparison operator. | allowed values: eq, neq, gt, gte, lt, lte, in, not_in, matches, starts_with, ends_with, range, exists | | `match.conditions[].source` | `string` | No | Provenance of the condition; stamped by the system, never read from a client. | allowed values: request, collection, surface, autofilter, business_rule, visibility | | `match.conditions[].value` | `any` | No | Literal to compare against, or a trigger reference `{trigger_ref: ...}`; a scalar for eq/neq and the comparisons, a non-empty list for in/not_in, absent for exists. | — | | `match.conditions[].value_from` | `anyOf(object, null)` | No | Dynamic reference read from the request's anchors instead of a literal `value`. | — | | `match.conditions[].value_from.anchor_attribute` | `string` | Yes | Anchor attribute whose value the condition compares against. | — | | `match.operator` | `string` | Yes | Boolean combinator applied to `conditions`. | allowed values: and, or, not | | `match.$and` | `Array` | No | Nodes that must all match. | >= 1 items; <= 100 items | | `match.$not` | `oneOf(object, object)` | No | A node that must not match. | — | | `match.$or` | `Array` | No | Nodes of which at least one must match. | >= 1 items; <= 100 items | | `match.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `match.*.eq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.eq.index` | `integer` | No | — | >= 0 | | `match.*.eq.n` | `integer` | No | — | >= 1 | | `match.*.eq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.eq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.eq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.exists` | `boolean` | No | — | — | | `match.*.gt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.gt.index` | `integer` | No | — | >= 0 | | `match.*.gt.n` | `integer` | No | — | >= 1 | | `match.*.gt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.gte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.gte.index` | `integer` | No | — | >= 0 | | `match.*.gte.n` | `integer` | No | — | >= 1 | | `match.*.gte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.gte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.in` | `anyOf(Array, object)` | No | — | — | | `match.*.in.index` | `integer` | No | — | >= 0 | | `match.*.in.n` | `integer` | No | — | >= 1 | | `match.*.in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.lt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.lt.index` | `integer` | No | — | >= 0 | | `match.*.lt.n` | `integer` | No | — | >= 1 | | `match.*.lt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.lte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.lte.index` | `integer` | No | — | >= 0 | | `match.*.lte.n` | `integer` | No | — | >= 1 | | `match.*.lte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.lte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.matches` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.matches.index` | `integer` | No | — | >= 0 | | `match.*.matches.n` | `integer` | No | — | >= 1 | | `match.*.matches.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.matches.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.matches.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.neq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `match.*.neq.index` | `integer` | No | — | >= 0 | | `match.*.neq.n` | `integer` | No | — | >= 1 | | `match.*.neq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.neq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.neq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `match.*.not_in` | `anyOf(Array, object)` | No | — | — | | `match.*.not_in.index` | `integer` | No | — | >= 0 | | `match.*.not_in.n` | `integer` | No | — | >= 1 | | `match.*.not_in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.not_in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `match.*.not_in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `name` | `string` | Yes | Name | — | | `organization_id` | `string` | Yes | Organization Id | — | | `rules` | `Array` | No | Rules | — | | `rules[].campaign_id` | `string` | Yes | Campaign Id | — | | `rules[].created_at` | `string` | Yes | Created At | format: date-time | | `rules[].created_by` | `anyOf(string, null)` | Yes | Created By | — | | `rules[].effect` | `oneOf(object, object, object, object)` | Yes | CampaignRuleEffect | — | | `rules[].effect.content_type` | `anyOf(string, null)` | No | Content Type | — | | `rules[].effect.decay_window` | `anyOf(string, null)` | No | Decay Window | — | | `rules[].effect.disclosure_type` | `anyOf(string, null)` | No | Disclosure Type | — | | `rules[].effect.mode` | `string` | No | Mode | allowed values: hard, soft | | `rules[].effect.position` | `integer` | Yes | Position | — | | `rules[].effect.respect_user_filters` | `boolean` | No | Respect User Filters | — | | `rules[].effect.type` | `string` | No | Type | allowed value: pin | | `rules[].effect.strength_pct` | `number` | Yes | Strength Pct | >= 0; <= 1 | | `rules[].id` | `string` | Yes | Id | — | | `rules[].name` | `string` | Yes | Name | — | | `rules[].record_match` | `anyOf(object, oneOf(object, object), null)` | Yes | Record Match | — | | `rules[].record_match.conditions` | `Array` | No | Leaf conditions and nested groups. | — | | `rules[].record_match.conditions[].field` | `string` | Yes | Object field the condition reads. | — | | `rules[].record_match.conditions[].on_missing` | `anyOf(string, null)` | No | With `value_from`: what the condition does when no anchor carries the attribute. | — | | `rules[].record_match.conditions[].operator` | `string` | Yes | Comparison operator. | allowed values: eq, neq, gt, gte, lt, lte, in, not_in, matches, starts_with, ends_with, range, exists | | `rules[].record_match.conditions[].source` | `string` | No | Provenance of the condition; stamped by the system, never read from a client. | allowed values: request, collection, surface, autofilter, business_rule, visibility | | `rules[].record_match.conditions[].value` | `any` | No | Literal to compare against, or a trigger reference `{trigger_ref: ...}`; a scalar for eq/neq and the comparisons, a non-empty list for in/not_in, absent for exists. | — | | `rules[].record_match.conditions[].value_from` | `anyOf(object, null)` | No | Dynamic reference read from the request's anchors instead of a literal `value`. | — | | `rules[].record_match.conditions[].value_from.anchor_attribute` | `string` | Yes | Anchor attribute whose value the condition compares against. | — | | `rules[].record_match.operator` | `string` | Yes | Boolean combinator applied to `conditions`. | allowed values: and, or, not | | `rules[].record_match.$and` | `Array` | No | Nodes that must all match. | >= 1 items; <= 100 items | | `rules[].record_match.$not` | `oneOf(object, object)` | No | A node that must not match. | — | | `rules[].record_match.$or` | `Array` | No | Nodes of which at least one must match. | >= 1 items; <= 100 items | | `rules[].record_match.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `rules[].record_match.*.eq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.eq.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.eq.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.eq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.eq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.eq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.exists` | `boolean` | No | — | — | | `rules[].record_match.*.gt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.gt.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.gt.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.gt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.gte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.gte.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.gte.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.gte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.gte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.in` | `anyOf(Array, object)` | No | — | — | | `rules[].record_match.*.in.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.in.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.lt` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.lt.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.lt.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.lt.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lt.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lt.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.lte` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.lte.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.lte.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.lte.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lte.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.lte.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.matches` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.matches.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.matches.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.matches.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.matches.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.matches.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.neq` | `anyOf(string \| number \| integer \| boolean \| null, object)` | No | — | — | | `rules[].record_match.*.neq.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.neq.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.neq.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.neq.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.neq.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].record_match.*.not_in` | `anyOf(Array, object)` | No | — | — | | `rules[].record_match.*.not_in.index` | `integer` | No | — | >= 0 | | `rules[].record_match.*.not_in.n` | `integer` | No | — | >= 1 | | `rules[].record_match.*.not_in.path` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.not_in.select` | `string` | No | — | allowed values: first, last, all, first_n, last_n, at_index, all_but_first, all_but_last | | `rules[].record_match.*.not_in.trigger_ref` | `string` | Yes | — | /^trigger\./ | | `rules[].updated_at` | `string` | Yes | Updated At | format: date-time | | `rules[].updated_by` | `anyOf(string, null)` | Yes | Updated By | — | | `starts_at` | `anyOf(string, null)` | Yes | Starts At | — | | `status` | `string` | Yes | Lifecycle state of a Campaign. | allowed values: draft, active, paused, archived | | `surface_ids` | `Array` | Yes | Surface Ids | — | | `surface_tags` | `Array` | Yes | Surface Tags | — | | `timezone` | `string` | No | IANA timezone identifier (e.g. `Europe/Bratislava`) the schedule was authored in, kept so it can be re-opened in that zone. **Informational only:** `starts_at` / `ends_at` are the UTC instants that govern when the campaign runs, and this field never shifts them — use it when re-planning a schedule, never to compute execution times. Requires at least one of `starts_at` / `ends_at`, and is cleared when both are cleared. Because tz databases differ between releases and between server and browser (`Europe/Kiev` vs `Europe/Kyiv`), a client whose own database does not know the identifier should keep the string as it is rather than discard or rewrite it. A scheduled campaign always reads back a zone — `UTC` when none was authored; an unscheduled one omits the field, so a response never shows the zone-without-schedule state that writes reject. | <= 64 characters | | `updated_at` | `string` | Yes | Updated At | format: date-time | | `updated_by` | `anyOf(string, null)` | Yes | Updated By | — | ##### Response example ```json { "catalog_ids": [ "example" ], "catalog_tags": [ "example" ], "channel_ids": [ "example" ], "channel_tags": [ "example" ], "created_at": "2026-04-15T12:00:00Z", "created_by": "example", "ends_at": "2026-04-15T12:00:00Z", "id": "example", "intents": [ "search" ], "match": { "conditions": [ { "field": "example", "on_missing": "fail_closed", "operator": "eq", "source": "request", "value": "example", "value_from": { "anchor_attribute": "example" } } ], "operator": "and" }, "name": "example", "organization_id": "example", "rules": [ { "campaign_id": "example", "created_at": "2026-04-15T12:00:00Z", "created_by": "example", "effect": { "content_type": "product", "decay_window": "example", "disclosure_type": "sponsored", "mode": "hard", "position": 1, "respect_user_filters": false, "type": "pin" }, "id": "example", "name": "example", "record_match": { "conditions": [ { "field": "example", "on_missing": "example", "operator": "eq", "source": "request", "value": "example", "value_from": "example" } ], "operator": "and" }, "updated_at": "2026-04-15T12:00:00Z", "updated_by": "example" } ], "starts_at": "2026-04-15T12:00:00Z", "status": "draft", "surface_ids": [ "example" ], "surface_tags": [ "example" ], "timezone": "UTC", "updated_at": "2026-04-15T12:00:00Z", "updated_by": "example" } ``` ### 401 Missing or invalid credentials #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.validation_errors` | `anyOf(Array, null)` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 403 Authenticated but not permitted #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.validation_errors` | `anyOf(Array, null)` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 404 Resource not found #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.validation_errors` | `anyOf(Array, null)` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 409 State conflict: the campaign is archived (terminal — every update is rejected, including a status change), or the requested `name` is already used by another campaign in the organization. #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.validation_errors` | `anyOf(Array, null)` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 422 Request validation failed. A targeting rejection lists every problem at once in `exception_details.problems`. #### application/json Schema: `anyOf(object, object)` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | Yes | The ``exception_details`` of a rejected targeting set: every problem found at once. | — | | `exception_details.problems` | `Array` | Yes | Every targeting violation found in one pass; never truncated. | — | | `exception_details.problems[].code` | `string` | Yes | Stable machine code of the violated rule. | allowed values: catalog_scope_required, intents_conflict, catalog_not_in_organization, channel_not_in_organization, tag_not_found, tag_dimension_mismatch, surface_not_in_catalog_scope, catalog_without_targeted_surface, channel_outside_catalog_scope, catalog_without_targeted_intent | | `exception_details.problems[].dimension` | `string` | Yes | The targeting axis the violation is about. | allowed values: catalog, channel, surface, intent | | `exception_details.problems[].message` | `string` | Yes | Human-readable explanation of the violation. | — | | `exception_details.problems[].values` | `Array` | Yes | The offending values on that axis. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | | `exception_details.validation_errors` | `anyOf(Array, null)` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | ##### Response example ```json { "exception_details": { "problems": [ { "code": "catalog_scope_required", "dimension": "catalog", "message": "example", "values": [ "example" ] } ] }, "reason": "example", "request_id": "example" } ``` ### 503 The catalog's attribute types could not be read, so the request could not be type-checked; retryable. #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.validation_errors` | `anyOf(Array, null)` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ```