# Generated store

The shopify_store scenario builds a store with up to three years of orders from a size, a history length, a currency and a timezone, the same store for the same parameters.

Test an app that reads a store with real history, such as a forecast that compares this year with last year. Load `shopify_store` with a size and a history length, then read the store back:

```bash
ADMIN="$RYSTIC_URL/admin/api/2026-10/graphql.json"
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"shopify_store","params":{"size":"small","history_days":730,"clock":"2026-11-08T12:00:00Z"}}'
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_read_all_orders" -H "Content-Type: application/json" -d '{"query": "{ shop { currencyCode ianaTimezone } productsCount { count } ordersCount(limit: null) { count } }"}' | jq -c '.data'
# -> {"ordersCount":{"count":876},"productsCount":{"count":50},"shop":{"currencyCode":"NOK","ianaTimezone":"Europe/Oslo"}}
```

The same parameters build the same store, record for record. Change `rng_seed` for another store of the same shape. Name `clock` to fix the day the history runs back from; without it, the history ends at the simulator's clock.

### Parameters

| Parameter | Values | Default |
|---|---|---|
| `size` | `small`: 50 products and about 100 variants. `medium`: about 1,000 variants. `large`: over 10,000 variants and at least 5,000 orders | `small` |
| `history_days` | 14 to 1095 days of orders before the clock | `365` |
| `currency` | `NOK`, `SEK`, `DKK`, `EUR` or `USD` | `NOK` |
| `timezone` | any IANA timezone, such as `Europe/Oslo` | `Europe/Oslo` |
| `future_orders_per_day` | 0 to 200 orders a day over the 30 days after the clock, each landing when the clock passes it ([Order webhooks](https://www.rystic.ai/docs/simulators/shopify/features/order-webhooks.md)) | `0` |
| `clock` | the instant the history ends at, in RFC 3339 | the simulator's clock |
| `rng_seed` | any integer | `0` |

`GET /_rystic/scenarios` lists the parameters with their defaults:

```bash
curl -s "$RYSTIC_URL/_rystic/scenarios" | jq -c '.scenarios[] | select(.name == "shopify_store") | [.params[].name]'
# -> ["size","history_days","currency","timezone","future_orders_per_day","clock","rng_seed"]
```

A value outside the list or a timezone the simulator does not know returns 400 and leaves the current scenario loaded:

```bash
curl -s -o /dev/null -w '%{http_code}\n' -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"shopify_store","params":{"timezone":"Mars/Olympus"}}'
# -> 400
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_read_all_orders" -H "Content-Type: application/json" -d '{"query": "{ ordersCount(limit: null) { count } }"}' | jq -c '.data.ordersCount'
# -> {"count":876}
```

Boot straight into a store with `rystic run shopify-twin --scenario 'shopify_store?size=medium&history_days=730'`.

### The 60-day order window

The scenario binds the token `shpat_read_all_orders` to the `read_all_orders` grant, so it reads every order. Any other token sees only the last 60 days, as a Shopify token without that grant does ([Order history window](https://www.rystic.ai/docs/simulators/shopify/features/order-window.md)):

```bash
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_placeholder" -H "Content-Type: application/json" -d '{"query": "{ ordersCount(limit: null) { count } }"}' | jq -c '.data.ordersCount'
# -> {"count":91}
```

### Seasons and sales

Orders follow a Nordic retail year: a quiet January and July, a busy December and a peak in Black Week that is strongest on Black Friday. Sales grow about 30% a year. Orders fall on local days and hours of the shop's timezone. ShopifyQL shows the peak against a quiet month:

```bash
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_read_all_orders" -H "Content-Type: application/json" -d '{"query": "{ shopifyqlQuery(query: \"FROM sales SHOW total_sales GROUP BY month SINCE -24m UNTIL today\") { tableData { rows } } }"}' | jq -c '[.data.shopifyqlQuery.tableData.rows[] | select(.month == "2025-11-01" or .month == "2025-01-01")]'
# -> [{"month":"2025-11-01","total_sales":"98851.2"},{"month":"2025-01-01","total_sales":"46789.8"}]
```

### What the store holds

- Products with sizes and colors, images, SKUs, barcodes, compare-at prices on some and a few draft and archived products.
- `Glacier Thermos`, which has stock and never sold.
- `Aurora Down Vest`, created two days before the clock: sold out, with stock incoming.
- Three locations: `Oslo lager` ships online orders, beside two shops. Each variant has stock at each location, with `available`, `on_hand`, `committed` and `incoming` quantities. About one in ten levels is sold out.
- A unit cost on every inventory item.
- A collection for each product type, plus `Salg`, `Nyheter` and `Bestselgere`.
- Customers with saved addresses, email consent, order counts and spend. Five saved segments.
- Orders through the online store and the shops' point of sale, with line items, discounts, included 25% VAT, payments, fulfillment orders and tracked shipments. Recent orders are still open or partly shipped.
- Returns, refunds for returned items, price-adjustment refunds and a few canceled orders.
- An active welcome code and member discount.
- A Black Week code, a summer code and a January sale for each year of history: expired, active or scheduled by the clock.
- Abandoned checkouts from the last 90 days and three open draft orders.
- A blog with articles, five pages, a main menu and two redirects.
- A primary `Norge` market in the shop's currency. A second market sells in EUR (in SEK for a EUR shop). The second market's catalog has a price list with a fixed price for every variant.
- Norwegian (Bokmål) as the primary language and English published, with English translations of the pages, the article titles and some product titles. `translatableResource`, `translatableResources` and `translatableResourcesByIds` read them back under `translations(locale: "en")`, as the last example in this section shows.
- Two themes, one published, each with layout, template, section, asset, config and locale files, which `themeFilesUpsert` can change.
- A `faq_entry` metaobject definition with six entries. Product metafields with their definitions.

```bash
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_read_all_orders" -H "Content-Type: application/json" -d '{"query": "{ products(first: 2, query: \"title:Glacier OR title:Aurora\") { nodes { title createdAt totalInventory } } }"}' | jq -c '.data.products.nodes'
# -> [{"createdAt":"2024-07-13T00:00:00Z","title":"Glacier Thermos","totalInventory":70},{"createdAt":"2026-11-06T10:00:00Z","title":"Aurora Down Vest","totalInventory":0}]
```

The new product's first variant has a unit cost and stock incoming at each location:

```bash
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_read_all_orders" -H "Content-Type: application/json" -d '{"query": "{ products(first: 1, query: \"title:Aurora\") { nodes { variants(first: 1) { nodes { inventoryItem { unitCost { amount currencyCode } inventoryLevels(first: 3) { nodes { location { name } quantities(names: [\"available\", \"incoming\"]) { name quantity } } } } } } } } }"}' | jq -c '.data.products.nodes[0].variants.nodes[0].inventoryItem | {unitCost, levels: [.inventoryLevels.nodes[] | {location: .location.name, available: (.quantities[] | select(.name == "available") | .quantity), incoming: (.quantities[] | select(.name == "incoming") | .quantity)}]}'
# -> {"unitCost":{"amount":"558.36","currencyCode":"NOK"},"levels":[{"location":"Oslo lager","available":0,"incoming":41},{"location":"Bergen butikk","available":0,"incoming":13},{"location":"Trondheim butikk","available":0,"incoming":62}]}
```

A product's content is in Norwegian, and its English title reads back as a translation:

```bash
curl -s "$ADMIN" -H "X-Shopify-Access-Token: shpat_read_all_orders" -H "Content-Type: application/json" -d '{"query": "{ translatableResource(resourceId: \"gid://shopify/Product/10001\") { translatableContent { key value locale } translations(locale: \"en\") { key value } } }"}' | jq -c '.data.translatableResource | {content: .translatableContent[0], translations}'
# -> {"content":{"key":"title","locale":"nb","value":"Glacier Thermos"},"translations":[{"key":"title","value":"Glacier Thermos"}]}
```

### Load time

A `small` store loads in under a second and a `medium` one in about two. A `large` store with two years of orders holds about 170,000 records and loads in about seven seconds.

---

Source: https://www.rystic.ai/docs/simulators/shopify/features/generated-store · Markdown: https://www.rystic.ai/docs/simulators/shopify/features/generated-store.md
