{
  "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 the domain resolution above, or from the channel settings in Fluit.\n\n`X-Language` selects the language for translated content, as an ISO 639-1 code (`sv`,\n`en`). It is optional and forgiving: omit it, or send a language the channel does not\npublish, and you get the channel's default — never an error. The channel decides which\nlanguages exist, and the session response lists them in `channel.languages` alongside the\nresolved `channel.language`.\n\nIt applies to the endpoints that actually return translated text — the session, the\ncategory tree, filters, product lists, product detail, search, type-ahead and the shopping\nassistant. Each operation's parameter list says whether it takes the header. Note that\nevery cacheable response varies on it regardless, so a shared cache in front of us can\nnever mix languages between visitors.\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. `POST\n/ecom/auth/logout` ends one early, and ending it is the only thing that makes the token\nstop working — dropping your copy of it stops *you* from sending it, but the token stays\nvalid for whoever else has one until it expires on its own. On a shared device, or after\na token has been anywhere you did not intend, that difference is the whole point. Call it\nbefore you clear your cookie or your token store, because afterwards you no longer have\nthe token the call needs.\n\nIt is idempotent: a token that is already ended, expired or simply unknown answers `204`\ntoo, so a retry after a lost response, or two browser tabs signing out at once, needs no\nspecial handling. Only a request with no `Authorization` header at all is an error.\n\nThe cart hangs off the session and goes with it. The next session starts empty whether or\nnot the visitor signs back in — which is what you want on a shared device, and worth\nknowing if you meant to keep something.\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# Accounts\n\nThere is no `POST /ecom/auth/register`, and its absence is a design decision rather than\na gap. A customer in Fluit is a record in the ERP that a salesperson may hold an agreement\nand a price list against — not a row a visitor creates by filling in a form.\n\nAccounts come into being two ways:\n\n- **At checkout.** Send `createAccount: true` and a `password` on\n  `POST /ecom/checkout/place`. The order creates the customer if the e-mail is new, and\n  the account is attached to it. The two fields work only together: `createAccount`\n  without a password creates no account and reports no error — and on a channel with\n  `guestCheckout: false` the order is then rejected as a guest order, which is the\n  confusing way to discover it. Send both or neither.\n- **In the ERP.** A salesperson creates the customer and invites the contact. This is the\n  normal path in B2B, where the account follows an agreement that was negotiated\n  somewhere other than a web form.\n\nThree channel settings decide what your checkout must offer, and all three are on the\nsession response under `channel.features`:\n\n| Setting | When true |\n| --- | --- |\n| `requireLogin` | Only signed-in customers can order. Neither guests nor `createAccount` get through — offer sign-in, not a registration form |\n| `guestCheckout` | Ordering without an account is allowed. When false, the visitor must either sign in or pass `createAccount` |\n| `showPricesWithoutLogin` | Prices are visible to anonymous visitors at all |\n\n`GET /ecom/checkout/data` returns `requireLogin` and `guestCheckout` too, so the checkout\npage does not have to carry them from the session.\n\nA customer who has an account but no password — invited from the ERP, or simply someone\nwho forgot — signs in with a one-time code: `POST /ecom/auth/request-code` followed by\n`POST /ecom/auth/one-time-login`. That is the way back in, and it is worth offering\nalongside the password field rather than only behind a \"forgot password\" link.\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\n**CORS does not stand in your way.** Browser requests are accepted from any origin — no\nallow-list, nothing to ask us for. That costs nothing to give: this surface is already\ncallable anonymously from any server, and returns only what the channel has published, so\nCORS was never what protected it. It only ever constrained browsers, which meant it only\nconstrained the clients doing what we recommend.\n\nBecause authentication is a bearer header and never a cookie, the policy sends no\ncredentials. Do not expect cookies to ride along on a cross-origin call, and do not put\nanything in one.\n\nFive response headers are exposed for JavaScript to read: `ETag` (without it you cannot\nrevalidate — see *Caching*), `Retry-After`, `Location`, `Idempotency-Replayed` and\n`Content-Disposition`. Everything outside that set and the CORS safelist is invisible to a\nbrowser client, whatever the server sent.\n\nServer to server is still the shape we recommend, for the reasons above rather than\nbecause the browser path is blocked.\n\nTwo practical consequences of proxying:\n\n- Forward the visitor's address in `X-Forwarded-For`, and prove that you may by sending a\n  storefront key in `X-Fluit-Storefront-Key`. Create the key under *Settings → API keys*\n  with the storefront permission; a full-access key does not carry it. With the key, the\n  rate limiter partitions on the forwarded address. Without it, the header is ignored and\n  every visitor shares your server's quota, because anyone can call this API and anyone\n  could otherwise pick a fresh address per request. Keep the key on your server; it never\n  belongs in a browser.\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# Pages, website mode and forms\n\n**Website mode.** A channel can be a plain website without a shop. The session response\nsays so in `features.commerceEnabled`. When it is `false`, every commerce surface answers\n`404` with a problem body — catalog, products, cart, checkout, payment callbacks,\naccounts, the portal, postal-code lookup and the shopping assistant — so a website cannot\ntake an order through the API either. Pages, the home page, navigation, forms, the\nnewsletter and articles work as usual.\n\n**Pages live at the root.** A content page's address is its parents' slugs plus its own —\n`/about`, `/services/tyre-change` — and the home page is `/`. Resolve an incoming path with\n`GET /ecom/pages/resolve?path=/services/tyre-change`: the page, or `404`. The last segment\nidentifies the page, so a stale path — a page moved under another parent, or the old\n`/pages/{slug}` address — still finds it; compare the response's `canonicalPath` with the\nrequested path and redirect permanently when they differ. A renamed slug leaves a URL\nredirect behind, like a renamed product. `navigation.reservedSlugs` in the session lists\nthe first path segments that are never pages (`cart`, `checkout`, `products`, `blog`, …),\nso a router can send those to the shop before asking the page API.\n\n**Sections.** A page and the home page are lists of sections with a `type`. Every section\nhas the same heading fields (`eyebrow`, `title`, `subtitle`, `buttons`, `tone`, `spacing`,\n`alignment`, `anchor`) plus fields for its own type. Button targets are resolved to\naddresses on the server. Reusable section collections are expanded in place — a client\nnever sees a collection reference. Treat an unknown `type` as \"skip this section\": new\ntypes are added without a version change.\n\n**Products in a section.** The product sections (`FeaturedProducts`, `ProductsByCategory`,\n…) and `ProductSpotlight` carry ids, not product data: fetch them with\n`GET /ecom/products/{id}` so that price, stock and documents are the ones this visitor\nshould see rather than a copy cached with the page. A `ProductSpotlight` section names one\nproduct and says how to show it; when its `documents` is `Selected` or `Category`, list the\nmatching entries from that product's `documents`.\n\n**Embeds.** An `Embed` section names an external https page (`url`), the frame's\naccessible name (`title`) and its `height` in pixels. Render it as a sandboxed `iframe`\nand allow the url's origin in your page's `frame-src`. `embed` is null when the stored\naddress cannot be embedded — skip the section then.\n\n**Forms.** A `Form` section carries the whole form: fields, labels, options, whether a\nfield is required, the consent text and a `formToken`. Submit it with\n`POST /ecom/forms/{formToken}/submissions`, as JSON or as `multipart/form-data` when it\nhas file fields. Send `formLoadedAtUnixMs` (when the form was rendered), the honeypot\nfield empty, `consentGiven` when the form has a consent text, and `sourceUrl`. A\nsubmission without `formLoadedAtUnixMs`, with the honeypot filled in, or sent faster than\na person can type is accepted with the same response as a real one and then dropped.\nA missing required field or consent comes back as a `400` problem. A form that has been\ndeactivated is left out of the page response, so a page never shows a form that cannot be\nsent.\n\n# Custom blocks\n\nA channel can define its own blocks: fields plus an HTML template, written in Fluit by a\ndeveloper and filled in by editors like any other block. A section with `type: \"Custom\"`\ncarries the block in `custom`:\n\n```json\n\"custom\": {\n  \"blockKey\": \"prislista\",\n  \"blockVersion\": 4,\n  \"useShell\": true,\n  \"html\": \"<ul class=\\\"prislista\\\">…</ul>\",\n  \"css\": \"[data-block=\\\"prislista\\\"] .rad{…}\",\n  \"fields\": {\n    \"rader\": [{ \"tjanst\": \"Tyre change\", \"pris\": 495, \"fran\": false,\n                \"lank\": { \"url\": \"/services/tyres\", \"openInNewTab\": false } }],\n    \"fotnot\": \"All prices include VAT.\"\n  }\n}\n```\n\n**Render the HTML, or build your own from `fields`.** The template is rendered on the\nserver, so the HTML is what the channel's own storefront shows. It is sanitized on the\nserver already — no scripts, event attributes or `javascript:` links — but sanitize it again\nbefore you insert it, as you would any HTML from an API. Put it in an element with\n`data-block=\"{blockKey}\"`: the CSS is scoped to that attribute and does not reach the rest\nof the page. When `useShell` is `true`, draw the section's heading fields around it as for\nany section; when it is `false`, the HTML draws everything itself.\n\n**Add the CSS once per page.** Every section with the same block carries the same `css`.\nDeduplicate on `blockKey` and `blockVersion` and put it in one `<style>` element.\n\n**`fields` is data.** Keys are the block's field keys. Text is a string, formatted text\nsanitized HTML, a number a number, a choice `{ key, label }`, a date ISO 8601, an image\n`{ url, alt, width, height }` and a link `{ url, openInNewTab }` — a link to a page already\nhas the page's current address. A list is an array of objects with the same shapes. A\nblock whose definition is missing, or that cannot be rendered, is left out of the page.\nA new version of a block changes every page that uses it at once, without the pages being\nrepublished.\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\n**`unitPrice` is what the customer pays.** Two things can lower a price: the price list\nhierarchy, and discount rules — campaign rules that apply to everyone, and agreement\ndiscounts bound to one customer. `unitPrice` carries both. The price before any line\ndiscount is `unitPriceBeforeDiscount`, and `discountAmount`, `discountPercent` and\n`discountName` say what the difference was and what it is called. The cached catalog\npayloads (`listPrice` on a product or a list row) carry the same net price for an\nanonymous visitor.\n\n> **Changed 2026-09-16.** `unitPrice` previously carried the price *before* discount\n> rules, so a storefront that displayed it showed a higher price than the cart charged.\n> If you were reading `unitPrice` you now get the right number and need no change; if you\n> were relying on it as a pre-discount reference, read `unitPriceBeforeDiscount` instead.\n\n**`isCustomerSpecific` decides what you may claim.** It is true when the price or the\ndiscount is bound to this customer — an agreement price list, a blanket agreement, a\ncustomer discount, or a rule conditioned on the customer or their group. Such a price is\nthat customer's own terms, not a reduction the shop advertised, so it must never be\npresented against `lowestPrice30Days` as a price drop. Do not infer this from `source`:\na customer discount lowers the price without changing where the price came from.\nCart lines (`GET /ecom/cart`) carry the same flag with the same meaning: when a line's\n`isCustomerSpecific` is true, its `discountAmount` is the customer's agreement, not a\nreduction to strike through.\n\n`quantityBreaks` lists the quantities where the unit price changes, from price list tiers\nand from discount rules with a minimum quantity alike, with the unit price at each tier\nalready net of discounts.\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**Pickup.** A shipping method with `isPickup` in `GET /ecom/checkout/data` is collected\nby the buyer. When the channel has pickup locations, `POST /ecom/checkout/pickup-locations`\nlists them with the cart's availability at each (`InStock`, `AvailableFrom` with a date, or\n`Unavailable`), and the buyer chooses one instead of entering a shipping address. Send its\n`warehouseId` as `pickupWarehouseId` on `POST /ecom/checkout/place`. The order is placed on\nthat location, stock is checked there rather than across the channel, and the location's\naddress becomes the delivery address, which is also what decides the VAT. A billing\naddress is still required when the buyer is a company or pays by invoice. A channel\nwithout pickup locations handles a pickup method exactly as before.\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\n**A `4xx` is a final answer, and it is replayed.** Once a call with a key has been\nrejected (out of stock, an expired cart, a validation error), that attempt is over. The\nshopper's next try, typically after changing the cart, is a new request and needs a new\nkey. Reuse the old one and you get the old error back for 24 hours. Keep a key only while\nthe outcome is unknown: no response, a network error or a `5xx`.\n\n**When you cannot simply retry.** If the response to `POST /ecom/checkout/place` was lost\nand the page has since been reloaded, the cart may already be an order, and sending the\nrequest again would only answer that the cart is converted. Ask\n`GET /ecom/checkout/attempts/{key}` instead, with the same session: it tells you whether\nthe attempt is still running, failed, or created an order, and if so which one, so you\ncan show the confirmation the shopper missed.\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`, `X-Channel` and `X-Language`.** All three are headers, none appears in\n> the URL, and each decides what the response contains. We send\n> `Vary: X-Tenant-Id, X-Channel, X-Language` on every cacheable response for exactly this\n> reason — a cache keyed on the URL alone would serve one tenant's catalogue to another,\n> and one visitor's language to the next.\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 | 120 / minute per IP, 600 for recognised crawlers |\n| Page-view and funnel tracking | 120 / minute per IP |\n| Shopping assistant | 20 / minute |\n| Submit a review | 5 / hour per IP |\n| Newsletter sign-up | 10 / hour per IP; one confirmation email per address every 10 minutes, at most 3 a day |\n| Newsletter unsubscribe | 20 / hour per token |\n| Form submission | 10 / hour per IP |\n| Domain resolution | 30 / minute per IP |\n| Payment webhooks | 30 / minute per tenant, endpoint and sender IP |\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 use `X-Forwarded-For` only from a caller with a storefront key. See\n*Architecture* above. A storefront that resolves its domain or relays a payment provider's\nwebhook should forward the visitor's or the provider's address the same way. Otherwise\nevery such call counts against the storefront server's own address.\n\nA newsletter sign-up above the per-address limit answers exactly like any other sign-up\nbut sends no email. A different answer would reveal which addresses are on the list.\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/logout": {
      "post": {
        "tags": [
          "Ecom.Account"
        ],
        "summary": "Log out",
        "description": "Ends the session behind the bearer token, server-side. The token stops working immediately, for this endpoint and every other one. Idempotent: a token that is already ended, expired or unknown also answers 204, so a retry after a lost response needs no special handling. Deleting your own cookie or clearing your token store is not a substitute — until this call lands, the token remains valid for the rest of its seven days, including the signed-in customer's orders and invoices. Call it before you drop the token. The cart hangs off the session, so it goes with it — the next session starts empty, signed in or not. That is the intended behaviour on a shared device; carry anything the visitor should keep on your own side before calling this.",
        "operationId": "EcomLogout",
        "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"
          },
          "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/event": {
      "post": {
        "tags": [
          "Ecom.Analytics"
        ],
        "summary": "Track a purchase funnel step",
        "description": "Records that a visitor took a step in the purchase funnel, named as the GA4 e-commerce event: view_item, add_to_cart, begin_checkout, add_shipping_info, add_payment_info or purchase. Cookie-free; ignored when the channel's analytics is off or the visitor is a bot. Called server-side from ecom-frontend.",
        "operationId": "EcomTrackFunnelEvent",
        "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/TrackFunnelEventRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "description": "Bad Request"
          },
          "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/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/portal/quick-order/resolve": {
      "post": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Resolve quick order lines",
        "description": "Looks up quick order lines for the signed-in customer without adding anything to the cart. One line per item: a reference followed by a quantity, separated by space, tab or semicolon. The reference can be the customer's own item number, our item number, a barcode or a cross reference; the customer's item number wins and its unit is converted. Lines that cannot be ordered are returned in 'rejected' with a reason. Add the resolved lines with POST /ecom/cart/lines.",
        "operationId": "EcomResolvePortalQuickOrder",
        "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/ResolvePortalQuickOrderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuickOrderResponse"
                }
              }
            }
          },
          "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/quick-order/resolve-file": {
      "post": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Resolve quick order lines from a file",
        "description": "Same as POST /ecom/portal/quick-order/resolve, from an uploaded file in the 'file' field: CSV or text in the same line format, or Excel (.xlsx) with the columns Artikelnummer and Antal. At most 2 MB and 500 lines.",
        "operationId": "EcomResolvePortalQuickOrderFile",
        "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": {
            "multipart/form-data": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuickOrderResponse"
                }
              }
            }
          },
          "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/cart/lines": {
      "post": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Add several lines to the cart",
        "description": "Adds several items to the cart for the signed-in customer, as quick order and reorder do. Each line passes the same checks as POST /ecom/cart/items: publication, stock limit, backorder rule and price. A line that fails does not stop the others; it is returned in 'failed' with its error code. The same item on two lines is added as one line with the sum.",
        "operationId": "EcomAddLinesToCart",
        "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/AddLinesToCartRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddLinesToCartResponse"
                }
              }
            }
          },
          "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/cart/items": {
      "post": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Add item to cart",
        "description": "Adds an item to the shopping cart, or increases the quantity when the same item is already there. A configurable product must be sent with selections: its price comes from the choices, and a made-to-order product without them cannot be manufactured. Two lines of the same product merge only when their configuration is identical — two differently configured curtains are two different goods and stay two lines, each addressable by its own cart item id.",
        "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. Answers 409 Cart.AmbiguousItem when the item lies on several lines (a configurable item with different configurations); address the line through /ecom/cart/lines/{cartItemId} instead.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "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. Answers 409 Cart.AmbiguousItem when the item lies on several lines (a configurable item with different configurations); address the line through /ecom/cart/lines/{cartItemId} instead.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "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/cart/lines/{cartItemId}": {
      "delete": {
        "tags": [
          "Ecom.Cart"
        ],
        "summary": "Remove cart line",
        "description": "Removes one cart line, addressed by its cartItemId from GET /ecom/cart. Use this rather than DELETE /ecom/cart/items/{itemId}: a configurable item can lie on several lines with different configurations.",
        "operationId": "EcomRemoveCartLine",
        "parameters": [
          {
            "name": "cartItemId",
            "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 line quantity",
        "description": "Updates the quantity of one cart line, addressed by its cartItemId from GET /ecom/cart. Use this rather than PATCH /ecom/cart/items/{itemId}: a configurable item can lie on several lines with different configurations.",
        "operationId": "EcomUpdateCartLine",
        "parameters": [
          {
            "name": "cartItemId",
            "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": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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. A variant (looked up by slug, item number or id) returns its variant master with `selectedVariantId` set to the variant. `canonicalSlug` is the page's canonical slug: the master's, except in channels that list variants as products of their own. A client that received the response on another slug should redirect to `/products/{canonicalSlug}?variant={variant seoSlug}`. `defaultVariantId` is the first purchasable variant in axis order.",
        "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": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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/products/{idOrSlug}/configuration": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Get the configuration schema for a product",
        "description": "Returns what the shopper has to choose before a made-to-order product can be added to the cart: the fields, grouped for display, and the options for each field with their price impact and an image when one is set. Fields without a group come first in a single unnamed group, so a product that was never grouped renders as a flat list. Calculated fields are left out — they are derived from the others. Pair this with POST .../configuration/price to price the choices as they are made. 404 when the product is not published on the channel, has no configurable features, or requires an on-site measurement: those products are sold through quote and order, not self-service, and are deliberately indistinguishable from a product that does not exist.",
        "operationId": "EcomGetProductConfiguration",
        "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/ProductConfigurationSchemaResponse"
                }
              }
            }
          },
          "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, product tag (tag=slug) and search term. Pass ids=guid,guid to fetch a hand-picked set; without an explicit sort they are returned in the order given. With a category, sort=category returns the products in the order they were assigned to that category; products only in a subcategory follow, in the default order. Pass onSale=true to list only products that are reduced right now: priced by a campaign price list or a public discount rule below the item's regular price, for an anonymous visitor in the channel's currency (customer-specific prices never count). 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": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "onSale",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "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": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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/tags": {
      "get": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "List product tags",
        "description": "Returns the product tags that have at least one product listed in the current channel, with the number of products per tag, sorted by name. Each tag is a landing page: list its products with GET /ecom/catalog/products?tag={slug}. Pass slug to look up a single tag — an empty list means no such tag page exists in this channel. Tags are shared by all channels, but the counts and the selection are channel-specific.",
        "operationId": "EcomGetTags",
        "parameters": [
          {
            "name": "slug",
            "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/TagsResponse"
                }
              }
            }
          },
          "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/{idOrSlug}/configuration/price": {
      "post": {
        "tags": [
          "Ecom.Catalog"
        ],
        "summary": "Price a product configuration",
        "description": "Prices a set of choices without saving anything, so a storefront can show the price updating as the shopper chooses. POST rather than GET because the choices do not fit a query string; the call has no side effects and is never cached. Returns the unit and total price together with isValid, which is false while a required field is missing or a value falls outside its range — the errors name the field so they can be shown next to the right input. The breakdown into base price and configuration surcharge is deliberately not part of this response: it describes how the merchant prices, not what the shopper pays.",
        "operationId": "EcomPriceProductConfiguration",
        "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/PriceProductConfigurationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductConfigurationPriceResponse"
                }
              }
            }
          },
          "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/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": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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 home page: its visible sections (banners filtered by date, section collections expanded, forms with their fields) and its SEO fields. The home page is also available as the page at path / through GET /ecom/pages/resolve.",
        "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/branding": {
      "patch": {
        "tags": [
          "Ecom.Channels"
        ],
        "summary": "Update channel branding",
        "description": "Updates the custom CSS and theme fields (colors, fonts, border radius) for the current channel's branding. Only fields present in the body are changed; null clears a field. 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/PatchEcomBrandingBody"
              }
            },
            "application/merge-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/PatchEcomBrandingBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "415": {
            "description": "Unsupported Media Type",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "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. Tool results carry only product cards (id, name, slug, image, and price and stock when the channel shows them), and an error event carries a generic message rather than the underlying one.",
        "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"
            }
          },
          {
            "name": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [
          {
            "TenantId": [ ],
            "EcomSession": [ ]
          }
        ]
      }
    },
    "/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"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "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, the resulting order number and the order's totals. The totals are null until the order exists; use them to report the purchase to your own analytics.",
        "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/checkout/pickup-locations": {
      "post": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "List pickup locations for the cart",
        "description": "Returns the channel's pickup locations (warehouses the channel offers for pickup), in the channel's order, with whether the session's cart can be picked up at each. `InStock`: everything is there today. `AvailableFrom`: something is on its way there (purchase, transfer or production) and everything is there from `availableFrom`. `Unavailable`: something is missing and not on its way within the channel's horizon; the location cannot be chosen. `lines` tells which item decides it. Call it when the buyer chooses a shipping method with `isPickup`, and again when the cart changes. The answer depends on the cart and is never cached, which is why it is a POST. An empty list means the channel has no pickup locations; a pickup method then works as before, without `pickupWarehouseId`.",
        "operationId": "EcomGetPickupLocations",
        "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/EcomPickupLocation"
                  }
                }
              }
            }
          },
          "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/attempts/{key}": {
      "get": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Get the outcome of a place-order attempt",
        "description": "Returns what became of a `POST /ecom/checkout/place` sent with the given `Idempotency-Key`, within the caller's own session. Use it when the response to place-order was lost, for example after a timeout followed by a page reload: the order may already exist and the cart is then converted, so sending the request again would only answer that the cart is already converted. `Completed` carries the order id and number, `Pending` means the original request is still running, and `Failed` means it got a final error and no order was created. Attempts are kept for 24 hours.",
        "operationId": "EcomGetPlaceOrderAttempt",
        "parameters": [
          {
            "name": "key",
            "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/PlaceOrderAttemptResponse"
                }
              }
            }
          },
          "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"
          },
          "503": {
            "description": "Service Unavailable"
          },
          "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. Pickup: when the shipping method has `isPickup` and the channel has pickup locations, send `pickupWarehouseId` from `POST /ecom/checkout/pickup-locations` instead of a shipping address. The order is then placed on that location, which becomes its delivery address. A billing address is required when the buyer is a company or pays by invoice. Without `pickupWarehouseId` a pickup order behaves as before and is placed on the channel's default warehouse.",
        "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}/review-invitation": {
      "patch": {
        "tags": [
          "Ecom.Checkout"
        ],
        "summary": "Set the buyer's review invitation choice on a checkout session",
        "description": "For checkouts at a payment provider: the buyer's opt-out from the review invitation after delivery is stored on the session and carried to the order when payment completes. Only while the session is pending.",
        "operationId": "EcomSetCheckoutSessionReviewInvitation",
        "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"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetCheckoutSessionReviewInvitationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "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/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. For providers that return shippingOptions, send shippingOptionId to change the shipping the payment charges for.",
        "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"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCheckoutSessionRequest"
              }
            }
          }
        },
        "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 its storefront path (pages live at the root, nested under their parent: /about, /services/tyres; the home page is /), its parentPageId and whether it is hidden from search engines. The menu itself comes from the session's navigation, not from this list. Read the page body with GET /ecom/pages/resolve?path= or 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/resolve": {
      "get": {
        "tags": [
          "Ecom.ContentPages"
        ],
        "summary": "Resolve a storefront path to a content page",
        "description": "Looks up the published page for a storefront path such as /about or /services/tyres; / is the channel's home page. The last segment identifies the page (slugs are unique per channel), so a stale path still finds it: compare the response's canonicalPath with the requested path and redirect permanently when they differ. That covers pages that moved under another parent and the old /pages/{slug} addresses. The first segment of a page path is never one of the storefront's own routes — see reservedSlugs in the session for the list.",
        "operationId": "EcomResolveContentPage",
        "parameters": [
          {
            "name": "path",
            "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/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/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, forms, FAQ — and a storefront renders each kind its own way, so treat an unfamiliar section type as something to skip rather than fail on. Section collections are already expanded and forms carry their fields. 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/custom-blocks/preview/{token}": {
      "get": {
        "tags": [
          "Ecom.ContentPages"
        ],
        "summary": "Get a custom block preview rendered by the block editor",
        "description": "Admin preview only: returns the rendered HTML and scoped CSS of an unsaved custom block draft. Requires a valid admin preview token for the channel in X-Admin-Preview. Tokens expire after 30 minutes.",
        "operationId": "EcomGetCustomBlockPreview",
        "parameters": [
          {
            "name": "token",
            "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/EcomCustomBlockPreviewResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          },
          "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/forms/{formToken}/submissions": {
      "post": {
        "tags": [
          "Ecom.ContentPages"
        ],
        "summary": "Submit a form shown on a page",
        "description": "Submits the answers to a form that a page section carries (section type Form; the section's form has the token, the fields and the consent text). Send the answers as 'data': a JSON string of an object keyed by field id. File fields need multipart/form-data, with the files as parts and the other properties as fields of the same name. 'formLoadedAtUnixMs' is required — when the form was shown — and so is consentGiven=true when the form has consent text. A submission that looks automated gets the same 200 as a real one and is dropped; the response never tells a bot it was caught. What happens next — a deal or a ticket, notification emails, a receipt — is set on the form in Fluit, not by the storefront.",
        "operationId": "EcomSubmitForm",
        "parameters": [
          {
            "name": "formToken",
            "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/SubmitFormRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SubmitFormRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitFormResponse"
                }
              }
            }
          },
          "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/help/articles/{slug}": {
      "get": {
        "tags": [
          "Ecom.Help"
        ],
        "summary": "Get a knowledge article by slug",
        "description": "Returns a single published knowledge article with its Markdown content, the same content rendered as sanitized HTML, and the products it applies to that are published on the channel.",
        "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, each with its content rendered as sanitized HTML.",
        "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/EcomItemArticleResponse"
                  }
                }
              }
            }
          },
          "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"
          },
          "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/{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"
          },
          "503": {
            "description": "Service Unavailable"
          },
          "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/portal/orders/{id}/reorder": {
      "get": {
        "tags": [
          "Ecom.Orders"
        ],
        "summary": "Preview reorder",
        "description": "Returns the lines of an earlier order with last time's and today's unit price, and the lines that can no longer be ordered with a reason. Nothing is added to the cart; lines are always added at today's price with POST /ecom/cart/lines.",
        "operationId": "EcomGetPortalReorder",
        "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/ReorderResponse"
                }
              }
            }
          },
          "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/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}/attachments": {
      "post": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Upload return attachment",
        "description": "Uploads one file to the return while it is being reviewed. Multipart form data with the file in the 'file' field.",
        "operationId": "EcomAddPortalReturnAttachment",
        "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/AddPortalReturnAttachmentResponse"
                }
              }
            }
          },
          "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/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/portal/returns/products": {
      "get": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Search products for a return without order",
        "description": "Searches sellable products (including discontinued) by item number or name. Needs at least two characters.",
        "operationId": "EcomSearchPortalReturnProducts",
        "parameters": [
          {
            "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": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PortalReturnProductOption"
                  }
                }
              }
            }
          },
          "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/returns/orderless": {
      "post": {
        "tags": [
          "Ecom.Returns"
        ],
        "summary": "Submit return without order",
        "description": "Creates a return request for a product that was not bought through an order in the shop. Always reviewed manually.",
        "operationId": "EcomSubmitPortalOrderlessReturn",
        "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/SubmitPortalOrderlessReturnRequest"
              }
            }
          },
          "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/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"
            }
          },
          {
            "name": "X-Language",
            "in": "header",
            "description": "Language for translated content, as an ISO 639-1 code (\"sv\", \"en\"). Omitting it answers in the channel's default language, and so does a language the channel does not publish — this never fails the request. The channel decides which languages exist; read them from the session response.",
            "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. When `canonicalHost` is set, the domain is an old address of the channel: redirect the visitor there permanently (301) instead of serving the shop on the old host. The answer depends only on the query, so it is cacheable and carries an ETag; follow the Cache-Control header instead of picking your own lifetime for it.",
        "operationId": "EcomResolveDomain",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "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/ResolveDomainResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "304": {
            "description": "Not modified — the If-None-Match you sent still matches. No body."
          },
          "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. trackingUrl links to the carrier's tracking page for trackingNumber when the shipping method has a tracking-link template, and is null otherwise. Each package carries its own trackingUrl: the same template applied to the package's tracking number.",
        "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. trackingUrl links to the carrier's tracking page for trackingNumber when the shipping method has a tracking-link template, and is null otherwise.",
        "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": {
      "AddCartLineFailure": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AddCartLineInput": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "AddLinesToCartRequest": {
        "type": "object",
        "properties": {
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddCartLineInput"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AddLinesToCartResponse": {
        "type": "object",
        "properties": {
          "cartId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "addedLineCount": {
            "type": "integer",
            "format": "int32"
          },
          "totalItems": {
            "type": "integer",
            "format": "int32"
          },
          "uniqueItemCount": {
            "type": "integer",
            "format": "int32"
          },
          "failed": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddCartLineFailure"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "AddPortalReturnAttachmentResponse": {
        "type": "object",
        "properties": {
          "attachmentId": {
            "type": "string",
            "format": "uuid"
          }
        },
        "additionalProperties": false
      },
      "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
          },
          "selections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationSelectionInput"
            },
            "description": "Konfigurationsvalen, för en produkt vars `isConfigurable` är sann. Hämta fälten\nfrån `GET /ecom/catalog/products/{idOrSlug}/configuration`.",
            "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
      },
      "CartTaxReductionResponse": {
        "type": "object",
        "properties": {
          "schemes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartTaxReductionScheme"
            },
            "nullable": true
          },
          "total": {
            "type": "number",
            "format": "double"
          },
          "requiresProperty": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "CartTaxReductionScheme": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "basis": {
            "type": "number",
            "format": "double"
          },
          "amount": {
            "type": "number",
            "format": "double"
          }
        },
        "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.\n            \n\nPOST finns för att GET-varianten tar sina id:n i frågesträngen. Med GUID:er tar en typisk\nURL-budget slut runt 50 artiklar, alltså långt under serverns tak på 100 — gränsen ser ut\nsom ett antalsproblem men är ett längdproblem, och POST är svaret på just det. Taket ä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
          },
          "iconName": {
            "type": "string",
            "nullable": true
          },
          "bannerHeadline": {
            "type": "string",
            "nullable": true
          },
          "bannerSubtitle": {
            "type": "string",
            "nullable": true
          },
          "bannerImageUrl": {
            "type": "string",
            "nullable": true
          },
          "metaTitle": {
            "type": "string",
            "nullable": true
          },
          "metaDescription": {
            "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
      },
      "ChannelLanguageOption": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "nativeName": {
            "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
          },
          "customerType": {
            "type": "string",
            "nullable": true
          },
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "orgNumber": {
            "type": "string",
            "nullable": true
          },
          "contactName": {
            "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
          },
          "allowsTaxReduction": {
            "type": "boolean"
          }
        },
        "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
          },
          "requireLogin": {
            "type": "boolean"
          },
          "allowGuestCheckout": {
            "type": "boolean"
          },
          "reviewPlatform": {
            "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
          },
          "isPickup": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "ConfigurationFieldDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "helpText": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string",
            "nullable": true
          },
          "isRequired": {
            "type": "boolean"
          },
          "minValue": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "maxValue": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "defaultValue": {
            "type": "string",
            "nullable": true
          },
          "showWhen": {
            "type": "string",
            "nullable": true
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationOptionDto"
            },
            "nullable": true
          },
          "selectableItems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationItemDto"
            },
            "nullable": true
          },
          "selectableItemsCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "hasMoreSelectableItems": {
            "type": "boolean"
          },
          "displayStyle": {
            "type": "string",
            "nullable": true
          },
          "swatchSize": {
            "type": "string",
            "nullable": true
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "step": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "followsFieldId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "priceDisplay": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfigurationFieldErrorDto": {
        "type": "object",
        "properties": {
          "fieldId": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfigurationGroupDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationFieldDto"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfigurationItemDto": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "imageUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfigurationOptionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean"
          },
          "priceImpact": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "group": {
            "type": "string",
            "nullable": true
          },
          "label": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ConfigurationOptionPriceDifference": {
        "type": "object",
        "properties": {
          "fieldId": {
            "type": "string",
            "format": "uuid"
          },
          "optionId": {
            "type": "string",
            "format": "uuid"
          },
          "priceDifference": {
            "type": "number",
            "format": "double"
          },
          "price": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false
      },
      "ConfigurationSelectionInput": {
        "type": "object",
        "properties": {
          "fieldId": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "text": {
            "type": "string",
            "nullable": true
          },
          "boolean": {
            "type": "boolean",
            "nullable": true
          },
          "optionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "selectedItemId": {
            "type": "string",
            "format": "uuid",
            "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
      },
      "ContentPageBreadcrumbResponse": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "path": {
            "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
          },
          "canonicalPath": {
            "type": "string",
            "description": "Sidans rätta adress. Skiljer den sig från adressen som efterfrågades ska storefronten\nomdirigera dit permanent.",
            "nullable": true
          },
          "seoTitle": {
            "type": "string",
            "description": "Titel i sökresultat och flik; tom betyder sidtiteln följd av kanalens namn.",
            "nullable": true
          },
          "ogImageUrl": {
            "type": "string",
            "description": "Bild vid delning; tom betyder den utvalda bilden, sidans första herobild eller logotypen.",
            "nullable": true
          },
          "noIndex": {
            "type": "boolean",
            "description": "Sidan ska inte indexeras."
          },
          "breadcrumbs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentPageBreadcrumbResponse"
            },
            "description": "Sidans förfäder, närmast roten först. Startsidan ingår inte.",
            "nullable": true
          },
          "isPreview": {
            "type": "boolean",
            "description": "Svaret är en adminförhandsvisning av ett utkast."
          }
        },
        "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"
          },
          "path": {
            "type": "string",
            "description": "Sidans adress i butiken, t.ex. `/tjanster/dackbyte`; startsidan är `/`.",
            "nullable": true
          },
          "pageType": {
            "type": "string",
            "description": "`Page`, `Home`, `BlogPost` eller `NewsPost`.",
            "nullable": true
          },
          "noIndex": {
            "type": "boolean",
            "description": "Sidan ska inte indexeras och hör inte hemma i en sitemap."
          }
        },
        "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
          },
          "imageUrl": {
            "type": "string",
            "description": "Kortets bild, när sektionen ritar kort med bild.",
            "nullable": true
          },
          "imageAlt": {
            "type": "string",
            "description": "Alt-text för kortets bild.",
            "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
          },
          "eyebrow": {
            "type": "string",
            "description": "Liten versal rad ovanför rubriken.",
            "nullable": true
          },
          "alignment": {
            "type": "string",
            "description": "Rubrikdelens justering: `Left` eller `Center`.",
            "nullable": true
          },
          "tone": {
            "type": "string",
            "description": "Bakgrund ur kanalens branding: `Default`, `Muted`, `Ink`, `Brand` eller `Accent`.",
            "nullable": true
          },
          "spacing": {
            "type": "string",
            "description": "Luft ovanför och under: `Normal`, `Compact` eller `Spacious`.",
            "nullable": true
          },
          "anchor": {
            "type": "string",
            "description": "HTML-id för länkar direkt till sektionen (`#ankare`).",
            "nullable": true
          },
          "buttons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionButtonResponse"
            },
            "description": "Knappar under rubrikdelen. Länkar till sidor har redan fått sidans aktuella adress.",
            "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
          },
          "heroLayout": {
            "type": "string",
            "nullable": true
          },
          "heroHeight": {
            "type": "string",
            "description": "Höjden på en hero med bakgrundsbild: Compact, Normal, Tall eller Full.",
            "nullable": true
          },
          "heroImageCaption": {
            "type": "string",
            "nullable": true
          },
          "heroPortraitUrl": {
            "type": "string",
            "nullable": true
          },
          "heroPortraitText": {
            "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
          },
          "categoryLayout": {
            "type": "string",
            "description": "Hur kategorierna ritas: `Badge`, `Tile` eller `Card`.",
            "nullable": true
          },
          "categoryImageAspect": {
            "type": "string",
            "description": "Bildformatet för `Tile` och `Card`: `Portrait` (3:4), `Square` eller `Landscape` (4:3).",
            "nullable": true
          },
          "categoryColumns": {
            "type": "integer",
            "description": "Antal kolumner för kategorierna på bred skärm (2–6); noll betyder att storefronten väljer.",
            "format": "int32"
          },
          "showProductCount": {
            "type": "boolean",
            "description": "Visa antalet produkter under kategorins namn."
          },
          "bannerImageUrl": {
            "type": "string",
            "nullable": true
          },
          "bannerLinkUrl": {
            "type": "string",
            "nullable": true
          },
          "htmlContent": {
            "type": "string",
            "nullable": true
          },
          "lead": {
            "type": "string",
            "description": "Ingress i ett textblock.",
            "nullable": true
          },
          "twoColumns": {
            "type": "boolean",
            "description": "Bryt brödtexten i två spalter på bred skärm."
          },
          "narrowText": {
            "type": "boolean",
            "description": "Smal textspalt."
          },
          "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
          },
          "cardVariant": {
            "type": "string",
            "description": "Hur korten ritas: `Icon`, `ImageTop`, `ImageLeft` eller `Figure`.",
            "nullable": true
          },
          "cardColumns": {
            "type": "integer",
            "description": "Antal kolumner för korten; noll betyder att storefronten väljer.",
            "format": "int32"
          },
          "form": {
            "$ref": "#/components/schemas/EcomSectionFormResponse"
          },
          "mediaText": {
            "$ref": "#/components/schemas/EcomSectionMediaTextResponse"
          },
          "faq": {
            "$ref": "#/components/schemas/EcomSectionFaqResponse"
          },
          "timeline": {
            "$ref": "#/components/schemas/EcomSectionTimelineResponse"
          },
          "logos": {
            "$ref": "#/components/schemas/EcomSectionLogosResponse"
          },
          "productSpotlight": {
            "$ref": "#/components/schemas/EcomSectionProductSpotlightResponse"
          },
          "embed": {
            "$ref": "#/components/schemas/EcomSectionEmbedResponse"
          },
          "contact": {
            "$ref": "#/components/schemas/EcomSectionContactResponse"
          },
          "custom": {
            "$ref": "#/components/schemas/EcomSectionCustomResponse"
          }
        },
        "additionalProperties": false,
        "description": "En sektion i ett sidsvar. Samma form för startsidan och innehållssidorna."
      },
      "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
          },
          "publicKey": {
            "type": "string",
            "nullable": true
          },
          "shippingOptions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreateCheckoutSessionShippingOption"
            },
            "nullable": true
          },
          "selectedShippingOptionId": {
            "type": "string",
            "nullable": true
          },
          "pricesUpdated": {
            "type": "boolean",
            "description": "True when the cart was repriced before the session started and a price changed or a\nline was removed — an expired campaign, another quantity tier, an item no longer for\nsale. The cart is always charged at today's price for its quantity; tell the customer\nthe amount differs from what they last saw."
          },
          "sdkScriptUrl": {
            "type": "string",
            "description": "The script to load the provider's SDK from, when it depends on the channel's settings\n(Nets: the test or live checkout.js, matching the environment the payment was created\nin). Null when the storefront decides it itself.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "CreateCheckoutSessionShippingOption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "price": {
            "type": "number",
            "format": "double"
          },
          "priceIncludingTax": {
            "type": "number",
            "format": "double"
          }
        },
        "additionalProperties": false,
        "description": "A shipping option the storefront shows next to the provider's checkout. Only present\nwhen the provider's checkout has no shipping choice of its own (Nets)."
      },
      "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
          },
          "contentHtml": {
            "type": "string",
            "nullable": true
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomArticleProduct"
            },
            "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
      },
      "EcomArticleProduct": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "Produktens adress i butiken: dess slug, eller id när den saknar slug.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "En produkt som en kunskapsartikel gäller, publicerad i kanalen."
      },
      "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": {
          "cartItemId": {
            "type": "string",
            "format": "uuid"
          },
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "mpn": {
            "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"
          },
          "discountAmount": {
            "type": "number",
            "format": "double"
          },
          "discountPercent": {
            "type": "number",
            "format": "double"
          },
          "isCustomerSpecific": {
            "type": "boolean"
          },
          "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"
          },
          "availableQuantity": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "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"
          },
          "subTotalBeforeDiscount": {
            "type": "number",
            "format": "double"
          },
          "lineDiscountAmount": {
            "type": "number",
            "format": "double"
          },
          "subTotal": {
            "type": "number",
            "format": "double"
          },
          "orderDiscountAmount": {
            "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"
          },
          "currency": {
            "type": "string",
            "nullable": true
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomCartItemResponse"
            },
            "nullable": true
          },
          "taxReduction": {
            "$ref": "#/components/schemas/CartTaxReductionResponse"
          }
        },
        "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",
            "description": "Id eller slug för produkten besökaren står på.",
            "nullable": true
          },
          "currentProductName": {
            "type": "string",
            "description": "Ignoreras. Namnet slås upp i kanalens katalog från CurrentProductId;\nfältet ligger kvar så att äldre klienter inte får 400.",
            "nullable": true
          },
          "currentCategory": {
            "type": "string",
            "description": "Kod eller slug för kategorin besökaren står i.",
            "nullable": true
          },
          "pricesIncludingVat": {
            "type": "boolean",
            "description": "Om besökaren ser priser inklusive moms. Utelämnat = kanalens standard.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomCustomBlockPreviewResponse": {
        "type": "object",
        "properties": {
          "blockKey": {
            "type": "string",
            "nullable": true
          },
          "useShell": {
            "type": "boolean"
          },
          "html": {
            "type": "string",
            "nullable": true
          },
          "css": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Ett eget block i förhandsvisning."
      },
      "EcomFormFieldResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "fieldType": {
            "type": "string",
            "nullable": true
          },
          "isRequired": {
            "type": "boolean"
          },
          "mapTo": {
            "type": "string",
            "nullable": true
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "placeholder": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Ett fält i ett formulär. `fieldType` är `Text`, `Email`, `Phone`,\n`Number`, `TextArea`, `Select`, `Checkbox` eller `File`.\nSvaret skickas med fältets `id` som nyckel."
      },
      "EcomItemArticleResponse": {
        "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
          },
          "contentHtml": {
            "type": "string",
            "description": "Artikelns Markdown som sanerad HTML. Tom när översättningen saknar innehåll.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "En kunskapsartikel kopplad till en artikel, med innehållet färdigt att visa."
      },
      "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
      },
      "EcomPickupLocation": {
        "type": "object",
        "properties": {
          "warehouseId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "address1": {
            "type": "string",
            "nullable": true
          },
          "address2": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "availability": {
            "$ref": "#/components/schemas/PickupAvailabilityStatus"
          },
          "availableFrom": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomPickupLocationLine"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomPickupLocationLine": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "availability": {
            "$ref": "#/components/schemas/PickupAvailabilityStatus"
          },
          "availableFrom": {
            "type": "string",
            "format": "date",
            "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
      },
      "EcomSectionButtonResponse": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "variant": {
            "type": "string",
            "nullable": true
          },
          "openInNewTab": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "EcomSectionContactResponse": {
        "type": "object",
        "properties": {
          "showAddress": {
            "type": "boolean"
          },
          "showPhone": {
            "type": "boolean"
          },
          "showEmail": {
            "type": "boolean"
          },
          "showOpeningHours": {
            "type": "boolean"
          },
          "mapImageUrl": {
            "type": "string",
            "nullable": true
          },
          "directionsUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionCustomResponse": {
        "type": "object",
        "properties": {
          "blockKey": {
            "type": "string",
            "description": "Blockets nyckel. HTML:en ska ligga i ett element med `data-block=\"{blockKey}\"`, som CSS:en är scopad till.",
            "nullable": true
          },
          "blockVersion": {
            "type": "integer",
            "description": "Blocktypens publicerade version. Noll när en förhandsvisning ritar ett block som aldrig\npublicerats; i en förhandsvisning är innehållet blockets utkast, som är nyare än versionen.",
            "format": "int32"
          },
          "useShell": {
            "type": "boolean",
            "description": "Om blocket ritas i standardomslaget, med sektionens `eyebrow`, `title`,\n`subtitle`, `buttons`, `tone` och `spacing`. Utan omslaget ritar\nHTML:en allt själv."
          },
          "html": {
            "type": "string",
            "description": "Blockets HTML, redan tvättad på servern. Tvätta den ändå innan den ritas.",
            "nullable": true
          },
          "css": {
            "type": "string",
            "description": "Blockets CSS, där varje regel börjar med `[data-block=\"{blockKey}\"]`. Samma för alla\nsektioner med samma block och version — lägg den en gång per sida.",
            "nullable": true
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/JsonNode"
            },
            "description": "Fälten som data, nycklat på fältens nycklar, för den som vill rita blocket själv.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Ett eget block, ritat på servern."
      },
      "EcomSectionEmbedResponse": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "height": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "description": "En `Embed`-sektion: en extern sida i en ram. `url` är alltid en absolut\nhttps-adress, `title` ramens tillgängliga namn och `height` höjden i pixlar."
      },
      "EcomSectionFaqItemResponse": {
        "type": "object",
        "properties": {
          "question": {
            "type": "string",
            "nullable": true
          },
          "answerHtml": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionFaqResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionFaqItemResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionFormResponse": {
        "type": "object",
        "properties": {
          "formToken": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "successMessage": {
            "type": "string",
            "nullable": true
          },
          "redirectUrl": {
            "type": "string",
            "nullable": true
          },
          "consentText": {
            "type": "string",
            "nullable": true
          },
          "aside": {
            "type": "string",
            "nullable": true
          },
          "asideHtml": {
            "type": "string",
            "nullable": true
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomFormFieldResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Ett formulär som ska ritas i sidan. Skicka in det med\n`POST /ecom/forms/{formToken}/submissions`."
      },
      "EcomSectionLogoResponse": {
        "type": "object",
        "properties": {
          "imageUrl": {
            "type": "string",
            "nullable": true
          },
          "altText": {
            "type": "string",
            "nullable": true
          },
          "linkUrl": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionLogosResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionLogoResponse"
            },
            "nullable": true
          },
          "grayscale": {
            "type": "boolean"
          },
          "size": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionMediaTextResponse": {
        "type": "object",
        "properties": {
          "mediaUrl": {
            "type": "string",
            "nullable": true
          },
          "altText": {
            "type": "string",
            "nullable": true
          },
          "videoUrl": {
            "type": "string",
            "nullable": true
          },
          "mediaPosition": {
            "type": "string",
            "nullable": true
          },
          "mediaAspect": {
            "type": "string",
            "nullable": true
          },
          "bodyHtml": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionProductSpotlightResponse": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "format": "uuid"
          },
          "layout": {
            "type": "string",
            "nullable": true
          },
          "mediaPosition": {
            "type": "string",
            "nullable": true
          },
          "badge": {
            "type": "string",
            "nullable": true
          },
          "bodyHtml": {
            "type": "string",
            "nullable": true
          },
          "mediaUrl": {
            "type": "string",
            "nullable": true
          },
          "altText": {
            "type": "string",
            "nullable": true
          },
          "showKeySpecs": {
            "type": "boolean"
          },
          "showPrice": {
            "type": "boolean"
          },
          "showStock": {
            "type": "boolean"
          },
          "showAddToCart": {
            "type": "boolean"
          },
          "documents": {
            "type": "string",
            "nullable": true
          },
          "documentIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "nullable": true
          },
          "documentCategory": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "En `ProductSpotlight`-sektion: en artikel presenterad stort.\n`layout` är `SideBySide`, `Banner` eller `Row`, `mediaPosition``Left` eller `Right`, och `documents``None`, `Selected` eller\n`Category`. Vid `Selected` listas `documentIds`, vid `Category` artikelns\nalla dokument i `documentCategory`; båda finns i produktsvarets `documents`."
      },
      "EcomSectionTimelineOptionResponse": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "bodyHtml": {
            "type": "string",
            "nullable": true
          },
          "buttons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionButtonResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionTimelineResponse": {
        "type": "object",
        "properties": {
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionTimelineStepResponse"
            },
            "nullable": true
          },
          "marker": {
            "type": "string",
            "nullable": true
          },
          "layout": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSectionTimelineStepResponse": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "nullable": true
          },
          "bodyHtml": {
            "type": "string",
            "nullable": true
          },
          "iconName": {
            "type": "string",
            "nullable": true
          },
          "buttons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionButtonResponse"
            },
            "nullable": true
          },
          "alternatives": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSectionTimelineOptionResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionActionButtonResponse": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "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"
          },
          "googleAdsConversionId": {
            "type": "string",
            "nullable": true
          },
          "googleAdsPurchaseLabel": {
            "type": "string",
            "nullable": true
          },
          "enhancedConversionsEnabled": {
            "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
          },
          "imageBackground": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionBusinessProfileResponse": {
        "type": "object",
        "properties": {
          "companyName": {
            "type": "string",
            "nullable": true
          },
          "organizationNumber": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "streetAddress": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "countryCode": {
            "type": "string",
            "nullable": true
          },
          "latitude": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "longitude": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "schemaType": {
            "type": "string",
            "description": "schema.org-typ för strukturerad data, t.ex. `AutoRepair`. Tom ger `LocalBusiness` eller `Organization`.",
            "nullable": true
          },
          "showInFooter": {
            "type": "boolean",
            "description": "Visa namn, adress och kontaktuppgifter i sidfoten."
          },
          "openingHours": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionOpeningHoursResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Företagsuppgifter att visa för besökare."
      },
      "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
          },
          "languages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChannelLanguageOption"
            },
            "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"
          },
          "businessProfile": {
            "$ref": "#/components/schemas/EcomSessionBusinessProfileResponse"
          },
          "productCard": {
            "$ref": "#/components/schemas/EcomSessionProductCardResponse"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionChromeResponse": {
        "type": "object",
        "properties": {
          "headerLayout": {
            "type": "string",
            "description": "\"Standard\" eller \"Inline\".",
            "nullable": true
          },
          "logoSize": {
            "type": "string",
            "description": "\"Small\", \"Medium\" eller \"Large\".",
            "nullable": true
          },
          "mobileSearch": {
            "type": "string",
            "description": "\"Row\" eller \"Icon\".",
            "nullable": true
          },
          "showLogoInFooter": {
            "type": "boolean"
          },
          "footerLogoUrl": {
            "type": "string",
            "description": "Logon i sidfoten. Null ger butikens vanliga logo.",
            "nullable": true
          },
          "mobileActionBar": {
            "$ref": "#/components/schemas/EcomSessionMobileActionBarResponse"
          },
          "footerNewsletter": {
            "$ref": "#/components/schemas/EcomSessionFooterNewsletterResponse"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionCustomerResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "nullable": true
          },
          "canPlaceOrders": {
            "type": "boolean",
            "description": "Falskt när kontaktens portalgrupper eller rollen Läsare inte ger rätt att lägga order."
          }
        },
        "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"
          },
          "showListPriceAndDiscount": {
            "type": "boolean"
          },
          "commerceEnabled": {
            "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.\nSaneras i storefrontens API-klient innan den renderas.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionFooterNewsletterResponse": {
        "type": "object",
        "properties": {
          "isEnabled": {
            "type": "boolean"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "text": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionImpersonationResponse": {
        "type": "object",
        "properties": {
          "by": {
            "type": "string",
            "nullable": true
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "description": "Vem som öppnat den här sessionen som kunden."
      },
      "EcomSessionMenuLinkResponse": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "openInNewTab": {
            "type": "boolean"
          },
          "sortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "showAsButton": {
            "type": "boolean",
            "description": "Rita länken som en knapp i sidhuvudet. Bara på toppmenyns översta nivå."
          },
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionMenuLinkResponse"
            },
            "description": "Undermeny, en nivå. Tom för länkar i en undermeny och i sidfoten.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionMobileActionBarResponse": {
        "type": "object",
        "properties": {
          "isEnabled": {
            "type": "boolean",
            "description": "Sant bara när raden är påslagen och har minst en knapp."
          },
          "buttons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionActionButtonResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionNavigationResponse": {
        "type": "object",
        "properties": {
          "catalogRootCategoryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "catalogMenuMode": {
            "type": "string",
            "nullable": true
          },
          "catalogMenuDepth": {
            "type": "integer",
            "format": "int32"
          },
          "catalogMenuLabel": {
            "type": "string",
            "nullable": true
          },
          "catalogMenuSortOrder": {
            "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\ndär storefronten läser dem (`+layout.svelte`).",
            "nullable": true
          },
          "promiseBar": {
            "$ref": "#/components/schemas/EcomSessionPromiseBarResponse"
          },
          "reservedSlugs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Första segment som butikens egna routes använder. En innehållssida kan aldrig heta så,\nså allt annat på roten är en sida (se `GET /ecom/pages/resolve`).",
            "nullable": true
          },
          "chrome": {
            "$ref": "#/components/schemas/EcomSessionChromeResponse"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionOpeningHoursResponse": {
        "type": "object",
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Veckodagar på engelska, som schema.org skriver dem (`Monday` …).",
            "nullable": true
          },
          "opens": {
            "type": "string",
            "description": "Öppnar, `HH:mm`.",
            "nullable": true
          },
          "closes": {
            "type": "string",
            "description": "Stänger, `HH:mm`.",
            "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\nhasMerchantReturnPolicy/shippingDetails och för produktfeedens g:shipping,\nså att produktsidan inte behöver ett extra anrop."
      },
      "EcomSessionProductCardResponse": {
        "type": "object",
        "properties": {
          "imageRatio": {
            "type": "string",
            "description": "\"Landscape\" (4:3), \"Square\" (1:1), \"Portrait\" (4:5) eller \"Wide\" (3:2).",
            "nullable": true
          },
          "imageFit": {
            "type": "string",
            "description": "\"Cover\" eller \"Contain\".",
            "nullable": true
          },
          "hover": {
            "type": "string",
            "description": "\"Zoom\", \"SecondImage\" eller \"None\".",
            "nullable": true
          },
          "showBrand": {
            "type": "boolean"
          },
          "keySpecRows": {
            "type": "integer",
            "description": "Antal nyckeldata i rutnätets kort, 0–4.",
            "format": "int32"
          },
          "showStock": {
            "type": "boolean"
          },
          "showVatNote": {
            "type": "boolean"
          },
          "showAddToCart": {
            "type": "boolean",
            "description": "Av: kortet har ingen köpyta och länkar bara till produktsidan."
          },
          "showCompare": {
            "type": "boolean"
          },
          "mobileColumns": {
            "type": "integer",
            "description": "Kort per rad i produktlistor på smal skärm, 1 eller 2.",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "EcomSessionPromiseBarItemResponse": {
        "type": "object",
        "properties": {
          "icon": {
            "type": "string",
            "description": "Ikonnamn ur butikens kurerade set, eller null.",
            "nullable": true
          },
          "text": {
            "type": "string",
            "description": "Löftet som besökaren läser det.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "EcomSessionPromiseBarResponse": {
        "type": "object",
        "properties": {
          "isEnabled": {
            "type": "boolean",
            "description": "Om butiken har slagit på raden."
          },
          "tone": {
            "type": "string",
            "description": "\"Ink\", \"Brand\" eller \"Accent\".",
            "nullable": true
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EcomSessionPromiseBarItemResponse"
            },
            "description": "Löftena i visningsordning. Tom lista när butiken inte fyllt i några — raden\nritas då inte alls, oavsett IsEnabled.",
            "nullable": true
          },
          "narrowBehavior": {
            "type": "string",
            "description": "\"Scroll\" eller \"Rotate\".",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "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"
          },
          "impersonation": {
            "$ref": "#/components/schemas/EcomSessionImpersonationResponse"
          }
        },
        "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\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\nfraktprislista saknar fast pris och utesluts — ett gissat fraktpris i feeden ä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
          },
          "taxPercent": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "FeedAvailability": {
        "enum": [
          "InStock",
          "OutOfStock",
          "BackOrder",
          "PreOrder"
        ],
        "type": "string"
      },
      "FeedCondition": {
        "enum": [
          "New",
          "Refurbished",
          "Used"
        ],
        "type": "string"
      },
      "FeedIneligibleReason": {
        "enum": [
          "DefectiveGrade",
          "MissingTitle",
          "MissingSlug",
          "MissingImage",
          "MissingPrice",
          "UnknownGrossPrice"
        ],
        "type": "string"
      },
      "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
          },
          "label": {
            "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
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "totalAmountIncludingTax": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "totalVat": {
            "type": "number",
            "format": "double",
            "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
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "totalAmount": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "totalAmountIncludingTax": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "totalVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "GradedAlternativeResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "grade": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "seoSlug": {
            "type": "string",
            "nullable": true
          },
          "listPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "listPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "stockStatus": {
            "$ref": "#/components/schemas/StockStatus"
          },
          "isCurrent": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "HomePageResponse": {
        "type": "object",
        "properties": {
          "layout": {
            "type": "string",
            "nullable": true
          },
          "fullWidth": {
            "type": "boolean"
          },
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentPageSectionResponse"
            },
            "nullable": true
          },
          "title": {
            "type": "string",
            "description": "Startsidans titel (för administrationen; butiken visar den inte som rubrik).",
            "nullable": true
          },
          "seoTitle": {
            "type": "string",
            "description": "Titel i sökresultat; tom betyder kanalens metatitel.",
            "nullable": true
          },
          "metaDescription": {
            "type": "string",
            "description": "Metabeskrivning; tom betyder kanalens.",
            "nullable": true
          },
          "ogImageUrl": {
            "type": "string",
            "description": "Bild vid delning; tom betyder sidans första herobild eller logotypen.",
            "nullable": true
          },
          "noIndex": {
            "type": "boolean",
            "description": "Be sökmotorerna att inte indexera startsidan."
          },
          "isPreview": {
            "type": "boolean",
            "description": "Svaret är en adminförhandsvisning av ett utkast."
          }
        },
        "additionalProperties": false
      },
      "HousingType": {
        "enum": [
          "Detached",
          "HousingCooperative",
          "OwnedApartment"
        ],
        "type": "string"
      },
      "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"
          },
          "unitPriceBeforeDiscount": {
            "type": "number",
            "format": "double"
          },
          "discountAmount": {
            "type": "number",
            "format": "double"
          },
          "discountPercent": {
            "type": "number",
            "format": "double"
          },
          "discountName": {
            "type": "string",
            "nullable": true
          },
          "discountEndsAt": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "isCustomerSpecific": {
            "type": "boolean"
          },
          "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",
          "Replacement"
        ],
        "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
      },
      "JsonNode": {
        "type": "object",
        "properties": {
          "options": {
            "$ref": "#/components/schemas/JsonNodeOptions"
          },
          "parent": {
            "$ref": "#/components/schemas/JsonNode"
          },
          "root": {
            "$ref": "#/components/schemas/JsonNode"
          }
        },
        "additionalProperties": false
      },
      "JsonNodeOptions": {
        "type": "object",
        "properties": {
          "propertyNameCaseInsensitive": {
            "type": "boolean"
          }
        },
        "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
      },
      "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."
      },
      "PatchEcomBrandingBody": {
        "type": "object",
        "properties": {
          "customCss": {
            "type": "string",
            "nullable": true
          },
          "primaryColor": {
            "type": "string",
            "nullable": true
          },
          "primaryHoverColor": {
            "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
          },
          "imageBackground": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Kroppen på PATCH /ecom/branding som den dokumenteras: platta strängfält, alla valfria.\nEtt utelämnat fält rörs inte; null (eller tom sträng) tömmer fältet så att temats\nstandardvärde gäller."
      },
      "PickupAvailabilityStatus": {
        "enum": [
          "InStock",
          "AvailableFrom",
          "Unavailable"
        ],
        "type": "string"
      },
      "PlaceEcomOrderResult": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PlaceOrderAttemptResponse": {
        "type": "object",
        "properties": {
          "state": {
            "$ref": "#/components/schemas/PlaceOrderAttemptState"
          },
          "statusCode": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "orderId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "PlaceOrderAttemptState": {
        "enum": [
          "Pending",
          "Completed",
          "Failed"
        ],
        "type": "string"
      },
      "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
          },
          "pickupWarehouseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "taxReduction": {
            "$ref": "#/components/schemas/PlaceOrderTaxReductionRequest"
          },
          "reviewInvitationOptOut": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PlaceOrderTaxReductionBuyerRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true
          },
          "identityNumber": {
            "type": "string",
            "description": "Personnummer, ÅÅÅÅMMDD-NNNN. Krypteras innan det sparas.",
            "nullable": true
          },
          "sharePercent": {
            "type": "number",
            "description": "Köparens andel av avdraget i procent.",
            "format": "double"
          }
        },
        "additionalProperties": false
      },
      "PlaceOrderTaxReductionRequest": {
        "type": "object",
        "properties": {
          "buyers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlaceOrderTaxReductionBuyerRequest"
            },
            "nullable": true
          },
          "housingType": {
            "$ref": "#/components/schemas/HousingType"
          },
          "propertyDesignation": {
            "type": "string",
            "nullable": true
          },
          "housingAssociationOrgNumber": {
            "type": "string",
            "nullable": true
          },
          "apartmentNumber": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Skattereduktion för arbete i hemmet: köparna med personnummer och andel, och bostaden. Bara\nnär kanalen erbjuder avdraget och betalsättet är faktura."
      },
      "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
          },
          "groups": {
            "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
      },
      "PortalReturnAttachmentDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fileName": {
            "type": "string",
            "nullable": true
          },
          "fileSize": {
            "type": "integer",
            "format": "int64"
          },
          "uploadedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "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",
            "nullable": true
          },
          "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
          },
          "isOrderless": {
            "type": "boolean"
          },
          "canAddAttachments": {
            "type": "boolean"
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PortalReturnAttachmentDetail"
            },
            "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
          },
          "serialNumber": {
            "type": "string",
            "nullable": true
          },
          "purchaseDate": {
            "type": "string",
            "nullable": true
          },
          "originalSeller": {
            "type": "string",
            "nullable": true
          },
          "purchaseEvidence": {
            "type": "string",
            "nullable": true
          },
          "faultDescription": {
            "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
      },
      "PortalReturnProductOption": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "name": {
            "type": "string",
            "nullable": true
          }
        },
        "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"
          },
          "isOrderless": {
            "type": "boolean"
          }
        },
        "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
          },
          "trackingUrl": {
            "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
          },
          "trackingUrl": {
            "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
          },
          "trackingUrl": {
            "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"
          },
          "contentHtml": {
            "type": "string",
            "nullable": true
          }
        },
        "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
      },
      "PriceProductConfigurationRequest": {
        "type": "object",
        "properties": {
          "quantity": {
            "type": "integer",
            "description": "Antal att prissätta. Minst 1.",
            "format": "int32"
          },
          "selections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationSelectionInput"
            },
            "description": "Valen så långt de är gjorda. Ofullständiga val är förväntade.",
            "nullable": true
          }
        },
        "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
      },
      "ProductConfigurationPriceResponse": {
        "type": "object",
        "properties": {
          "isValid": {
            "type": "boolean"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "unitPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "totalPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationFieldErrorDto"
            },
            "nullable": true
          },
          "optionPriceDifferences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationOptionPriceDifference"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ProductConfigurationSchemaResponse": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigurationGroupDto"
            },
            "nullable": true
          }
        },
        "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"
          },
          "priceAfterTaxReduction": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxReductionName": {
            "type": "string",
            "nullable": true
          },
          "priceValidUntil": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "discountEndsAt": {
            "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"
          },
          "isConfigurable": {
            "type": "boolean"
          },
          "leadTimeDays": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "isMadeToOrder": {
            "type": "boolean"
          },
          "madeToOrderText": {
            "type": "string",
            "nullable": true
          },
          "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"
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TagSummaryResponse"
            },
            "nullable": true
          },
          "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
          },
          "defaultVariantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "selectedVariantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "canonicalSlug": {
            "type": "string",
            "nullable": true
          },
          "variantsHaveOwnPages": {
            "type": "boolean"
          },
          "grade": {
            "type": "string",
            "nullable": true
          },
          "gradedAlternatives": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GradedAlternativeResponse"
            },
            "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
          },
          "categoryName": {
            "type": "string",
            "nullable": true
          },
          "categorySortOrder": {
            "type": "integer",
            "format": "int32"
          },
          "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
          },
          "productPath": {
            "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
          },
          "feedId": {
            "type": "string",
            "nullable": true
          },
          "feedTitle": {
            "type": "string",
            "nullable": true
          },
          "feedDescription": {
            "type": "string",
            "nullable": true
          },
          "feedBrand": {
            "type": "string",
            "nullable": true
          },
          "feedMpn": {
            "type": "string",
            "nullable": true
          },
          "feedProductType": {
            "type": "string",
            "nullable": true
          },
          "normalizedGtin": {
            "type": "string",
            "nullable": true
          },
          "identifierExists": {
            "type": "boolean"
          },
          "availability": {
            "$ref": "#/components/schemas/FeedAvailability"
          },
          "availabilityDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "condition": {
            "$ref": "#/components/schemas/FeedCondition"
          },
          "feedPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "feedSalePrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "salePriceEffectiveFrom": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "salePriceEffectiveTo": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "ineligibleReason": {
            "$ref": "#/components/schemas/FeedIneligibleReason"
          },
          "isEligible": {
            "type": "boolean",
            "readOnly": 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
          },
          "secondImageUrl": {
            "type": "string",
            "nullable": true
          },
          "listPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "listPriceIncludingVat": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxPercent": {
            "type": "number",
            "format": "double"
          },
          "priceAfterTaxReduction": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "taxReductionName": {
            "type": "string",
            "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"
          },
          "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"
          },
          "isVariantMaster": {
            "type": "boolean"
          },
          "hasPriceRange": {
            "type": "boolean"
          },
          "isConfigurable": {
            "type": "boolean"
          },
          "variantSwatches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantSwatchResponse"
            },
            "nullable": true
          },
          "variantSwatchTotal": {
            "type": "integer",
            "format": "int32"
          },
          "matchedVariantId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "matchedVariantSlug": {
            "type": "string",
            "nullable": true
          },
          "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
      },
      "QuickOrderLineResponse": {
        "type": "object",
        "properties": {
          "lineNumber": {
            "type": "integer",
            "format": "int32"
          },
          "reference": {
            "type": "string",
            "nullable": true
          },
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "requestedQuantity": {
            "type": "number",
            "format": "double"
          },
          "customerUnit": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "unitPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "matchedField": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "QuickOrderRejectedLineResponse": {
        "type": "object",
        "properties": {
          "lineNumber": {
            "type": "integer",
            "format": "int32"
          },
          "reference": {
            "type": "string",
            "nullable": true
          },
          "requestedQuantity": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "QuickOrderResponse": {
        "type": "object",
        "properties": {
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuickOrderLineResponse"
            },
            "nullable": true
          },
          "rejected": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuickOrderRejectedLineResponse"
            },
            "nullable": true
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          }
        },
        "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
      },
      "ReorderLineResponse": {
        "type": "object",
        "properties": {
          "lineNumber": {
            "type": "integer",
            "format": "int32"
          },
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "itemNumber": {
            "type": "string",
            "nullable": true
          },
          "itemName": {
            "type": "string",
            "nullable": true
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string",
            "nullable": true
          },
          "previousUnitPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "currentUnitPrice": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "priceChanged": {
            "type": "boolean"
          },
          "rejection": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ReorderResponse": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "orderNumber": {
            "type": "string",
            "nullable": true
          },
          "previousCurrencyCode": {
            "type": "string",
            "nullable": true
          },
          "currencyCode": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReorderLineResponse"
            },
            "nullable": true
          }
        },
        "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
          },
          "canonicalHost": {
            "type": "string",
            "description": "Host name the channel answers on now, set only when the requested domain is an old address\n(e.g. `acme_web.shop.fluit.cloud` after the channel moved to `acme-web.shop.fluit.cloud`).\nRedirect the visitor there permanently. Null for a domain that is the channel's own.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Minimal response for domain resolution — routing info only."
      },
      "ResolvePortalQuickOrderRequest": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "En artikel per rad: referens följt av antal.",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "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
      },
      "SetCheckoutSessionReviewInvitationRequest": {
        "type": "object",
        "properties": {
          "optOut": {
            "type": "boolean",
            "description": "Sant när köparen inte vill få en inbjudan att lämna omdöme."
          }
        },
        "additionalProperties": false,
        "description": "Köparens val."
      },
      "StockResponse": {
        "type": "object",
        "properties": {
          "stock": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ItemStockResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "StockStatus": {
        "enum": [
          "InStock",
          "LowStock",
          "OutOfStock",
          "BackOrder"
        ],
        "type": "string"
      },
      "SubmitFormRequest": {
        "type": "object",
        "properties": {
          "data": {
            "type": "string",
            "description": "Svaren: en JSON-sträng med ett objekt där fältets id är nyckeln.",
            "nullable": true
          },
          "sourceUrl": {
            "type": "string",
            "description": "Sidan besökaren skickade från. Sparas som källa på inskicket.",
            "nullable": true
          },
          "consentGiven": {
            "type": "boolean",
            "description": "Besökaren kryssade i formulärets samtyckesruta."
          },
          "honeypot": {
            "type": "string",
            "description": "Ett dolt fält som ska lämnas tomt.",
            "nullable": true
          },
          "captchaToken": {
            "type": "string",
            "nullable": true
          },
          "formLoadedAtUnixMs": {
            "type": "integer",
            "description": "När formuläret visades, i millisekunder sedan epoken.",
            "format": "int64",
            "nullable": true
          },
          "utmSource": {
            "type": "string",
            "nullable": true
          },
          "utmMedium": {
            "type": "string",
            "nullable": true
          },
          "utmCampaign": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitFormResponse": {
        "type": "object",
        "properties": {
          "received": {
            "type": "boolean",
            "description": "Alltid true — även när inskicket sorterats bort som automatiserat."
          }
        },
        "additionalProperties": false
      },
      "SubmitPortalOrderlessReturnRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "integer",
            "format": "int32"
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubmitPortalOrderlessReturnRequestLine"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "SubmitPortalOrderlessReturnRequestLine": {
        "type": "object",
        "properties": {
          "itemId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "serialNumber": {
            "type": "string",
            "nullable": true
          },
          "purchaseDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "originalSeller": {
            "type": "string",
            "nullable": true
          },
          "purchaseEvidence": {
            "type": "string",
            "nullable": true
          },
          "faultDescription": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "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\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
      },
      "TagResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "slug": {
            "type": "string",
            "nullable": true
          },
          "productCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      },
      "TagSummaryResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "slug": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TagsResponse": {
        "type": "object",
        "properties": {
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TagResponse"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TrackFunnelEventRequest": {
        "type": "object",
        "properties": {
          "step": {
            "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
      },
      "UpdateCheckoutSessionRequest": {
        "type": "object",
        "properties": {
          "shippingOptionId": {
            "type": "string",
            "description": "The shipping option the customer picked in the storefront. Only for providers that\nreturn `shippingOptions`; omit it to keep the current choice.",
            "nullable": true
          }
        },
        "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
          },
          "publicKey": {
            "type": "string",
            "nullable": true
          },
          "shippingOptions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreateCheckoutSessionShippingOption"
            },
            "nullable": true
          },
          "selectedShippingOptionId": {
            "type": "string",
            "nullable": true
          },
          "pricesUpdated": {
            "type": "boolean"
          },
          "sdkScriptUrl": {
            "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"
          },
          "displayType": {
            "$ref": "#/components/schemas/VariantDisplayType"
          },
          "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"
          },
          "colorHex": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "VariantDisplayType": {
        "enum": [
          "Buttons",
          "Swatch",
          "Dropdown"
        ],
        "type": "string"
      },
      "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"
          },
          "isPurchasable": {
            "type": "boolean"
          },
          "incoming": {
            "$ref": "#/components/schemas/IncomingStockResponse"
          },
          "mainImageUrl": {
            "type": "string",
            "nullable": true
          },
          "seoSlug": {
            "type": "string",
            "nullable": true
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductImageResponse"
            },
            "nullable": true
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductDocumentResponse"
            },
            "nullable": true
          },
          "gtin": {
            "type": "string",
            "nullable": true
          },
          "isConfigurable": {
            "type": "boolean"
          },
          "values": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantValueRef"
            },
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "VariantSwatchResponse": {
        "type": "object",
        "properties": {
          "valueId": {
            "type": "string",
            "format": "uuid"
          },
          "value": {
            "type": "string",
            "nullable": true
          },
          "colorHex": {
            "type": "string",
            "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"
      ]
    }
  ]
}