{
  "openapi": "3.0.4",
  "info": {
    "title": "Fluit Headless Commerce API",
    "description": "The commerce API behind Fluit's own storefront, available for your own front end.\nCatalogue, search, pricing, cart, checkout and the signed-in customer's pages — the same\nendpoints our storefront calls, reading the same ERP that runs the warehouse and the\nledger. There is no separate commerce database to keep in sync: stock is the stock, the\nprice is the price a salesperson would quote, and an order placed here is an order in\nFluit.\n\n# Getting started\n\nThree calls get you from a domain name to a product listing.\n\n**1. Resolve the domain to a tenant and a channel.** This is the only call that needs no\nheaders — it runs above tenant context and exists to establish it.\n\n```bash\ncurl \"https://api.erp.fluit.cloud/ecom/session/resolve?domain=shop.acme.com\"\n# { \"tenantId\": \"…\", \"channelCode\": \"web\", \"channelName\": \"Acme Web\" }\n```\n\nYou can skip this call and configure the tenant id and channel code directly if you know\nthem. It exists for the multi-domain case, where one deployment serves several storefronts\nand the domain decides which.\n\n**2. Open a session.** The response carries the token you send from here on, plus the\nchannel's full configuration: currency, language, feature flags, branding, navigation and\nSEO settings.\n\n```bash\ncurl \"https://api.erp.fluit.cloud/ecom/session\" \\\n  -H \"X-Tenant-Id: $FLUIT_TENANT_ID\" \\\n  -H \"X-Channel: web\"\n# { \"token\": \"…\", \"expiresAt\": \"…\", \"isAuthenticated\": false, \"cartItemCount\": 0, \"channel\": { … } }\n```\n\n**3. Call everything else** with the tenant, the channel and the session token.\n\n```bash\ncurl \"https://api.erp.fluit.cloud/ecom/catalog/products?pageSize=20\" \\\n  -H \"X-Tenant-Id: $FLUIT_TENANT_ID\" \\\n  -H \"X-Channel: web\" \\\n  -H \"Authorization: Bearer $SESSION_TOKEN\"\n```\n\n# Tenants and channels\n\nTwo headers scope every request.\n\n`X-Tenant-Id` selects the company. It is a GUID, and it is not a secret: this surface only\never returns what the channel has published, so knowing the id gets you the same catalogue\na visitor sees in the browser. Everything that is not public — cost prices, other\ncustomers, the ledger — lives behind endpoints this API does not have.\n\n`X-Channel` selects the storefront within that company. A channel owns its assortment,\nprice list, currency, language, VAT display and branding, so the same item can be\npublished in two channels at different prices under different names. **Omitting the header\nfalls back to the first active channel**, which is convenient in a single-channel tenant\nand a silent source of wrong prices in a multi-channel one. Send it explicitly.\n\nChannel codes come from `GET /ecom/channels`, or from the domain resolution above.\n\n# Sessions\n\n`Authorization: Bearer` carries an `EcomSession` token. It is an opaque handle, not a JWT\n— do not try to decode it, and do not expect claims inside it.\n\nA session is not a login. `GET /ecom/session` issues one to an anonymous visitor, and that\nanonymous session carries a cart. Signing in through `POST /ecom/auth/login` or the\none-time-code endpoints upgrades the session in place, so the cart survives the login and\nprices switch to the customer's agreement prices in the same moment. `isAuthenticated` on\nthe session response tells you which state you are in.\n\nAnonymous is not the same as tokenless. The cart belongs to the session, so every cart,\ncheckout and customer-portal call needs that token even before anyone has signed in —\nwithout it they answer `401`. The sign-in endpoints need one too, because signing in\nupgrades a session that must already exist. Get the token first, then use it throughout.\n\nThree groups work without a token: catalogue and content read the same for everyone, and\n`GET /ecom/session/resolve` runs before there is a session to have. Two more take one when\noffered and answer anyway without it: `GET /ecom/catalog/prices` and\n`GET /ecom/catalog/stock` fall back to list prices and channel-level stock. Each\noperation's `security` says which case it is.\n\nSessions live for seven days and extend themselves as they are used.\n\nRequests from crawlers are recognised by user agent and served without persisting a\nsession, so indexing a catalogue does not fill the session table.\n\n# Architecture: calling from your own server\n\nThe intended shape is server to server. Your front end calls your own backend, and your\nbackend calls Fluit — holding the tenant id, the session token and any customer\ncredentials on your side, and exposing to the browser only what that page needs.\n\nThis is how our own storefront is built. It is a SvelteKit app whose pages load through\nserver routes, with a thin set of proxy endpoints under its own origin for the calls that\nhave to happen after hydration. The browser never talks to this API directly.\n\nThat shape also decides the CORS answer: browser requests come from your origin, which is\nallow-listed per tenant in configuration rather than open to the world. Ask us to add a\ndomain if you need direct browser calls. Server-side calls have no such restriction.\n\nTwo practical consequences of proxying:\n\n- Forward the visitor's address in `X-Forwarded-For`. The rate limiter partitions on it,\n  and without it every visitor shares your server's quota.\n- Cache what does not change per visitor — but you do not have to work out which is which.\n  Every response says so itself in `Cache-Control`. See *Caching* below.\n\n# Catalog and search\n\nProducts are addressed by slug or id — `GET /ecom/catalog/products/{idOrSlug}` accepts\neither, so a URL can carry the readable one. Lists take `category`, `search`, `sort`,\n`page` and `pageSize`, plus attribute filters as `attr_{code}=value1,value2`.\n\n`GET /ecom/catalog/filters` returns the facets available for a given category or search,\nwith counts, so the filter panel reflects what is actually in the result rather than the\nfull attribute vocabulary. `GET /ecom/catalog/suggest` powers type-ahead.\n\nSearch is index-backed with relevance ranking, synonyms and typo tolerance — not a\nsubstring match — so results are ordered by relevance unless you pass an explicit `sort`.\n\n# Prices, stock and VAT\n\nPrices come from the channel's price list, and from the customer's agreement prices when\nthe session is signed in. The same product therefore has no single price: it has the price\nfor this channel and this visitor. Fetch prices for a set of items in one call with\n`GET /ecom/catalog/prices?itemIds=…` rather than reading them off cached product payloads.\n\nWhether amounts include VAT is a channel setting, and some channels let the visitor toggle\nit. Read `channel.features.showPricesIncludingVat` and `allowCustomerVatToggle` from the\nsession response and render accordingly — the numbers on the wire follow the channel, and\na front end that assumes one convention will be wrong on the other.\n\nStock is available separately through `GET /ecom/catalog/stock?itemIds=…`, aggregated\naccording to the channel's `stockAggregation` setting. `showStock` and\n`showOutOfStockProducts` decide whether a storefront is supposed to display it at all.\n\nBoth endpoints take at most **100 ids per request**, and both have a `POST` variant that\ntakes the same ids in a JSON body:\n\n```bash\ncurl -X POST \"https://api.erp.fluit.cloud/ecom/catalog/prices\" \\\n  -H \"X-Tenant-Id: $FLUIT_TENANT_ID\" -H \"X-Channel: web\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"itemIds\": [\"…\", \"…\"] }'\n```\n\nThe `POST` exists because of URL length, not because of the limit: with GUIDs a typical\nURL budget runs out around 50 ids, well before the 100 the server actually allows. Same\nhandler, same response body, so you can switch without touching your parser. The limit is\nthe same on both — it protects response time, since the price engine runs per item — so\nchunk into batches of 100 either way. One difference worth knowing: the `GET` silently\nskips ids it cannot parse, while a malformed id in the JSON array fails the whole request\nwith `400`.\n\nNeither variant is cacheable. Both answer `no-store`, because the price depends on the\nsigned-in customer's agreement.\n\n# From cart to order\n\nThe cart hangs off the session, so there is no cart id to carry:\n\n1. `POST /ecom/cart/items` with an item id and a quantity.\n2. `GET /ecom/checkout/data` for the shipping and payment methods this channel offers, plus\n   the known customer details when signed in.\n3. `POST /ecom/checkout/sessions` to start payment with the channel's provider.\n4. `POST /ecom/checkout/place` to place the order.\n5. `GET /ecom/checkout/sessions/{sessionId}/confirmation` on the return page.\n\nStep 3 is provider-agnostic. The response carries a `renderMode` and exactly one of\n`htmlSnippet`, `redirectUrl` or `clientSecret`, and your front end acts on the mode rather\nthan on the provider's name. That is what lets a tenant change payment provider without a\nfront-end release.\n\n`POST /ecom/checkout/apply-code` takes both discount coupons and gift cards; the response\nsays which it was and what it did to the total.\n\n> The endpoints under `/ecom/kco/*` are the Klarna-specific predecessor of the same flow.\n> They still work and our own storefront still uses them, but new builds should use\n> `/ecom/checkout/sessions`. The KCO endpoints will not gain features.\n\n# Payment provider callbacks\n\n`POST /ecom/checkout/webhooks/{provider}` and `POST /ecom/kco/push` are **inbound**. The\npayment provider calls them when a payment settles; you never do. They are documented\nbecause you may need to configure their URLs in the provider's dashboard, and because\nseeing them here explains how an order can change state without your front end doing\nanything.\n\n# Customer portal\n\nEverything under `/ecom/portal/*` is the signed-in customer's own record: orders,\ninvoices, shipments, quote requests, returns, support tickets, addresses and profile. All\nof it requires a session that has been authenticated, and all of it is scoped to that\ncustomer — there is no way to read another customer's data through these endpoints.\n\nThis is not the same thing as Fluit's partner portal, which lives under `/portal` and has\nits own API. The names are close; the surfaces are unrelated.\n\n# Idempotency\n\nA timeout on `POST /ecom/checkout/place` is the one failure that a storefront cannot\nreason its way out of on its own: the order may or may not exist, and asking again\nwithout protection either places a second one or answers that the cart is already\nconverted — which tells you the order exists but not what it was called.\n\nSend an `Idempotency-Key` header to close that gap. Use a unique value per logical\nattempt, a UUID is the obvious choice, and reuse the *same* value on every retry of that\nattempt:\n\n```bash\ncurl -X POST \"https://api.erp.fluit.cloud/ecom/checkout/place\" \\\n  -H \"X-Tenant-Id: $FLUIT_TENANT_ID\" \\\n  -H \"X-Channel: web\" \\\n  -H \"Authorization: Bearer $SESSION_TOKEN\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ … }'\n```\n\nThe first call runs normally. A retry with the same key replays the original response —\nthe same status, the same body and the same `Location` — with `Idempotency-Replayed:\ntrue` added, and without the handler running again. Other response headers are not\nreplayed, so read the outcome from the body rather than from them. Keys are scoped to\nthe session and kept for 24 hours.\n\n**The header is optional here.** That is a deliberate difference from the Fluit Public\nAPI, where it is required on every POST: this surface already has clients, and making it\nmandatory would have broken all of them at once. Omit it and the call behaves exactly as\nit did before.\n\nTwo responses exist only when you send the header:\n\n- `422` — the key was already used for a *different* request, meaning another endpoint or\n  the same endpoint with a different body. Use a new key for new requests; reuse a key\n  only when retrying the same one.\n- `503` with `Retry-After` — a request with the same key is still in flight. Retry with\n  the same key once it finishes.\n\nA `5xx` is never replayed. Server errors are not a final answer to your request, so the\nkey is released and a retry genuinely runs the operation again.\n\nHonoured on `POST /ecom/checkout/place` and `POST /ecom/checkout/sessions`.\n**`POST /ecom/cart/items` deliberately does not honour it**, and that is not an\noversight: adding the same item twice is a thing shoppers legitimately do, and\nsuppressing the second add would silently drop a real one.\n\n# Pagination\n\nList endpoints return a fixed envelope:\n\n```json\n{\n  \"items\": [],\n  \"totalCount\": 0,\n  \"page\": 1,\n  \"pageSize\": 20,\n  \"totalPages\": 0,\n  \"hasPreviousPage\": false,\n  \"hasNextPage\": false\n}\n```\n\n`page` is 1-based. Page sizes are clamped per endpoint; ask for more than the maximum and\nyou get the maximum, not an error.\n\n# Caching\n\nEvery response carries a `Cache-Control` header, and following it is better than inventing\nyour own TTLs. There are two kinds.\n\n**Channel-wide responses** — the catalogue, search, categories, brands, filters, content\npages, widgets and the redirect table — answer `public, max-age=…` with an `ETag`:\n\n| Response | max-age |\n| --- | --- |\n| Category tree | 3600 |\n| Product detail | 600 |\n| Product feed | 3600 |\n| Product lists, search, filters, brands | 300 |\n| Content pages, widgets, redirects | 300 |\n| Type-ahead suggestions | 300 |\n\nThose numbers are not advice — they are the same TTLs the API uses for its own internal\ncache. Honouring them therefore adds no staleness that we do not already have.\n\nRevalidation is cheap: send the `ETag` back as `If-None-Match` and an unchanged response\nanswers `304` with no body.\n\n```bash\ncurl -H \"If-None-Match: $ETAG\" \\\n     -H \"X-Tenant-Id: $FLUIT_TENANT_ID\" -H \"X-Channel: web\" \\\n     \"https://api.erp.fluit.cloud/ecom/catalog/categories\"\n```\n\n**Everything else answers `no-store`** and carries no `ETag`. That is the default for the\nwhole surface, not a list we maintain: the cart, checkout, the customer portal, prices and\nstock all fall under it, and so does any endpoint we add tomorrow. Prices in particular\ndepend on the signed-in customer's agreement, so there is no shared version of them to\nkeep. Caching those per authenticated customer inside your own layer is fine — that is a\ndistinction only you can draw.\n\n> **If you put a shared cache or CDN in front of this API, you must vary on\n> `X-Tenant-Id` and `X-Channel`.** Both are headers, neither appears in the URL, and both\n> decide what the response contains. We send `Vary: X-Tenant-Id, X-Channel` on every\n> cacheable response for exactly this reason — a cache keyed on the URL alone would serve\n> one tenant's catalogue to another.\n\nImages and documents under `/ecom/catalog/assets/*` are immutable for practical purposes\nand answer `public, max-age=86400` and `3600` respectively.\n\n# Errors\n\n| Status | Means |\n| --- | --- |\n| `400` | The request is malformed, or a value is invalid |\n| `401` | The endpoint needs a session and none was sent, or the token has expired |\n| `403` | The session exists but is not allowed to see this record |\n| `404` | No such product, page, channel or record in this channel |\n| `409` | The record is not in a state where this makes sense — a cancelled order, a used coupon |\n| `429` | Rate limit — see below |\n\n**Do not assume one body shape.** This surface predates the reference and carries three,\nand a client that parses every failure as RFC 7807 will throw on two of them:\n\n- Most `400`, `403`, `404`, `409` and `500` responses are `application/problem+json` per\n  RFC 7807, with `type`, `title`, `status` and `detail`, plus an `errors` object on\n  validation failures.\n- Some endpoints — mainly the channel-`404` on catalogue, content and blog reads, and the\n  `400` on the id-list endpoints — answer `application/json` with a flat\n  `{ \"error\": \"…\" }` instead. Each operation's documented response schema is the truth;\n  where it says `ProblemDetails` you get RFC 7807, otherwise expect the flat shape.\n- `401` has **no body at all**. The status code is the whole message.\n- `429` is `application/json` with problem-like fields, but not the problem media type.\n\nBranch on the status code and the `Content-Type`, not on the assumption. Consolidating\nthese onto one shape would break clients that read the current one, so it will happen as\nan announced change rather than quietly.\n\nA `404` from a catalogue endpoint usually means \"not published in this channel\" rather\nthan \"does not exist\". That distinction is deliberate: an unpublished product should be\nindistinguishable from a missing one.\n\n# Rate limiting\n\nEvery endpoint is rate limited. Quotas are per tenant and channel, and then per visitor —\nby client IP for anonymous traffic and by session token once there is one — so one busy\nvisitor cannot spend the channel's budget.\n\n| Traffic | Limit |\n| --- | --- |\n| Catalogue and search | 300 / minute per IP |\n| Session | 60 / minute per IP anonymous, 120 with a token, 600 for recognised crawlers |\n| Cart | 60 / minute |\n| Checkout | 10 / minute |\n| Sign-in | 5 / minute per IP |\n| Request a login code | 3 / 15 minutes per IP |\n| Verify a login code | 10 / minute per IP |\n| Help articles | 100 / minute per channel |\n| Page-view tracking | 300 / minute per channel |\n| Shopping assistant | 20 / minute |\n| Submit a review | 5 / hour per IP |\n| Newsletter sign-up | 10 / hour per IP |\n| Newsletter unsubscribe | 20 / hour per token |\n\n**There are no `X-RateLimit-*` headers on this API.** You discover the quota by hitting it:\na rejected request returns `429` with `Retry-After` in seconds and a problem-shaped\n`application/json` body. Honour `Retry-After` rather than retrying on a fixed delay.\n\nThe per-IP partitions depend on `X-Forwarded-For` reaching us. See *Architecture* above.\n\n# Status and stability\n\nThis is the API behind our own storefront, and it moves with it. Additive changes — new\nendpoints, new optional fields, new enum members — happen without notice, so read\ndefensively and ignore fields you do not recognise. Changes that break an existing\ncontract are announced in the Fluit changelog before they ship.\n\nIt is a different promise from the Fluit Public API, which is versioned for third-party\nintegrations. If you are synchronising an external system rather than building a\nstorefront, that is the surface you want.",
    "contact": {
      "name": "Fluit",
      "url": "https://fluit.se",
      "email": "info@fluit.se"
    },
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.erp.fluit.cloud",
      "description": "Production"
    }
  ],
  "paths": {
    "/ecom/portal/context": {
      "get": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "Get portal context",
        "description": "Returns portal modules and permissions for the authenticated customer contact.",
        "operationId": "EcomGetPortalContext",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalContextResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/auth/login": {
      "post": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "Login",
        "description": "Authenticates a customer contact using email and password. Associates the session with the customer.",
        "operationId": "EcomLogin",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomLoginResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/auth/one-time-login": {
      "post": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "One-time code login",
        "description": "Authenticates a customer contact using a one-time email code. Associates the session with the customer.",
        "operationId": "EcomOneTimeLogin",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OneTimeLoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomLoginResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/auth/request-code": {
      "post": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "Request auth code",
        "description": "Sends a 6-digit verification code to the specified email address. Used for password reset and one-time login.",
        "operationId": "EcomRequestCode",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestCodeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/auth/reset-password": {
      "post": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "Reset password",
        "description": "Resets the customer password using a verified 6-digit code. Auto-logs in the customer on success.",
        "operationId": "EcomResetPassword",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResetPasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomLoginResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/auth/verify-code": {
      "post": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "Verify auth code",
        "description": "Validates a 6-digit code without consuming it. Returns validity status and remaining attempts.",
        "operationId": "EcomVerifyCode",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyCodeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyEcomAuthCodeResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/addresses": {
      "post": {
        "tags": [
          "Ecom.Addresses"
        ],
        "summary": "Create address",
        "description": "Creates a new delivery address for the authenticated customer.",
        "operationId": "EcomCreatePortalAddress",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePortalAddressRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "get": {
        "tags": [
          "Ecom.Addresses"
        ],
        "summary": "List addresses",
        "description": "Returns delivery addresses for the authenticated customer.",
        "operationId": "EcomGetPortalAddresses",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalAddressListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/addresses/{id}": {
      "delete": {
        "tags": [
          "Ecom.Addresses"
        ],
        "summary": "Delete address",
        "description": "Deletes a delivery address for the authenticated customer.",
        "operationId": "EcomDeletePortalAddress",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "Ecom.Addresses"
        ],
        "summary": "Update address",
        "description": "Updates an existing delivery address for the authenticated customer.",
        "operationId": "EcomUpdatePortalAddress",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePortalAddressRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/addresses/{id}/set-default": {
      "post": {
        "tags": [
          "Ecom.Addresses"
        ],
        "summary": "Set default address",
        "description": "Sets a delivery address as the default for the authenticated customer.",
        "operationId": "EcomSetDefaultPortalAddress",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/analytics/pageview": {
      "post": {
        "tags": [
          "Ecom.Analytics"
        ],
        "summary": "Track page view",
        "description": "Records a page view for analytics. Called server-side from ecom-frontend.",
        "operationId": "EcomTrackPageView",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrackPageViewRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/blog": {
      "get": {
        "tags": [
          "Ecom.Blog"
        ],
        "summary": "List published blog posts / news",
        "description": "Returns the channel's published posts, newest first, with their tags and cover images. Filter with ?tag= and choose the kind with ?type=BlogPost (default) or ?type=NewsPost; an unrecognised type falls back to BlogPost. Page is 1-based, default pageSize is 12 and the maximum is 50. Scheduled posts appear on their publish date, so the response is time-dependent and cached for a short while.",
        "operationId": "EcomGetBlogPosts",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BlogListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/blog/related": {
      "get": {
        "tags": [
          "Ecom.Blog"
        ],
        "summary": "Get related articles for an article",
        "description": "Returns other published posts related to the one identified by ?slug=, ranked by how many tags they share with it and falling back to the most recent when nothing overlaps. Candidates are of the same kind as the source post, so a news post never suggests a blog post. Default ?take= is 3 and the maximum is 12. The article itself is never included, and an unknown slug returns an empty list rather than 404.",
        "operationId": "EcomGetRelatedArticles",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BlogPostListItem"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/cart/items": {
      "post": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Add item to cart",
        "description": "Adds an item to the shopping cart or increases quantity if already present.",
        "operationId": "EcomAddToCart",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddToCartRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddToCartResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/cart": {
      "delete": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Clear shopping cart",
        "description": "Removes all items from the shopping cart.",
        "operationId": "EcomClearCart",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "get": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Get shopping cart",
        "description": "Returns the current shopping cart contents with product details.",
        "operationId": "EcomGetCart",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomCartResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/cart/items/{itemId}": {
      "delete": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Remove item from cart",
        "description": "Removes an item from the shopping cart.",
        "operationId": "EcomRemoveCartItem",
        "parameters": [
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveFromCartResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Update cart item quantity",
        "description": "Updates the quantity of an item in the shopping cart.",
        "operationId": "EcomUpdateCartItem",
        "parameters": [
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCartItemRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateCartItemQuantityResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/products/{itemId}/widgets": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get matching product widgets for a product",
        "description": "Returns active HTML widgets that match the given product based on category, supplier, item group, price range, etc.",
        "operationId": "EcomGetProductWidgets",
        "parameters": [
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProductWidgetResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/assets/{tenantId}/{id}/download": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get product document",
        "description": "Serves a public product document (datasheet, manual, firmware). No authentication required. Tenant resolved from URL. PDFs are served inline, everything else as a download.",
        "operationId": "EcomGetAssetDocument",
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/assets/{tenantId}/{id}": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get product image",
        "description": "Serves a public product image. No authentication required. Tenant resolved from URL. Pass ?w= with one of the supported widths to get a resized WebP variant for responsive srcset.",
        "operationId": "EcomGetAssetImage",
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "w",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/brands": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get brands (suppliers) by id",
        "description": "Returns display data for the suppliers referenced by a Brands section.",
        "operationId": "EcomGetBrands",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandsResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/categories": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get category tree",
        "description": "Returns the hierarchical category tree for navigation.",
        "operationId": "EcomGetCategories",
        "parameters": [
          {
            "name": "parentId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoriesResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/filters": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get catalog filters",
        "description": "Returns filterable attributes with available values and product counts. Counts are scoped to the channel, the optional category and the optional search term.",
        "operationId": "EcomGetFilters",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogFiltersResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/list-widgets": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get product list page widgets",
        "description": "Returns active ProductList_Banner and ProductList_Sidebar widgets matching the current channel and optional category.",
        "operationId": "EcomGetListWidgets",
        "parameters": [
          {
            "name": "categoryCode",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ListWidgetResponse"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/products/{idOrSlug}": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get product details",
        "description": "Returns complete product information including stock status, images, and attributes. Only shows products active on the current channel.",
        "operationId": "EcomGetProduct",
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductDetailResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/product-feed": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Product feed rows",
        "description": "Returns feed-ready product rows (identifiers, prices, availability, Google product category) for building Google Shopping and Meta catalog feeds. Page size is clamped to 1-1000.",
        "operationId": "EcomGetProductFeed",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 500
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResult_ProductFeedRowResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/products": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "List products",
        "description": "Returns a paginated list of products with optional filtering by category and search term. Pass ids=guid,guid to fetch a hand-picked set; without an explicit sort they are returned in the order given. Only includes products active on the current channel.",
        "operationId": "EcomGetProducts",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResult_ProductSummaryResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/related-products": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get related products by type",
        "description": "Returns up to 8 unique related product summaries for the given item IDs and relation type. Types: Related, Accessory, CrossSell, UpSell, SparePart.",
        "operationId": "EcomGetRelatedProducts",
        "parameters": [
          {
            "name": "itemIds",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ItemRelationType"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RelatedProductsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/stock": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get stock status for items",
        "description": "Returns aggregated stock status for a batch of items. Returns nothing when the storefront hides stock, or when it hides stock from visitors who are not logged in and the request carries no customer session. Maximum 100 item ids per request; ids that cannot be parsed are skipped. With GUIDs the URL runs out before that limit does — use POST /ecom/catalog/stock for longer lists.",
        "operationId": "EcomGetStock",
        "parameters": [
          {
            "name": "itemIds",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StockResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          },
          {
            "TenantId": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get stock status for items (ids in body)",
        "description": "Identical to GET /ecom/catalog/stock — same handler, same response body — but takes the item ids in a JSON body, for id lists long enough to strain the URL. Both variants enforce the same limit of 100 ids per request. Returns nothing when the storefront hides stock, or when it hides stock from visitors who are not logged in and the request carries no customer session.",
        "operationId": "EcomPostStock",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CatalogBatchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StockResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          },
          {
            "TenantId": [ ]
          }
        ]
      }
    },
    "/ecom/catalog/suggest": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Search suggestions",
        "description": "Type-ahead suggestions for the storefront search field: matching products, term completions and categories.",
        "operationId": "EcomGetSearchSuggestions",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 6
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchSuggestionsResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/catalog/search": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Search products",
        "description": "Full-text product search with relevance ranking, synonym expansion, typo tolerance and zero-result fallbacks.",
        "operationId": "EcomSearchProducts",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 24
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductSearchResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/homepage": {
      "get": {
        "tags": [
          "Ecom.Channels"
        ],
        "summary": "Get home page configuration",
        "description": "Returns the channel's configured home page layout and visible sections, filtered by date for banners.",
        "operationId": "EcomGetHomePage",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HomePageResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/branding/ai-suggest": {
      "post": {
        "tags": [
          "Ecom.Channels"
        ],
        "summary": "Suggest branding CSS for an element",
        "description": "Proposes custom CSS for one element of the storefront, given the channel's current branding and a description of what to change, and streams the suggestion as server-sent events. It only proposes — PATCH /ecom/branding is what stores anything. Part of the in-store admin overlay and gated by the same admin preview token, so it is not an endpoint a storefront calls on a visitor's behalf.",
        "operationId": "EcomBrandingAiSuggest",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EcomBrandingAiSuggestBrandingAiRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/channels": {
      "get": {
        "tags": [
          "Ecom.Channels"
        ],
        "summary": "List available channels",
        "description": "Returns a list of available channels from DomainMappings. No authentication or tenant context required.",
        "operationId": "EcomGetChannels",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ChannelInfoResponse"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/branding": {
      "patch": {
        "tags": [
          "Ecom.Channels"
        ],
        "summary": "Update channel custom CSS",
        "description": "Updates the custom CSS for the current channel's branding. Requires the X-Channel header and a valid X-Admin-Preview token for the same tenant and channel. The CSS is sanitised before it is stored, so the value in the response is what was actually saved and may differ from what was sent.",
        "operationId": "EcomPatchBrandingCss",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchChannelBrandingCssRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PatchChannelBrandingCssPatchBrandingCssResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/chat/stream": {
      "post": {
        "tags": [
          "Ecom.Chat"
        ],
        "summary": "Ask the shopping assistant",
        "description": "Answers a shopper's question against the channel's own catalogue and content, and streams the reply as server-sent events rather than returning a body. Each event is a `data:` line carrying a JSON fragment; concatenate the text fragments as they arrive and stop at the terminating event. Because the response is a stream, the 200 has no schema to document — read the event format from the frames themselves. Requires a session, and is enabled per channel through the enableChatAssistant feature flag.",
        "operationId": "EcomChatStream",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EcomChatStreamEcomChatRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/checkout/apply-code": {
      "post": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Apply code",
        "description": "Validates a gift card or discount code for checkout use.",
        "operationId": "EcomApplyCode",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApplyCodeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApplyCodeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/checkout/shipping-prices": {
      "post": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Calculate shipping prices",
        "description": "Calculates shipping prices for all enabled shipping methods based on destination country and cart weight using FreightPriceList.",
        "operationId": "EcomCalculateShippingPrices",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalculateShippingPricesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EcomShippingPrice"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/checkout/sessions": {
      "post": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Create checkout session",
        "description": "Creates a checkout session at the channel's configured payment provider. Send an Idempotency-Key header and a retry after a timeout returns the original session instead of opening a second one at the provider. The header is optional.",
        "operationId": "EcomCreateCheckoutSession",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional on this API. Send a unique value (UUID recommended) per logical attempt and reuse it when retrying after a timeout: the original response is replayed, marked with Idempotency-Replayed: true, instead of the operation running twice. Keys are scoped to the session and kept for 24 hours. Omit the header and the call behaves as it always has.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckoutSessionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateCheckoutSessionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "422": {
            "description": "Unprocessable Content",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/checkout/data": {
      "get": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Get checkout data",
        "description": "Returns available shipping methods, payment methods and pre-filled customer data for the checkout page.",
        "operationId": "EcomGetCheckoutData",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutDataResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/checkout/sessions/{sessionId}/confirmation": {
      "get": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Get checkout confirmation",
        "description": "Returns the payment provider's confirmation view and the resulting order number.",
        "operationId": "EcomGetCheckoutSessionConfirmation",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCheckoutSessionConfirmationResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/postal-codes/lookup": {
      "get": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Verify a postal code and get its city",
        "description": "Status is NoRegister, Verified, CityMismatch, UnknownCode or InvalidFormat. Advisory only — never blocks an order from being placed.",
        "operationId": "EcomLookupPostalCode",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LookupPostalCodePostalCodeLookupDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/postal-codes/suggest": {
      "get": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Prefix search on postal code or city",
        "description": "Terms shorter than two characters return an empty list.",
        "operationId": "EcomSuggestPostalCodes",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "take",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LookupPostalCodePostalCodeMatchDto"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/checkout/webhooks/{provider}": {
      "post": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Payment provider webhook",
        "description": "Callback from a payment provider when a checkout completes. Creates the sales order. Idempotent.",
        "operationId": "EcomPaymentProviderWebhook",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/checkout/place": {
      "post": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Place order",
        "description": "Completes checkout and creates a sales order from the shopping cart. Send an Idempotency-Key header and a retry after a timeout replays the original response instead of placing a second order, or answering that the cart is already converted. The header is optional; without it the call behaves as it always has.",
        "operationId": "EcomPlaceOrder",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "Optional on this API. Send a unique value (UUID recommended) per logical attempt and reuse it when retrying after a timeout: the original response is replayed, marked with Idempotency-Replayed: true, instead of the operation running twice. Keys are scoped to the session and kept for 24 hours. Omit the header and the call behaves as it always has.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlaceOrderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceEcomOrderResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "422": {
            "description": "Unprocessable Content",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/checkout/sessions/{sessionId}": {
      "patch": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Update checkout session",
        "description": "Refreshes an existing checkout session at its payment provider when the cart has changed.",
        "operationId": "EcomUpdateCheckoutSession",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateCheckoutSessionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/pages": {
      "get": {
        "tags": [
          "Ecom.ContentPages"
        ],
        "summary": "Get published content pages for navigation",
        "description": "Returns every published page in the channel as a flat list, in the tenant's own sort order. Each entry carries parentPageId and showInNavigation, so a storefront can build a nested menu from one call and still know which pages exist without appearing in it. Read the page body itself with GET /ecom/pages/{slug}.",
        "operationId": "EcomGetContentPages",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ContentPageNavResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/pages/{slug}": {
      "get": {
        "tags": [
          "Ecom.ContentPages"
        ],
        "summary": "Get a published content page by slug",
        "description": "Returns a published page with its SEO fields and its sections in display order. Sections are typed — text, image, testimonial, feature cards — and a storefront renders each kind its own way, so treat an unfamiliar section type as something to skip rather than fail on. An unpublished or missing page both return 404; the two are deliberately indistinguishable.",
        "operationId": "EcomGetContentPageBySlug",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentPageDetailResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/help/articles/{slug}": {
      "get": {
        "tags": [
          "Ecom.Help"
        ],
        "summary": "Get a knowledge article by slug",
        "description": "Returns a single published knowledge article with full Markdown content.",
        "operationId": "EcomGetHelpArticleBySlug",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomArticleDetailResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/help/articles": {
      "get": {
        "tags": [
          "Ecom.Help"
        ],
        "summary": "List published FAQ / knowledge articles",
        "description": "Returns published knowledge articles grouped by category, with locale-aware translations.",
        "operationId": "EcomGetHelpArticles",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomArticlesResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/help/articles/by-item/{itemId}": {
      "get": {
        "tags": [
          "Ecom.Help"
        ],
        "summary": "Get knowledge articles for a specific item",
        "description": "Returns published knowledge articles linked to the given item ID.",
        "operationId": "EcomGetHelpArticlesByItem",
        "parameters": [
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EcomArticleListItem"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/help/search": {
      "get": {
        "tags": [
          "Ecom.Help"
        ],
        "summary": "Search knowledge articles",
        "description": "Full-text search across published knowledge articles. Returns up to 10 results.",
        "operationId": "EcomSearchHelpArticles",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EcomSearchResult"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/portal/invoices": {
      "get": {
        "tags": [
          "Ecom.Invoices"
        ],
        "summary": "List invoices",
        "description": "Returns invoices for the authenticated customer.",
        "operationId": "EcomGetPortalInvoices",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalInvoiceListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/kco/sessions": {
      "post": {
        "tags": [
          "Ecom.Kco"
        ],
        "summary": "Create KCO session",
        "description": "Creates a Kustom Checkout session and returns an HTML snippet for embedding the checkout widget. Deprecated alias for POST /ecom/checkout/sessions.",
        "operationId": "EcomCreateKcoSession",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKcoSessionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateKcoSessionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/kco/sessions/{sessionId}/confirmation": {
      "get": {
        "tags": [
          "Ecom.Kco"
        ],
        "summary": "Get KCO confirmation",
        "description": "Returns the Kustom Checkout confirmation HTML snippet and order details. Deprecated alias for GET /ecom/checkout/sessions/{sessionId}/confirmation.",
        "operationId": "EcomGetKcoConfirmation",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetKcoConfirmationResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/kco/push": {
      "post": {
        "tags": [
          "Ecom.Kco"
        ],
        "summary": "KCO push callback",
        "description": "Webhook called by Kustom when checkout is completed. Creates a SalesOrder and acknowledges to Kustom. Idempotent.",
        "operationId": "EcomKcoPushCallback",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/kco/sessions/{sessionId}": {
      "put": {
        "tags": [
          "Ecom.Kco"
        ],
        "summary": "Update KCO session",
        "description": "Updates an existing Kustom Checkout session when the cart has changed. Deprecated alias for PATCH /ecom/checkout/sessions/{sessionId}.",
        "operationId": "EcomUpdateKcoSession",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateKcoSessionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/newsletter/confirm": {
      "post": {
        "tags": [
          "Ecom.Newsletter"
        ],
        "summary": "Confirm newsletter subscription",
        "description": "Confirms a pending newsletter subscription using the token from the confirmation email. The token is consumed and cannot be reused.",
        "operationId": "EcomConfirmNewsletterSubscription",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmNewsletterSubscriptionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfirmNewsletterSubscriptionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests"
          }
        }
      }
    },
    "/ecom/newsletter/subscribe": {
      "post": {
        "tags": [
          "Ecom.Newsletter"
        ],
        "summary": "Subscribe to newsletter",
        "description": "Signs an email address up for the channel newsletter. Always double opt-in: a confirmation email is sent and the subscription is not mailable until the link is followed.",
        "operationId": "EcomSubscribeToNewsletter",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscribeToNewsletterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscribeToNewsletterResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests"
          }
        }
      }
    },
    "/ecom/newsletter/unsubscribe": {
      "post": {
        "tags": [
          "Ecom.Newsletter"
        ],
        "summary": "Unsubscribe from newsletter",
        "description": "Withdraws newsletter consent using the token from an email, passed as a query parameter. Requires no login — one-click unsubscribe per RFC 8058. Idempotent; any request body is ignored.",
        "operationId": "EcomUnsubscribeFromNewsletter",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnsubscribeFromNewsletterResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests"
          }
        }
      }
    },
    "/ecom/portal/orders/{id}": {
      "get": {
        "tags": [
          "Ecom.Orders"
        ],
        "summary": "Get order detail",
        "description": "Returns the full detail of a specific order for the authenticated customer.",
        "operationId": "EcomGetPortalOrderById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalOrderDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/orders": {
      "get": {
        "tags": [
          "Ecom.Orders"
        ],
        "summary": "List orders",
        "description": "Returns a paginated list of orders for the authenticated customer.",
        "operationId": "EcomGetPortalOrders",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalOrderListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/catalog/prices": {
      "get": {
        "tags": [
          "Ecom.Pricing"
        ],
        "summary": "Get prices for items",
        "description": "Returns calculated prices for a batch of items. Supports customer-specific pricing for authenticated B2B sessions via the session token. Respects channel price lists, campaigns, and quantity breaks. Maximum 100 item ids per request; ids that cannot be parsed are skipped. With GUIDs the URL runs out before that limit does — use POST /ecom/catalog/prices for longer lists.",
        "operationId": "EcomGetPrices",
        "parameters": [
          {
            "name": "itemIds",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          },
          {
            "TenantId": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "Ecom.Pricing"
        ],
        "summary": "Get prices for items (ids in body)",
        "description": "Identical to GET /ecom/catalog/prices — same handler, same response body — but takes the item ids in a JSON body. Use it when the id list is long enough to strain the URL: with GUIDs a typical URL budget runs out around 50 ids, well below the limit of 100 that both variants enforce. Respects channel price lists, campaigns and quantity breaks, and returns the customer's agreement prices when the session is signed in.",
        "operationId": "EcomPostPrices",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CatalogBatchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          },
          {
            "TenantId": [ ]
          }
        ]
      }
    },
    "/ecom/portal/profile": {
      "get": {
        "tags": [
          "Ecom.Profile"
        ],
        "summary": "Get profile",
        "description": "Returns profile details for the authenticated customer contact.",
        "operationId": "EcomGetPortalProfile",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalProfileResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "Ecom.Profile"
        ],
        "summary": "Update profile",
        "description": "Updates the authenticated contact's profile details.",
        "operationId": "EcomUpdatePortalProfile",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePortalProfileRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/quotes/{id}/accept": {
      "post": {
        "tags": [
          "Ecom.Quotes"
        ],
        "summary": "Accept a quote",
        "description": "Accepts a sent quote on behalf of the authenticated customer.",
        "operationId": "EcomAcceptPortalQuote",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/quotes/{id}/decline": {
      "post": {
        "tags": [
          "Ecom.Quotes"
        ],
        "summary": "Decline a quote",
        "description": "Declines a sent quote on behalf of the authenticated customer.",
        "operationId": "EcomDeclinePortalQuote",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeclinePortalQuoteRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/quotes/{id}": {
      "get": {
        "tags": [
          "Ecom.Quotes"
        ],
        "summary": "Get quote detail",
        "description": "Returns the full detail of a specific quote for the authenticated customer.",
        "operationId": "EcomGetPortalQuoteById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalQuoteDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/quotes": {
      "get": {
        "tags": [
          "Ecom.Quotes"
        ],
        "summary": "List quotes",
        "description": "Returns a paginated list of quotes for the authenticated customer.",
        "operationId": "EcomGetPortalQuotes",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalQuoteListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/quotes/request": {
      "post": {
        "tags": [
          "Ecom.Quotes"
        ],
        "summary": "Request a quote",
        "description": "Creates a new quote request from the customer portal.",
        "operationId": "EcomRequestPortalQuote",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestPortalQuoteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestPortalQuoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/redirects": {
      "get": {
        "tags": [
          "Ecom.Redirects"
        ],
        "summary": "Get active URL redirects for the current channel",
        "description": "Returns the channel's whole active redirect table in one call: source path, target path and the HTTP status to answer with (301 or 302). Intended to be read once and held in memory by the storefront, not called per request — the table is channel-wide and changes only when someone edits it in Fluit.",
        "operationId": "EcomGetRedirects",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "Send back the ETag from an earlier response to revalidate cheaply. Unchanged content answers 304 with no body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RedirectResponse"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not Modified"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      }
    },
    "/ecom/portal/returns/{id}/cancel": {
      "post": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Cancel return",
        "description": "Cancels a return request that has not yet been received.",
        "operationId": "EcomCancelPortalReturn",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/returns/{id}": {
      "get": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Get return detail",
        "description": "Returns the full detail of a specific return for the authenticated customer.",
        "operationId": "EcomGetPortalReturnById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalReturnDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/returns": {
      "get": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "List returns",
        "description": "Returns a paginated list of returns for the authenticated customer.",
        "operationId": "EcomGetPortalReturns",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalReturnListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Submit return",
        "description": "Creates a new return request from the customer portal.",
        "operationId": "EcomSubmitPortalReturn",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitPortalReturnRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitPortalReturnResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/orders/{orderId}/return-eligibility": {
      "get": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Check return eligibility",
        "description": "Checks whether an order is eligible for return and returns returnable line details.",
        "operationId": "EcomGetReturnEligibility",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalReturnEligibilityResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/returns/{id}/label": {
      "get": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Get return label",
        "description": "Downloads or redirects to the return shipping label for a return.",
        "operationId": "EcomGetReturnLabel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Found"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/catalog/products/{idOrSlug}/reviews": {
      "get": {
        "tags": [
          "Ecom.Reviews"
        ],
        "summary": "Get product reviews",
        "description": "Returns a page of approved reviews plus a rating summary (average and distribution) covering all approved reviews for the product on this channel.",
        "operationId": "EcomGetProductReviews",
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 10
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductReviewsResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Ecom.Reviews"
        ],
        "summary": "Submit product review",
        "description": "Submits a review for a product on the current channel. The review is always created as pending and is not visible until a moderator approves it.",
        "operationId": "EcomSubmitProductReview",
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitProductReviewRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitProductReviewResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests"
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          },
          {
            "TenantId": [ ]
          }
        ]
      }
    },
    "/ecom/session": {
      "get": {
        "tags": [
          "Ecom.Session"
        ],
        "summary": "Get or create session",
        "description": "Returns a session token and full channel context including branding, features, and analytics. Creates an EcomSession entity on first visit and reuses it on subsequent visits.",
        "operationId": "EcomGetSession",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EcomSessionResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          },
          {
            "TenantId": [ ]
          }
        ]
      }
    },
    "/ecom/session/resolve": {
      "get": {
        "tags": [
          "Ecom.Session"
        ],
        "summary": "Resolve domain to tenant",
        "description": "Maps a domain (e.g., shop.acme.com) to tenant and channel IDs. Returns routing info only — full channel data comes from GET /ecom/session.",
        "operationId": "EcomResolveDomain",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResolveDomainResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [ ]
      }
    },
    "/ecom/portal/shipments/{id}": {
      "get": {
        "tags": [
          "Ecom.Shipments"
        ],
        "summary": "Get shipment detail",
        "description": "Returns the full detail of a specific shipment for the authenticated customer.",
        "operationId": "EcomGetPortalShipmentById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalShipmentDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/shipments": {
      "get": {
        "tags": [
          "Ecom.Shipments"
        ],
        "summary": "List shipments",
        "description": "Returns a paginated list of shipments for the authenticated customer.",
        "operationId": "EcomGetPortalShipments",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalShipmentListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/tickets/{id}/attachments": {
      "post": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "Upload ticket attachment",
        "description": "Uploads one file to the ticket. Multipart form data with the file in the 'file' field.",
        "operationId": "EcomAddPortalTicketAttachment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddPortalTicketAttachmentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/tickets/{id}/comments": {
      "post": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "Reply on ticket",
        "description": "Adds a customer-visible message to the ticket thread.",
        "operationId": "EcomAddPortalTicketComment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddPortalTicketCommentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddPortalTicketCommentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/tickets/{id}/attachments/{attachmentId}": {
      "get": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "Download ticket attachment",
        "description": "Downloads one attachment from a ticket the contact is allowed to see.",
        "operationId": "EcomDownloadPortalTicketAttachment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "attachmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/tickets/{id}": {
      "get": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "Get ticket",
        "description": "Returns one ticket with its attachments and customer-visible messages. Internal notes are never included.",
        "operationId": "EcomGetPortalTicketById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalTicketDetail"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/tickets": {
      "get": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "List tickets",
        "description": "Returns the authenticated contact's tickets. Contacts with the ViewAllTickets permission see every ticket belonging to their company.",
        "operationId": "EcomGetPortalTickets",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "openOnly",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortalTicketListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "Create ticket",
        "description": "Creates a support ticket for the authenticated customer and contact. Order and order line are optional; when given they are verified against the session's customer and the item is taken from the order line.",
        "operationId": "EcomSubmitPortalTicket",
        "parameters": [
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitPortalTicketRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitPortalTicketResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/ecom/portal/tickets/{id}/product": {
      "patch": {
        "tags": [
          "Ecom.Tickets"
        ],
        "summary": "Supply product details on ticket",
        "description": "Lets the customer fill in serial number, firmware and hardware revision. The completion is added to the ticket thread so the agent sees it arrive.",
        "operationId": "EcomUpdatePortalTicketProduct",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Channel",
            "in": "header",
            "description": "Channel (storefront) code within the tenant. Decides assortment, price list, currency, language and branding. Omitting it falls back to the tenant's first active channel, so send it explicitly whenever the tenant runs more than one storefront.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePortalTicketProductRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Wait the number of seconds given in Retry-After. The body is problem-shaped application/json, not application/problem+json. This API sends no X-RateLimit-* headers — the quota is discovered here.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "format": "int32"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AddPortalTicketAttachmentResponse": {
        "type": "object",
        "properties": {
          "attachmentId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "additionalProperties": false
      },
      "AddPortalTicketCommentRequest": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AddPortalTicketCommentResponse": {
        "type": "object",
        "properties": {
          "commentId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "additionalProperties": false
      },
      "AddToCartRequest": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AddToCartResponse": {
        "type": "object",
        "properties": {
          "cartId": {
            "type": "string",
            "format": "uuid"
          },
          "totalItems": {
            "type": "integer",
            "format": "int32"
          },
          "uniqueItemCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "ApplyCodeRequest": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ApplyCodeResponse": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "balance": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "currencyId": {
            "type": "string",
            "nullable": true
          },
          "discountName": {
            "type": "string",
            "nullable": true
          },
          "discountCalculation": {
            "type": "string",
            "nullable": true
          },
          "discountValue": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AttributeDataType": {
        "enum": [
          "Text",
          "WholeNumber",
          "DecimalNumber",
          "Boolean",
          "Date",
          "Url",
          "Html",
          "Color"
        ],
        "type": "string"
      },
      "BackorderBehavior": {
        "enum": [
          "CreateBackorder",
          "CancelRemaining",
          "HoldOrder"
        ],
        "type": "string"
      },
      "BlogListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BlogPostListItem"
            },
            "nullable": true
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "totalPages": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "BlogPostListItem": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "excerpt": {
            "type": "string",
            "nullable": true
          },
          "featuredImageUrl": {
            "type": "string",
            "nullable": true
          },
          "authorName": {
            "type": "string",
            "nullable": true
          },
          "publishDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "pageType": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "BrandResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "website": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "BrandsResponse": {
        "type": "object",
        "properties": {
          "brands": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BrandResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "BreadcrumbResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "level": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "CalculateShippingPricesRequest": {
        "type": "object",
        "properties": {
          "countryCode": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CatalogBatchRequest": {
        "type": "object",
        "properties": {
          "itemIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Artikel-id:n att slå upp. Dubbletter tas bort; ordningen saknar betydelse.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Bodyn till POST-varianterna av batchuppslagen.\r\n            \r\n\r\nPOST finns för att GET-varianten tar sina id:n i frågesträngen. Med GUID:er tar en typisk\r\nURL-budget slut runt 50 artiklar, alltså långt under serverns tak på 100 — gränsen ser ut\r\nsom ett antalsproblem men är ett längdproblem, och POST är svaret på just det. Taket är\r\ndetsamma på båda varianterna, så batchlogiken hos klienten förblir en."
      },
      "CatalogFiltersResponse": {
        "type": "object",
        "properties": {
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FilterGroupResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CategoriesResponse": {
        "type": "object",
        "properties": {
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CategoryResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "productCount": {
            "type": "integer",
            "format": "int32"
          },
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CategorySummaryResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "slug": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ChannelInfoResponse": {
        "required": [
          "code",
          "currency",
          "name",
          "type"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "nullable": true
          },
          "logoUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CheckoutCountry": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CheckoutCustomerAddress": {
        "type": "object",
        "properties": {
          "street1": {
            "type": "string",
            "nullable": true
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CheckoutCustomerData": {
        "type": "object",
        "properties": {
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "defaultAddress": {
            "$ref": "#/components/schemas/CheckoutCustomerAddress"
          },
          "savedAddresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckoutSavedAddress"
            },
            "nullable": true
          },
          "defaultBackorderBehavior": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CheckoutDataResponse": {
        "type": "object",
        "properties": {
          "shippingMethods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckoutShippingMethod"
            },
            "nullable": true
          },
          "paymentMethods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckoutPaymentMethod"
            },
            "nullable": true
          },
          "countries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckoutCountry"
            },
            "nullable": true
          },
          "customer": {
            "$ref": "#/components/schemas/CheckoutCustomerData"
          },
          "settings": {
            "$ref": "#/components/schemas/CheckoutSettings"
          }
        },
        "additionalProperties": false
      },
      "CheckoutPaymentMethod": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CheckoutSavedAddress": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "street1": {
            "type": "string",
            "nullable": true
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "contactPerson": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "CheckoutSettings": {
        "type": "object",
        "properties": {
          "allowBackorderChoice": {
            "type": "boolean"
          },
          "allowRequestedDeliveryDate": {
            "type": "boolean"
          },
          "allowBackorders": {
            "type": "boolean"
          },
          "checkoutCustomerMode": {
            "type": "string",
            "nullable": true
          },
          "checkoutProvider": {
            "type": "string",
            "nullable": true
          },
          "termsPageSlug": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CheckoutShippingMethod": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "estimatedDelivery": {
            "type": "string",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "usesDynamicPricing": {
            "type": "boolean"
          },
          "freeShippingThreshold": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfirmNewsletterSubscriptionRequest": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfirmNewsletterSubscriptionResponse": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ContentPageDetailResponse": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "metaDescription": {
            "type": "string",
            "nullable": true
          },
          "layout": {
            "type": "string",
            "nullable": true
          },
          "fullWidth": {
            "type": "boolean"
          },
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentPageSectionResponse"
            },
            "nullable": true
          },
          "pageType": {
            "type": "string",
            "nullable": true
          },
          "excerpt": {
            "type": "string",
            "nullable": true
          },
          "featuredImageUrl": {
            "type": "string",
            "nullable": true
          },
          "authorName": {
            "type": "string",
            "nullable": true
          },
          "publishDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ContentPageNavResponse": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "parentPageId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "showInNavigation": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ContentPageSectionFeatureCardResponse": {
        "type": "object",
        "properties": {
          "iconName": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "text": {
            "type": "string",
            "nullable": true
          },
          "linkUrl": {
            "type": "string",
            "nullable": true
          },
          "linkText": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ContentPageSectionImageResponse": {
        "type": "object",
        "properties": {
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "linkUrl": {
            "type": "string",
            "nullable": true
          },
          "caption": {
            "type": "string",
            "nullable": true
          },
          "altText": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ContentPageSectionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "subtitle": {
            "type": "string",
            "nullable": true
          },
          "cssClass": {
            "type": "string",
            "nullable": true
          },
          "heroImageUrl": {
            "type": "string",
            "nullable": true
          },
          "heroCtaText": {
            "type": "string",
            "nullable": true
          },
          "heroCtaUrl": {
            "type": "string",
            "nullable": true
          },
          "heroTextColor": {
            "type": "string",
            "nullable": true
          },
          "heroVideoUrl": {
            "type": "string",
            "nullable": true
          },
          "heroVideoPosterUrl": {
            "type": "string",
            "nullable": true
          },
          "itemGroupId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "categoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "productIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "maxProducts": {
            "type": "integer",
            "format": "int32"
          },
          "productSort": {
            "type": "string",
            "nullable": true
          },
          "productColumns": {
            "type": "integer",
            "format": "int32"
          },
          "categoryIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "parentCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "bannerImageUrl": {
            "type": "string",
            "nullable": true
          },
          "bannerLinkUrl": {
            "type": "string",
            "nullable": true
          },
          "htmlContent": {
            "type": "string",
            "nullable": true
          },
          "supplierIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "maxBrands": {
            "type": "integer",
            "format": "int32"
          },
          "videoUrl": {
            "type": "string",
            "nullable": true
          },
          "articleType": {
            "type": "string",
            "nullable": true
          },
          "articleLinkText": {
            "type": "string",
            "nullable": true
          },
          "galleryImages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentPageSectionImageResponse"
            },
            "nullable": true
          },
          "testimonials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentPageSectionTestimonialResponse"
            },
            "nullable": true
          },
          "featureCards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentPageSectionFeatureCardResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ContentPageSectionTestimonialResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "type": "string",
            "nullable": true
          },
          "author": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true
          },
          "rating": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "avatarUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateCheckoutSessionRequest": {
        "type": "object",
        "properties": {
          "locale": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateCheckoutSessionResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string",
            "nullable": true
          },
          "renderMode": {
            "type": "string",
            "nullable": true
          },
          "htmlSnippet": {
            "type": "string",
            "nullable": true
          },
          "redirectUrl": {
            "type": "string",
            "nullable": true
          },
          "clientSecret": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateKcoSessionRequest": {
        "type": "object",
        "properties": {
          "locale": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateKcoSessionResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "htmlSnippet": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreatePortalAddressRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "street1": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "contactPerson": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean"
          },
          "notes": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "DeclinePortalQuoteRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomArticleCategory": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "nullable": true
          },
          "icon": {
            "type": "string",
            "nullable": true
          },
          "articleCount": {
            "type": "integer",
            "format": "int32"
          },
          "articles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomArticleListItem"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomArticleDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "icon": {
            "type": "string",
            "nullable": true
          },
          "articleType": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "summary": {
            "type": "string",
            "nullable": true
          },
          "content": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomArticleListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "icon": {
            "type": "string",
            "nullable": true
          },
          "articleType": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "summary": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomArticlesResponse": {
        "type": "object",
        "properties": {
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomArticleCategory"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomAuthCodePurpose": {
        "enum": [
          "PasswordReset",
          "OneTimeLogin"
        ],
        "type": "string"
      },
      "EcomBrandingAiSuggestBrandingAiRequest": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string",
            "nullable": true
          },
          "currentCss": {
            "type": "string",
            "nullable": true
          },
          "brandingContext": {
            "$ref": "#/components/schemas/EcomBrandingAiSuggestBrandingContextDto"
          },
          "selectedElements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomBrandingAiSuggestElementInfoDto"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomBrandingAiSuggestBrandingContextDto": {
        "type": "object",
        "properties": {
          "primaryColor": {
            "type": "string",
            "nullable": true
          },
          "accentColor": {
            "type": "string",
            "nullable": true
          },
          "fontHeading": {
            "type": "string",
            "nullable": true
          },
          "fontBody": {
            "type": "string",
            "nullable": true
          },
          "borderRadius": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomBrandingAiSuggestElementInfoDto": {
        "type": "object",
        "properties": {
          "selector": {
            "type": "string",
            "nullable": true
          },
          "tagName": {
            "type": "string",
            "nullable": true
          },
          "classNames": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "computedStyles": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomCartItemResponse": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          },
          "lineTotal": {
            "type": "number",
            "format": "double"
          },
          "taxAmount": {
            "type": "number",
            "format": "double"
          },
          "lineTotalIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "addedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "EcomCartResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "totalItems": {
            "type": "integer",
            "format": "int32"
          },
          "uniqueItemCount": {
            "type": "integer",
            "format": "int32"
          },
          "subTotal": {
            "type": "number",
            "format": "double"
          },
          "taxAmount": {
            "type": "number",
            "format": "double"
          },
          "totalIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "isTaxEnabled": {
            "type": "boolean"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomCartItemResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomChatMessageDto": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "nullable": true
          },
          "content": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomChatStreamEcomChatRequest": {
        "type": "object",
        "properties": {
          "content": {
            "type": "string",
            "nullable": true
          },
          "history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomChatMessageDto"
            },
            "nullable": true
          },
          "currentProductId": {
            "type": "string",
            "nullable": true
          },
          "currentProductName": {
            "type": "string",
            "nullable": true
          },
          "currentCategory": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomLoginResult": {
        "type": "object",
        "properties": {
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSearchResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "articleType": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "summary": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionAnalyticsResponse": {
        "type": "object",
        "properties": {
          "googleAnalyticsId": {
            "type": "string",
            "nullable": true
          },
          "facebookPixelId": {
            "type": "string",
            "nullable": true
          },
          "googleTagManagerId": {
            "type": "string",
            "nullable": true
          },
          "customHeadScript": {
            "type": "string",
            "nullable": true
          },
          "analyticsEnabled": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionBrandingResponse": {
        "type": "object",
        "properties": {
          "storeName": {
            "type": "string",
            "nullable": true
          },
          "logoUrl": {
            "type": "string",
            "nullable": true
          },
          "faviconUrl": {
            "type": "string",
            "nullable": true
          },
          "primaryColor": {
            "type": "string",
            "nullable": true
          },
          "primaryHoverColor": {
            "type": "string",
            "nullable": true
          },
          "secondaryColor": {
            "type": "string",
            "nullable": true
          },
          "accentColor": {
            "type": "string",
            "nullable": true
          },
          "accentHoverColor": {
            "type": "string",
            "nullable": true
          },
          "backgroundColor": {
            "type": "string",
            "nullable": true
          },
          "textColor": {
            "type": "string",
            "nullable": true
          },
          "headerBackgroundColor": {
            "type": "string",
            "nullable": true
          },
          "headerTextColor": {
            "type": "string",
            "nullable": true
          },
          "footerBackgroundColor": {
            "type": "string",
            "nullable": true
          },
          "footerTextColor": {
            "type": "string",
            "nullable": true
          },
          "fontHeading": {
            "type": "string",
            "nullable": true
          },
          "fontBody": {
            "type": "string",
            "nullable": true
          },
          "borderRadius": {
            "type": "string",
            "nullable": true
          },
          "customCss": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionChannelResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "features": {
            "$ref": "#/components/schemas/EcomSessionFeaturesResponse"
          },
          "branding": {
            "$ref": "#/components/schemas/EcomSessionBrandingResponse"
          },
          "analytics": {
            "$ref": "#/components/schemas/EcomSessionAnalyticsResponse"
          },
          "navigation": {
            "$ref": "#/components/schemas/EcomSessionNavigationResponse"
          },
          "seo": {
            "$ref": "#/components/schemas/EcomSessionSeoResponse"
          },
          "policies": {
            "$ref": "#/components/schemas/EcomSessionPoliciesResponse"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionCustomerResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionFeaturesResponse": {
        "type": "object",
        "properties": {
          "guestCheckout": {
            "type": "boolean"
          },
          "showPricesWithoutLogin": {
            "type": "boolean"
          },
          "showPricesIncludingVat": {
            "type": "boolean"
          },
          "allowCustomerVatToggle": {
            "type": "boolean"
          },
          "showLowestPrice30Days": {
            "type": "boolean"
          },
          "showStock": {
            "type": "boolean"
          },
          "showOutOfStockProducts": {
            "type": "boolean"
          },
          "requireLogin": {
            "type": "boolean"
          },
          "allowBackorders": {
            "type": "boolean"
          },
          "allowBackorderChoice": {
            "type": "boolean"
          },
          "allowRequestedDeliveryDate": {
            "type": "boolean"
          },
          "minimumOrderValue": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "enableChatAssistant": {
            "type": "boolean"
          },
          "enableAddToCartModal": {
            "type": "boolean"
          },
          "stockAggregation": {
            "type": "string",
            "nullable": true
          },
          "showSku": {
            "type": "boolean"
          },
          "showCrossReferences": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionFooterColumnResponse": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionMenuLinkResponse"
            },
            "nullable": true
          },
          "html": {
            "type": "string",
            "description": "Fritext (HTML) ovanför länkarna — adress, kontaktuppgifter, öppettider.\r\nSaneras i storefrontens API-klient innan den renderas.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionMenuLinkResponse": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "openInNewTab": {
            "type": "boolean"
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionNavigationResponse": {
        "type": "object",
        "properties": {
          "catalogRootCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "catalogMenuMode": {
            "type": "string",
            "nullable": true
          },
          "catalogMenuDepth": {
            "type": "integer",
            "format": "int32"
          },
          "showCategorySidebar": {
            "type": "boolean"
          },
          "topMenuLinks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionMenuLinkResponse"
            },
            "nullable": true
          },
          "footerColumns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionFooterColumnResponse"
            },
            "nullable": true
          },
          "socialLinks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionSocialLinkResponse"
            },
            "description": "Sociala kanaler som ikoner i footern. Ligger under navigation eftersom det är\r\ndär storefronten läser dem (`+layout.svelte`).",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionPoliciesResponse": {
        "type": "object",
        "properties": {
          "applicableCountry": {
            "type": "string",
            "nullable": true
          },
          "returns": {
            "$ref": "#/components/schemas/EcomSessionReturnPolicyResponse"
          },
          "shipping": {
            "$ref": "#/components/schemas/EcomSessionShippingPolicyResponse"
          },
          "productFeedEnabled": {
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "description": "Retur- och fraktvillkor för kanalen. Butiken använder dem för schema.org\r\nhasMerchantReturnPolicy/shippingDetails och för produktfeedens g:shipping,\r\nså att produktsidan inte behöver ett extra anrop."
      },
      "EcomSessionResponse": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "isAuthenticated": {
            "type": "boolean"
          },
          "isTestEnvironment": {
            "type": "boolean"
          },
          "environmentLabel": {
            "type": "string",
            "nullable": true
          },
          "cartItemCount": {
            "type": "integer",
            "format": "int32"
          },
          "customer": {
            "$ref": "#/components/schemas/EcomSessionCustomerResponse"
          },
          "channel": {
            "$ref": "#/components/schemas/EcomSessionChannelResponse"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionReturnPolicyResponse": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "returnWindowDays": {
            "type": "integer",
            "format": "int32"
          },
          "shippingCost": {
            "type": "string",
            "nullable": true
          },
          "requireReason": {
            "type": "boolean"
          },
          "generateReturnLabel": {
            "type": "boolean"
          },
          "returnInstructions": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionSeoResponse": {
        "type": "object",
        "properties": {
          "metaTitle": {
            "type": "string",
            "nullable": true
          },
          "metaDescription": {
            "type": "string",
            "nullable": true
          },
          "baseUrl": {
            "type": "string",
            "nullable": true
          },
          "searchEngineIndexingEnabled": {
            "type": "boolean"
          },
          "robotsTxtExtra": {
            "type": "string",
            "nullable": true
          },
          "redirectToPrimaryDomain": {
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "description": "Kanalens SEO-standardvärden. Används av storefronten som fallback för sidor\r\nsom saknar egen meta title/description."
      },
      "EcomSessionShippingPolicyResponse": {
        "type": "object",
        "properties": {
          "cheapestPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "cheapestName": {
            "type": "string",
            "nullable": true
          },
          "freeShippingThreshold": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "minTransitDays": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "maxTransitDays": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "maxHandlingDays": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "description": "Sammanfattning av kanalens billigaste leveranssätt. Leveranssätt som prissätts via\r\nfraktprislista saknar fast pris och utesluts — ett gissat fraktpris i feeden är\r\nvärre än inget alls."
      },
      "EcomSessionSocialLinkResponse": {
        "type": "object",
        "properties": {
          "platform": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "EcomShippingPrice": {
        "type": "object",
        "properties": {
          "shippingMethodId": {
            "type": "string",
            "format": "uuid"
          },
          "price": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "FilterGroupResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "dataType": {
            "type": "string",
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "values": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FilterValueResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "FilterValueResponse": {
        "type": "object",
        "properties": {
          "value": {
            "type": "string",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "GetCheckoutSessionConfirmationResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string",
            "nullable": true
          },
          "htmlSnippet": {
            "type": "string",
            "nullable": true
          },
          "redirectUrl": {
            "type": "string",
            "nullable": true
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "GetKcoConfirmationResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "htmlSnippet": {
            "type": "string",
            "nullable": true
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "HomePageResponse": {
        "type": "object",
        "properties": {
          "layout": {
            "type": "string",
            "nullable": true
          },
          "fullWidth": {
            "type": "boolean"
          },
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomePageSectionResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "HomePageSectionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "subtitle": {
            "type": "string",
            "nullable": true
          },
          "cssClass": {
            "type": "string",
            "nullable": true
          },
          "heroImageUrl": {
            "type": "string",
            "nullable": true
          },
          "heroCtaText": {
            "type": "string",
            "nullable": true
          },
          "heroCtaUrl": {
            "type": "string",
            "nullable": true
          },
          "heroTextColor": {
            "type": "string",
            "nullable": true
          },
          "heroVideoUrl": {
            "type": "string",
            "nullable": true
          },
          "heroVideoPosterUrl": {
            "type": "string",
            "nullable": true
          },
          "itemGroupId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "categoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "productIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "maxProducts": {
            "type": "integer",
            "format": "int32"
          },
          "productSort": {
            "type": "string",
            "nullable": true
          },
          "productColumns": {
            "type": "integer",
            "format": "int32"
          },
          "categoryIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "parentCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "bannerImageUrl": {
            "type": "string",
            "nullable": true
          },
          "bannerLinkUrl": {
            "type": "string",
            "nullable": true
          },
          "htmlContent": {
            "type": "string",
            "nullable": true
          },
          "supplierIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "maxBrands": {
            "type": "integer",
            "format": "int32"
          },
          "videoUrl": {
            "type": "string",
            "nullable": true
          },
          "articleType": {
            "type": "string",
            "nullable": true
          },
          "articleLinkText": {
            "type": "string",
            "nullable": true
          },
          "galleryImages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PageSectionImageResponse"
            },
            "nullable": true
          },
          "testimonials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PageSectionTestimonialResponse"
            },
            "nullable": true
          },
          "featureCards": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PageSectionFeatureCardResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "IncomingStockResponse": {
        "type": "object",
        "properties": {
          "expectedDate": {
            "type": "string",
            "format": "date"
          },
          "quantity": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "isConfirmed": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ItemPriceResponse": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          },
          "listPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30Days": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "priceListName": {
            "type": "string",
            "nullable": true
          },
          "quantityBreaks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuantityBreakResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ItemRelationType": {
        "enum": [
          "Related",
          "Accessory",
          "CrossSell",
          "UpSell",
          "SparePart",
          "GradedAlternative"
        ],
        "type": "string"
      },
      "ItemStockResponse": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "availableQuantity": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "incoming": {
            "$ref": "#/components/schemas/IncomingStockResponse"
          }
        },
        "additionalProperties": false
      },
      "ListWidgetResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "placement": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "style": {
            "type": "string",
            "nullable": true
          },
          "htmlContent": {
            "type": "string",
            "nullable": true
          },
          "cssClass": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LoginRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "password": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LookupPostalCodePostalCodeLookupDto": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "nullable": true
          },
          "normalizedCode": {
            "type": "string",
            "nullable": true
          },
          "formattedCode": {
            "type": "string",
            "nullable": true
          },
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LookupPostalCodePostalCodeMatchDto"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "LookupPostalCodePostalCodeMatchDto": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "formattedCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "municipality": {
            "type": "string",
            "nullable": true
          },
          "county": {
            "type": "string",
            "nullable": true
          },
          "latitude": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "longitude": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "OneTimeLoginRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "code": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PageSectionFeatureCardResponse": {
        "type": "object",
        "properties": {
          "iconName": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "text": {
            "type": "string",
            "nullable": true
          },
          "linkUrl": {
            "type": "string",
            "nullable": true
          },
          "linkText": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PageSectionImageResponse": {
        "type": "object",
        "properties": {
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "linkUrl": {
            "type": "string",
            "nullable": true
          },
          "caption": {
            "type": "string",
            "nullable": true
          },
          "altText": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PageSectionTestimonialResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "type": "string",
            "nullable": true
          },
          "author": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true
          },
          "rating": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "avatarUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PagedResult_ProductFeedRowResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductFeedRowResponse"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          },
          "totalPages": {
            "type": "integer",
            "format": "int32",
            "readOnly": true
          },
          "hasPreviousPage": {
            "type": "boolean",
            "readOnly": true
          },
          "hasNextPage": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "PagedResult_ProductSummaryResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          },
          "totalPages": {
            "type": "integer",
            "format": "int32",
            "readOnly": true
          },
          "hasPreviousPage": {
            "type": "boolean",
            "readOnly": true
          },
          "hasNextPage": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "PatchChannelBrandingCssPatchBrandingCssResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Sant när CSS:en sparades."
          },
          "customCss": {
            "type": "string",
            "description": "CSS:en som faktiskt lagrades, efter sanering.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Resultatet av en CSS-uppdatering."
      },
      "PatchChannelBrandingCssRequest": {
        "type": "object",
        "properties": {
          "customCss": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Request DTO for PATCH /ecom/branding."
      },
      "PlaceEcomOrderResult": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PlaceOrderBillingAddressRequest": {
        "type": "object",
        "properties": {
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "street1": {
            "type": "string",
            "nullable": true
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PlaceOrderRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "orgNumber": {
            "type": "string",
            "nullable": true
          },
          "shippingStreet1": {
            "type": "string",
            "nullable": true
          },
          "shippingStreet2": {
            "type": "string",
            "nullable": true
          },
          "shippingPostalCode": {
            "type": "string",
            "nullable": true
          },
          "shippingCity": {
            "type": "string",
            "nullable": true
          },
          "shippingCountry": {
            "type": "string",
            "nullable": true
          },
          "shippingAddressId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingAddress": {
            "$ref": "#/components/schemas/PlaceOrderBillingAddressRequest"
          },
          "shippingMethodId": {
            "type": "string",
            "format": "uuid"
          },
          "paymentMethodId": {
            "type": "string",
            "format": "uuid"
          },
          "backorderBehavior": {
            "$ref": "#/components/schemas/BackorderBehavior"
          },
          "requestedDeliveryDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "saveAddress": {
            "type": "boolean"
          },
          "createAccount": {
            "type": "boolean"
          },
          "password": {
            "type": "string",
            "nullable": true
          },
          "customerReference": {
            "type": "string",
            "nullable": true
          },
          "orderNote": {
            "type": "string",
            "nullable": true
          },
          "marketingConsent": {
            "type": "boolean"
          },
          "giftCardCode": {
            "type": "string",
            "nullable": true
          },
          "discountCode": {
            "type": "string",
            "nullable": true
          },
          "marketingConsentText": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalAddressItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "contactPerson": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "street1": {
            "type": "string",
            "nullable": true
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean"
          },
          "notes": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalAddressListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalAddressItem"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalContextResponse": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "contactName": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true
          },
          "modules": {
            "$ref": "#/components/schemas/PortalModulesResponse"
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalInvoiceListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "invoiceDate": {
            "type": "string",
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "totalVat": {
            "type": "number",
            "format": "double"
          },
          "paidAmount": {
            "type": "number",
            "format": "double"
          },
          "balance": {
            "type": "number",
            "format": "double"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalInvoiceListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalInvoiceListItem"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalModulesResponse": {
        "type": "object",
        "properties": {
          "dashboard": {
            "type": "boolean"
          },
          "orders": {
            "type": "boolean"
          },
          "createOrders": {
            "type": "boolean"
          },
          "orderApproval": {
            "type": "boolean"
          },
          "invoices": {
            "type": "boolean"
          },
          "shipments": {
            "type": "boolean"
          },
          "tickets": {
            "type": "boolean"
          },
          "addresses": {
            "type": "boolean"
          },
          "contacts": {
            "type": "boolean"
          },
          "catalog": {
            "type": "boolean"
          },
          "quotes": {
            "type": "boolean"
          },
          "returns": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PortalOrderAddressResponse": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "street": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalOrderDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "deliveryStatus": {
            "type": "string",
            "nullable": true
          },
          "deliveredPercentage": {
            "type": "number",
            "format": "double"
          },
          "orderDate": {
            "type": "string",
            "nullable": true
          },
          "requestedDeliveryDate": {
            "type": "string",
            "nullable": true
          },
          "plannedDeliveryDate": {
            "type": "string",
            "nullable": true
          },
          "customerReference": {
            "type": "string",
            "nullable": true
          },
          "externalNotes": {
            "type": "string",
            "nullable": true
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "totalVat": {
            "type": "number",
            "format": "double"
          },
          "totalAmountIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "contactName": {
            "type": "string",
            "nullable": true
          },
          "shippingAddress": {
            "$ref": "#/components/schemas/PortalOrderAddressResponse"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalOrderLineResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalOrderLineResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          },
          "discountPercent": {
            "type": "number",
            "format": "double"
          },
          "lineTotal": {
            "type": "number",
            "format": "double"
          },
          "lineTotalIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalOrderListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "deliveryStatus": {
            "type": "string",
            "nullable": true
          },
          "orderDate": {
            "type": "string",
            "nullable": true
          },
          "requestedDeliveryDate": {
            "type": "string",
            "nullable": true
          },
          "customerReference": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "totalAmountIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "lineCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalOrderListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalOrderListItem"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalProfileResponse": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "mobile": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "department": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "nullable": true
          },
          "customerName": {
            "type": "string",
            "nullable": true
          },
          "customerNumber": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalQuoteDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "quoteNumber": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "quoteDate": {
            "type": "string",
            "nullable": true
          },
          "validUntil": {
            "type": "string",
            "nullable": true
          },
          "sentAt": {
            "type": "string",
            "nullable": true
          },
          "acceptedAt": {
            "type": "string",
            "nullable": true
          },
          "declinedAt": {
            "type": "string",
            "nullable": true
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "totalVat": {
            "type": "number",
            "format": "double"
          },
          "totalAmountIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "contactName": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalQuoteLineResponse"
            },
            "nullable": true
          },
          "customerMessage": {
            "type": "string",
            "nullable": true
          },
          "canAccept": {
            "type": "boolean"
          },
          "canDecline": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PortalQuoteLineResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          },
          "discountPercent": {
            "type": "number",
            "format": "double"
          },
          "lineTotal": {
            "type": "number",
            "format": "double"
          },
          "lineTotalIncludingTax": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false
      },
      "PortalQuoteListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "quoteNumber": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "quoteDate": {
            "type": "string",
            "nullable": true
          },
          "validUntil": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "totalAmountIncludingTax": {
            "type": "number",
            "format": "double"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "lineCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalQuoteListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalQuoteListItem"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalReturnDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "returnNumber": {
            "type": "string",
            "nullable": true
          },
          "rmaNumber": {
            "type": "string",
            "nullable": true
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "returnDate": {
            "type": "string",
            "nullable": true
          },
          "receivedDate": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "totalVat": {
            "type": "number",
            "format": "double"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "externalNotes": {
            "type": "string",
            "nullable": true
          },
          "returnTrackingUrl": {
            "type": "string",
            "nullable": true
          },
          "returnLabelUrl": {
            "type": "string",
            "nullable": true
          },
          "canCancel": {
            "type": "boolean"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalReturnLineDetail"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalReturnEligibilityResponse": {
        "type": "object",
        "properties": {
          "eligible": {
            "type": "boolean"
          },
          "ineligibleReason": {
            "type": "string",
            "nullable": true
          },
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "requireReason": {
            "type": "boolean"
          },
          "allowPartialReturn": {
            "type": "boolean"
          },
          "returnInstructions": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalReturnEligibleLine"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalReturnEligibleLine": {
        "type": "object",
        "properties": {
          "orderLineId": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "deliveredQuantity": {
            "type": "integer",
            "format": "int32"
          },
          "alreadyReturnedQuantity": {
            "type": "integer",
            "format": "int32"
          },
          "maxReturnableQuantity": {
            "type": "integer",
            "format": "int32"
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false
      },
      "PortalReturnLineDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          },
          "lineTotal": {
            "type": "number",
            "format": "double"
          },
          "reason": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalReturnListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalReturnSummary"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalReturnSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "returnNumber": {
            "type": "string",
            "nullable": true
          },
          "rmaNumber": {
            "type": "string",
            "nullable": true
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "returnDate": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "lineCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalShipmentAddressResponse": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "street": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalShipmentDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "shipmentNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "trackingNumber": {
            "type": "string",
            "nullable": true
          },
          "shippedDate": {
            "type": "string",
            "nullable": true
          },
          "deliveredDate": {
            "type": "string",
            "nullable": true
          },
          "createdDate": {
            "type": "string",
            "nullable": true
          },
          "shippingMethodName": {
            "type": "string",
            "nullable": true
          },
          "totalWeightKg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "deliveryAddress": {
            "$ref": "#/components/schemas/PortalShipmentAddressResponse"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalShipmentLineResponse"
            },
            "nullable": true
          },
          "packages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalShipmentPackageResponse"
            },
            "nullable": true
          },
          "orderNumbers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalShipmentLineResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalShipmentListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "shipmentNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "trackingNumber": {
            "type": "string",
            "nullable": true
          },
          "shippedDate": {
            "type": "string",
            "nullable": true
          },
          "deliveredDate": {
            "type": "string",
            "nullable": true
          },
          "createdDate": {
            "type": "string",
            "nullable": true
          },
          "deliveryName": {
            "type": "string",
            "nullable": true
          },
          "deliveryCity": {
            "type": "string",
            "nullable": true
          },
          "deliveryCountry": {
            "type": "string",
            "nullable": true
          },
          "packageCount": {
            "type": "integer",
            "format": "int32"
          },
          "orderNumbers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalShipmentListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalShipmentListItem"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PortalShipmentPackageResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "packageNumber": {
            "type": "string",
            "nullable": true
          },
          "trackingNumber": {
            "type": "string",
            "nullable": true
          },
          "weightKg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lengthCm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "widthCm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "heightCm": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalTicketAttachment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fileName": {
            "type": "string",
            "nullable": true
          },
          "contentType": {
            "type": "string",
            "nullable": true
          },
          "fileSize": {
            "type": "integer",
            "format": "int64"
          },
          "createdDate": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "PortalTicketDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "ticketNumber": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "priority": {
            "type": "string",
            "nullable": true
          },
          "createdDate": {
            "type": "string",
            "format": "date-time"
          },
          "resolvedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "closedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "resolutionNotes": {
            "type": "string",
            "nullable": true
          },
          "reportedByContactName": {
            "type": "string",
            "nullable": true
          },
          "canReply": {
            "type": "boolean"
          },
          "product": {
            "$ref": "#/components/schemas/PortalTicketProduct"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalTicketMessage"
            },
            "nullable": true
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalTicketAttachment"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalTicketListResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalTicketSummary"
            },
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          },
          "seesAllCompanyTickets": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PortalTicketMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string",
            "nullable": true
          },
          "authorName": {
            "type": "string",
            "nullable": true
          },
          "isFromCustomer": {
            "type": "boolean"
          },
          "createdDate": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "PortalTicketProduct": {
        "type": "object",
        "properties": {
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "serialNumber": {
            "type": "string",
            "nullable": true
          },
          "firmwareVersion": {
            "type": "string",
            "nullable": true
          },
          "hardwareRevision": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PortalTicketSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "ticketNumber": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "priority": {
            "type": "string",
            "nullable": true
          },
          "createdDate": {
            "type": "string",
            "format": "date-time"
          },
          "lastActivityAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "reportedByContactName": {
            "type": "string",
            "nullable": true
          },
          "commentCount": {
            "type": "integer",
            "format": "int32"
          },
          "attachmentCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "PricesResponse": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "nullable": true
          },
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ItemPriceResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "ProductAttributeResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "value": {
            "type": "string",
            "nullable": true
          },
          "dataType": {
            "$ref": "#/components/schemas/AttributeDataType"
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "colorCode": {
            "type": "string",
            "nullable": true
          },
          "showInCompare": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ProductCrossReferenceResponse": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "referenceNumber": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ProductDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "shortDescription": {
            "type": "string",
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "brand": {
            "type": "string",
            "nullable": true
          },
          "brandSlug": {
            "type": "string",
            "nullable": true
          },
          "brandLogoUrl": {
            "type": "string",
            "nullable": true
          },
          "gtin": {
            "type": "string",
            "nullable": true
          },
          "mpn": {
            "type": "string",
            "nullable": true
          },
          "crossReferences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductCrossReferenceResponse"
            },
            "nullable": true
          },
          "listPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "listPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "priceValidUntil": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "regularPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "regularPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30Days": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30DaysIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "isOnSale": {
            "type": "boolean"
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "stock": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WarehouseStockResponse"
            },
            "nullable": true
          },
          "aggregatedAvailableQuantity": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "incoming": {
            "$ref": "#/components/schemas/IncomingStockResponse"
          },
          "isPhasingOut": {
            "type": "boolean"
          },
          "stockDisplayMode": {
            "type": "string",
            "nullable": true
          },
          "mainImageUrl": {
            "type": "string",
            "nullable": true
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductImageResponse"
            },
            "nullable": true
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductDocumentResponse"
            },
            "nullable": true
          },
          "breadcrumbs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BreadcrumbResponse"
            },
            "nullable": true
          },
          "primaryCategory": {
            "$ref": "#/components/schemas/CategorySummaryResponse"
          },
          "weight": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "length": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "width": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "height": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductAttributeResponse"
            },
            "nullable": true
          },
          "relatedProducts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          },
          "accessories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          },
          "crossSellProducts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          },
          "variantAttributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantAttributeResponse"
            },
            "nullable": true
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantResponse"
            },
            "nullable": true
          },
          "grade": {
            "type": "string",
            "nullable": true
          },
          "averageRating": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "reviewCount": {
            "type": "integer",
            "format": "int32"
          },
          "allowPreOrder": {
            "type": "boolean"
          },
          "salesStartDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "metaTitle": {
            "type": "string",
            "nullable": true
          },
          "metaDescription": {
            "type": "string",
            "nullable": true
          },
          "seoSlug": {
            "type": "string",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductDocumentResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "contentType": {
            "type": "string",
            "nullable": true
          },
          "fileSize": {
            "type": "integer",
            "format": "int64"
          },
          "languageCode": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "ProductFeedRowResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "seoSlug": {
            "type": "string",
            "nullable": true
          },
          "itemGroupId": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "brand": {
            "type": "string",
            "nullable": true
          },
          "gtin": {
            "type": "string",
            "nullable": true
          },
          "mpn": {
            "type": "string",
            "nullable": true
          },
          "grade": {
            "type": "string",
            "nullable": true
          },
          "mainImageUrl": {
            "type": "string",
            "nullable": true
          },
          "additionalImageUrls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "currency": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "priceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "regularPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "regularPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "isOnSale": {
            "type": "boolean"
          },
          "priceValidFrom": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "priceValidTo": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "allowPreOrder": {
            "type": "boolean"
          },
          "salesStartDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "googleProductCategory": {
            "type": "string",
            "nullable": true
          },
          "productType": {
            "type": "string",
            "nullable": true
          },
          "weightKg": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "widthCm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "heightCm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "depthCm": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "countryOfOrigin": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductImageResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "altText": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ProductKeySpecResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "value": {
            "type": "string",
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductReviewResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "rating": {
            "type": "integer",
            "format": "int32"
          },
          "author": {
            "type": "string",
            "nullable": true
          },
          "date": {
            "type": "string",
            "format": "date-time"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "text": {
            "type": "string",
            "nullable": true
          },
          "isVerifiedPurchase": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ProductReviewSummaryResponse": {
        "type": "object",
        "properties": {
          "averageRating": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          },
          "distribution": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int32"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductReviewsResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductReviewResponse"
            },
            "nullable": true
          },
          "summary": {
            "$ref": "#/components/schemas/ProductReviewSummaryResponse"
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          },
          "hasMore": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ProductSearchResponse": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "nullable": true
          },
          "products": {
            "$ref": "#/components/schemas/PagedResult_ProductSummaryResponse"
          },
          "correctedQuery": {
            "type": "string",
            "nullable": true
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchCategoryMatch"
            },
            "nullable": true
          },
          "popularProducts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          },
          "alternativeTerms": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductSummaryResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "mpn": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "mainImageUrl": {
            "type": "string",
            "nullable": true
          },
          "listPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "listPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "regularPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "regularPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30Days": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30DaysIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "isOnSale": {
            "type": "boolean"
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "isPhasingOut": {
            "type": "boolean"
          },
          "seoSlug": {
            "type": "string",
            "nullable": true
          },
          "brand": {
            "type": "string",
            "nullable": true
          },
          "brandSlug": {
            "type": "string",
            "nullable": true
          },
          "keySpecs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductKeySpecResponse"
            },
            "nullable": true
          },
          "incoming": {
            "$ref": "#/components/schemas/IncomingStockResponse"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductWidgetResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "placement": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "style": {
            "type": "string",
            "nullable": true
          },
          "htmlContent": {
            "type": "string",
            "nullable": true
          },
          "cssClass": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "QuantityBreakResponse": {
        "type": "object",
        "properties": {
          "minQuantity": {
            "type": "number",
            "format": "double"
          },
          "maxQuantity": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "unitPrice": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false
      },
      "RedirectResponse": {
        "type": "object",
        "properties": {
          "fromPath": {
            "type": "string",
            "nullable": true
          },
          "toPath": {
            "type": "string",
            "nullable": true
          },
          "statusCode": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "RelatedProductsResponse": {
        "type": "object",
        "properties": {
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RemoveFromCartResponse": {
        "type": "object",
        "properties": {
          "cartId": {
            "type": "string",
            "format": "uuid"
          },
          "totalItems": {
            "type": "integer",
            "format": "int32"
          },
          "uniqueItemCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "RequestCodeRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "purpose": {
            "$ref": "#/components/schemas/EcomAuthCodePurpose"
          }
        },
        "additionalProperties": false
      },
      "RequestPortalQuoteRequest": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RequestPortalQuoteRequestLine"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RequestPortalQuoteRequestLine": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "note": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "RequestPortalQuoteResponse": {
        "type": "object",
        "properties": {
          "quoteId": {
            "type": "string",
            "format": "uuid"
          },
          "quoteNumber": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ResetPasswordRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "newPassword": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ResolveDomainResponse": {
        "type": "object",
        "properties": {
          "tenantId": {
            "type": "string",
            "nullable": true
          },
          "channelCode": {
            "type": "string",
            "nullable": true
          },
          "channelName": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Minimal response for domain resolution — routing info only."
      },
      "SearchCategoryMatch": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "productCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "SearchSuggestionsResponse": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "nullable": true
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummaryResponse"
            },
            "nullable": true
          },
          "terms": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SuggestedCategory"
            },
            "nullable": true
          },
          "didYouMean": {
            "type": "string",
            "nullable": true
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "StockResponse": {
        "type": "object",
        "properties": {
          "stock": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ItemStockResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "StockStatus": {
        "enum": [
          "InStock",
          "LowStock",
          "OutOfStock",
          "BackOrder"
        ],
        "type": "string"
      },
      "SubmitPortalReturnRequest": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "reason": {
            "type": "integer",
            "format": "int32"
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubmitPortalReturnRequestLine"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitPortalReturnRequestLine": {
        "type": "object",
        "properties": {
          "orderLineId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "notes": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitPortalReturnResponse": {
        "type": "object",
        "properties": {
          "returnId": {
            "type": "string",
            "format": "uuid"
          },
          "returnNumber": {
            "type": "string",
            "nullable": true
          },
          "rmaNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitPortalTicketRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "orderId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "orderLineId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "serialNumber": {
            "type": "string",
            "nullable": true
          },
          "firmwareVersion": {
            "type": "string",
            "nullable": true
          },
          "hardwareRevision": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitPortalTicketResponse": {
        "type": "object",
        "properties": {
          "ticketId": {
            "type": "string",
            "format": "uuid"
          },
          "ticketNumber": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitProductReviewRequest": {
        "type": "object",
        "properties": {
          "rating": {
            "type": "integer",
            "format": "int32"
          },
          "authorName": {
            "type": "string",
            "nullable": true
          },
          "body": {
            "type": "string",
            "nullable": true
          },
          "authorEmail": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "honeypot": {
            "type": "string",
            "nullable": true
          },
          "captchaToken": {
            "type": "string",
            "nullable": true
          },
          "formLoadedAtUnixMs": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitProductReviewResponse": {
        "type": "object",
        "properties": {
          "reviewId": {
            "type": "string",
            "format": "uuid"
          },
          "requiresModeration": {
            "type": "boolean"
          },
          "isVerifiedPurchase": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "SubscribeToNewsletterRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "language": {
            "type": "string",
            "nullable": true
          },
          "consentText": {
            "type": "string",
            "description": "Den exakta text som visades bredvid fältet. Butiken skickar med den så att\r\nsamtycket går att belägga i efterhand — texten sparas ordagrant.",
            "nullable": true
          },
          "honeypot": {
            "type": "string",
            "nullable": true
          },
          "captchaToken": {
            "type": "string",
            "nullable": true
          },
          "formLoadedAtUnixMs": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubscribeToNewsletterResponse": {
        "type": "object",
        "properties": {
          "confirmationSent": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "SuggestedCategory": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TrackPageViewRequest": {
        "type": "object",
        "properties": {
          "pagePath": {
            "type": "string",
            "nullable": true
          },
          "statusCode": {
            "type": "integer",
            "format": "int32"
          },
          "referrer": {
            "type": "string",
            "nullable": true
          },
          "utmSource": {
            "type": "string",
            "nullable": true
          },
          "utmMedium": {
            "type": "string",
            "nullable": true
          },
          "utmCampaign": {
            "type": "string",
            "nullable": true
          },
          "utmTerm": {
            "type": "string",
            "nullable": true
          },
          "utmContent": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UnsubscribeFromNewsletterResponse": {
        "type": "object",
        "properties": {
          "unsubscribed": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "UpdateCartItemQuantityResponse": {
        "type": "object",
        "properties": {
          "cartId": {
            "type": "string",
            "format": "uuid"
          },
          "totalItems": {
            "type": "integer",
            "format": "int32"
          },
          "uniqueItemCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "UpdateCartItemRequest": {
        "type": "object",
        "properties": {
          "quantity": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "UpdateCheckoutSessionResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string",
            "nullable": true
          },
          "renderMode": {
            "type": "string",
            "nullable": true
          },
          "htmlSnippet": {
            "type": "string",
            "nullable": true
          },
          "redirectUrl": {
            "type": "string",
            "nullable": true
          },
          "clientSecret": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UpdateKcoSessionResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "format": "uuid"
          },
          "htmlSnippet": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UpdatePortalAddressRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "street1": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "contactPerson": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UpdatePortalProfileRequest": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "mobile": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "department": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "UpdatePortalTicketProductRequest": {
        "type": "object",
        "properties": {
          "serialNumber": {
            "type": "string",
            "nullable": true
          },
          "firmwareVersion": {
            "type": "string",
            "nullable": true
          },
          "hardwareRevision": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "VariantAttributeResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "values": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantAttributeValueResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "VariantAttributeValueResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "value": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "VariantResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "listPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "listPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "regularPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "regularPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30Days": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "lowestPrice30DaysIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "isOnSale": {
            "type": "boolean"
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "incoming": {
            "$ref": "#/components/schemas/IncomingStockResponse"
          },
          "mainImageUrl": {
            "type": "string",
            "nullable": true
          },
          "seoSlug": {
            "type": "string",
            "nullable": true
          },
          "values": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantValueRef"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "VariantValueRef": {
        "type": "object",
        "properties": {
          "attributeId": {
            "type": "string",
            "format": "uuid"
          },
          "valueId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "additionalProperties": false
      },
      "VerifyCodeRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "nullable": true
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "purpose": {
            "$ref": "#/components/schemas/EcomAuthCodePurpose"
          }
        },
        "additionalProperties": false
      },
      "VerifyEcomAuthCodeResult": {
        "type": "object",
        "properties": {
          "isValid": {
            "type": "boolean"
          },
          "remainingAttempts": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "WarehouseStockResponse": {
        "type": "object",
        "properties": {
          "warehouseId": {
            "type": "string",
            "format": "uuid"
          },
          "warehouseCode": {
            "type": "string",
            "nullable": true
          },
          "warehouseName": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "availableQuantity": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "leadTime": {
            "type": "string",
            "nullable": true
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "isAvailableForPickup": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "TenantId": {
        "type": "apiKey",
        "description": "The tenant (company) whose storefront the request applies to. A GUID, obtained from GET /ecom/session/resolve or configured directly. Required on every request except the domain resolution itself.",
        "name": "X-Tenant-Id",
        "in": "header"
      },
      "EcomSession": {
        "type": "http",
        "description": "Session token from GET /ecom/session, sent as `Authorization: Bearer {token}`. An opaque handle, not a JWT — do not decode it. Anonymous sessions are normal and carry a cart; signing in upgrades the same session.",
        "scheme": "bearer",
        "bearerFormat": "opaque"
      }
    }
  },
  "security": [
    {
      "TenantId": [ ]
    }
  ],
  "tags": [
    {
      "name": "Ecom.Session",
      "description": "Establishing context: map a domain to a tenant and channel, then open a session. The session response also carries the channel's whole configuration — currency, language, feature flags, branding, navigation and SEO — so a storefront can render its shell from one call."
    },
    {
      "name": "Ecom.Channels",
      "description": "The storefronts a tenant runs, and the home page each one presents. A channel owns its assortment, price list, currency and branding, so the same item can be published in two channels at different prices."
    },
    {
      "name": "Ecom.Catalog",
      "description": "Products, categories, brands, search, facets and type-ahead. Products are addressed by slug or id, and only what is published in the current channel is visible. Includes the product feed for Google Merchant and the binary endpoints that serve images and documents."
    },
    {
      "name": "Ecom.Pricing",
      "description": "Prices for a set of items in one call. The answer depends on the channel and on whether the session is signed in, because a signed-in customer gets their agreement prices. Do not cache the result across visitors."
    },
    {
      "name": "Ecom.Redirects",
      "description": "The channel's URL redirect table, for a storefront that handles its own routing. Read once and cache — it is channel-wide and changes rarely."
    },
    {
      "name": "Ecom.ContentPages",
      "description": "CMS pages: the navigable list and a page by slug, with its sections. Used for terms, about, delivery information and anything else the tenant edits without a deploy."
    },
    {
      "name": "Ecom.Blog",
      "description": "Blog and news posts for the channel, with tag filtering and related-post lookup."
    },
    {
      "name": "Ecom.Help",
      "description": "Knowledge base articles: browse by category, read by slug, search, and look up the articles attached to a specific product."
    },
    {
      "name": "Ecom.Cart",
      "description": "The cart hangs off the session, so there is no cart id to pass around. Anonymous sessions have carts too, and the cart survives signing in."
    },
    {
      "name": "Ecom.Checkout",
      "description": "Checkout from available methods to placed order, including discount coupons, gift cards, shipping price calculation and postal code lookup. Payment runs through a provider-agnostic session: the response says how to render it, not which provider produced it."
    },
    {
      "name": "Ecom.Kco",
      "description": "Klarna Checkout, the provider-specific predecessor of the checkout session endpoints. Still supported, but new builds should use Ecom.Checkout instead. The push endpoint here is an inbound callback from Klarna, not something a storefront calls."
    },
    {
      "name": "Ecom.Account",
      "description": "Signing in: password, one-time code by email, and password reset. All of them upgrade the existing session rather than replacing it, so the cart carries over."
    },
    {
      "name": "Ecom.Profile",
      "description": "The signed-in customer's own contact details."
    },
    {
      "name": "Ecom.Addresses",
      "description": "The signed-in customer's delivery addresses, including which one is the default."
    },
    {
      "name": "Ecom.Orders",
      "description": "The signed-in customer's order history and order details, with line level fulfilment status."
    },
    {
      "name": "Ecom.Invoices",
      "description": "The signed-in customer's invoices."
    },
    {
      "name": "Ecom.Shipments",
      "description": "Deliveries against the signed-in customer's orders, with carrier tracking where the carrier provides it."
    },
    {
      "name": "Ecom.Quotes",
      "description": "Quote requests: a signed-in customer asks for a price on a set of items, then accepts or declines what comes back. An accepted quote becomes a sales order in the ERP."
    },
    {
      "name": "Ecom.Returns",
      "description": "Customer-initiated returns: check what an order is eligible to return, register the return, print the label and follow it. Registering moves no stock — receiving at the warehouse does."
    },
    {
      "name": "Ecom.Tickets",
      "description": "Support cases the customer opens from their own pages, with the message thread and attachments. Cases land in the same queue as those created inside the ERP and from the support mailbox."
    },
    {
      "name": "Ecom.Reviews",
      "description": "Product reviews: read the approved ones, submit a new one. Submissions are anonymous-capable and go through moderation before they appear."
    },
    {
      "name": "Ecom.Newsletter",
      "description": "Newsletter sign-up with double opt-in, and one-click unsubscribe per RFC 8058. The unsubscribe endpoint is quota'd per token rather than per IP, because mail providers share addresses across many recipients."
    },
    {
      "name": "Ecom.Chat",
      "description": "The shopping assistant: a server-sent event stream that answers product questions against the channel's own catalogue and content."
    },
    {
      "name": "Ecom.Analytics",
      "description": "Page-view ingestion for the tenant's own storefront reporting, feeding the SEO and traffic views inside Fluit. It records what your storefront tells it; it is not a public analytics service."
    }
  ],
  "x-logo": {
    "url": "https://fluit.se/fluit-logo.svg",
    "altText": "Fluit ERP",
    "href": "https://fluit.se"
  },
  "x-tagGroups": [
    {
      "name": "Storefront",
      "tags": [
        "Ecom.Session",
        "Ecom.Channels",
        "Ecom.Catalog",
        "Ecom.Pricing",
        "Ecom.Redirects"
      ]
    },
    {
      "name": "Content",
      "tags": [
        "Ecom.ContentPages",
        "Ecom.Blog",
        "Ecom.Help"
      ]
    },
    {
      "name": "Buying",
      "tags": [
        "Ecom.Cart",
        "Ecom.Checkout"
      ]
    },
    {
      "name": "Payment providers",
      "tags": [
        "Ecom.Kco"
      ]
    },
    {
      "name": "Customer portal",
      "tags": [
        "Ecom.Account",
        "Ecom.Profile",
        "Ecom.Addresses",
        "Ecom.Orders",
        "Ecom.Invoices",
        "Ecom.Shipments",
        "Ecom.Quotes",
        "Ecom.Returns",
        "Ecom.Tickets"
      ]
    },
    {
      "name": "Engagement",
      "tags": [
        "Ecom.Reviews",
        "Ecom.Newsletter",
        "Ecom.Chat"
      ]
    },
    {
      "name": "Insight",
      "tags": [
        "Ecom.Analytics"
      ]
    }
  ]
}