# Update a surface `PATCH /platform/v1/catalogs/{catalog_id}/surfaces/{surface_id}` Update the object types a surface is queried with and answers with. `intent` and `slug` are immutable — the surface id is derived from them. The new values reach the Discovery pipeline on the next cache write, not instantly. ## Request example ```bash curl --request PATCH \ --url 'https://api.eu1.luigisbox.ai/platform/v1/catalogs//surfaces/' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "input_type": "product", "output_types": [ "product", "category", "brand", "query", "article" ] }' ``` ## Path Parameters | Name | Type | Required | Description | Constraints | |---|---|---|---|---| | `catalog_id` | `string` | Yes | Catalog Id | — | | `surface_id` | `string` | Yes | Surface Id | — | ## Request Body **Required.** ### application/json Schema: `object` #### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `input_type` | `anyOf(string, null)` | No | Catalog object type of the anchor objects this surface is queried with — what a recommender request names in `anchor_ids`. Required for `recommender` surfaces and rejected for every other kind, which take no anchor. Omit it to leave the stored value untouched. An explicit `null` is rejected with `422` on a `recommender`, whose anchor type cannot be cleared, and accepted as a no-op on every other kind, which never carry one. | — | | `output_types` | `Array` | No | Catalog object types this surface may answer with. Non-empty; duplicates collapse and input order is preserved. Acts as an allowlist: a request asking for a type absent from this list is rejected rather than answered with fewer results. It is a property of the surface, not of its kind — a recommender declares whatever it recommends, so a category recommender carries `category`. | >= 1 items | #### Request body example ```json { "input_type": "product", "output_types": [ "product", "category", "brand", "query", "article" ] } ``` ## 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 | | `422` | Request validation failed | application/json: object | ### 200 Successful Response #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `catalog_id` | `string` | Yes | Catalog Id | — | | `created_at` | `string` | Yes | Created At | format: date-time | | `default_version` | `anyOf(integer, null)` | Yes | Default Version | — | | `id` | `string` | Yes | Id | — | | `input_type` | `anyOf(string, null)` | Yes | Catalog object type of the anchor objects this surface is queried with — what a recommender request names in `anchor_ids`. Required for `recommender` surfaces and rejected for every other kind, which take no anchor. | — | | `intent` | `string` | Yes | The kind of discovery this Surface performs — it fixes which Discovery API endpoint serves the Surface and what input a request must supply (`search` a query, `recommender` anchor objects, `collection` a scope). It is the contract, not the algorithm: a `search` Surface may retrieve lexically, semantically or both. Immutable — it is the middle slot of the surface id `lbs__`, so changing it means a new Surface. | allowed values: search, recommender, collection | | `output_types` | `Array` | Yes | Catalog object types this surface may answer with. Non-empty; duplicates collapse and input order is preserved. Acts as an allowlist: a request asking for a type absent from this list is rejected rather than answered with fewer results. It is a property of the surface, not of its kind — a recommender declares whatever it recommends, so a category recommender carries `category`. | — | | `slug` | `string` | Yes | Slug | — | | `tags` | `Array` | Yes | The surface-dimension tags this surface belongs to, ordered by name, each carrying both its id and its name — the `id` is what the `tag_id` filter takes, the `name` is what a person reads, and neither needs a further request. Read-only here: membership is changed through `POST /platform/v1/tags`, or `PATCH /platform/v1/tags/{tag_id}` with `add_members` / `remove_members`, which carry `{catalog_id, surface_id}` pairs. A surface belongs to at most 20 tags. | — | | `tags[].id` | `string` | Yes | The tag's id — what `tag_id` filters and the tag writes take. | — | | `tags[].name` | `string` | Yes | Tag name as `namespace:value`, lowercased on write (`intent:SEARCH` is stored as `intent:search`). The `intent`, `language` and `recommender` namespaces are platform-managed: writing one requires `platform:reserved-tags:write` and deleting one requires `platform:reserved-tags:delete`. | <= 256 characters; /^[a-z][a-z0-9_-]{0,63}:[^\s:]{1,191}$/ | | `updated_at` | `string` | Yes | Updated At | format: date-time | ##### Response example ```json { "catalog_id": "example", "created_at": "2026-04-15T12:00:00Z", "default_version": 1, "id": "example", "input_type": "example", "intent": "search", "output_types": [ "example" ], "slug": "example", "tags": [ { "id": "example", "name": "example" } ], "updated_at": "2026-04-15T12:00:00Z" } ``` ### 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.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | 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": { "error_code": "example", "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.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | 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": { "error_code": "example", "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.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | 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": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 422 Request validation failed #### 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.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | 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": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ```