# Update dataset

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

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

Updates a dataset's name, description, metadata, status or store
configuration. Send the changes inside a `fields` object; only the keys
you include are touched.

## Accepted fields

| Field | Rule |
| --- | --- |
| `name` | Non-empty string. Trimmed, internal whitespace collapsed, cut to 200 characters. |
| `description` | Any string (empty allowed). Whitespace collapsed, cut to 2000 characters. |
| `metadata` | A plain JSON object. **Replaces** the whole `metadata` value; it is not merged. |
| `status` | `active`, `draft` or `archived`. |
| `config.storeDomain` | Bare domain, no scheme, path or query. |
| `config.storeUrl` | `http(s)://` URL with a host — unlike create, the scheme is required here. |
| `config.storeName` | Non-empty string, cut to 200 characters. |
| `config.storeId` | Non-empty string, cut to 200 characters. |
| `config.targetGender` | `mens`, `womens` or `null`. |

Config fields can be sent nested (`"config": { "storeUrl": … }`) or as
dot-path keys (`"config.storeUrl": …`).

## Validation is all-or-nothing

Every field you send is checked before anything is written. If any field
is invalid, or is not in the list above, nothing is changed and the
response is **422** with a `details` array naming each failing field.

| `details[].path` | `details[].message` |
| --- | --- |
| `fields` | `No valid fields to update. Allowed fields: name, description, metadata, status, config.storeDomain, config.storeUrl, config.storeName, config.storeId, config.targetGender.` (sent when `fields` is omitted or empty) |
| `<key>` or `config.<key>` | `Unsupported field: <key>` |
| `config` | `config must be an object.` |
| `name` | `name must be a non-empty string.` |
| `description` | `description must be a string.` |
| `config.storeDomain` | `config.storeDomain must be a valid domain excluding any path, query parameters, or HTTP method.` |
| `config.storeUrl` | `config.storeUrl must be a valid http(s) URL.` |
| `config.storeName` | `config.storeName must be a non-empty string.` |
| `config.storeId` | `config.storeId must be a non-empty string.` |
| `config.targetGender` | `config.targetGender must be 'mens', 'womens', or null.` |
| `status` | `status must be 'active', 'draft', or 'archived'.` |
| `metadata` | `metadata must be a plain object.` |

## Good to know

- Changing `status` here only flips the flag. Use *Activate dataset* and
  *Archive dataset* when you also want items, queue rows and data sources
  to follow, which is almost always.
- Changing `config.targetGender` does not rewrite the gender already
  stored on existing items; only items ingested afterwards pick it up.
- The response `dataset` is the full stored record, including
  `organizationId` and `autoPublish`, unlike *Retrieve dataset*.

## Example request

```bash
curl -X PATCH "https://stylor.ai/api/v1/datasets/YOUR_DATASET_ID" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "fields": {
    "name": "Spring 2026 — final",
    "metadata": {
      "owner": "merchandising"
    }
  }
}'
```

## Path parameters

- `datasetId` (string (uuid), required): The dataset to update.

## Request body

Content type `application/json`, required.

- `fields` (object, required): The fields to change. Config values can be nested under `config` or sent as dot-path keys (`config.storeUrl`). Unrecognised keys are rejected with 422 and nothing is written.
  - `name` (string): New display name. Non-empty; whitespace collapsed; cut to 200 characters. [max length 200]
  - `description` (string): New description. Empty string allowed; whitespace collapsed; cut to 2000 characters. [max length 2000]
  - `metadata` (object): Replaces the whole `metadata` object. Must be a plain object, not an array.
  - `status` (string): Lifecycle state of a dataset. - `draft` — newly created; not visible to search, chat or the widget. - `active` — live. Any number of datasets can be active at once. - `archived` — parked. Data is kept, items and sources are switched off. [default `"draft"`; one of `"draft"`, `"active"`, `"archived"`]
  - `config` (object): Partial store configuration. Only the keys you send are changed.
    - `storeDomain` (string): Bare domain — no scheme, path or query.
    - `storeUrl` (string): Full `http://` or `https://` URL with a host.
    - `storeName` (string): Non-empty; cut to 200 characters. [max length 200]
    - `storeId` (string): Non-empty; cut to 200 characters. [max length 200]
    - `targetGender` (string, nullable): `mens`, `womens` or `null`. Does not rewrite existing items. [one of `"mens"`, `"womens"`]

### Examples

#### Rename

Change the name and set metadata.

```json
{
  "fields": {
    "name": "Spring 2026 — final",
    "metadata": {
      "owner": "merchandising"
    }
  }
}
```

#### Store settings

Update store settings with a nested `config` object.

```json
{
  "fields": {
    "config": {
      "storeUrl": "https://shop.example-boutique.com",
      "storeDomain": "shop.example-boutique.com",
      "targetGender": "womens"
    }
  }
}
```

#### Single settings

Update individual store settings with dot-path keys.

```json
{
  "fields": {
    "config.storeName": "Example Boutique EU",
    "config.storeId": "eu_store_1"
  }
}
```

