# Create dataset

`POST https://stylor.ai/api/v1/datasets`

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

Creates an empty dataset in `draft` status and returns its id. A dataset is
the container everything else hangs off: items are uploaded into it, data
sources fill it, and search and chat run against it once it is active.

## Required configuration

`config` must carry all six store fields — `storeUrl`, `storeId`,
`storeDomain`, `storeName`, `currency` and `countryCode`. Each is validated
in the order the keys appear in your JSON, then the six required keys are
checked for presence. Any key outside the allowed set is rejected.

## Good to know

- The new dataset is `draft`. Call *Activate dataset* once it holds items
  to make it visible to search, chat and the widget.
- `name` is trimmed before it is stored. `description` defaults to an
  empty string.
- `storeUrl` may be sent with or without a scheme (`example.com` or
  `https://example.com`); it is stored exactly as sent.
- `currency` and `countryCode` are validated case-insensitively but stored
  exactly as sent, so send them in upper case (`USD`, `US`).
- `countryCode` is validated on create but is **not** persisted on the
  dataset and does not appear in later reads.
- The response is `200` with just the `datasetId`; it does not echo the
  record. Follow up with *Retrieve dataset* if you need it.

## Example request

```bash
curl -X POST "https://stylor.ai/api/v1/datasets" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Spring 2026",
  "description": "Women'\''s spring drop",
  "config": {
    "storeUrl": "https://example-boutique.com",
    "storeId": "58712345678",
    "storeDomain": "example-boutique.com",
    "storeName": "Example Boutique",
    "currency": "USD",
    "countryCode": "US",
    "targetGender": "womens"
  }
}'
```

## Request body

Content type `application/json`, required.

- `name` (string, required): Display name. Must be non-empty after trimming. [example `"Spring 2026"`]
- `description` (string): Free-text description. [default `""`; example `"Women's spring drop"`]
- `autoPublish` (boolean): Whether newly processed items go live automatically. Anything other than an explicit `false` is treated as `true`. [default `true`]
- `config` (object, required): Store details. All six of `storeUrl`, `storeId`, `storeDomain`, `storeName`, `currency` and `countryCode` are required; `targetGender` is optional. No other keys are accepted.
  - `storeUrl` (string, required): The storefront URL, with or without a scheme. Must have a hostname containing a dot; `localhost` is rejected. [example `"https://example-boutique.com"`]
  - `storeId` (string, required): Your identifier for the store. Non-empty. [example `"58712345678"`]
  - `storeDomain` (string, required): Bare domain of the store, for example `shop.example.com`. [example `"example-boutique.com"`]
  - `storeName` (string, required): Display name of the store. Non-empty. [example `"Example Boutique"`]
  - `currency` (string, required): Three-letter ISO 4217 code. Send it in upper case; it is stored exactly as sent. [example `"USD"`]
  - `countryCode` (string, required): Two-letter ISO 3166-1 alpha-2 code. Required and validated, but not stored on the dataset. [example `"US"`]
  - `targetGender` (string, nullable): Force every item in this dataset to one gender. Omit or send `null` to keep per-item detection. [one of `"mens"`, `"womens"`; example `"womens"`]

### Examples

#### Store details

A named dataset with full store details and a target gender.

```json
{
  "name": "Spring 2026",
  "description": "Women's spring drop",
  "config": {
    "storeUrl": "https://example-boutique.com",
    "storeId": "58712345678",
    "storeDomain": "example-boutique.com",
    "storeName": "Example Boutique",
    "currency": "USD",
    "countryCode": "US",
    "targetGender": "womens"
  }
}
```

#### Required fields

Only a name and the store details every dataset needs.

```json
{
  "name": "Menswear import",
  "config": {
    "storeUrl": "example.com",
    "storeId": "store_main",
    "storeDomain": "example.com",
    "storeName": "Example",
    "currency": "CAD",
    "countryCode": "CA"
  }
}
```

## Responses

### 200

The dataset was created.

Content type `application/json`:

- `datasetId` (string (uuid), required): Id of the new dataset. Use it as the `datasetId` path parameter everywhere else.

Example:

```json
{
  "datasetId": "c9b3c6c0-9b0f-4f42-bf3f-2b7a6b1b9d31"
}
```

## 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. Please check the organizationId. | The organization that owns the key no longer exists. Checked after the body is validated. |
| 422 | Dataset name is required. | `name` is missing, not a string, or blank after trimming. |
| 422 | config must be an object. | `config` is `null`, an array or a primitive. |
| 422 | Unrecognized key "<key>" in config. | `config` contains a key other than `storeUrl`, `storeId`, `storeDomain`, `storeName`, `currency`, `countryCode` or `targetGender`. |
| 422 | Store URL is required. | `config.storeUrl` is present but empty or not a string. |
| 422 | Invalid hostname. Please provide a valid domain name | `config.storeUrl` has no hostname or points at `localhost`. |
| 422 | Invalid domain. Please provide a complete domain (e.g., example.com) | `config.storeUrl`'s hostname has no dot (for example a bare word). |
| 422 | Invalid URL format. Please provide a valid URL (e.g., example.com or https://example.com) | `config.storeUrl` cannot be parsed as a URL even after `https://` is prepended. |
| 422 | Store ID is required. | `config.storeId` is present but empty or not a string. |
| 422 | Store domain is required. | `config.storeDomain` is present but empty or not a string. |
| 422 | Store domain must be a valid domain (e.g., stylor.com or shop.example.com) | `config.storeDomain` is not a bare domain — no scheme, no path, and a TLD of at least two letters. |
| 422 | Store name is required. | `config.storeName` is present but empty or not a string. |
| 422 | Currency is required. | `config.currency` is present but empty or not a string. |
| 422 | Currency must be a valid 3-letter ISO currency code (e.g., USD, CAD, EUR) | `config.currency` is not three letters. |
| 422 | Country code is required. | `config.countryCode` is present but empty or not a string. |
| 422 | Country code must be a valid 2-letter ISO country code (e.g., US, CA, GB) | `config.countryCode` is not two letters. |
| 422 | Target gender must be 'mens', 'womens', or null. | `config.targetGender` is set to anything other than `mens`, `womens` or `null`. |
| 422 | Missing key "<key>" in config. | One of the six required `config` keys is absent. They are checked in the order `storeUrl`, `storeId`, `storeDomain`, `storeName`, `currency`, `countryCode` and the first missing one is reported. |

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