# Create a data source

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

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

Connects a store to a dataset. The only supported `type` today is
`shopify-api`, which reads the public storefront catalogue at
`https://<store>.myshopify.com/products.json`. You can pass either the full
`products.json` URL or the bare store origin; the sync worker appends
`/products.json` and pages through the catalogue 250 products at a time.

`syncPeriod` controls automatic syncing, in minutes. A source is due when at
least `syncPeriod` minutes have passed since its last successful sync (a source
that has never synced is due immediately), at which point the scheduler enqueues
a sync job for it. Pass `0` to disable automatic syncing and trigger jobs
yourself with **Create sync job**. Only `active` sources are scheduled, and only
`active` sources accept manual jobs.

`disableGenerativeAssets` is accepted but AI-generated thumbnails are currently
turned off platform-wide, so the stored value is always `true`.

Creating a source does not start a sync. Trigger one explicitly, or wait for the
scheduler.

The response is `200`, not `201`.

## Example request

```bash
curl -X POST "https://stylor.ai/api/v1/datasets/YOUR_DATASET_ID/sources" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "shopify-api",
  "endpoint": "https://example-store.myshopify.com/products.json",
  "syncPeriod": 1440,
  "status": "active"
}'
```

## Path parameters

- `datasetId` (string (uuid), required): The dataset the source will feed. Must belong to your organization.

## Request body

Content type `application/json`, required.

- `type` (string, required): Connector type. Only `shopify-api` is supported. [one of `"shopify-api"`]
- `endpoint` (string (uri), required): Absolute URL of the store's public `products.json` endpoint, or the store origin. Must parse as a URL.
- `syncPeriod` (integer, required): Automatic sync interval in minutes. Must be a JSON number: `0` (never sync automatically) or at least `60`. Strings are rejected on create. [minimum 0]
- `status` (string, required): Whether the source is scheduled and accepts sync jobs. [one of `"active"`, `"inactive"`]
- `disableGenerativeAssets` (boolean): Accepted for forward compatibility; currently always stored as `true`. [default `true`]

### Examples

#### Daily sync

A Shopify source that syncs every 24 hours.

```json
{
  "type": "shopify-api",
  "endpoint": "https://example-store.myshopify.com/products.json",
  "syncPeriod": 1440,
  "status": "active"
}
```

#### Manual sync

A Shopify source that syncs only when you start a job.

```json
{
  "type": "shopify-api",
  "endpoint": "https://example-store.myshopify.com",
  "syncPeriod": 0,
  "status": "active"
}
```

## Responses

### 200

The source was created.

Content type `application/json`:

- `success` (boolean, required): Always `true` on a 2xx response.
- `source` (DataSource, required): A store connection that feeds a dataset. Sources carry no credentials: the Shopify connector reads the public storefront catalogue, so the only stored configuration is the endpoint URL. Nothing is redacted from responses.
  - `sourceId` (string (uuid), required): Unique identifier of the source. Generated on create. [example `"8a4d2f1e-3c5b-4e6a-9d7f-2b1c3d4e5f6a"`]
  - `datasetId` (string (uuid), required): The dataset this source feeds. [example `"3f1c2a9e-6b7d-4c1e-9a2b-1d4e5f6a7b8c"`]
  - `organizationId` (string (uuid), required): The organization that owns the source. [example `"5b7e9c1a-2d4f-4a6b-8c1d-9e2f3a4b5c6d"`]
  - `type` (string, required): Connector type. `shopify-api` reads a Shopify storefront's public `products.json`. [one of `"shopify-api"`]
  - `status` (string, required): `active` sources are picked up by the scheduler and accept manual sync jobs. `inactive` sources are ignored by the scheduler and reject manual jobs. [one of `"active"`, `"inactive"`]
  - `sync` (object, required): Scheduling state.
    - `period` (integer, required): Automatic sync interval in minutes. `0` means never sync automatically. [minimum 0; example `1440`]
    - `previous` (string (date-time), nullable): When the last **successful** sync finished. `null` until the first success; failed runs do not update it. [example `"2026-09-11T03:00:12.418Z"`]
    - `lastStats` (object): Counters from the most recent completed or failed sync run. **Only present on list responses.** All zeros if no run has finished.
      - `retrieved` (integer, required): Products fetched from the store (capped at 25,000 per run). [minimum 0; example `1284`]
      - `added` (integer, required): Products not previously in the dataset that were queued for import. [minimum 0; example `12`]
      - `updated` (integer, required): Existing products whose store data changed and were queued for re-processing. [minimum 0; example `37`]
      - `archived` (integer, required): Dataset items whose SKU no longer appears in the store, marked archived. [minimum 0; example `3`]
      - `failed` (integer, required): Products that could not be queued. Details are in the log's `error`-level entries. [minimum 0; example `0`]
  - `config` (object, required): Connector configuration.
    - `endpoint` (string (uri), required): The store URL supplied on create or update. Fetched as `<endpoint>/products.json?limit=250&page=N` (the `/products.json` suffix is added if missing). [example `"https://example-store.myshopify.com/products.json"`]
    - `disableGenerativeAssets` (boolean, required): Whether AI-generated thumbnails are skipped for items from this source. Currently always `true`. [example `true`]
  - `createdAt` (string (date-time), required): When the source was created. [example `"2026-08-01T14:22:09.101Z"`]
  - `updatedAt` (string (date-time), required): When the source was last modified, including scheduler updates to `sync.previous`. [example `"2026-09-11T03:00:12.420Z"`]

Example:

```json
{
  "success": true,
  "source": {
    "sourceId": "8a4d2f1e-3c5b-4e6a-9d7f-2b1c3d4e5f6a",
    "datasetId": "3f1c2a9e-6b7d-4c1e-9a2b-1d4e5f6a7b8c",
    "organizationId": "5b7e9c1a-2d4f-4a6b-8c1d-9e2f3a4b5c6d",
    "type": "shopify-api",
    "status": "active",
    "sync": {
      "period": 1440,
      "previous": null
    },
    "config": {
      "endpoint": "https://example-store.myshopify.com/products.json",
      "disableGenerativeAssets": true
    },
    "createdAt": "2026-09-12T09:15:41.002Z",
    "updatedAt": "2026-09-12T09:15:41.002Z"
  }
}
```

## 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 is not parseable JSON. This response carries no rate-limit headers. |
| 400 | Request body must be a JSON object. | The request body is empty, an array, or a JSON scalar. This response carries no rate-limit headers. |
| 404 | Dataset not found or does not belong to organization | No dataset with that `datasetId` belongs to the organization that owns the key. Checked after field validation. |
| 422 | type must be "shopify-api" | `type` is missing or is anything other than `shopify-api`, and it is the only invalid field. |
| 422 | endpoint is required and must be a string | `endpoint` is missing, empty, or not a string, and it is the only invalid field. |
| 422 | endpoint must be a valid URL | `endpoint` is a string that does not parse as an absolute URL, and it is the only invalid field. |
| 422 | syncPeriod is required and must be 0 (Never) or at least 60 minutes | `syncPeriod` is missing, not a JSON number, or a number between 1 and 59, and it is the only invalid field. |
| 422 | status must be "active" or "inactive" | `status` is missing or not one of the two allowed values, and it is the only invalid field. |
| 422 | Validation failed. | More than one field is invalid. `details` lists one `{ path, message }` entry per invalid field, using the messages above. A single-field failure also carries `details`.Body: `{"error":"Validation failed.","details":[{"path":"type","message":"type must be \"shopify-api\""},{"path":"syncPeriod","message":"syncPeriod is required and must be 0 (Never) or at least 60 minutes"}]}` |

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