# Update styling

`PUT https://stylor.ai/api/v1/datasets/{datasetId}/styling`

- Authentication: private API key, sent as `Authorization: Bearer sgpt-sk-…::…`
- Rate-limit resource: `api.v1.datasets.styling.update`
- Demo key: not accepted
- Web page: https://stylor.ai/guides/rest/v1/update-styling

Replaces the dataset's styling settings, its sizing settings, or
both: send `styling`, `sizing` or the two. Each one sent is replaced
whole, as `GET /v1/datasets/{datasetId}/styling` returns it: a field
left out is saved empty, except `tryOn`, `brandSizes.enabled` and
`brandSizes.logos`, which are on unless sent as `false`. New looks and
chats follow the change at once.

Unknown brand slugs in `excludedBrands` are dropped; more than 40
known ones is refused.

Text is shortened rather than refused: a house basic to one line of 80
characters, and each of `choosing` and `picture` to 1,200. Unknown rule
ids in `off` and repeated basics are dropped.

## Example request

```bash
curl -X PUT "https://stylor.ai/api/v1/datasets/YOUR_DATASET_ID/styling" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "sizing": {
    "brandSizes": {
      "enabled": true,
      "logos": false,
      "excludedBrands": [
        "zara",
        "boohoo"
      ]
    }
  }
}'
```

## Path parameters

- `datasetId` (string (uuid), required): The dataset's id, as returned when it was created.

## Request body

Content type `application/json`, required.

- `styling` (DatasetStyling): A store's styling settings, as the dataset's Styling tab edits them.
  - `off` (array<string>, required): Ids of the rules switched off. Every other rule is on. [one of `"printLeads"`, `"palette"`, `"sets"`, `"materials"`, `"occasion"`, `"tuckAsShown"`, `"plainBackground"`]
  - `basics` (array<object>, required): House basics, at most 8. [max items 8]
    - `text` (string, required): The piece, in a few words. One line, at most 80 characters. [max length 80]
    - `slot` (string, required): What part of an outfit it is. [one of `"top"`, `"bottom"`, `"footwear"`, `"outerwear"`, `"headwear"`, `"accessory"`]
    - `basicId` (string): The basic's id, kept across saves. Send it back unchanged to keep it when you rename a basic; left out, a new one is assigned.
  - `choosing` (string, required): The store's own rules for choosing pieces, one per line. At most 1,200 characters. [max length 1200]
  - `picture` (string, required): The store's own rules for how looks are drawn, one per line. At most 1,200 characters. [max length 1200]
  - `tryOn` (boolean): Whether shoppers may see a look on their own photo (`userPhotoUrl` on `POST /v2/agent/looks`). Left out of an update, it is on. [default `true`]
- `sizing` (DatasetSizing): How the stylist finds a shopper's size in this store.
  - `brandSizes` (object): Sizes they wear at other brands.
    - `enabled` (boolean): Ask shoppers what they wear at other brands and work out their size here from it. Off, the stylist asks for their measurements instead. [default `true`]
    - `logos` (boolean): Show other brands' logos on the sizes card. Off, their names only. [default `true`]
    - `excludedBrands` (array<string>): Slugs of brands the card leaves out and the stylist never uses as a reference (e.g. `uniqlo`, `levis`, `hm`). Unknown slugs are dropped. [max items 40]

### Examples

#### Brands left out

Names only on the card, and two brands never offered.

```json
{
  "sizing": {
    "brandSizes": {
      "enabled": true,
      "logos": false,
      "excludedBrands": [
        "zara",
        "boohoo"
      ]
    }
  }
}
```

#### Measurements only

Never ask about other brands; ask for measurements.

```json
{
  "sizing": {
    "brandSizes": {
      "enabled": false
    }
  }
}
```

#### House basics

Every rule on, with jeans and boots to finish looks.

```json
{
  "styling": {
    "off": [],
    "basics": [
      {
        "text": "Dark-wash straight-leg jeans",
        "slot": "bottom"
      },
      {
        "text": "Brown leather cowboy boots",
        "slot": "footwear"
      }
    ],
    "choosing": "",
    "picture": "Always cowboy boots."
  }
}
```

## Responses

### 200

The settings as saved.

Content type `application/json`:

- `styling` (DatasetStyling, required): A store's styling settings, as the dataset's Styling tab edits them.
  - `off` (array<string>, required): Ids of the rules switched off. Every other rule is on. [one of `"printLeads"`, `"palette"`, `"sets"`, `"materials"`, `"occasion"`, `"tuckAsShown"`, `"plainBackground"`]
  - `basics` (array<object>, required): House basics, at most 8. [max items 8]
    - `text` (string, required): The piece, in a few words. One line, at most 80 characters. [max length 80]
    - `slot` (string, required): What part of an outfit it is. [one of `"top"`, `"bottom"`, `"footwear"`, `"outerwear"`, `"headwear"`, `"accessory"`]
    - `basicId` (string): The basic's id, kept across saves. Send it back unchanged to keep it when you rename a basic; left out, a new one is assigned.
  - `choosing` (string, required): The store's own rules for choosing pieces, one per line. At most 1,200 characters. [max length 1200]
  - `picture` (string, required): The store's own rules for how looks are drawn, one per line. At most 1,200 characters. [max length 1200]
  - `tryOn` (boolean): Whether shoppers may see a look on their own photo (`userPhotoUrl` on `POST /v2/agent/looks`). Left out of an update, it is on. [default `true`]

Example:

```json
{
  "styling": {
    "off": [],
    "basics": [
      {
        "text": "Dark-wash straight-leg jeans",
        "slot": "bottom"
      },
      {
        "text": "Brown leather cowboy boots",
        "slot": "footwear"
      }
    ],
    "choosing": "",
    "picture": "Always cowboy boots."
  }
}
```

## Errors

Every error is a JSON object with an `error` string. Match on the status code; the message is written for people.

| Status | Message | When |
| --- | --- | --- |
| 400 | Request body must be valid JSON. | The body is not parseable JSON. |
| 400 | Request body must be a JSON object. | The body is empty, an array, or a JSON primitive. |
| 404 | Dataset not found. | No dataset with this `datasetId` belongs to the key's organization. |
| 422 | styling must be an object. | Neither `styling` nor `sizing` is sent, or `styling` is an array or not an object. |
| 422 | sizing must be an object. | `sizing` is an array or not an object. |
| 422 | sizing.brandSizes.excludedBrands must be an array of brand slugs. | `excludedBrands` is sent and is not an array. |
| 422 | At most 40 brands can be left out. | `excludedBrands` holds more than 40 known brands. |
| 422 | "<text>" needs a slot: one of top, bottom, footwear, outerwear, headwear, accessory. | A house basic has text but no `slot`, or a slot that is not one of these. |
| 422 | At most 8 house basics. | `basics` holds more than 8 pieces after empty ones and repeats are dropped. |

### Shared authentication, quota and rate-limit errors

| Status | Message | When |
| --- | --- | --- |
| 401 | API key missing. | The `Authorization` header is absent, is not `Bearer <key>`, or the key does not have three dash-separated segments. |
| 401 | Authentication failed: public key is invalid or not recognized. | A public-key endpoint was called with a key that does not exist. |
| 401 | Authentication failed: private key provided instead of a public key. | A public-key endpoint was called with an `sgpt-sk-…` key. |
| 401 | Authentication failed: token is not recognized. | A private-key endpoint was called with a key whose lookup segment does not exist. |
| 401 | Authentication failed: private key is invalid. | A private-key endpoint was called with a key whose secret does not match. |
| 401 | Authentication failed: public key provided instead of a private key. | A private-key endpoint was called with an `sgpt-pk-…` key. |
| 401 | Authentication failed: bearer token format is invalid. | A private key was sent without its `::` lookup segment. |
| 401 | Authentication failed: API key could not be read. Regenerate the key. | The key's embedded organization data could not be decrypted or parsed. |
| 403 | This key does not have the "<scope>" scope. | A private-key endpoint was called with a key whose scopes do not include the one the endpoint needs. Nothing is counted against your quota. See Authentication. |
| 403 | This key's scopes do not cover this endpoint. | A key with scopes called a private endpoint that no scope grants yet. Give the key every scope to reach it. |
| 403 | Demo mode is not available for private-key routes. Demos are only supported with public API keys. | The demo key was sent to a private-key endpoint. |
| 403 | Demo mode is not supported on this API route. Check the documentation for available demo endpoints. | The demo key was sent to a public-key endpoint that opts out of demo mode. |
| 403 | No active subscription for this organization. | The organization that owns the key has no active subscription. |
| 403 | No quota found for organization "<organizationId>". | The organization has no quota record for the current billing period. |
| 403 | No entitlement for "<resource>". | The plan's quota template has no entry for this endpoint. |
| 403 | Access denied to "<resource>". | The plan's quota template turns this endpoint off. |
| 429 | Monthly quota exceeded for "<resource>" (<quota>/<quota> used). | The billing-period quota for this endpoint is exhausted. `Retry-After` gives the seconds until the period resets. |
| 429 | RPM limit exceeded for "<resource>" (<used>/<limit> used). | More than the allowed requests per minute were sent. `Retry-After` is 60. |
| 500 | Internal server error. | An unexpected failure on the server. The message is always this string; details are logged, never returned. Quota consumed by the request is refunded, as it is for every 5xx response. |
