# Create a collection `POST /platform/v1/catalogs/{catalog_id}/collections` Register a membership collection (one per `collection_type` and `member_type` pair) or a curated collection (one per `slug` and `member_type`). The id is built from those fields and returned; a second registration of the same id is a `409`. A catalog holds at most 1000 collections. Types and attributes are checked against the ones the catalog has ingested. Filter trees are normalised before they are stored — a negative leaf gains an `exists` on its field beside it (see `filters`) — so the resource this returns can differ from the body sent. The response carries the stored form. ## Request example ```bash curl --request POST \ --url 'https://api.eu1.luigisbox.ai/platform/v1/catalogs//collections' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "ancestry_attribute": "lbx:category_ids", "collection_type": "category", "kind": "membership", "member_type": "product", "membership_attribute": "lbx:category_ids" }' ``` ## Path Parameters | Name | Type | Required | Description | Constraints | |---|---|---|---|---| | `catalog_id` | `string` | Yes | Catalog Id | — | ## Request Body **Required.** ### application/json Schema: `oneOf(object, object)` #### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `ancestry_attribute` | `anyOf(string, null)` | No | Attribute of the collection type holding the ancestor `@id`s of each collection object, e.g. `lbx:category_ids`, which on a category holds its ancestors and then the category itself. It must exist on the collection type, under the same rules as `membership_attribute`. Omit it for a collection type without hierarchy. | — | | `collection_type` | `string` | Yes | Catalog object type whose objects are the collections, e.g. `category`. It must exist in the catalog; `curated` and `all` are reserved. | >= 1 characters; <= 64 characters; /^[^/:\s]+$/ | | `kind` | `string` | Yes | `membership`: every object of `collection_type` is a collection of the member objects that reference it through `membership_attribute`. | allowed value: membership | | `member_type` | `string` | Yes | Catalog object type the collection returns. It must exist in the catalog. | >= 1 characters; <= 64 characters; /^[^/:\s]+$/ | | `membership_attribute` | `string` | Yes | Attribute of the member type holding the `@id` of every collection object the member belongs to, e.g. `lbx:category_ids`, which holds every category an object is in and every category above them. It must exist on the member type; `@`-prefixed attributes skip that check. The `private:`, `signals:`, `ml:` and `lexical:` namespaces are refused. | >= 1 characters; <= 256 characters; /^\S+$/ | | `filters` | `oneOf(object, object)` | Yes | Filter over the member type, in map syntax, e.g. `{"label": {"eq": "new"}}`. Fields must exist on the member type; `@`-prefixed fields skip that check but not the value check. The `private:`, `signals:`, `ml:` and `lexical:` namespaces are refused. A negative condition (`neq`, `not_in`) is stored with an `exists` on the same field, so it means "has the field, and it differs"; a `$not` over a single positive condition gets the same. `@id`, `@type` and `@updated_at` are exempt. Once stored, at most 100 conditions (each added `exists` included; groups are not counted), in groups nested at most 10 deep, and 65536 bytes, with at most 100 values in one `in` / `not_in` list and integers that fit in 64 bits. A filter past the depth or condition cap is a `validation_errors` entry of type `filter_too_complex`. | — | | `filters.$and` | `Array` | No | Nodes that must all match. | >= 1 items | | `filters.$not` | `oneOf(object, object)` | No | Filter over the member type, in map syntax, e.g. `{"label": {"eq": "new"}}`. Fields must exist on the member type; `@`-prefixed fields skip that check but not the value check. The `private:`, `signals:`, `ml:` and `lexical:` namespaces are refused. A negative condition (`neq`, `not_in`) is stored with an `exists` on the same field, so it means "has the field, and it differs"; a `$not` over a single positive condition gets the same. `@id`, `@type` and `@updated_at` are exempt. Once stored, at most 100 conditions (each added `exists` included; groups are not counted), in groups nested at most 10 deep, and 65536 bytes, with at most 100 values in one `in` / `not_in` list and integers that fit in 64 bits. A filter past the depth or condition cap is a `validation_errors` entry of type `filter_too_complex`. | — | | `filters.$or` | `Array` | No | Nodes of which at least one must match. | >= 1 items | | `filters.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `filters.*.eq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.exists` | `boolean` | No | — | — | | `filters.*.gt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.gte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.in` | `Array` | No | — | >= 1 items | | `filters.*.lt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.lte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.neq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.not_in` | `Array` | No | — | >= 1 items | | `name` | `string` | Yes | Human-readable name, for display. It takes no part in the id and cannot be changed. | >= 1 characters; <= 256 characters | | `slug` | `string` | Yes | Id segment of your choice: lowercase ASCII letters, digits, `_` and `-`, up to 256 characters. The collection id is `collection:curated/{slug}/member:{member_type}`, so one slug registers one collection per member type. It cannot be changed. | >= 1 characters; <= 256 characters; /^[a-z0-9_-]+$/ | #### Request body example ```json { "ancestry_attribute": "lbx:category_ids", "collection_type": "category", "kind": "membership", "member_type": "product", "membership_attribute": "lbx:category_ids" } ``` ## Responses | Status | Description | Content | |---|---|---| | `201` | Successful Response | application/json: oneOf(object, object) | | `400` | Malformed request | 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 | application/json: object | | `422` | Request validation failed | application/json: object | | `503` | The catalog's types could not be read, so the collection could not be checked against them. Retryable: `Retry-After` carries the number of seconds to wait. | application/json: object | ### 201 Successful Response #### application/json Schema: `oneOf(object, object)` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `ancestry_attribute` | `anyOf(string, null)` | Yes | Attribute of the collection type holding the ancestor `@id`s of each collection object, or `null` for a collection type without hierarchy. | — | | `catalog_id` | `string` | Yes | Catalog the collection belongs to. | — | | `collection_type` | `string` | Yes | Catalog object type whose objects are the collections, e.g. `category`. It must exist in the catalog; `curated` and `all` are reserved. | — | | `created_at` | `string` | Yes | When the collection was registered. | format: date-time | | `id` | `string` | Yes | Collection id, built from the registration. A membership collection is `collection:{collection_type}/member:{member_type}`; a request serves one of its objects as `collection:{@id}/member:{member_type}`. A curated collection is `collection:curated/{slug}/member:{member_type}`, and a request serves it by this id. | — | | `kind` | `string` | Yes | `membership`: every object of `collection_type` is a collection of the member objects that reference it through `membership_attribute`. | allowed value: membership | | `member_type` | `string` | Yes | Catalog object type the collection returns. It must exist in the catalog. | — | | `membership_attribute` | `string` | Yes | Attribute of the member type holding the `@id` of every collection object the member belongs to. | — | | `updated_at` | `string` | Yes | When the collection was last changed; its registration time until then. | format: date-time | | `filters` | `oneOf(object, object)` | Yes | Filter over the member type, in map syntax, in its stored form: negations carry the `exists` added on write. | — | | `filters.$and` | `Array` | No | Nodes that must all match. | >= 1 items | | `filters.$and[].*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `filters.$and[].*.eq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$and[].*.exists` | `boolean` | No | — | — | | `filters.$and[].*.gt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$and[].*.gte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$and[].*.in` | `Array` | No | — | >= 1 items | | `filters.$and[].*.lt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$and[].*.lte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$and[].*.neq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$and[].*.not_in` | `Array` | No | — | >= 1 items | | `filters.$not` | `oneOf(object, object)` | No | Filter over the member type, in map syntax, e.g. `{"label": {"eq": "new"}}`. Fields must exist on the member type; `@`-prefixed fields skip that check but not the value check. The `private:`, `signals:`, `ml:` and `lexical:` namespaces are refused. A negative condition (`neq`, `not_in`) is stored with an `exists` on the same field, so it means "has the field, and it differs"; a `$not` over a single positive condition gets the same. `@id`, `@type` and `@updated_at` are exempt. Once stored, at most 100 conditions (each added `exists` included; groups are not counted), in groups nested at most 10 deep, and 65536 bytes, with at most 100 values in one `in` / `not_in` list and integers that fit in 64 bits. A filter past the depth or condition cap is a `validation_errors` entry of type `filter_too_complex`. | — | | `filters.$not.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `filters.$not.*.eq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$not.*.exists` | `boolean` | No | — | — | | `filters.$not.*.gt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$not.*.gte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$not.*.in` | `Array` | No | — | >= 1 items | | `filters.$not.*.lt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$not.*.lte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$not.*.neq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$not.*.not_in` | `Array` | No | — | >= 1 items | | `filters.$or` | `Array` | No | Nodes of which at least one must match. | >= 1 items | | `filters.$or[].*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `filters.$or[].*.eq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$or[].*.exists` | `boolean` | No | — | — | | `filters.$or[].*.gt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$or[].*.gte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$or[].*.in` | `Array` | No | — | >= 1 items | | `filters.$or[].*.lt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$or[].*.lte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$or[].*.neq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.$or[].*.not_in` | `Array` | No | — | >= 1 items | | `filters.*` | `object` | No | Operators applied to one field. Several operators in one object combine as AND. | — | | `filters.*.eq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.exists` | `boolean` | No | — | — | | `filters.*.gt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.gte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.in` | `Array` | No | — | >= 1 items | | `filters.*.lt` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.lte` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.neq` | `string \| number \| integer \| boolean \| null` | No | — | — | | `filters.*.not_in` | `Array` | No | — | >= 1 items | | `name` | `string` | Yes | Human-readable name, for display. It takes no part in the id and cannot be changed. | — | | `slug` | `string` | Yes | Id segment chosen at registration. | — | ##### Response example ```json { "ancestry_attribute": "example", "catalog_id": "example", "collection_type": "example", "created_at": "2026-04-15T12:00:00Z", "id": "collection:category/member:product", "kind": "membership", "member_type": "example", "membership_attribute": "example", "updated_at": "2026-04-15T12:00:00Z" } ``` ### 400 Malformed request #### 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" } ``` ### 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" } ``` ### 409 State conflict #### 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" } ``` ### 503 The catalog's types could not be read, so the collection could not be checked against them. Retryable: `Retry-After` carries the number of seconds to wait. #### 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" } ```