{
  "openapi": "3.1.0",
  "info": {
    "title": "Mercury Storefront API",
    "version": "1",
    "description": "The storefront surface of Mercury: search, collections, autocomplete, zones, and the legacy product, page, collection and filter reads. v1 is today's surface and is frozen in shape: fixes keep its keys and values. v2 will mirror Shopify's Storefront API shapes. Generated from api-types/src/storefront.",
    "x-audience": "public"
  },
  "servers": [
    {
      "url": "https://mercury.plutocracy.io",
      "description": "Every region, latency-routed. /api/* is the main API in Sydney (the US and EU regions proxy it there)."
    }
  ],
  "tags": [
    {
      "name": "search",
      "description": "Storefront search routes.\n\nSource controllers:\n- libs/controllers/src/search.controller.ts (LibSearchController — the route table)\n- apps/view/src/controllers/search.controller.ts (mounts LibSearchController at\n  the ROOT of the view app — canonical storefront paths, used below)\n- apps/server/src/controllers/search.controller.ts (mounts the same routes\n  under the `/search` prefix on the main API AND adds two jwt-guarded\n  shadows: `/search/query/:term` and `/search/collection/:handle`. On the\n  main API the redis-auth variants therefore live at\n  `/search/search/query/:term`, `/search/collection/:handle` etc. — pinned\n  by apps/server/test/app.e2e-spec.ts).\n\nAuth: \"Origin+bypass redis strategy\" = whitelisted `Origin` header, or\n`?bypassToken=` query parameter (or the `X-BypassToken` header, which only\nthis strategy reads — see storefront/auth.ts). Failures return the standard\n`ApiErrorResponse` with code 401."
    },
    {
      "name": "zone",
      "description": "Zone (product recommendation) routes.\n\nSource controllers:\n- libs/controllers/src/zone-view.controller.ts (LibZoneViewController — the\n  route table; each handler is registered at BOTH `/query/:id` and `/query`)\n- apps/view/src/controllers/zone.controller.ts (mounts it at `/zone` on the\n  storefront view app — canonical paths below)\n\nThe jwt-guarded zone-admin CRUD on apps/server is out of scope here (owned\nby the admin surface); note however that apps/server's ZoneAdminController\nalso exposes `GET /zone/query/:id` under jwt auth.\n\nAuth: Origin+bypass redis strategy (whitelisted `Origin` header or\n`?bypassToken=`). 401 → standard `ApiErrorResponse`.\nPinned by apps/view/test/app.e2e-spec.ts."
    }
  ],
  "paths": {
    "/search/collection/{handle}": {
      "get": {
        "operationId": "SearchCollectionAliasRoute",
        "summary": "Rendered collection by handle: products, facets and merchandising for a collection page.",
        "description": "Alias of `CollectionByHandleRoute` (identical behaviour).\nAuth: Origin+bypass redis strategy.",
        "tags": [
          "search"
        ],
        "parameters": [
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "JSON-stringified `FilterOperator` array of additional filters.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort override; `random` shuffles the returned page. Defaults to the\ncollection's Shopify sort order.",
            "schema": {
              "type": "string",
              "enum": [
                "manual",
                "az",
                "za",
                "oldest",
                "newest",
                "priceLow",
                "priceHigh",
                "random"
              ]
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tracking",
            "in": "query",
            "required": false,
            "description": "`none` disables analytics tracking for this request.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "grid_config",
            "in": "query",
            "required": false,
            "description": "See `SearchQueryParams.grid_config` — same truthy-string trap.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderedCollection"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "SearchCollectionAliasRoute",
          "key": "GET /search/collection/:handle",
          "source": "api-types/src/storefront/search.ts#L184",
          "app": "view",
          "handler": "LibSearchController.aliasCollection",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/search/collection/{handle}",
          "legacy": false,
          "quirks": []
        }
      }
    },
    "/search/product/{handles}": {
      "get": {
        "operationId": "SearchProductsByHandlesRoute",
        "summary": "Rendered products by comma-separated handles (capped at 20; each product run through `mapOutput` with `?currency=`).",
        "description": "Rendered products by comma-separated handles (capped at 20; each product\nrun through `mapOutput` with `?currency=`). Missing handles yield `null`\nentries in the array (redis JSON.GET misses are passed through).\nAuth: Origin+bypass redis strategy.",
        "tags": [
          "search"
        ],
        "parameters": [
          {
            "name": "handles",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "anyOf": [
                      {
                        "type": "null"
                      },
                      {
                        "$ref": "#/components/schemas/RenderedProduct"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "SearchProductsByHandlesRoute",
          "key": "GET /search/product/:handles",
          "source": "api-types/src/storefront/search.ts#L198",
          "app": "view",
          "handler": "LibSearchController.getSearchProductsByHandles",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/search/product/{handles}",
          "legacy": false,
          "quirks": []
        }
      }
    },
    "/search/query/{term}": {
      "get": {
        "operationId": "SearchQueryRoute",
        "summary": "Full rendered search: autocomplete suggestions + faceted results.",
        "description": "Full rendered search: autocomplete suggestions + faceted results.\nAuth: Origin+bypass redis strategy.\n400 `No term specified` when term is empty or the literal string \"undefined\".\n406 when the underlying redis query fails.",
        "tags": [
          "search"
        ],
        "parameters": [
          {
            "name": "term",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "in": "query",
            "required": false,
            "description": "Results to return; max 50, default 20. Arrives as a string.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "required": false,
            "description": "Results to skip; should be a multiple of `take`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number. When present, `skip` is recomputed as\n`take * (page - 1)` (default take 20). Values < 1 → 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "required": false,
            "description": "Alias for `page` honoured by the pagination helper.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "JSON-stringified `FilterOperator` array of additional filters.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Mercury sort order (e.g. `newest`, `priceLow`, `az`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Narrow all product `prices` maps to this currency.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tracking",
            "in": "query",
            "required": false,
            "description": "`none` disables analytics tracking for this request.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Client user id used to key the analytics entry.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "red",
            "in": "query",
            "required": false,
            "description": "Allow the search to return redirect URLs for the term (swagger-documented).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sug",
            "in": "query",
            "required": false,
            "description": "Allow the search to return similar search terms (swagger-documented).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "grid_config",
            "in": "query",
            "required": false,
            "description": "Include the store Grid Config in the response.\nBUG: string-truthiness check — `grid_config=false` STILL includes it\n(pinned by libs/controllers/src/search.controller.spec.ts). Omit the\nparameter (or send an empty string) to exclude it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RenderedSearch"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "SearchQueryRoute",
          "key": "GET /search/query/:term",
          "source": "api-types/src/storefront/search.ts#L158",
          "app": "view",
          "handler": "LibSearchController.search",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/search/query/{term}",
          "legacy": false,
          "quirks": []
        }
      }
    },
    "/search/suggest/{term}": {
      "get": {
        "operationId": "SearchSuggestRoute",
        "summary": "Autocomplete suggestions from the store's suggestion dictionary (redis FT.SUGGET, fuzzy).",
        "description": "Autocomplete suggestions from the store's suggestion dictionary\n(redis FT.SUGGET, fuzzy).\nAuth: Origin+bypass redis strategy.",
        "tags": [
          "search"
        ],
        "parameters": [
          {
            "name": "term",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuggestResponse"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "SearchSuggestRoute",
          "key": "GET /search/suggest/:term",
          "source": "api-types/src/storefront/search.ts#L134",
          "app": "view",
          "handler": "LibSearchController.autocomplete",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/search/suggest/{term}",
          "legacy": false,
          "quirks": []
        }
      }
    },
    "/zone/query": {
      "get": {
        "operationId": "ZoneQueryRoute",
        "summary": "Execute a zone query without an id — the zone is located via `?templateName=` (or the store default).",
        "description": "Execute a zone query without an id — the zone is located via\n`?templateName=` (or the store default).\nAuth: Origin+bypass redis strategy.",
        "tags": [
          "zone"
        ],
        "parameters": [
          {
            "name": "templateName",
            "in": "query",
            "required": false,
            "description": "Template used to locate a zone when no `:id` is given (or the id misses):\n`cart` | `collection` | `product` | `cms` | `index` | `wishlist`; any\nother value falls back to the first zone in the store's index.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tracking",
            "in": "query",
            "required": false,
            "description": "`none` disables analytics tracking for this request.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Client user id used to key the analytics entry.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Narrow rendered product `prices` maps to this currency.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "JSON-stringified filter operator array applied to the zone query.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZoneResponse"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "ZoneQueryRoute",
          "key": "GET /zone/query",
          "source": "api-types/src/storefront/zone.ts#L80",
          "app": "view",
          "handler": "LibZoneViewController.executeZoneQuery",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/zone/query",
          "legacy": false,
          "quirks": []
        }
      },
      "post": {
        "operationId": "ZoneProgrammaticQueryRoute",
        "summary": "Programmatic zone query without an id (template lookup, e.g.",
        "description": "Programmatic zone query without an id (template lookup, e.g.\n`?templateName=cart` with the cart lines in the body).\nAuth: Origin+bypass redis strategy.",
        "tags": [
          "zone"
        ],
        "parameters": [
          {
            "name": "templateName",
            "in": "query",
            "required": false,
            "description": "Template used to locate a zone when no `:id` is given (or the id misses):\n`cart` | `collection` | `product` | `cms` | `index` | `wishlist`; any\nother value falls back to the first zone in the store's index.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tracking",
            "in": "query",
            "required": false,
            "description": "`none` disables analytics tracking for this request.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Client user id used to key the analytics entry.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Narrow rendered product `prices` maps to this currency.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "JSON-stringified filter operator array applied to the zone query.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ZoneProgrammaticBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created (Nest answers POST with 201).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZoneResponse"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "ZoneProgrammaticQueryRoute",
          "key": "POST /zone/query",
          "source": "api-types/src/storefront/zone.ts#L107",
          "app": "view",
          "handler": "LibZoneViewController.executeZoneProgrammaticQuery",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/zone/query",
          "legacy": false,
          "quirks": []
        }
      }
    },
    "/zone/query/{id}": {
      "get": {
        "operationId": "ZoneQueryByIdRoute",
        "summary": "Execute a zone query by zone id.",
        "description": "Execute a zone query by zone id.\nAuth: Origin+bypass redis strategy.\nResponse: the rendered zone (zone document + `products`). When zone\nresolution fails in ANY way the FALLBACK payload `{ title: '', products }`\nis returned with 200; when the matched rule group is `empty` the body is\nEMPTY (handler returns undefined).",
        "tags": [
          "zone"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "templateName",
            "in": "query",
            "required": false,
            "description": "Template used to locate a zone when no `:id` is given (or the id misses):\n`cart` | `collection` | `product` | `cms` | `index` | `wishlist`; any\nother value falls back to the first zone in the store's index.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tracking",
            "in": "query",
            "required": false,
            "description": "`none` disables analytics tracking for this request.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Client user id used to key the analytics entry.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Narrow rendered product `prices` maps to this currency.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "JSON-stringified filter operator array applied to the zone query.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZoneResponse"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "ZoneQueryByIdRoute",
          "key": "GET /zone/query/:id",
          "source": "api-types/src/storefront/zone.ts#L67",
          "app": "view",
          "handler": "LibZoneViewController.executeZoneQuery",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/zone/query/{id}",
          "legacy": false,
          "quirks": []
        }
      },
      "post": {
        "operationId": "ZoneProgrammaticQueryByIdRoute",
        "summary": "Programmatic zone query by id (cart contents etc.",
        "description": "Programmatic zone query by id (cart contents etc. in the body).\nAuth: Origin+bypass redis strategy.\nResponds 201 (Nest default for POST) with the same payload semantics as\n`ZoneQueryByIdRoute`.",
        "tags": [
          "zone"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "templateName",
            "in": "query",
            "required": false,
            "description": "Template used to locate a zone when no `:id` is given (or the id misses):\n`cart` | `collection` | `product` | `cms` | `index` | `wishlist`; any\nother value falls back to the first zone in the store's index.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tracking",
            "in": "query",
            "required": false,
            "description": "`none` disables analytics tracking for this request.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Client user id used to key the analytics entry.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Narrow rendered product `prices` maps to this currency.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "JSON-stringified filter operator array applied to the zone query.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ZoneProgrammaticBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created (Nest answers POST with 201).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZoneResponse"
                }
              }
            }
          },
          "401": {
            "description": "No valid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "An error, in the standard envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "origin": []
          },
          {
            "bypassToken": []
          }
        ],
        "x-mercury": {
          "contract": "ZoneProgrammaticQueryByIdRoute",
          "key": "POST /zone/query/:id",
          "source": "api-types/src/storefront/zone.ts#L93",
          "app": "view",
          "handler": "LibZoneViewController.executeZoneProgrammaticQuery",
          "guards": [
            "redis"
          ],
          "auth": "storefront",
          "url": "https://mercury.plutocracy.io/zone/query/{id}",
          "legacy": false,
          "quirks": []
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiErrorResponse": {
        "description": "Error envelope produced by the server's HttpErrorFilter for all thrown\nHttpExceptions (pinned by e2e tests).",
        "type": "object",
        "properties": {
          "code": {
            "type": "number"
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "error",
          "method",
          "path",
          "timestamp"
        ],
        "x-source": "api-types/src/common.ts"
      },
      "ConditionalZoneConditions": {
        "description": "RELATED (NOT merged): `admin.ConditionalZone` is the entity-side wrapper of\nthe stricter admin rule groups.",
        "type": "object",
        "properties": {
          "ruleGroups": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ZoneDefaultConditions"
                },
                {
                  "$ref": "#/components/schemas/ZoneRuleGroup"
                }
              ]
            }
          }
        },
        "required": [
          "ruleGroups"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "FilterOperator": {
        "description": "A single filter operator. Sent to the server as a JSON-stringified array in\nthe `query` query-string parameter and echoed back verbatim under the\n`query` key of rendered search/collection responses.\n\nKnown `type` values in the codebase: `tag`, `vendor`, `type`, `price`,\n`sale`, `inventory`, `title`, `id`, `total_stock`.",
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "condition": {
            "type": "string",
            "description": "e.g. `equals`, `more_than`, `less_than`, `between`, `contains`, `doesNotContains`"
          },
          "value": {}
        },
        "required": [
          "type"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "GridConfig": {
        "description": "The store's Grid Config JSON as stored in redis (JSON.GET on the store\nconfig key). It is a merchant-editable document; only the top-level keys\nobserved in defaults (`libs/shared/src/classes/defaults/grid.ts`) and the\ne2e fakes are pinned, everything else is open.\n\nRELATED (deliberately NOT merged): the admin surface's `admin.GridConfig`\ntypes the SAME document as the strict DB entity shape\n(libs/database store.entity.ts `GridConfig`) served/accepted by\n`GET/POST /app/grid`. This storefront view stays loose because the redis\ncopy is merchant-edited and the storefront never validates it.",
        "type": "object",
        "properties": {
          "grid": {
            "type": "object",
            "properties": {
              "elements": {
                "type": "object",
                "additionalProperties": {}
              },
              "core": {
                "type": "object",
                "additionalProperties": {}
              },
              "breakpoints": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "index": {
                      "type": "number"
                    },
                    "label": {
                      "type": "string"
                    },
                    "minwidth": {
                      "type": "number"
                    },
                    "columns": {
                      "type": "number"
                    },
                    "format": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "index",
                    "label",
                    "minwidth",
                    "columns",
                    "format"
                  ],
                  "additionalProperties": {}
                }
              },
              "currentBreakpoint": {
                "type": "object",
                "additionalProperties": {}
              },
              "styles": {
                "type": "string"
              }
            },
            "additionalProperties": {},
            "description": "The grid layout (admin grid editor)."
          },
          "currency": {
            "type": "string",
            "description": "The mapping keys storefronts and Collection Kit bundles read (kit-contract/v1 GridConfig)."
          },
          "locale": {
            "type": "string"
          },
          "delimiter": {
            "type": "string"
          },
          "sizeMapping": {},
          "colourMapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "imageMapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "facetMapping": {},
          "integrations": {},
          "skuSearch": {}
        },
        "additionalProperties": {},
        "x-source": "api-types/src/storefront/models.ts"
      },
      "ImplodeMode": {
        "description": "Product implode/explode mode stored on the redis product. Observed values:\nimploded products use `3` (see `RedisSearchService.mapImploded`, which only\nacts when `implode == 3`). The collection storefront app names the values in\nits `ExplodeType` enum (collection/src/app/classes/product.ts):\n`1` = Relation, `2` = Norelation, `3` = Imploded.",
        "type": "number",
        "enum": [
          1,
          2,
          3
        ],
        "x-source": "api-types/src/models/product.ts"
      },
      "ManualZoneConditions": {
        "description": "Conditions of a manual zone. Known in the admin surface as `ManualZone`.",
        "type": "object",
        "properties": {
          "productIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "products": {},
          "collectionId": {
            "type": "string"
          },
          "collection": {}
        },
        "x-source": "api-types/src/models/zone.ts"
      },
      "MercurySortOrder": {
        "description": "Mercury sort order accepted by search/collection query params and echoed\nback on rendered collections (`sort_order`).",
        "type": "string",
        "enum": [
          "manual",
          "bestSelling",
          "az",
          "za",
          "oldest",
          "newest",
          "priceLow",
          "priceHigh",
          "random"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "RedisPriceEntry": {
        "description": "Per-currency price entry inside the redis product `prices` map.\nShape produced at indexing time; treat values as numbers-or-strings.",
        "type": "object",
        "properties": {
          "price": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          },
          "compare_at_price": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          }
        },
        "required": [
          "price"
        ],
        "x-source": "api-types/src/models/product.ts"
      },
      "RedisPricesMap": {
        "description": "The `prices` field of redis/rendered products: keyed by uppercase currency\ncode. When a `currency` query param is supplied, `mapOutput` narrows the\nmap to ONLY that currency's key (`{ [currency]: prices[currency] }`).",
        "type": "object",
        "additionalProperties": {
          "$ref": "#/components/schemas/RedisPriceEntry"
        },
        "x-source": "api-types/src/models/product.ts"
      },
      "RedisProduct": {
        "description": "A product document as stored in redis (JSON.GET on the product key).\nThis is the shape returned RAW by the `shopify/product/handle(s)` alias\nroutes, which skip `mapOutput` entirely.\n\nNOTE: prices here are integer minor units (cents); `mapOutput` divides by\n100 before sending. Fields written only for some stores are optional.",
        "type": "object",
        "properties": {
          "collectionPosition": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "Position of the product per collection handle."
          },
          "id": {
            "type": "string",
            "description": "Shopify GID, e.g. `gid://shopify/Product/1`."
          },
          "title": {
            "type": "string"
          },
          "implode": {
            "$ref": "#/components/schemas/ImplodeMode"
          },
          "imploded": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RedisProduct"
            },
            "description": "Imploded products only: the variant products as STORED in Redis (unmapped: prices in minor units), filtered to\nthe request's tag clauses (RedisSearchService.mapImploded)."
          },
          "description_string": {
            "type": "string"
          },
          "tags_string": {
            "type": "string"
          },
          "handle": {
            "type": "string"
          },
          "min_price": {
            "type": "number",
            "description": "Minor units (cents) in the stored document."
          },
          "max_price": {
            "type": "number",
            "description": "Minor units (cents) in the stored document."
          },
          "compare_at_min_price": {
            "type": "number",
            "description": "Minor units (cents) in the stored document."
          },
          "compare_at_max_price": {
            "type": "number",
            "description": "Minor units (cents) in the stored document."
          },
          "vendor": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "on_sale": {
            "type": "boolean"
          },
          "total_stock": {
            "type": "number"
          },
          "normalised_popularity": {
            "type": "number"
          },
          "normalised_stock": {
            "type": "number"
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedVariant"
            }
          },
          "prices": {
            "$ref": "#/components/schemas/RedisPricesMap"
          },
          "created_date": {
            "type": "string",
            "description": "ISO date string on the wire."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "image": {
            "type": "string"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "options": {},
          "related": {
            "type": "string",
            "description": "Stringified Juno related-products object."
          },
          "meta": {
            "$ref": "#/components/schemas/RedisProductMeta"
          }
        },
        "required": [
          "id",
          "title",
          "implode",
          "handle",
          "min_price",
          "max_price",
          "compare_at_min_price",
          "compare_at_max_price",
          "vendor",
          "type",
          "on_sale",
          "total_stock",
          "variants",
          "prices",
          "created_date",
          "tags"
        ],
        "x-source": "api-types/src/models/product.ts"
      },
      "RedisProductMeta": {
        "description": "`meta` blob carried from the redis product to the rendered product.",
        "type": "object",
        "properties": {
          "reviews": {},
          "badges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedBadge"
            }
          },
          "personalise": {
            "type": "object",
            "properties": {
              "description": {
                "type": "string"
              }
            },
            "required": [
              "description"
            ]
          },
          "related": {}
        },
        "additionalProperties": {},
        "x-source": "api-types/src/models/product.ts"
      },
      "RenderedBadge": {
        "description": "Badge entry under `meta.badges`.",
        "type": "object",
        "properties": {
          "content": {
            "type": "string"
          },
          "background": {
            "type": "string"
          },
          "colour": {
            "type": "string"
          },
          "positiony": {
            "type": "string",
            "enum": [
              "top",
              "centery",
              "bottom"
            ]
          },
          "positionx": {
            "type": "string",
            "enum": [
              "left",
              "centerx",
              "right"
            ]
          },
          "classes": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "manual",
              "automatic"
            ]
          }
        },
        "required": [
          "content",
          "background",
          "colour",
          "positiony",
          "positionx",
          "classes",
          "type"
        ],
        "x-source": "api-types/src/models/product.ts"
      },
      "RenderedCollection": {
        "description": "Response of `GET collection/:handle` (rendered collection): the stored\nShopify collection body spread at the top level, plus campaign/config,\nfacets/products, sort_order and the echoed query.",
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShopifyCollectionRule"
            }
          },
          "title": {
            "type": "string"
          },
          "handle": {
            "type": "string"
          },
          "body_html": {
            "type": "string"
          },
          "metafields": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {}
            },
            "description": "Namespace -> key -> value (save-collection.service mapMetafieldsIntoCorrectFormat)."
          },
          "updated_at": {
            "type": "string"
          },
          "disjunctive": {
            "type": "boolean"
          },
          "published_at": {
            "type": "string"
          },
          "collection_type": {
            "type": "string"
          },
          "published_scope": {
            "type": "string"
          },
          "template_suffix": {
            "type": [
              "string",
              "null"
            ]
          },
          "admin_graphql_api_id": {
            "type": "string"
          },
          "campaign": {
            "description": "Campaign attached to the collection, if any."
          },
          "config": {
            "type": "object",
            "properties": {
              "implode": {
                "type": "boolean"
              }
            },
            "required": [
              "implode"
            ],
            "additionalProperties": {},
            "description": "Collection config plus the collection's `implode` flag."
          },
          "facets": {
            "$ref": "#/components/schemas/SearchFacets"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedProduct"
            }
          },
          "sort_order": {
            "$ref": "#/components/schemas/MercurySortOrder",
            "description": "Resolved sort order actually applied."
          },
          "query": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FilterOperator"
            },
            "description": "Echo of the parsed `query` filter operators ([] when unparsable)."
          },
          "grid_config": {
            "$ref": "#/components/schemas/GridConfig",
            "description": "Present when `grid_config` was requested (any non-empty string)."
          }
        },
        "required": [
          "id",
          "title",
          "handle",
          "body_html",
          "config",
          "facets",
          "products",
          "sort_order",
          "query"
        ],
        "additionalProperties": {},
        "x-source": "api-types/src/storefront/models.ts"
      },
      "RenderedProduct": {
        "description": "A product AFTER `RedisSearchService.mapOutput` — the shape appearing in\n`products` arrays of rendered search/collection/zone responses and in\n`GET search/product/:handles`.\n\nDifferences from `RedisProduct`:\n- prices divided by 100 (major units);\n- `0` prices become `undefined` (the code does `x / 100 || undefined`),\n  so free products serialise with the price fields ABSENT;\n- `max_price` / `compare_at_max_price` are dropped from the output object;\n- `prices` narrowed to the requested currency when `?currency=` was sent.",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "implode": {
            "$ref": "#/components/schemas/ImplodeMode"
          },
          "handle": {
            "type": "string"
          },
          "min_price": {
            "type": "number",
            "description": "Major units. Absent/undefined when the stored price was 0 or missing."
          },
          "compare_at_min_price": {
            "type": "number",
            "description": "Major units. Absent/undefined when the stored price was 0 or missing."
          },
          "vendor": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "on_sale": {
            "type": "boolean"
          },
          "total_stock": {
            "type": "number"
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedVariant"
            }
          },
          "prices": {
            "$ref": "#/components/schemas/RedisPricesMap"
          },
          "created_date": {
            "type": "string",
            "description": "ISO date string on the wire."
          },
          "options": {},
          "normalised_stock": {
            "type": "number"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "image": {
            "type": "string"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "related": {
            "type": "string",
            "description": "Stringified Juno related-products object."
          },
          "imploded": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RedisProduct"
            },
            "description": "As stored: see RedisProduct.imploded (mapOutput passes it through unmapped)."
          },
          "meta": {
            "$ref": "#/components/schemas/RedisProductMeta"
          }
        },
        "required": [
          "id",
          "title",
          "implode",
          "handle",
          "vendor",
          "type",
          "on_sale",
          "total_stock",
          "variants",
          "prices",
          "created_date",
          "tags"
        ],
        "x-source": "api-types/src/models/product.ts"
      },
      "RenderedSearch": {
        "description": "Response of `GET search/query/:term` (rendered search).\nSpread order (pinned by libs/controllers/src/search.controller.spec.ts):\n`suggested` from autocomplete first, then the search-result keys (which can\nOVERRIDE `suggested`), then optionally `grid_config`.",
        "type": "object",
        "properties": {
          "suggested": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Autocomplete suggestions for the term ([] when the dictionary errors)."
          },
          "facets": {
            "$ref": "#/components/schemas/SearchFacets"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedProduct"
            }
          },
          "query": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FilterOperator"
            },
            "description": "Echo of the parsed `query` filter operators ([] when unparsable)."
          },
          "search": {
            "type": "string",
            "description": "The cleaned search term actually executed."
          },
          "redirect": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present when a search-term redirect is configured for the term."
          },
          "grid_config": {
            "$ref": "#/components/schemas/GridConfig",
            "description": "Present when `grid_config` was requested (any non-empty string)."
          }
        },
        "required": [
          "suggested",
          "facets",
          "products",
          "query",
          "search"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "RenderedVariant": {
        "description": "Variant shape stored on redis products and echoed on rendered products.",
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "id": {
            "type": "number"
          },
          "inventory": {
            "type": "number"
          },
          "price": {
            "type": [
              "string",
              "null"
            ]
          },
          "compare_at_price": {
            "type": [
              "string",
              "null"
            ]
          },
          "option1": {
            "type": [
              "string",
              "null"
            ]
          },
          "option2": {
            "type": [
              "string",
              "null"
            ]
          },
          "option3": {
            "type": [
              "string",
              "null"
            ]
          },
          "grams": {
            "type": "number"
          }
        },
        "required": [
          "title",
          "id",
          "inventory",
          "price",
          "compare_at_price"
        ],
        "additionalProperties": {},
        "x-source": "api-types/src/models/product.ts"
      },
      "RenderedZone": {
        "description": "A rendered zone: the stored zone document (redis JSON) spread at the top\nlevel with a `products` array of rendered products appended.",
        "type": "object",
        "properties": {
          "deletedDate": {
            "type": [
              "string",
              "null"
            ]
          },
          "storeId": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "subtitle": {
            "type": [
              "string",
              "null"
            ]
          },
          "shortcode": {
            "type": "string"
          },
          "default": {
            "type": "boolean"
          },
          "area": {
            "type": "string",
            "enum": [
              "cart",
              "shortcode",
              "wishlist",
              "cms",
              "product",
              "collection"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "manual",
              "automatic"
            ]
          },
          "submode": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "collection",
                  "conditional",
                  "recommended"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "conditions": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ManualZoneConditions"
              },
              {
                "$ref": "#/components/schemas/ConditionalZoneConditions"
              }
            ]
          },
          "config": {
            "$ref": "#/components/schemas/ZoneConfig"
          },
          "appearance": {
            "type": "string",
            "enum": [
              "grid",
              "carousel",
              "list",
              "tiles",
              "single"
            ]
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedProduct"
            }
          }
        },
        "required": [
          "id",
          "title",
          "products"
        ],
        "additionalProperties": {},
        "x-source": "api-types/src/storefront/models.ts"
      },
      "SearchFacets": {
        "description": "Facets attached to redis-backed search / collection / zone results\n(`RedisSearchService.searchProducts` / `searchCollection`).\n`count` is the TOTAL number of matching documents (pre-pagination).",
        "type": "object",
        "properties": {
          "vendors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VendorFacetRow"
            }
          },
          "types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TypeFacetRow"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TagFacetRow"
            }
          },
          "count": {
            "type": "number"
          }
        },
        "required": [
          "vendors",
          "types",
          "tags",
          "count"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "ShopifyCollectionRule": {
        "description": "Shopify smart-collection rule.",
        "type": "object",
        "properties": {
          "column": {
            "type": "string"
          },
          "relation": {
            "type": "string"
          },
          "condition": {
            "type": "string"
          }
        },
        "required": [
          "column",
          "relation",
          "condition"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "SuggestResponse": {
        "description": "Autocomplete response: plain array of suggestion strings (FT.SUGGET).",
        "type": "array",
        "items": {
          "type": "string"
        },
        "x-source": "api-types/src/storefront/models.ts"
      },
      "TagFacetRow": {
        "description": "Tag facet row. Unlike vendor/type rows this is computed in JS\n(`countEntriesInAnArray`), so `count` is always a real number.",
        "type": "object",
        "properties": {
          "value": {
            "type": "string"
          },
          "count": {
            "type": "number"
          }
        },
        "required": [
          "value",
          "count"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "TypeFacetRow": {
        "description": "Type facet row (redis aggregate — see `VendorFacetRow` re. `count`).",
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "count": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          }
        },
        "required": [
          "type",
          "count"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "VendorFacetRow": {
        "description": "Vendor facet row. Produced by a redis `FT.AGGREGATE ... GROUPBY",
        "type": "object",
        "properties": {
          "vendor": {
            "type": "string"
          },
          "count": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          }
        },
        "required": [
          "vendor",
          "count"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "ZoneCartLine": {
        "description": "Cart line sent in the programmatic (POST) body — used by cart zones to\nevaluate conditional rule groups against the products in the cart.",
        "type": "object",
        "properties": {
          "handle": {
            "type": "string",
            "description": "Product handle of the cart line."
          }
        },
        "required": [
          "handle"
        ],
        "additionalProperties": {},
        "x-source": "api-types/src/storefront/zone.ts"
      },
      "ZoneConfig": {
        "description": "Zone config as it appears on the redis zone document.\nRELATED (NOT merged): `admin.ZoneConfig` types the stored entity, where\n`implode` is optional; on the rendered redis document it is present.",
        "type": "object",
        "properties": {
          "allowDiscountedProducts": {
            "type": "boolean"
          },
          "allowOutOfStock": {
            "type": "boolean"
          },
          "implode": {
            "type": "boolean"
          }
        },
        "required": [
          "allowDiscountedProducts",
          "allowOutOfStock",
          "implode"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "ZoneDefaultConditions": {
        "description": "Default rule-group entry (a plain products+collection fallback that can sit\nalongside rule groups in `ruleGroups`). Known in the admin surface as\n`ZoneDefault`.",
        "type": "object",
        "properties": {
          "products": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "collection": {
            "type": "string"
          }
        },
        "required": [
          "products",
          "collection"
        ],
        "x-source": "api-types/src/models/zone.ts"
      },
      "ZoneFallback": {
        "description": "Fallback zone payload — returned when zone resolution throws (unknown id,\nno matching template, empty rule-group, redis error): a popularity-ordered\nproduct list with an empty title and NONE of the zone metadata.",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "const": ""
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RenderedProduct"
            }
          }
        },
        "required": [
          "title",
          "products"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "ZoneProgrammaticBody": {
        "description": "Programmatic zone body (POST variant). Only `lines` is read (cart zones);\neverything else passes through untouched.",
        "type": "object",
        "properties": {
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ZoneCartLine"
            }
          }
        },
        "additionalProperties": {},
        "x-source": "api-types/src/storefront/zone.ts"
      },
      "ZoneResponse": {
        "description": "Zone endpoint wire response: a rendered zone, the fallback shape, or an\nEMPTY body (the handler returns `undefined` when the matched rule group is\n`empty`).",
        "anyOf": [
          {
            "$ref": "#/components/schemas/RenderedZone"
          },
          {
            "$ref": "#/components/schemas/ZoneFallback"
          }
        ],
        "x-source": "api-types/src/storefront/models.ts"
      },
      "ZoneRule": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "price",
              "product"
            ]
          },
          "value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          },
          "condition": {
            "type": "string",
            "enum": [
              "productTypeContains",
              "productTypeDoesNotContains",
              "productTagsContains",
              "productTagsDoesNotContains",
              "lower",
              "higher"
            ]
          }
        },
        "required": [
          "type",
          "value",
          "condition"
        ],
        "x-source": "api-types/src/models/zone.ts"
      },
      "ZoneRuleGroup": {
        "description": "Rule group on the redis zone document.\nRELATED (NOT merged): `admin.ZoneRuleGroup` types the stored entity, where\n`default`/`products`/`rules` are required; the redis document copies\nobserved on the wire may omit them, hence the looser optionality here.",
        "type": "object",
        "properties": {
          "default": {
            "type": "boolean"
          },
          "products": {},
          "productIds": {},
          "mode": {
            "type": "string",
            "enum": [
              "manual",
              "collection",
              "recommended",
              "empty"
            ]
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ZoneRule"
            }
          }
        },
        "required": [
          "mode"
        ],
        "x-source": "api-types/src/storefront/models.ts"
      }
    },
    "securitySchemes": {
      "origin": {
        "type": "apiKey",
        "in": "header",
        "name": "Origin",
        "description": "The storefront's own origin, sent by the browser. Mercury looks it up among the store's registered origins (`origin:<host>`); localhost origins are ignored."
      },
      "bypassToken": {
        "type": "apiKey",
        "in": "query",
        "name": "bypassToken",
        "description": "A per-store token for server-side and local calls, issued from the Mercury admin. Works on every storefront route (the `X-BypassToken` header is read by the `redis` guard only)."
      }
    }
  }
}