## Responses

### 200

The updated dataset.

Content type `application/json`:

- `dataset` (Dataset, required): The full stored dataset record. Returned by *Update dataset*, *Activate dataset* and *Archive dataset*. List and retrieve responses project a subset — see `DatasetSummary` and `DatasetDetail`. Internal `_id` and `__v` fields are stripped by *Update dataset* but are still present on the activate and archive responses; ignore them.
  - `datasetId` (string (uuid), required): Unique id of the dataset. Use it as the `datasetId` path parameter. [example `"c9b3c6c0-9b0f-4f42-bf3f-2b7a6b1b9d31"`]
  - `organizationId` (string (uuid), required): Id of the organization that owns the dataset — the one behind your API key. [example `"0a2c1f4e-5b6d-7e8f-9012-3456789abcde"`]
  - `status` (string, required): Lifecycle state of a dataset. - `draft` — newly created; not visible to search, chat or the widget. - `active` — live. Any number of datasets can be active at once. - `archived` — parked. Data is kept, items and sources are switched off. [default `"draft"`; one of `"draft"`, `"active"`, `"archived"`]
  - `name` (string, required): Display name. Trimmed on create; cut to 200 characters on update. [example `"Spring 2026"`]
  - `description` (string): Free-text description. Defaults to an empty string; cut to 2000 characters on update. [example `"Women's spring drop"`]
  - `metadata` (object): Arbitrary JSON you attach to the dataset. Omitted when never set. Replaced wholesale by *Update dataset*. [example `null`]
  - `config` (DatasetConfig, required): Store details attached to a dataset. Set on create, editable field-by-field on update.
    - `storeUrl` (string): The storefront URL, stored exactly as sent. Create accepts it with or without a scheme; update requires `http://` or `https://`. [example `"https://example-boutique.com"`]
    - `storeId` (string): Your identifier for the store (for example the Shopify shop id). Cut to 200 characters on update. [max length 200; example `"58712345678"`]
    - `storeDomain` (string): Bare domain of the store — no scheme, path or query. [example `"example-boutique.com"`]
    - `storeName` (string): Display name of the store. Cut to 200 characters on update. [max length 200; example `"Example Boutique"`]
    - `currency` (string): Three-letter ISO 4217 code such as `USD`. Validated case-insensitively but stored exactly as sent. [min length 3; max length 3; example `"USD"`]
    - `countryName` (string): Country of the store. Not settable through this API; present only when it was set by another integration. [example `"United States"`]
    - `targetGender` (string, nullable): Forces every item ingested into this dataset to this gender instead of the detected one. `null` keeps detection on. [default `null`; one of `"mens"`, `"womens"`]
  - `autoPublish` (boolean, required): Whether newly processed items go live automatically. Set on create; `true` unless explicitly `false`. [default `true`]
  - `createdAt` (string (date-time), required): When the dataset was created. [example `"2026-03-02T09:14:31.208Z"`]
  - `updatedAt` (string (date-time), required): When the dataset record was last written. [example `"2026-08-19T16:40:02.911Z"`]

Example:

```json
{
  "dataset": {
    "datasetId": "c9b3c6c0-9b0f-4f42-bf3f-2b7a6b1b9d31",
    "organizationId": "0a2c1f4e-5b6d-7e8f-9012-3456789abcde",
    "status": "active",
    "name": "Spring 2026 — final",
    "description": "Women's spring drop",
    "metadata": {
      "owner": "merchandising"
    },
    "config": {
      "storeUrl": "https://example-boutique.com",
      "storeId": "58712345678",
      "storeDomain": "example-boutique.com",
      "storeName": "Example Boutique",
      "currency": "USD",
      "targetGender": "womens"
    },
    "autoPublish": true,
    "createdAt": "2026-03-02T09:14:31.208Z",
    "updatedAt": "2026-09-12T10:02:44.130Z"
  }
}
```

## 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 request body could not be parsed as JSON. |
| 400 | Request body must be a JSON object. | The body is empty, or is valid JSON but not an object (for example an array, a string or `null`). |
| 401 | Organization not found for the provided organizationId. | The organization that owns the key no longer exists. |
| 404 | Dataset not found for this organization. | No dataset with that id belongs to your organization. |
| 404 | Update failed: dataset document not found or no changes applied. | The dataset disappeared between the ownership check and the write. |
| 422 | datasetId is required. | `datasetId` is empty or blank. |
| 422 | fields must be an object. | `fields` is `null`, an array, or a primitive. |
| 422 | Validation failed. | One or more fields failed validation, or `fields` was omitted or empty. Nothing was written. `details` has one `{ path, message }` entry per failing field; the possible messages are listed in the description. The organization and dataset are checked first, so a 401 or 404 takes precedence.Body: `{"error":"Validation failed.","details":[{"path":"config.storeUrl","message":"config.storeUrl must be a valid http(s) URL."},{"path":"colour","message":"Unsupported field: colour"}]}` |

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