# Categories

## Filter Categories

- **Endpoint:** [GET /Categories](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/filtercategoriesasync.md)  
   **Description:** Filters the tenant's Categories using OData query options and returns the matching resources as a paginated collection.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Headers

### Accept-Language
   Indicates the preferred natural language and locale for localized content.
   - omitted / null: content in the tenant's default culture.
   - culture-code (e.g. en-US, es): content localized in the specified culture.
   - * and q-factor weighting are currently not supported.

## OData Query Support

### $select
   Comma-separated list of root-resource property names to include in the response.
   The available property names are published in /Categories/$metadata. Selecting properties of embedded resources is not supported.

**Example:** `$select=cat_title,cat_description`

### $expand
   Comma-separated list of embedded collections to include.
   By default, embedded collections are not included. Only embedded collections (not embedded resources) are supported.

**Example:** `$expand=Products`

### $filter
   OData v4.01 URI conventions. Filtering is restricted to properties of the root resource (Category).

- **Comparison operators:** eq, ne, gt, ge, lt, le
   - **Logical operators:** and, or, not
   - **Set operator:** in
   - **String functions:** contains, startswith, endswith

**Examples:**
   - `$filter=cat_title eq 'Outdoor'`
   - `$filter=contains(cat_title, 'pro')`
   - `$filter=cat_id in (1, 2, 3)`
   - `$filter=cat_id gt 100 and contains(cat_title, 'pro')`

### $orderby
   Single property, ascending by default. Multi-property ordering and ordering on embedded properties are not supported.

**Examples:**
   - `$orderby=cat_title (asc by default)`
   - `$orderby=cat_title asc`
   - `$orderby=cat_ref desc`

### Pagination — $top, $skip, $skipToken
   - **$top** — maximum number of items returned.
   - **$skip** — number of items to skip (offset paging).
   - **$skipToken** — continuation-token paging. Recommended when no $orderby is applied (faster than offset paging).

Paging applies only to the root resource (Category), never to embedded collections.

## Response
   **200 OK** — CollectionRepresentation with the matching Category resources, pagination metadata (Count, Skip, Top, HasNext, continuation token) and the cultures echoed from Accept-Language.

## Error Responses
   - **400 Bad Request** — Invalid OData expression or unsupported $orderby/$filter field (returns ValidationFailureRepresentation).
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Tenant or model not found.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
GET /Categories?$filter=contains(cat_title,'pro')&$orderby=cat_title asc&$top=20 HTTP/1.1
X-API-KEY: your-api-key-here
Accept-Language: en-US
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

## Create Category

- **Endpoint:** [POST /Categories](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/createcategoryasync.md)  
   **Description:** Creates a new Category from the supplied JSON object and returns its location in the Location header.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Headers

### Accept-Language
   Culture used to interpret the localized field values supplied in the request body.
   - omitted / null: tenant's default culture.
   - culture-code (e.g. en-US, es): the specified culture.

## Request Body
   A single JSON object whose properties match the writable fields published by /Categories/$metadata. The body is normalized before reaching the application layer:

- Must be a non-empty JSON object.
   - A JSON array is accepted only if it contains exactly one element.
   - Values are coerced to CLR primitives (string, long, double, bool, null), nested objects and arrays.

## Response
   **201 Created** — The Location header points to the new resource: /Categories({id}). No response body.

## Error Responses
   - **400 Bad Request** — Payload normalization or validation failure (empty body, multi-element array, invalid field values, etc.). Validation errors are returned as ValidationFailureRepresentation.
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Tenant or referenced resource not found.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
POST /Categories HTTP/1.1
X-API-KEY: your-api-key-here
Accept-Language: en-US
Content-Type: application/json

