# Update widget settings

`PATCH https://stylor.ai/api/v1/widget/settings`

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

`enabled: false` hides every widget (the chat and complete the look)
on every store from the next page view; an open chat hides itself on
its next message. It is not cached anywhere. The chat API called
without a widget session is unaffected.

`holdoutPercent` sets the share of visits that never see the chat
widget, a whole number from 0 to 50. Any change of share ends the
running test and starts a new one with a new `id` (visits split at
one share cannot be pooled with another); the ended test moves to
`pastHoldouts`, newest first, and its results stay readable. `0` ends
the test. The same share again changes nothing.

## Example request

```bash
curl -X PATCH "https://stylor.ai/api/v1/widget/settings" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": false
}'
```

## Request body

Content type `application/json`, required.

- `enabled` (boolean): Show the widgets (`true`) or hide them everywhere (`false`).
- `holdoutPercent` (integer): The share of visits kept from the chat widget, in percent. `0` ends the test. [minimum 0; maximum 50]

### Examples

#### Hide every widget

Hides the chat and complete the look on every store from the next page view.

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

#### Hold out 10% of visits

Starts a test in which one visit in ten never sees the chat widget.

```json
{
  "holdoutPercent": 10
}
```

## Responses

### 200

The settings after the change.

Content type `application/json`:

- `enabled` (boolean, required): `false` when every widget is switched off.
- `holdout` (WidgetHoldout | null, nullable, required): The running holdout test, or `null`.
  - Option 1: WidgetHoldout
    - `id` (string, required): The test.
    - `percent` (integer, required): The share of visits kept from the chat widget.
    - `startedAt` (string (date-time), required): When the test started.
    - `endedAt` (string (date-time)): When it ended. Only on `pastHoldouts`.
  - Option 2: null
- `pastHoldouts` (array<WidgetHoldout>, required): Tests that ended, newest first (the last 20).
  - `id` (string, required): The test.
  - `percent` (integer, required): The share of visits kept from the chat widget.
  - `startedAt` (string (date-time), required): When the test started.
  - `endedAt` (string (date-time)): When it ended. Only on `pastHoldouts`.

Example:

```json
{
  "enabled": true,
  "holdout": {
    "id": "hold_3f9a1c2b7d4e5f60",
    "percent": 10,
    "startedAt": "2026-11-04T12:00:00.000Z"
  },
  "pastHoldouts": []
}
```

## 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. |
| 409 | The holdout changed while this was being saved. Load the settings again and retry. | Another change to the holdout was saved at the same moment. |
| 422 | Validation failed. | Neither field was sent, `enabled` is not a boolean, or `holdoutPercent` is not a whole number from 0 to 50. `details` names each. |

### 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. |