{
  "cat_title": "Outdoor",
  "cat_ref": "OUT-001",
  "cat_description": "Outdoor products category"
}
```

**Response:**  
```http
HTTP/1.1 201 Created
Location: /Categories(123)
```

## Get Category

- **Endpoint:** [GET /Categories({categoryIdentifier})](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/searchcategoryasync.md)  
   **Description:** Returns a single Category resource identified by its route identifier, optionally shaping the response with $select and $expand.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Headers

## OData Query Support

### $select
   Comma-separated list of root-resource property names to include in the response. See /Categories/$metadata for the available properties. Selecting properties of embedded resources is not supported.

**Example:** `$select=cat_title,cat_description`

**Example:** `$expand=Products`

**$filter, $orderby, $top, $skip and $skipToken are not applicable to this endpoint.**

## Response
   **200 OK** — EntityRepresentation with the requested Category resource.

## Error Responses
   - **400 Bad Request** — Invalid $select or $expand expression (returns ValidationFailureRepresentation).
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Category with the given identifier does not exist.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
GET /Categories(123)?$select=cat_title,cat_description&$expand=Products HTTP/1.1
X-API-KEY: your-api-key-here
Accept-Language: en-US
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

## Update Category

- **Endpoint:** [PATCH /Categories({categoryId})](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/updatecategoryasync.md)  
   **Description:** Updates an existing Category identified by its route identifier, applying only the writable fields supplied in the request body (PATCH semantics).

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Headers

## Request Body
   A single JSON object with the subset of writable fields to update (PATCH semantics — only the supplied fields are modified). Field names must match those published by /Categories/$metadata. Normalization rules:

- Must be a non-empty JSON object.
   - A JSON array is accepted only if it contains exactly one element.
   - Values are coerced to CLR primitives, nested objects and arrays.

## Response
   **200 OK** — Update applied.

## Error Responses
   - **400 Bad Request** — Payload normalization or validation failure (returns ValidationFailureRepresentation for validation errors).
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Category with the given identifier does not exist.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
PATCH /Categories(123) HTTP/1.1
X-API-KEY: your-api-key-here
Accept-Language: en-US
Content-Type: application/json

{
  "cat_title": "Outdoor & Camping"
}
```

**Response:**  
```http
HTTP/1.1 200 OK
```

## Delete Category

- **Endpoint:** [DELETE /Categories({categoryId})](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/deletecategoryasync.md)  
   **Description:** Deletes the Category identified by its route identifier.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Response
   **204 No Content** — Category deleted.

## Error Responses
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Category with the given identifier does not exist.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
DELETE /Categories(123) HTTP/1.1
X-API-KEY: your-api-key-here
```

**Response:**  
```http
HTTP/1.1 204 No Content
```

## Filter Category Products

- **Endpoint:** [GET /Categories({categoryIdentifier})/Products](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/filterproductsincategoryasync.md)  
   **Description:** Filters and returns a paginated collection of Product resources scoped to the given Category, using OData query options.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Headers

## OData Query Support

### $select
   Comma-separated list of root-resource property names to include in the response. See /Products/$metadata for the available properties. Selecting properties of embedded resources is not supported.

**Example:** `$select=prod_description,prod_tags`

### $expand
   Comma-separated list of embedded collections to include (e.g. Variants). By default, embedded collections are not included. Only embedded collections (not embedded resources) are supported.

**Example:** `$expand=Variants`

### $filter
   OData v4.01 URI conventions. Filtering is restricted to properties of the root resource (Product).

**Examples:**
   - `$filter=prod_ref eq 'SKU-001'`
   - `$filter=contains(prod_description, 'pro')`
   - `$filter=prod_id gt 100 and contains(prod_description, 'pro')`

### $orderby
   Single property, ascending by default. Multi-property ordering and ordering on embedded properties are not supported.

**Examples:**
   - `$orderby=prod_description`
   - `$orderby=prod_ref desc`

Paging applies only to the root resource (Product), never to embedded collections.

## Response
   **200 OK** — CollectionRepresentation with the matching Product resources, pagination metadata (Count, Skip, Top, HasNext, continuation token) and the cultures echoed from Accept-Language.

## Error Responses
   - **400 Bad Request** — Invalid OData expression or unsupported $orderby/$filter field (returns ValidationFailureRepresentation).
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Category with the given identifier does not exist.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
GET /Categories(123)/Products?$filter=contains(prod_description,'pro')&$top=20 HTTP/1.1
X-API-KEY: your-api-key-here
Accept-Language: en-US
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

## Get Category Product

- **Endpoint:** [GET /Categories({categoryIdentifier})/Products({productIdentifier})](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/searchproductincategoryasync.md)  
   **Description:** Returns a single Product resource by its identifier, scoped to the given Category.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Headers

## OData Query Support

**Example:** `$select=prod_description,prod_tags`

**Example:** `$expand=Variants`

## Response
   **200 OK** — EntityRepresentation with the Product resource scoped to the given Category.

## Error Responses
   - **400 Bad Request** — Invalid OData expression (returns ValidationFailureRepresentation).
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Category or Product with the given identifier does not exist, or the Product is not associated with the Category.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
GET /Categories(123)/Products(456)?$expand=Variants HTTP/1.1
X-API-KEY: your-api-key-here
Accept-Language: en-US
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

## Get Categories Changelog

- **Endpoint:** [GET /Categories/Changelog](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/filtercategorieschangelogasync.md)  
   **Description:** Returns the changelog entries for the tenant's Categories as a collection, filterable and pageable with OData query options.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## OData Query Support
   Supports $filter, $orderby, $top, $skip over the changelog fields. $select, $expand and $skipToken are not supported by this endpoint.

## Response
   **200 OK** — Collection of changelog entries (CollectionRepresentation).

## Error Responses
   - **400 Bad Request** — Invalid OData expression.
   - **401 Unauthorized** — Missing or invalid API key.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
GET /Categories/Changelog?$orderby=changedAt desc&$top=50 HTTP/1.1
X-API-KEY: your-api-key-here
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

## Get Category Changelog

- **Endpoint:** [GET /Categories({categoryIdentifier})/Changelog](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/filtercategorychangelogasync.md)  
   **Description:** Returns the changelog entries for a single Category identified by its route identifier, filterable and pageable with OData query options.

## Authentication
   Requires a valid API key in the X-API-KEY header.

## OData Query Support
   Supports $filter, $orderby, $top, $skip over the changelog fields. $select, $expand and $skipToken are not supported by this endpoint.

## Response
   **200 OK** — Collection of changelog entries for the specified Category.

## Example

**Request:**  
```http
GET /Categories(123)/Changelog?$top=20 HTTP/1.1
X-API-KEY: your-api-key-here
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

## Get Category Layout

- **Endpoint:** [GET /Categories/layout](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/getcategoryformlayoutasync.md)  
   **Description:** The layout describes how the Category edit form is rendered. It is a JSON document with a sections array; each section has a title and a rows array; each row has a fields array and, optionally, a divider (dividerType and dividerTitle).

Each field carries an attributeId and a grid size — one of quarter, half, threeQuarters or row. When a row has a divider its dividerType is one of normal, wide or lineBreak. dividerType and dividerTitle are omitted from a row when they have no value, so a row without a divider contains only its fields.

The Category model has a single layout shared by all categories. A layout must already be configured for the tenant; if none exists the request fails.

## Update Category Layout

- **Endpoint:** [PUT /Categories/layout](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/updatecategoryformlayoutasync.md)  
   **Description:** Fully overwrites the currently stored Category layout. The payload is not merged with the existing layout — it replaces it entirely.

A layout must already exist for the tenant; this operation replaces it and does not create one. If none exists the request returns 404 Not Found.

## ⚠️ All attributes are required
   The payload must contain every attribute currently in the stored layout — no more, no less.
   Omitting an attribute removes it from the form. Adding an attribute that is not already in the layout is also rejected. Use GET /Categories/layout first to obtain the current set of attributes.

This is different from PATCH /v2/AttributeSets({id}), where layouts may contain any subset of the tenant's attributes.

## Layout Structure
   The payload has the same shape as the GET /Categories/layout response:

```json
   {
     "sections": [
       {
         "title": "General",
         "rows": [
           {
             "fields": [
               { "attributeId": "cat_title", "size": "half" },
               { "attributeId": "cat_ref",   "size": "half" }
             ]
           },
           {
             "dividerType": "normal",
             "dividerTitle": "Details",
             "fields": [
               { "attributeId": "cat_description", "size": "row" }
             ]
           }
         ]
       }
     ]
   }
   ```

- size — one of: quarter, half, threeQuarters, row.
   - dividerType — optional; one of: normal, wide, lineBreak. dividerTitle is optional and only meaningful when dividerType is set. Both are omitted from rows that have no divider.

## Validation Rules
   If any rule is violated the request returns 400 Bad Request describing each failure:
   - The layout must contain every attribute currently in the stored layout — no more, no less.
   - No attributeId may appear more than once.
   - Each field size must be one of: quarter, half, threeQuarters, row.
   - The sum of field sizes within any single row must not exceed a full row.
   - Every row must contain at least one field.
   - Section title values must be unique (case-insensitive).
   - When a row sets dividerType it must be one of: normal, wide, lineBreak.

## Get Category Metadata

- **Endpoint:** [GET /Categories/$metadata](https://docs.api.saleslayer.com/apis/catalog-v2.0/categories/getcategorymetadataasync.md)  
   **Description:** The metadata is a JSON Schema document that describes:
   - The implicit and custom properties exposed by the Category resource.
   - The embedded resources (Products, Variants) and their relationships.

The schema should be consulted to build requests against the other Category endpoints (which fields can be used in $select, $filter, $orderby, request bodies, etc.).

## Authentication
   Requires a valid API key in the X-API-KEY header.

## Response
   **200 OK** — Returns the JSON Schema as a ServiceDocumentRepresentation.

## Error Responses
   - **401 Unauthorized** — Missing or invalid API key.
   - **404 Not Found** — Tenant or model not found.
   - **500 Internal Server Error** — Unexpected technical error.

## Example

**Request:**  
```http
GET /Categories/$metadata HTTP/1.1
X-API-KEY: your-api-key-here
```

**Response:**  
```http
HTTP/1.1 200 OK
Content-Type: application/json
```
